Android LiveData Observer not being triggered second time

Viewed 7323

I'm working on an activity where users are allowed to filter their search results. Initially, I fetch all data but when the user wants to filter it, I need to call another query and update the view. I am using Android ROOM and ViewModel for this.

The problem is that each time I fetch new data I need to create a new observer as the old one "stops" firing up. Normally, this should work with one observer, which would be called every time data is updated. Can you help me understand why this is happening, please?

Activity:

package com.jds.fitnessjunkiess.getfitapp.Activities.ExercisesView;

import android.arch.lifecycle.ViewModelProviders;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;

import com.jds.fitnessjunkiess.getfitapp.Activities.ExercisesView.Adapters.ExercisesAdapter;
import com.jds.fitnessjunkiess.getfitapp.Data.ViewModels.ExerciseViewModel;
import com.jds.fitnessjunkiess.getfitapp.R;

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);
//      }
//    });
  }
}

View Model:

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

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

  public void setFilters(String muscleGroups, String type) {
    this.data = this.repository.filterSelect(muscleGroups, type);
  }

  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 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();
}
4 Answers

You need make some changes because you are reassigning the LiveData object instead of add the items filtered.

You need chante your LiveData> data as MutableLiveData> and in the setFilters method you need get the arraylist from the repository and add it in the MutableLiveData using the setValue method.

ExerciseViewModel

 MutableLiveData<List<Exercise>> data = new MutableLiveData<>();


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

I hope it helps you.

In your setFilters method, you are reassigning a whole new live data to the live data instance you have. Live data only fires event when it's value get changed. So instead you can use a mutable live data, and set it's value by setValue() of this class. And your observer will be called.

I am facing the same issue my second observer is not called when I go through a button click while when I call in the onCreateView function of BottomSheetDialogFragament both the observers are called.

private fun getKey() {
        if (ApiConstants.publicKey == null) {
            sendOTPViewModel.key.observe(viewLifecycleOwner, Observer { t ->
                if (t == ApiConstants.KEY_SUCCESS) {
                    getKey()
                }
            })

        } else {
            validateUser(number, from, BaseActivity.getDeviceId(mContext))
        }
    }

 private fun validateUser(loginId: String?, loginType: String?, deviceId: String?) {

        sendOTPViewModel.getValidateUser(loginId, loginType, deviceId).observe(viewLifecycleOwner, Observer { t ->
            LoggerUtils.E(TAG, t)
        })

    }

Kotlin Answer

Remove these two points in your function if you are using:

  1. = viewModelScope.launch { }
  2. suspend
Related