I want to make a custom view in android with MVVM architecture. First of all, I want to ask, is ViewModel working perfectly with a custom view as it works in case of activity or fragment? Can we get ViewModel from ViewModel provider in a custom view?
If I need to make a separate custom view what will the correct approach?
A better alternative would be to use the new API view.findViewTreeViewModelStoreOwner() which gives you the viewModelStoreOwner(Fragment if view is attached to the fragment o/w activity)
You can create ViewModelProvider and then get the ViewModel.
Below is an example of code in Kotlin
private val viewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(viewModelStoreOwner).get(ViewModel::class.java)
}
Similarly, there are other similar APIs like view.findViewTreeLifecycleOwner() and view.findViewTreeSavedStateRegistryOwner()
It is a much cleaner approach as you don't have to typecast your context into Activity or Fragment and will scale to other implementations of ViewModelStoreOwner as well.
One thing to note here is that view may have a shorter life span compared to Activity/Fragment and so you might have to make a custom view Lifecycle(so that your LiveData subscription gets managed properly) using LifecycleRegistry based on onAttachedToWindow and onDetachedFromWindow callbacks
Q: Can we get ViewModel from ViewModel provider in a custom view?
Ans: Simple answer would be yes you can !
But How? (Further explanation) ViewModelProviders required either context as Activity or Fragment. So you can retrieve context from your CustomView class using getContext() which would be Activity/Fragment where you're using it.
Cast that context to either of type & provide it to ViewModelProviders which will give you object of that Activity/Fragment container.
Hence using like this, you can share ViewModel between your CustomView and Activity/Fragment.
Side Note: You can also make your CustomView implement LifeCycleObserver, in such way you can also make your view respect lifecycle of Activity/Fragment for initialization/destruction stuffs.
Related
In this link it is instructed to use viewModel() in any composable and in an activity, we will get the same object while calling viewModel(). Although it is instructed to use viewModel() inside a composable, I was able to use it in setContent{} (outside of any composable) also.
In this link it is instructed to use viewModels() in an activity or a fragment to get the object of a class that extends ViewModel.
In both of the cases, we are getting an object of a class that extends ViewModel. So, why do we need to use two different approaches (viewModel() and viewModels())?
If you ask if you can use only viewModel without viewModels in Compose, the answer is yes. But in some cases it is more convenient to use both of them.
viewModels belongs to the androidx.activity package, is an extension to ComponentActivity, and has nothing to do with Compose. It was used in view-based Android and can still be used with Compose when you need to initialize or update your view model with some activity-specific callbacks.
In turn, viewModel is part of Compose and allows you to easily create/access a view model from any Composable.
You can call it directly inside setContent since it already belongs to the composable scope, but you would not be as comfortable calling it anywhere else in the activity, such as in onActivityResult(I know it's deprecated, it's just an example). You can still do it as shown in this answer, but in some cases viewModels may be easier to use.
I want to pass a LiveData as is to BindingAdapter.
Inside the BindingAdapter I want to do a Transformations.map and display different options the user can select and when clicked send the result back using the same LiveData.
In order to observe the LiveData in the BindingAdapter I need access to the LifecycleOwner, ideally of the fragment view. I need that if I want to call .observe on the liveData or set the LifecycleOwner on the new binding I create inside the BindingAdapter.
Any idea how I can do that?
Firstly, I'd like to recommend you not to go down the road of placing too much of your business logic into your BindingAdapters. Besides it being a good practice to use binders to simply set style attributes, I have personally seen spaghetti-code disasters made by placing too much logic into the adapters. This is a really sketchy practice as the code is run for every element that listens to your binding, every time your livedata changes so your logic can get pretty hectic, very quickly and your app performance can be decreased quite swiftly, too.
Having said that, I don't think you should be passing in LiveData into your binding but instead the Object E that is being held in your livedata. This way you can:
keep your work inside the fragment that both has a LifecycleOwner and is the recommended way to observe for changes
pass into your LiveData instance the result of your Transformation and
display it in your UI by receiving it directly through your binding
This way your adapter has only the logic to display the result/results and all the work is being handled correctly by the fragment.
ViewModel Implementation
If you'd like to take this a step further, following Google's recommended Architecture Components, I'd suggest you to place the logic inside your ViewModel (should you be following the MVVM pattern) and avoid the use of the fragment altogether. You'd place the LiveData variable inside your viewModel (say var itemColor: LiveData<Int> = MutableLiveData<Int>(R.color.colorPrimary)) , connect it to your binding through xml
i.e.
app:showColor="#{viewModel.itemColor}"
and place all the logic of your Transformations inside a function in the viewModel. Setting the value to itemColor would directly send the value to your bindingadapter (showColor) and you could use the value as needed without even touching the fragment or observing the variable!
Note: please remember to set the lifecycleOwner to your binding inside the fragment as so : binding.lifecycleOwner = this, otherwise the adapter will not listen for changes.
I hope this helped, Panos.
I would like to refactor my Custom View to use android architecture components. However, I see that
ViewModelProviders.of(...)
takes only Activity or fragment. Any idea how to make it work? Should I use fragment instead of Custom View?
It is possible to get obtain a ViewModel instance in View, although it's not recommended. As per this post:
While it is easy enough to obtain the ViewModels inside an Activity or a Fragment, it is not straightforward to obtain this instance inside a View. The main reason behind this is because Views are supposed to be independent of all processing and even if all your logic is inside a ViewModel, the fact that you are accessing that ViewModel inside the View makes it reliant on something that it shouldn't. The recommended way of controlling a View is to pass parameters to it based on the state of the ViewModel from the Fragment or the Activity.
The point is to try to get an Activity from the context:
override val activity: FragmentActivity by lazy {
try {
context as FragmentActivity
} catch (exception: ClassCastException) {
throw ClassCastException("Please ensure that the provided Context is a valid FragmentActivity")
}
}
override var viewModel = ViewModelProvider(activity).get(SharedViewModel::class.java)
As mentioned thought, I'd try to avoid this approach if possible.
I was wondering whether android binding is compatible with live data on conceptual level.
There is a simple task: call server after button is clicked.
So in my view I have
android:onClick="#{viewmodel::onOrderButtonClick}"
and proper onOrderButtonClick(View) method is defined in ViewModel.
But in order to make server call via LiveData I need my Fragment reference (observe() method needs LifecycleOwner instance as first parameter).
Of course I cannot hold reference to fragment in my ViewModel.
What is the pattern here? Do I really need to implement all event methods in the fragment class and delegate them back into view model class?
After some digging there is a bad news and a good one.
The bad news is that the fragment has to be used anyway (there is always some code in the fragment for each livedata event)
The good one is that it can be done relatively clean:
Call getOrderObservable() from fragment to view model. It returns
MutableLiveData<> created in view model's ctor.
Then call observe() on that observable In view model's onOrderButtonClick()
In onOrderButtonClick() in view model just call setValue()
That solution in my opinion minimalizes amount of code in the fragment. Still it looks not so elegant to separate making the network call and handling the result
I learned how to create Application-scoped ObjectGraph, and #Inject into my Views from it.
Then I learned how to create Activity-scoped ObjectGraph, and #Inject into my View's from an Activity's Context.
Now I need to learn how to create a Fragment-scoped ObjectGraph, since Fragment is not a Context and is not injected into my Views by the LayoutInflater.
As I see the problem: I need a way to reference a Fragment from inside a View, but I think this is essentially wrong, and I need another solution
I ended up using Mortar for that https://github.com/square/mortar