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
Related
Is there a possibility to modify Lombok's #Getter for one type of field? There is a MutableLiveData that is child of LiveData. I want Lombok to create getters for MutableLiveData fields that return LiveData not MutableLiveData. I hope you understand what I mean.
To picture what I'm talking about I am adding some code:
public class ViewModelAccount extends ViewModel {
private MutableLiveData<String> selectedLanguagesReadable = new MutableLiveData<>();
public LiveData<String> getSelectedLanguagesReadable() {
return selectedLanguagesReadable;
}
}
It is about MVVM pattern in Android and removing boilerplate code in ViewModels. Thanks.
This answer might help you,
#Getter(AccessLevel.NONE) private boolean hasObject;
public boolean hasObject() {
return hasObject;
}
i found it here Edit lombok getter method name for boolean member having prefix "has"
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 used the code showen below to not expose the MutableLiveData in the main activity
class CalculatorViewModel : ViewModel(){
private val operation = MutableLiveData<String>()
val stringOperation :LiveData<String>
get() = operation
}
but I figured out a way to access the value of MutableLiveData via the LiveData getter and even change it and this is my code to do it:
(viewModel.stringOperation as MutableLiveData).value = ""
MutableLiveData is supposed to be used when you need to modify LiveData outside of your viewmodel. If you don't want it to get exposed from ViewModel, you must use LiveData.
If you look at LiveData class, you will notice that LiveData is an Abstract class, which means you have to extend it before you can use it. For cases such as yours, you would extend LiveData class. In that child class, you would for example call api and and update the value using private methods. So basically your LiveData class will be responsible for loading and updating the data.
Check out this link for an example:
https://developer.android.com/topic/libraries/architecture/livedata#extend_livedata
This is how to properly use LiveData class as per documentation:
public class StockLiveData extends LiveData<BigDecimal> {
private StockManager stockManager;
private SimplePriceListener listener = new SimplePriceListener() {
#Override
public void onPriceChanged(BigDecimal price) {
setValue(price);
}
};
public StockLiveData(String symbol) {
stockManager = new StockManager(symbol);
}
#Override
protected void onActive() {
stockManager.requestPriceUpdates(listener);
}
#Override
protected void onInactive() {
stockManager.removeUpdates(listener);
}
}
To sum up, Use MutableLiveData when you want to modify data outside of LiveData/ViewModel. For all other cases, provide your own implementation of LiveData.
I have a complex screen in my project which I'm breaking in more than one fragment. I'm trying to follow the MVVM architecture for these classes, so which fragment has its own ViewModel and Contract class.
The issue is that all the ViewModels needs the same object instance (let's call it Book) to do Room transactions.
Does it have a correct way to share data (or LiveData) between ViewModels? I know the concept of Shared ViewModel but I don't know if I can apply it to this case. I also thought about using MediatorLiveData but didn't get a good approach on it too.
I'm thinking about having a class (let's call BookObservableProvider) with a LiveData<Book> (or Rx Subject<Book>) where each ViewModel injects the same instance and load/update always the same value.
Is it a good approach?
In my personal opinion your approach is not bad for this situation, but if want to try something else, I can advise you RxBus method. Here is a great article about it. With this approach you can simply publish data in activity, which holds fragments, and then listen to this particular event in all your fragments.
Something like :
//Activity
RxBus.publish(RxEvent.EventOnBookProvide(bookObject)
and
//Fragment
RxBus.listen(RxEvent.EventOnBookProvide::class.java).subscribe {
useObject(it)
}
And don't forget to use Disposable and .dispose() it in onDestroy() if using Activity and onDestroyView() if using fragment.
You should share those data between fragments/activities (maybe using Intents for activities) , and than handle those data by the other ViewModel
The answer is as usual, it depends.
If the reason behind your question is Room access, then it is recmended to have a DataRepository class that handles all Database access and you just pass that repository singleton to each AndroidViewModel.
mRepository = ((MainApp) application).getRepository();
In MainApp:
public DataRepository getRepository() {
return DataRepository.getInstance(getDatabase(), mAppExecutors);
}
And the Repository:
public class DataRepository {
private static DataRepository sInstance;
private MediatorLiveData<String> mObservableString;
private DataRepository(final AppDatabase database, final AppExecutors executors) {
mObservableString.addSource(database.myDao().loadString(),
mString -> {
if (database.getDatabaseCreated().getValue() != null) {
mObservableString.postValue(mString);
}
});
}
public static DataRepository getInstance(final AppDatabase database, final AppExecutors executors) {
if (sInstance == null) {
synchronized (DataRepository.class) {
if (sInstance == null) {
sInstance = new DataRepository(database, executors);
}
}
}
return sInstance;
}
// and then your access methods
public LiveData<String> getString() {
return mObservableString;
}
In the repository it is recommended to have a MediatorLivedata if you want to change the reference (source). Otherwise a normal LiveData does the job.
Regarding ViewModels:
In theory each fragment gets it's own Viewmodel. And if you get it by using requireActivity() as reference, you can get each ViewModel everywhere and have it therefore shared.
As an example:
viewModelA = new ViewModelProvider(requireActivity()).get(ViewModelA.class);
viewModelB = new ViewModelProvider(requireActivity()).get(ViewModelB.class);
This you could call in every Fragment and get the same ViewModel instances. If the DataRepository setup seems overkill to you, make one ViewModel with Room access and load it from every Fragment.
I am facing the same issue. But if you don't have different view models for the various fragments or the design does not necessitate using different view models for the various fragments you can just share one fragment between the entire activity( all the other fragment) and they will all share the same data instances.
follow this link for more https://developer.android.com/guide/fragments/communicate
what you need to do is make sure all the fragments initiate the view model(main view model) with the same context.
public class FilterFragment extends Fragment {
private ListViewModel viewModel;
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
// Update the selected filters UI
});
}
}
take note of this
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
requireActivity() makes sure all fragments call the context of the host activity.
you can't share data with activities this way though since the view model instance is destroyed when the activity is destroyed
I have developed app base on android data binding library: https://developer.android.com/topic/libraries/data-binding/index.html
class SignInViewModel extends BaseObservable {
#Bindable
public String getLogin() {
return login;
}
#Bindable
public String getPassword() {
return password;
}
}
and now I want to use ViewModelProviders from new library:
https://developer.android.com/topic/libraries/architecture/guide.html
SignInViewModel signInViewModel = ViewModelProviders.of(this).get(SignInViewModel.class);
How it combine? any idea? or should be combined these two libraries?
Edit
I change to:
class SignInViewModel extends ViewModel {
public ObservableField<String> login = new ObservableField<>("");
public ObservableField<String> password = new ObservableField<>("");
}
and now compiles, but question is: is it right way?
It's a known incompatibility. You can't extend BaseObservable and AndroidViewModel at the same time, so you can't use #Bindable making two-way data binding impossible*.
This will be fixed after arch components 1.0 final (on the data binding side).
*Edit: You can make your own ObservableViewModel: https://gist.github.com/JoseAlcerreca/4b66f9953d50b483d80e6b9ad7172685
Maybe this didn't exist when when the question was asked, but there is another option explained in this article: https://medium.com/google-developers/android-data-binding-observability-9de4ff3fe038
Basically instead of extending from BaseObservable you can implement android.databinding.Observable.
It's slightly more work as you need to also do the following:
Create this variable in your model class
private PropertyChangeRegistry registry = new PropertyChangeRegistry();
Implement the overriden methods like this
#Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
registry.add(callback);
}
#Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
registry.remove(callback);
}
Replace all the "BR" calls with these:
registry.notifyChange(this, BR.bar);
Everything else works the same as extending from BaseObservable. So I think maybe this is the solution that Jose might have been alluding to which probably wasn't available back then. It seems to work.
Update: As Eugene Brusov has mentioned, you can now use LiveData with data binding. This is what I'm doing now and it's much easier with less boilerplate. See https://developer.android.com/topic/libraries/data-binding/architecture.
It's possible with Android Studio 3.1 Canary 6 (https://androidstudio.googleblog.com/2017/12/android-studio-31-canary-6-is-now.html):
You can now use a LiveData object as an observable field in data binding expressions. The ViewDataBinding class now includes a new setLifecycle method that you need to use to use to observe LiveData objects.
You can find more details and sample in this Medium post.
This can be also solved using a wrapper:
class SignInViewModelWrapper extends ViewModel {
public final SignInViewModel model = new SignInViewModel();
}
class SignInViewModel extends BaseObservable {
#Bindable
public String getLogin() {
return login;
}
#Bindable
public String getPassword() {
return password;
}
}
You can then get the view model like this:
SignInViewModel signInViewModel = ViewModelProviders.of(this).get(SignInViewModelWrapper.class).model;