1

I am using the clean architecture with MVVM pattern so the room part goes into the data layer and I'm returning observables from there to domain layer and using them in presentation layer by wrapping them in a LiveData. Now, the problem is that after insertion/deletion/update the list is not getting updated immediately in UI.

Viewmodel in Presentation Layer:

public class WordViewModel extends BaseViewModel<WordNavigator> {

//get all the use cases here
private GetAllWords getAllWords;
private InsertWord insertWord;
private DeleteThisWord deleteThisWord;
private UpdateThisWord updateThisWord;
private GetTheIndexOfTopWord getTheIndexOfTopWord;

//data
public MutableLiveData<List<Word>> allWords;


public WordViewModel(GetAllWords getAllWords, InsertWord insertWord, DeleteThisWord deleteThisWord, UpdateThisWord updateThisWord, GetTheIndexOfTopWord getTheIndexOfTopWord) {
    this.getAllWords = getAllWords;
    this.insertWord = insertWord;
    this.deleteThisWord = deleteThisWord;
    this.updateThisWord = updateThisWord;
    this.getTheIndexOfTopWord = getTheIndexOfTopWord;
}

public void getAllWords() {
    getAllWords.execute(new DisposableObserver<List<Word>>() {
        @Override
        public void onNext(List<Word> words) {
            allWords.setValue(words);
        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onComplete() {

        }
    }, GetAllWords.Params.getAllWords());
}


public void insertWord(Word word) {
    insertWord.execute(new DisposableObserver<Boolean>() {
        @Override
        public void onNext(Boolean aBoolean) {
            if (aBoolean)
                Log.e("ganesh", "word inserted successfully!!!");
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onComplete() {

        }
    }, InsertWord.Params.insertWord(word));

}

public void getTheIndexOfTopWord(final String action) {
    getTheIndexOfTopWord.execute(new DisposableObserver<Word>() {
        @Override
        public void onNext(Word word) {
            if (word != null)
                getNavigator().updateTopIndex(word.getWordId(), action);
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onComplete() {

        }
    }, GetTheIndexOfTopWord.Params.getTheIndexOfTopWord());
}

public void deleteThisWord(int wordId) {
    deleteThisWord.execute(new DisposableObserver<Boolean>() {
        @Override
        public void onNext(Boolean aBoolean) {
            if (aBoolean)
                Log.e("ganesh", "word deleted successfully!!!");
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onComplete() {

        }
    }, DeleteThisWord.Params.deleteThisWord(wordId));
}

public void updateThisWord(int wordId, String newWord) {
    updateThisWord.execute(new DisposableObserver<Boolean>() {
        @Override
        public void onNext(Boolean aBoolean) {
            if (aBoolean)
                Log.e("ganesh", "word updated successfully!!!");
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onComplete() {

        }
    }, UpdateThisWord.Params.updateThisWord(wordId, newWord));
}

public MutableLiveData<List<Word>> getWords() {
    if (allWords == null) {
        allWords = new MutableLiveData<>();
    }
    return allWords;
}

@Override
protected void onCleared() {
    super.onCleared();

    if (getAllWords != null)
        getAllWords = null;
    if (deleteThisWord != null)
        deleteThisWord = null;
    if (insertWord != null)
        insertWord = null;
    if (updateThisWord != null)
        updateThisWord = null;
    if (getTheIndexOfTopWord != null)
        getTheIndexOfTopWord = null;

}
}

DAO in Data Layer:

@Dao
public interface WordDao {
@Insert
void insertThisWord(Word word);

@Query("delete from word_table")
void deleteAll();

@Query("select * from word_table order by word_id asc")
List<Word> getAllWords();

@Query("delete from word_table where word_id = :wordId")
void deleteThisWord(int wordId);

@Query("update word_table set word = :newWord where word_id = :wordId")
void updateThisWord(int wordId, String newWord);

@Query("select * from word_table order by word_id asc limit 1")
Word getTheIndexOfTopWord();
}

Repository in Data Layer:

public class WordRepositoryImpl implements WordRepository {

private ApiInterface apiInterface;
private SharedPreferenceHelper sharedPreferenceHelper;
private Context context;

private WordDao wordDao;
private WordRoomDatabase db;

public WordRepositoryImpl(ApiInterface apiInterface, SharedPreferenceHelper sharedPreferenceHelper, WordRoomDatabase db, Context context) {
    this.apiInterface = apiInterface;
    this.sharedPreferenceHelper = sharedPreferenceHelper;
    this.context = context;
    this.db = db;
    wordDao = db.wordDao();
}


@Override
public Observable<Integer> sum(final int a, final int b) {
    return Observable.fromCallable(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return (a + b);
        }
    });
}

@Override
public Observable<List<Word>> getAllWords() {
    return Observable.fromCallable(new Callable<List<Word>>() {
        @Override
        public List<Word> call() throws Exception {
            List<Word> list = new ArrayList<>();
            List<com.example.data.models.Word> listWords = db.wordDao().getAllWords();
            for (com.example.data.models.Word item : listWords) {
                list.add(new Word(item.getWordId(), item.getWord(), item.getWordLength()));
            }
            return list;
        }
    });
}

@Override
public Observable<Boolean> insertThisWord(final Word word) {
    return Observable.fromCallable(new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            com.example.data.models.Word item = new com.example.data.models.Word(word.getWord(), word.getWordLength());
            db.wordDao().insertThisWord(item);
            return true;
        }
    });
}

@Override
public Observable<Boolean> deleteThisWord(final int wordId) {
    return Observable.fromCallable(new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            db.wordDao().deleteThisWord(wordId);
            return true;
        }
    });
}

