问题
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