Android LiveData and Room: getValue returning NULL

大兔子大兔子 提交于 2021-02-07 10:30:46

问题


When reading data from DB using ROOM and returning LiveData, getValue() method returns null. I have been unable to understand what is going wrong for a while now. Can you please assist with this issue? There is data in the database, it seems like it is more of an issue of how I am using LiveData objects.

Activity:

public class ExercisesViewActivity extends AppCompatActivity implements View.OnClickListener {

  private ExerciseViewModel exerciseViewModel;
  private ExercisesAdapter recyclerViewerAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_exercises_view);

    Toolbar toolbar = findViewById(R.id.toolbar_exercise_view_activity);
    toolbar.setTitle("Exercises");
    setSupportActionBar(toolbar);

    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null) {
      actionBar.setDisplayHomeAsUpEnabled(true);
      actionBar.setDisplayShowHomeEnabled(true);
    }

    RecyclerView recyclerView = findViewById(R.id.exercise_view_recycle_viewer);
    RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
    recyclerView.setLayoutManager(layoutManager);
    this.recyclerViewerAdapter = new ExercisesAdapter();
    recyclerView.setAdapter(recyclerViewerAdapter);

    this.exerciseViewModel = ViewModelProviders.of(this).get(ExerciseViewModel.class);
    this.exerciseViewModel.setFilters("", "");
//    this.exerciseViewModel.selectAll();
    this.exerciseViewModel.select().observe(this, exercises -> {
      if (exercises != null) {
        this.recyclerViewerAdapter.updateDataset(exercises);
      }
    });

    Button button = findViewById(R.id.test_button);
    button.setOnClickListener(this);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        onBackPressed();
        break;
    }

    return true;
  }

  @Override
  public void onClick(View v) {
    this.exerciseViewModel.setFilters("", "");
//    this.exerciseViewModel.select().observe(this, exercises -> {
//      if (exercises != null) {
//        this.recyclerViewerAdapter.updateDataset(exercises);
//      }
//    });
  }
}

ViewModel:

public class ExerciseViewModel extends AndroidViewModel {
  ExercisesRepository repository;
  MutableLiveData<List<Exercise>> data;

  public ExerciseViewModel(Application application) {
    super(application);
    this.data = new MutableLiveData<>();
    this.repository = new ExercisesRepository(application);
  }

  public void setFilters(String muscleGroups, String type) {
    LiveData<List<Exercise>> listLiveData = this.repository.filterSelect(muscleGroups, type);
    this.data.setValue(listLiveData.getValue());
  }

  public void selectAll() {
//    this.data.setValue(this.repository.selectAll().getValue());
  }

  public LiveData<List<Exercise>> select() {
    return data;
  }

  public void insert(Exercise exercise) {
    this.repository.insert(exercise);
  }
}

Repository:

public class ExercisesRepository {
  private ExerciseDao dao;

  public ExercisesRepository(Application context) {
    WorkoutRoomDatabase database = WorkoutRoomDatabase.getDb(context);
    this.dao = database.exerciseDao();
  }

  public LiveData<List<Exercise>> filterSelect(String muscleGroups, String type) {
    return this.dao.filterSelect("%" + muscleGroups + "%", "%" + type + "%");
  }

  public LiveData<List<Exercise>> selectAll() {
    return this.dao.selectAll();
  }

  public void insert(Exercise exercise) {
    new insertAsyncTask(this.dao).execute(exercise);
  }

  private static class insertAsyncTask extends AsyncTask<Exercise, Void, Void> {

    private ExerciseDao exerciseDao;

    insertAsyncTask(ExerciseDao  dao) {
      exerciseDao = dao;
    }

    @Override
    protected Void doInBackground(final Exercise... params) {
      exerciseDao.insert(params[0]);
      return null;
    }
  }
}

DAO:

@Dao
public interface ExerciseDao {
  @Query("SELECT * FROM exercises WHERE muscleGroups LIKE :muscleGroup AND type LIKE :type")
  LiveData<List<Exercise>> filterSelect(String muscleGroup, String type);
  @Query("SELECT * FROM exercises")
  LiveData<List<Exercise>> selectAll();
  @Insert
  void insert(Exercise exercise);
  @Update
  void update(Exercise exercise);
  @Delete
  void delete(Exercise exercise);
  @Query("DELETE FROM exercises")
  void deleteAll();
}