@Override
public Observable<Boolean> updateThisWord(final int wordId, final String newWord) {
    return Observable.fromCallable(new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            db.wordDao().updateThisWord(wordId, newWord);
            return true;
        }
    });
}

@Override
public Observable<Word> getTheIndexOfTopWord() {
    return Observable.fromCallable(new Callable<Word>() {
        @Override
        public Word call() throws Exception {
            com.example.data.models.Word item = db.wordDao().getTheIndexOfTopWord();
            Word word = new Word(item.getWordId(), item.getWord(), item.getWordLength());
            return word;
        }
    });
}
}

GetAllWordsUseCase in Domain Layer:

public class GetAllWords extends UseCase<List<Word>, GetAllWords.Params> {

private WordRepository wordRepository;

public GetAllWords(PostExecutionThread postExecutionThread, WordRepository wordRepository) {
    super(postExecutionThread);
    this.wordRepository = wordRepository;
}

@Override
public Observable<List<Word>> buildUseCaseObservable(Params params) {
    return wordRepository.getAllWords();
}

public static final class Params {
    private Params() {
    }

    public static GetAllWords.Params getAllWords() {
        return new GetAllWords.Params();
    }
}
}

UseCase Base Class in domain layer:

public abstract class UseCase<T, Params> {

private final PostExecutionThread postExecutionThread;
private final CompositeDisposable compositeDisposable;

public UseCase(PostExecutionThread postExecutionThread) {
    this.postExecutionThread = postExecutionThread;
    this.compositeDisposable = new CompositeDisposable();
}

/**
 * Builds an {@link Observable} which will be used when executing the current {@link UseCase}.
 */
public abstract Observable<T> buildUseCaseObservable(Params params);

/**
 * Dispose from current {@link CompositeDisposable}.
 */
public void dispose() {
    if (!compositeDisposable.isDisposed()) {
        compositeDisposable.dispose();
    }
}

/**
 * Executes the current use case.
 *
 * @param observer {@link DisposableObserver} which will be listening to the observable build
 *                 by {@link #buildUseCaseObservable(Params)} ()} method.
 * @param params   Parameters (Optional) used to build/execute this use case.
 */
public void execute(DisposableObserver<T> observer, Params params) {
    if (observer != null) {
        final Observable<T> observable = this.buildUseCaseObservable(params)
                .subscribeOn(Schedulers.io())
                .observeOn(postExecutionThread.getScheduler());
        addDisposable(observable.subscribeWith(observer));
    }
}

/**
 * Dispose from current {@link CompositeDisposable}.
 */
private void addDisposable(Disposable disposable) {
    if (disposable != null && compositeDisposable != null)
        compositeDisposable.add(disposable);
}
}

Finally, WordActivity in Presentation Layer

public class WordActivity extends BaseActivity<WordViewModel> implements 
View.OnClickListener, WordNavigator {

@Inject
WordViewModel wordViewModel;

private Button deleteButton, updateButton, addButton;
private EditText editTextWord;
private WordListAdapter adapter;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_word);
    ((MainApplication) getApplicationContext()).getComponent().inject(this);
    editTextWord = findViewById(R.id.activity_word_et_word);
    deleteButton = findViewById(R.id.activity_main_delete_button);
    updateButton = findViewById(R.id.activity_main_update_button);
    addButton = findViewById(R.id.activity_word_btn_add_word);

    deleteButton.setOnClickListener(this);
    updateButton.setOnClickListener(this);
    addButton.setOnClickListener(this);


    RecyclerView recyclerView = findViewById(R.id.recyclerview);
    adapter = new WordListAdapter(this);
    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));

    getViewModel().setNavigator(this);

    getViewModel().getAllWords();

    getViewModel().getWords().observe(this, new Observer<List<Word>>() {
        @Override
        public void onChanged(@android.support.annotation.Nullable List<Word> words) {
            adapter.setWords(words);
        }
    });

}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.activity_main_delete_button:
            getViewModel().getTheIndexOfTopWord(TOP_INDEX_ACTION_DELETE);
            break;
        case R.id.activity_main_update_button:
            getViewModel().getTheIndexOfTopWord(TOP_INDEX_ACTION_UPDATE);
            break;
        case R.id.activity_word_btn_add_word:
            handleAddButtonClick();
            break;
    }
}

