I'm implementing the viewModel and for communicate between the viewModel and fragment I'm doing this :
public class SplashViewModel extends AndroidViewModel {
private LiveData<Boolean> actions;
public SplashViewModel(#NonNull Application application) {
super(application);
actions= new MutableLiveData<>();
}
public void aViewModelMethod() {
//doing some stuff
if (stuff == X){
//I need to hide a view for exemple, I'm doing this
actions.postValue(true);
}
}
Now inside my Fragment I have an observable who is trigger when actions.postValue(true) is reached
viewModel.actions.observe(getViewLifecycleOwner(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean action) {
if (action){
databinding.myView.setVisibility(View.VISIBLE);
}
}
});
This work fine but if I have a lot of communication I need to implement each time a new variable, and observe it ?
It's ok when they are 4 or 5 but what I am suppose to do when they are hundreds ?
I try to change boolean by an integer with a switch and a list of actions, but when the viewModel is initialize it's possible that several postValue are trigger and when I created the observable I'm only get the last one, that make sense.
Usually, I have two observable live data in my view model. First is represent the state of the whole screen. Second I use for "single-shot" events like toasts, navigation, showing dialogs.
My view model:
class PinCreateViewModel(...) : ViewModel() {
val event = MutableLiveData<BaseEvent<String>>()
val state = MutableLiveData<PinCreateViewState>()
}
I have a single state object for the whole screen:
sealed class PinCreateViewState {
object FirstInput : PinCreateViewState()
data class SecondInput(val firstEnteredPin: String) : PinCreateViewState()
object Error : PinCreateViewState()
object Loading : PinCreateViewState()
}
I think with this approach it's easy to think about my screen states, easy to design my screen as a finite state machine, and easy to debug. Especially, I like this approach to very complex screens. In this case, I have a single source of truth for my whole screen state.
But sometimes I want to show dialogs, toast or open new screens. These things are not part of my screen state. And this is why I want to handle them separately. And in this case, I use Events:
sealed class BaseEvent(private val content: String) {
var hasBeenHandled = false
private set
fun getContentIfNotHandled(): String? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
fun peekContent(): String = content
}
class ErrorEvent(content: String) : BaseEvent(content)
class MessageEvent(content: String) : BaseEvent(content)
And my Fragment interaction with ViewModel looks like this:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
observe(viewModel.event, this::onEvent)
observe(viewModel.state, this::render)
}
private fun render(state: PinCreateViewState) {
when (state) {
PinCreateViewState.FirstInput -> setFirstInputState()
is PinCreateViewState.SecondInput -> setSecondInputState()
PinCreateViewState.Error -> setErrorState()
PinCreateViewState.Loading -> setLoadingState()
}
}
fun onEvent(event: BaseEvent<String>) {
event.getContentIfNotHandled()?.let { text ->
when (event) {
is MessageEvent -> showMessage(text)
is ErrorEvent -> showError(text)
}
}
}
I really like Kotlin Sealed classes because it forces me to handle all possible cases. And I can find unhandled states even before compilation.
PostValue method Posts a task to the main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
If you will have hundreds of communication between your fragment and viewModel so you just have to deduce your fragment logic like if you have to show view on some conditions then just observe one non mutable live data in your fragment and use two live data's mutable and another non mutable .... use non mutable to set that boolean on every sort of stuff and checks in your viewModel and in the beginning assign that live data to your non mutable one.
private val _liveData = MutableLiveData<Boolean>()
internal val liveData: LiveData<Boolean> = _liveData
It's the better approach ,i hope i understand you question better if not please elaborate it more so that i can help .
My activity has a Google's ViewModel that fetches some model items. These items are then transformed into adapter items of a RecyclerView. There are also many types of adapter items supported by one RecyclerView.
I would like to have separate view model object for each of these model objects so that I can have more complex logic encapsulated only within that "small" view model.
Currently when I have some asynchronous logic (that needs to be stopped in onCleared()) that is related only to some adapter item I have to somehow route callbacks through main view model so that everything is properly unregistered.
I was considering using ViewModelProvider::get(key, modelClass) but my items are changing over time and I can't find a nice way to "clear" old items.
How are you handling these cases in your projects?
Edit: To add more information about my concern, maybe in different words: I want my "small" ViewModel to live as long as the model item which it represents. It means that:
I must receive onCleared() callback in the same scenarios in which parent of these items receive
I must receive onCleared() callback when item is no longer
Edit: Please try to compare it to a ViewPager with Fragments as items. Every individual model item is represented as a Fragment with its ViewModel. I would like achieve something similar but for RecyclerView.
androidx.lifecycle.ViewModel's are not meant to be used on RecyclerView items by default
Why?
ViewModel is AAC (Android Architecture Component) whose sole purpose is to survive configuration changes of Android Activity/Fragment lifecycle, so that data can be persisted via ViewModel for such case.
This achieved by caching VM instance in storage tied to hosting activity.
That's why it shouldn't be used on RecyclerView (ViewHolder) Items directly as the Item View itself would be part of Activity/Fragment and it (RecyclerView/ViewHolder) doesn't contain any specific API to provide ViewModelStoreOwner (From which ViewModels are basically derived for given Activity/Fragment instance).
Simplistic syntax to get ViewModel is:
ViewModelProvider(this).get(ViewModel::class.java)
& here this would be referred to Activity/Fragment context.
So even if you end up using ViewModel in RecyclerView Items, It would give you same instance due to context might be of Activity/Fragment is the same across the RecyclerView which doesn't make sense to me. So ViewModel is useless for RecyclerView or It doesn't contribute to this case much.
TL;DR
Solution?
You can directly pass in LiveData object that you need to observe from your Activity/Fragment's ViewModel in your RecyclerView.Adapter class. You'll need to provide LifecycleOwner as well for you adapter to start observing that given live data.
So your Adapter class would look something like below:
class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
init {
liveDataToObserve.observe(lifecycleOwner) { t ->
// Notify data set or something...
}
}
}
If this is not the case & you want to have it on ViewHolder class then you can pass your LiveData object during onCreateViewHolder method to your ViewHolder instance along with lifecycleOwner.
Bonus point!
If you're using data-binding on RecyclerView items then you can easily obtain lifecyclerOwner object from your binding class. All you need to do is set it during onCreateViewHolder() something like below:
class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder: ViewHolder {
// Some piece of code for binding
binding.lifecycleOwner = this#RecyclerViewAdapter.lifecycleOwner
// Another piece of code and return viewholder
}
}
class ViewHolder(private val someLiveData: LiveData<T>, binding: ViewDataBinding): RecyclerView.ViewHolder(binding.root) {
init {
someLiveData.observe(requireNotNull(binding.lifecycleOwner)) { t->
// set your UI by live data changes here
}
}
}
So yes, you can use wrapper class for your ViewHolder instances to provide you LiveData out of the box but I would discourage it if wrapper class is extending ViewModel class.
As soon as concern about mimicking onCleared() method of ViewModel, you can make a method on your wrapper class that gets called when ViewHolder gets recycled or detaches from window via method onViewRecycled() or onViewDetachedFromWindow() whatever fits best in your case.
Edit for comment of #Mariusz: Concern about using Activity/Fragment as LifecycleOwner is correct. But there would be slightly misunderstanding reading this as POC.
As soon as one is using lifecycleOwner to observe LiveData in given RecyclerViewHolder item, it is okay to do so because LiveData is lifecycle aware component and it handles subscription to lifecycle internally thus safe to use. Even if you can explicitly remove observation if wanted to, using onViewRecycled() or onViewDetachedFromWindow() method.
About async operation inside ViewHolder:
If you're using coroutines then you can use lifecycleScope from lifecycleOwner to call your operation and then provide data back to particular observing LiveData without explicitly handling clear out case (LifecycleScope would take care of it for you).
If not using Coroutines then you can still make your asyc call and provide data back to observing LiveData & not to worry about clearing your async operation during onViewRecycled() or onViewDetachedFromWindow() callbacks. Important thing here is LiveData which respects lifecycle of given LifecycleOwner, not the ongoing async operation.
Don't know if google has nice support for nested ViewModel's, looks like not.
Thankfully, we don't need to stick to androidx.lifecycle.ViewModel to apply MVVM approach where we need. And there is a small example I decided to write:
Fragment, nothing changes:
#Override public void onCreate(#Nullable Bundle savedInstanceState) {
final ItemListAdapter adapter = new ItemListAdapter();
binding.getRoot().setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
viewModel.getItems().observe(getViewLifecycleOwner(), adapter::submitList);
}
ItemListAdapter, in addition to populate view, it also becomes responsible for notifying item's observers - should they continue to listen, or not. In my example adapter was ListAdapter which extends RecyclerView.Adapter, so it receives list of items. This is unintentionally, just edited some code I already have. It's probably much better to use different base implementation, but it's acceptable for demonstration purposes:
#Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(parent);
}
#Override public void onBindViewHolder(Holder holder, int position) {
holder.lifecycle.setCurrentState(Lifecycle.State.RESUMED);
holder.bind(getItem(position));
}
#Override public void onViewRecycled(Holder holder) {
holder.lifecycle.setCurrentState(Lifecycle.State.DESTROYED);
}
// Idk, but these both may be used to pause/resume, while bind/recycle for start/stop.
#Override public void onViewAttachedToWindow(Holder holder) { }
#Override public void onViewDetachedFromWindow(Holder holder) { }
Holder. It implements LifecycleOwner, which allows to unsubscribe automatically, just copied from androidx.activity.ComponentActivity sources so all should be okay :D :
static class Holder extends RecyclerView.Holder implements LifecycleOwner {
/*pkg*/ LifecycleRegistry lifecycle = new LifecycleRegistry(this);
/*pkg*/ Holder(ViewGroup parent) { /* creating holder using parent's context */ }
/*pkg*/ void bind(ItemViewModel viewModel) {
viewModel.getItem().observe(this, binding.text1::setText);
}
#Override public Lifecycle getLifecycle() { return lifecycle; }
}
List view-model, "classique" androidx-ish ViewModel, but very rough, also provide nested view models. Please, pay attention, in this sample all view-models start to operate immediately, in constructor, until parent view-model is commanded to clear! Don't Try This at Home!
public class ItemListViewModel extends ViewModel {
private final MutableLiveData<List<ItemViewModel>> items = new MutableLiveData<>();
public ItemListViewModel() {
final List<String> list = Items.getInstance().getItems();
// create "nested" view-models which start background job immediately
final List<ItemViewModel> itemsViewModels = list.stream()
.map(ItemViewModel::new)
.collect(Collectors.toList());
items.setValue(itemsViewModels);
}
public LiveData<List<ItemViewModel>> getItems() { return items; }
#Override protected void onCleared() {
// need to clean nested view-models, otherwise...
items.getValue().stream().forEach(ItemViewModel::cancel);
}
}
Item's view-model, using a bit of rxJava to simulate some background work and updates. Intentionally I do not implement it as androidx....ViewModel, just to highlight that view-model is not what google names ViewModel but what behaves as view-model. In actual program it most likely will extend, though:
// Wow, we can implement ViewModel without androidx.lifecycle.ViewModel, that's cool!
public class ItemViewModel {
private final MutableLiveData<String> item = new MutableLiveData<>();
private final AtomicReference<Disposable> work = new AtomicReference<>();
public ItemViewModel(String topicInitial) {
item.setValue(topicInitial);
// start updating ViewModel right now :D
DisposableHelper.set(work, Observable
.interval((long) (Math.random() * 5 + 1), TimeUnit.SECONDS)
.map(i -> topicInitial + " " + (int) (Math.random() * 100) )
.subscribe(item::postValue));
}
public LiveData<String> getItem() { return item; }
public void cancel() {
DisposableHelper.dispose(work);
}
}
Few notes, in this sample:
"Parent" ViewModel lives in activity scope, so all its data (nested view models) as well.
In this example all nested vm start to operate immediately. Which is not what we want. We want to modify constructors, onBind, onRecycle and related methods accordingly.
Please, test it on memory leaks.
Although that is true that Android uses ViewModels in Android Architecture Components it does not mean that they are just part of AAC. In fact, ViewModels are one of the components of the MVVM Architecture Pattern, which is not Android only related. So ViewModel's actual purpose is not to preserve data across Android's lifecycle changes. However, because of exposing its data without having a View's reference makes it ideal for the Android specific case in which the View can be recreated without affecting to the component that holds its state (the ViewModel). Nonetheless, it has other benefits such as facilitating the Separation of Concerns among others.
It is also important to mention that your case can not be 100% compared to the ViewPager-Fragments case, as the main difference is that the ViewHolders will be recycled between items. Even if ViewPager's Fragments are destroyed and recreated, they will still represent the same Fragment with that same data. That is why they can safely bind the data provided by their already existing ViewModel. However, in the ViewHolder case, when it is recreated, it can be representing a totally new item, so the data its supposed ViewModel could be providing may be incorrect, referencing the old item.
That being said you could easily make the ViewHolder become a ViewModelStoreOwner:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ViewModelStoreOwner {
private var viewModelStore: ViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = viewModelStore
}
This can still be useful if the data provided by the ViewModel is the same independently of the ViewHolder's item (shared state between all items). However, if that is not the case, then you would need to invalidate the ViewModelStore by calling viewModelStore.clear() and create a new ViewModel instance probably in ViewHolder's onViewRecycled. You will loose the advantage of keeping the state no matter the view's lifecycle, but can sometimes still be useful as to follow Separation of Concerns.
Finally, regarding to the option of using a LiveData instance to control the state, no matter if it is provided by a ViewHolder's shared or specific ViewModel or it is passed through the Adapter, you will need a LifecycleOwner to observe it. A better approach to using the current Fragment or Activity lifecycle is to just use the specific ViewHolder's actual lifecycle, as they are actually created and destroyed, by making them implement the LifecycleOwner interface. I created a small library which does exactly that.
I followed this wonderfull answer HERE by aeracode with a one exception. Instead of ViewModel I've used Rx BehaviourSubject that work perfectly for me.
In case of coroutines You can use alternatively StateFlow.
clas MyFragment: Fragment(){
private val listSubject = BehaviorSubject.create<List<Items>>()
...
private fun observeData() {
viewModel.listLiveData.observe(viewLifecycleOwner) { list ->
listSubject.onNext(list)
}
}
}
RecyclerView
class MyAdapter(
private val listObservable: BehaviorSubject<List<Items>>
) : RecyclerView.Adapter<MyViewHolder>() {
[...]
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindToData(getItem(position))
}
override fun onViewRecycled(holder: MyViewHolder) {
holder.onViewRecycled()
}
...
class MyViewHolder(val binding: LayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
private var disposable: Disposable? = null
fun bindToData(item: Item) = with(binding) {
titleTv.text = item.title
disposable = listObservable.subscribe(::setItemList) <- Here You listen
}
fun onViewRecycled() {
disposable?.dispose()
}
}
I'm trying to use two-way databinding on a EditText, that works fine if I expose the field as MutableLiveData as it is usually seen on examples I found online.
However there are good reasons not to expose MutableLiveData and those reasons aren't magically invalid because I decided to use the databinding library.
EDIT: The main motivation here is MyViewModel should remain in control of setting data (this is the reason why it is not recommended to expose MutableLiveData directly), in the setter I can perform whatever checks or transformations necessary and then just call setValue on the LiveData.
I usually expose a LiveData getter and a separate setter from my ViewModel, I tried to get this working with two-way data binding by using the InverseMethod() annotation, but that won't really work because databinding is looking for a InverseMethod to getValue() of the LiveData itself.
Here is a simple example:
public class MyViewModel extends ViewModel {
private MutableLiveData<String> mEmail = new MutableLiveData<>();
// #InverseMethod("setEmail") ### THIS DOESN'T WORK
public LiveData<String> getEmail() {
return mEmail;
}
// ### I WANT DATA-BINDING TO USE THIS METHOD
public void setEmail(String email) {
if (mEmail.getValue() != email) {
mEmail.setValue(email);
}
}
}
and this how a want to bind it
<EditText
android:id="#+id/input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={viewmodel.email}"/>
the only workaround so far that works is using one-way data-binding to set the text on the EditText and then attach a TextWatcher and call my ViewModel.setter from there.
EDIT:
second workaround is to extend MutableLiveData and then do the checks and transformations in an overridden setValue ... that's a lot of boilerplate to write.
We have been recommended to switch from ObservableField to LiveData for data binding because it is lifecycle-aware. We have also been recommended not expose MutableLiveData because the view model should control assignment.
This works perfectly for one-way data binding and in those cases I would only expose LiveData.
We want to use two-way data binding, which by definition moves assignment from the view model to the UI, so I think in this case exposing MutableLiveData is correct. I say this as we are doing it on purpose because we want our UI to be able to assign values so that we have cleaner views.
I've forgotten about this issue for a while, but as a workaround I've extended MutableLiveData slightly and use this instead every time I need control over the setter.
import androidx.core.util.Consumer;
import androidx.lifecycle.MutableLiveData;
public class DelegatedMutableLiveData<T> extends MutableLiveData<T> {
private Delegate<T> mDelegate;
public interface Delegate<T> {
void onSet(T value, Consumer<T> setter);
}
public DelegatedMutableLiveData(Delegate<T> delegate) {
mDelegate = delegate;
}
public DelegatedMutableLiveData(T value, Delegate<T> delegate) {
super(value);
mDelegate = delegate;
}
#Override
public void setValue(T value) {
if (mDelegate != null) {
mDelegate.onSet(value, super::setValue);
} else {
super.setValue(value);
}
}
}
now use DelegatedMutableLiveData as follows:
private final DelegatedMutableLiveData<Integer> mMyLiveData = new DelegatedMutableLiveData<>((value, setter) -> {
// do your checks and change value if necessary
setter.accept(value); // or don't accept if you don't want to change the current value
});
I had exactly the same question, that´s how i found yours.
I know it´s not exactly what you are looking for but an option would be to observe mEmail from your ViewModel, and implement your setEmail() code in it (after the value itself has been set of course... i don´t know how to control setting the value which is what you are looking for i guess)
val observer = Observer<String> { setEmail(it)}
fun setEmail(value:String){ //Your code }
init{
mEmail.observeForever(observer)
}
//Don´t forget to remove the observer
I'm following the google tutorial for Room persistence but i'm stuck, right now I have the tutorial all working fine but I need to expand it and be able to pass parameters to the ViewModel because what I need is to be able to submit different queries to the repo, and maybe i'm wrong but right now i'm doing it in the ViewModel which should be able to read his field and choose the right method to talk with the repo.
WordViewModel:
public class WordViewModel extends AndroidViewModel {
private WordRepository mRepository;
private LiveData<List<Word>> mAllWords;
public int mode = 0;
public WordViewModel (Application application) {
super(application);
mRepository = new WordRepository(application);
if (mode==0)
mAllWords = mRepository.getAllWords();
else
mAllWords = mRepository.getSomethingElse();
}
LiveData<List<Word>> getAllWords() { return mAllWords; }
public void insert(Word word) { mRepository.insert(word); }
}
Then in the activity the triggers the model view we got this
mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);
mWordViewModel.mode=1; //MY ADDITION, not working
...
mWordViewModel.getAllWords().observe(this, new Observer<List<Word>>() {
#Override
public void onChanged(#Nullable final List<Word> words) {
// Update the cached copy of the words in the adapter.
adapter.setWords(words);
}
});
...
Now the problem is that the field access and edit (the "mode" field) i've made is not working, it's like the field is getting resetted when the ViewModel is actually called and so it's always 0. What am i Missing? What is the easiest workaround considering that mode is just for explaining and eventually i'll need a lot of parameters (so creating various ViewModel is not an option)
I think you're running in to issues related to lifecycle of ViewModel itself and different variables etc you're using. I'd recommend using something like MediatorLiveData for what you're trying to do...for example (this is in Kotlin btw as that's what I'm using for similar logic I have)
class WordViewModel : ViewModel() {
.....
val mode: MutableLiveData<Int> = MutableLiveData()
val mAllWords = MediatorLiveData<List<Word>>().apply {
this.addSource(mode) {
if (mode.value == 0)
this.value = mRepository.getAllWords()
else
this.value = mRepository.getSomethingElse()
}
}
init {
mode.value = 0
}
fun setMode(m: Int) {
mode.value = m
}
}
The code where I'm doing this here is https://github.com/joreilly/galway-bus-android/blob/master/base/src/main/java/com/surrus/galwaybus/ui/viewmodel/BusStopsViewModel.kt
I'm trying to implement a simple App using Architecture Components.
I can get the info from RestApi services using Retrofit2.
I can show the info in the respective Recyclerview and when I rotate the phone everything works as it should.
Now I want to filter by a new kind of object (by string)
Can someone guide me a little with the ViewModel, I don't know what is the best practice to do that...
I'm using MVVM...
This is my ViewModel:
public class ListItemViewModel extends ViewModel {
private MediatorLiveData<ItemList> mList;
private MeliRepository meliRepository;
/* Empty Contructor.
* To have a ViewModel class with non-empty constructor,
* I have to create a Factory class which would create instance of you ViewModel and
* that Factory class has to implement ViewModelProvider.Factory interface.
*/
public ListItemViewModel(){
meliRepository = new MeliRepository();
}
public LiveData<ItemList> getItemList(String query){
if(mList == null){
mList = new MediatorLiveData<>();
LoadItems(query);
}
}
private void LoadItems(String query){
String queryToSearch = TextUtils.isEmpty(query) ? "IPOD" : query;
mList.addSource(
meliRepository.getItemsByQuery(queryToSearch),
list -> mList.setValue(list)
);
}
}
UPDATE
I resolved this using transformation a package from lifecycle library...
enter link description here
public class ListItemViewModel extends ViewModel {
private final MutableLiveData<String> mQuery = new MutableLiveData<>();
private MeliRepository meliRepository;
private LiveData<ItemList> mList = Transformations.switchMap(mQuery, text -> {
return meliRepository.getItemsByQuery(text);
});
public ListItemViewModel(MeliRepository repository){
meliRepository = repository;
}
public LiveData<ItemList> getItemList(String query){
return mList;
}
}
#John this is my solution. I'm using lifecycle library and the solution was easier than I thought. Thx!
I'm more familiar with doing this in Kotlin but you should be able to translate this to Java easily enough (or perhaps now is a good time to start using Kotlin :) )....adapting similar pattern I have here I believe you'd do something like:
val query: MutableLiveData<String> = MutableLiveData()
val mList = MediatorLiveData<List<ItemList>>().apply {
this.addSource(query) {
this.value = meliRepository.getItemsByQuery(query)
}
}
fun setQuery(q: String) {
query.value = q
}
I'm using this pattern in following https://github.com/joreilly/galway-bus-android/blob/master/app/src/main/java/com/surrus/galwaybus/ui/viewmodel/BusStopsViewModel.kt