Skip to content Skip to sidebar Skip to footer

How To Retrieve List Of Objects From Dao Via Livedata<> In View Model When There Is No Change To Trigger Onchange Method In View Model In Android

I am using android studios to build a dictionary app. Everything has went fairly well with help from the SO community and now I have another question. I have setup several custo

Solution 1:

LiveData solution

@Zohaib Amir's comment is correct. You can call LiveData#observe() on anywhere you want as long as you remember to clear the observers. One drawback with this approach is that you have to keep the reference to these observers and LiveData already has Transformations.switchMap() so that you can avoid that.

ViewModel

// Instance variable that stores the current dictionaryId that user wants to see.privatefinalMutableLiveData<Long> currentDictionaryId = new MutableLiveData<>();

// Instance variable that stores the current list of words. This will automatically change when currentDictionaryId value changes.privatefinalLiveData<List<Word>> words =Transformations.switchMap(currentDictionaryId, dictionaryId -> 
    mWordRepository.getWordByDictionaryId(dictionaryId));



// Set new dictionaryIdpublic void setDictionaryId(long dictionaryId) {
    currentDictionaryId.postValue(dictionaryId);
}

// Get LiveData to listen topublicLiveData<List<Word>> getWords() {
    return words;
}

Activity

// onCreate()// get all words from WordDao
    mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
    mWordViewModel.setDictionaryId(dictionaryId);
    mWordViewModel.getWords().observe(MainActivity.this, new Observer<List<Word>>() {
        @Overridepublic void onChanged(@Nullable List<Word> words) {
            mainAdapter.setWords(words);
            mAllWords = words;
            mainAdapter.notifyDataSetChanged();
        }
    });

    // note that the order of calling setDictionaryId and getWords doesn't matter. setDictionaryId is supposed to call at anytime.
// OnOptionsItemSelected
    ...
    changeDictionaryDialog.setView(view);
        changeDictionaryDialog.setPositiveButton("OK", newDialogInterface.OnClickListener() {
            @OverridepublicvoidonClick(DialogInterface dialog, int which) {
                finalStringdictionaryValue= dictionarySpinner.getSelectedItem().toString();

                for (Dictionary dictionary : mDictionaryList) {
                    if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                        dictionaryId = dictionary.getDid();
                        mWordViewModel.setDictionaryId(dictionaryId);
                        break;
                    }
                }
            }
        });
    ...

This time I prepared other solutions so that you can compare. Other solutions (almost) do the exact same thing.


Non-reactive solution

You can implement above feature based on callbacks, and this means that you have to manually handle lifecycle changes, notifications, and threading. Also, there is a functional difference that LiveData will automatically notify the observer when there is a change in the db, while this callback design is just one-time look up only.

In this example, we will use Executor to execute tasks in the background thread.

WordDao

@Query("SELECT * FROM word_table WHERE dictionary_id =:dictionaryId")public List<Word> getWordsByDictionaryID(long dictionaryId); // notice that livedata is gone.

WordRepository Database access must be done in a background thread. So when we access db, we need to switch to a background thread at some point. In this example, we will switch to the background thread in this layer.

// This executor is needed to run Runnables on a background thread. In real application, you may// create this executor outside of this repository and later inject it to this repository.privatefinalExecutorioExecutor= Executors.newSingleThreadExecutor();


privatevoidgetWordByDictionaryId(long dictionaryId, Consumer<List<Word>> callback) {
    ioExecutor.execute(() -> {
        List<Word> wordList = wordDao.getWordsByDictionaryId(dictionaryId);
        callback.accept(wordList);
    });
}

WordViewModel There isn't much ViewModel does in this example. Just pass the parameters to the repository.

publicvoidgetWordByDictionaryId(long dictionaryId, Consumer<List<Word>> callback){
    mWordRepository.getWordByDictionaryId(dictionaryId, callback);
}

Activity Note that Consumer#accept will run on a background thread. Therefore before you do anything with the ui, you need to switch back to the ui thread.

// onCreate
mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
mWordViewModel.getWordByDictionaryId(dictionaryId, words -> {
    runOnUiThread(() -> {
        if (isFinishing() || isDestroyed()) return; // if the activity is finishing, don't do anything.

        mainAdapter.setWords(words);
        mAllWords = words;
        mainAdapter.notifyDataSetChanged();
    });
});


// onOptionsItemSelected
changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    @Overridepublic void onClick(DialogInterface dialog, int which) {
        final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

        for (Dictionary dictionary : mDictionaryList) {
            if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                dictionaryId = dictionary.getDid();
                mWordViewModel.getWordByDictionaryId(dictionaryId, words -> {
                    runOnUiThread(() -> {
                        if (isFinishing() || isDestroyed()) return; // if the activity is finishing, don't do anything.

                        mainAdapter.setWords(words);
                        mAllWords = words;
                        mainAdapter.notifyDataSetChanged();
                    });
                });
                break;
            }
        }
    }
});


RxJava

RxJava solution will look much like the LiveData solution. It has many advantages over LiveData: it has lots of observables and operators to work with. These advantages are more apparent in more complex applications, where you need to do conditional or delayed async tasks, periodic tasks, multiple requests chained one after the other, error handlings, etc.

WordDao

@Query("SELECT * FROM word_table WHERE dictionary_id =:dictionaryId")public Flowable<List<Word>> getWordsByDictionaryID(long dictionaryId);

ViewModel

privatefinal BehaviorProcessor<Long> currentDictionaryId = BehaviorProcessor.create();

// Set new dictionaryIdpublicvoidsetDictionaryId(long dictionaryId) {
    currentDictionaryId.onNext(dictionaryId);
}

// Get Flowable to listen topublic Flowable<List<Word>> getWords() {
    return currentDictionaryId.switchMap(dictionaryId -> mWordRepository
        .getWordByDictionaryId(dictionaryId)
        // add ".take(1)" if you need one-time look up.
        .subscribeOn(Schedulers.io())); 
}

Activity

private fianl CompositeDisposable disposables = new CompositeDisposable();

// onCreate()
mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
mWordViewModel.setDictionaryId(dictionaryId);
disposables.add(mWordViewModel.getWords()
    .observeOn(AndroidSchedulers.mainThread()))
    .subscribe(words -> {
        mainAdapter.setWords(words);
        mAllWords = words;
        mainAdapter.notifyDataSetChanged();
    });


// onOptionsItemSelected()
 changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    @Override
    publicvoidonClick(DialogInterface dialog, int which) {
        final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

        for (Dictionary dictionary : mDictionaryList) {
            if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                dictionaryId = dictionary.getDid();
                mWordViewModel.setDictionaryId(dictionaryId);
                break;
            }
        }
    }
});

// onDestroy()
disposables.clear();

Post a Comment for "How To Retrieve List Of Objects From Dao Via Livedata<> In View Model When There Is No Change To Trigger Onchange Method In View Model In Android"