UPDATE:

If in my viewModel I change filter function to return a new LiveData object, correct data is being fetched:

  public LiveData<List<Exercise>> filterSelect(String muscleGroups, String type) {
    return this.dao.filterSelect("%" + muscleGroups + "%", "%" + type + "%");
  }

But then in my Activity I need to create a new observer, as now data is provided by new instances of LiveData:

 @Override
  public void onClick(View v) {
    this.exerciseViewModel.setFilters("Biceps", "weight");
    this.exerciseViewModel.select().observe(this, exercises -> {
      if (exercises != null) {
        this.recyclerViewerAdapter.updateDataset(exercises);
      }
    });
  }

This is definetely now the right way of doing this :/


回答1:


Rather than MutableLiveData you should look at MediatorLiveData which will allow you to propopgate the changes from the repository.

This would look something like below where you add the repository data as a data source to the mediatorLiveData. This will then trigger a callback when you receive data from the repository, which you can then emit from the mediatorLiveData by calling setValue.

public class ExerciseViewModel extends AndroidViewModel {
    ExercisesRepository repository;
    MediatorLiveData<List<Exercise>> mediatorLiveData = new MediatorLiveData<>();

    public ExerciseViewModel(Application application) {
        super(application);
        this.repository = new ExercisesRepository(application);
    }

    public void setFilters(String muscleGroups, String type) {
        LiveData<List<Exercise>> repositoryLiveData = this.repository.filterSelect(muscleGroups, type);
        mediatorLiveData.addSource(repositoryLiveData, exercisList -> {
            mediatorLiveData.removeSource(repositoryLiveData);
            mediatorLiveData.setValue(exercisList);
        });
    }

    public LiveData<List<Exercise>> select() {
        return mediatorLiveData;
    }



回答2:


You trying to fetch like search in filterSelete with empty value. this.exerciseViewModel.setFilters("", "");. This filter is basically a issue. You can't do empty like search. Instated of this, you can use loadAll query.

@Query("SELECT * FROM exercises") LiveData<List<Exercise>> loadAll();

Edit:

public LiveData<List<Exercise>> filterSelect(String muscleGroups, String type) {

if(muscleGroups == "" && type == "") {
 return this.dao.loadAll()

}else {

return this.dao.filterSelect("%" + muscleGroups + "%", "%" + type + "%");

}

Hope, It's will solve your problem.




回答3:


I managed to solve this issue by using MediatorLiveData and Transformations.

So now my ViewModel constructor looks like this:

public ExerciseViewModel(Application application) {
    super(application);
    this.repository = new ExercisesRepository(application);
    this.filterMutableLiveData = new MutableLiveData<>();

    ExercisesFilter filter = new ExercisesFilter();
    filter.muscleGroup = "";
    filter.type = "";
    this.filterMutableLiveData.setValue(filter);

    LiveData<List<Exercise>> source = Transformations.switchMap(
        this.filterMutableLiveData, value -> repository.filterSelect(value.muscleGroup, value.type)
    );

    this.data.addSource(source, val -> this.data.setValue(val));
  }

And I have added this method:

 public void setFilterMutableLiveData(String muscleGroups, String type) {
    ExercisesFilter filter1 = new ExercisesFilter();
    filter1.muscleGroup = muscleGroups;
    filter1.type = type;
    this.filterMutableLiveData.setValue(filter1);
  }

With this approach, every time filters are updated, the function passed to Transformations.switchMap is triggered to fetch new data. In the end, new LiveData object is added to MediatorLiveData object, therefore one observer can be utilised for all sources.

Source: https://github.com/googlesamples/android-sunflower/blob/99df6189af927b5cc477167923bbbd5a4ca1ece9/app/src/main/java/com/google/samples/apps/sunflower/viewmodels/PlantListViewModel.kt#L42



来源:https://stackoverflow.com/questions/52136438/android-livedata-and-room-getvalue-returning-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!