public void handleAddButtonClick() {
    String text = editTextWord.getText().toString();
    if (text.equals("")) {
        Toast.makeText(getApplicationContext(), R.string.empty_not_saved, Toast.LENGTH_LONG).show();
    } else {
        Word word = new Word(text, text.length());
        getViewModel().insertWord(word);
        editTextWord.setText("");
        editTextWord.clearFocus();
    }
}

@Override
public void updateTopIndex(Integer wordId, String action) {
    if (action.equals(TOP_INDEX_ACTION_DELETE))
        getViewModel().deleteThisWord(wordId);
    else
        getViewModel().updateThisWord(wordId, "dsakagdad");
}

@Override
public WordViewModel getViewModel() {
    return wordViewModel;
}
}


**getViewModel().getWords().observe(this, new Observer<List<Word>>() {
    @Override
    public void onChanged(@android.support.annotation.Nullable List<Word> words) {
        adapter.setWords(words);
    }
});**

//This portion is getting called only once but not when I 
  insert/update/delete words from room database!

Can anyone go through these and help me out here!

ganeshraj020794
  • 188
  • 2
  • 13

1 Answers1

0

This method in the DAO will query for the list and return it like a normal SQL query:

@Query("select * from word_table order by word_id asc")
List<Word> getAllWords();

If you want to observe for changes you might wanna consider using an RxJava2 Flowable/Observable or a LiveData for that.

As I prefer the RxJava approach, It will look like this:

@Query("select * from word_table order by word_id asc")
Flowable<List<Word>> getAllWords();
// or
Observable<List<Word>> getAllWords();

Difference between Flowable and Observable

With that done, you might wanna change the getAllWords method in the repository to return that Flowable/Observable.

Note: Either using an Observable or a Flowable both will emit the query result initially once and then start observing for further changes till unsubscribed to.

Read more about Room with RxJava

Ahmed Ashraf
  • 2,795
  • 16
  • 25
  • Hi, Thanks for your time but I am already returning Observable> from my repository by wrapping the room's List. If you look at my viewmodel, I am then pushing these observables into the LiveData but not getting updates on UI. I will try your solution but not sure if it will work. – ganeshraj020794 Aug 31 '18 at 05:56
  • Hey, it's done! Thanks for the valuable input. I used both Flowable and Single to control the UI changes. – ganeshraj020794 Aug 31 '18 at 10:00
  • Glad to be of help, the problem is conceptual, it's not the repository's problem, it's the DAO, List getAllWords(); means querying for the data once, on the other hand Flowable> getAllWords(); means observing the changes of the query. – Ahmed Ashraf Aug 31 '18 at 13:14
  • Not sure I understand this. Maybe I'm on the wrong question. What if instead of providing direct access to the DB objects, I want to provide a liveData of a converted list, instead? Meaning that instead of "Word" that exists in the DB, I would create a new class that is based on it, and I want to provide a liveData that will provide a list of it instead? – android developer Mar 09 '21 at 08:23
  • @androiddeveloper then you map the list to something else on repository level.. you can use the "map" operator for that http://reactivex.io/documentation/operators/map.html – Ahmed Ashraf Mar 09 '21 at 09:22
  • @AhmedAshraf I actually found this: https://proandroiddev.com/livedata-transformations-4f120ac046fc , meaning I can use `Transformations.map(` on the live data. Anyway, thanks. – android developer Mar 09 '21 at 09:30
  • @androiddeveloper yeah sure, if you're using LiveData for that then that's the way to go indeed – Ahmed Ashraf Mar 09 '21 at 12:37
  • @AhmedAshraf I have actually got some weird issue with this though, as the DB should be initialized on the background thread, while the trasnformation should be on the UI thread. If you can, please have a look: https://stackoverflow.com/q/66544908/878126 – android developer Mar 09 '21 at 12:47