Android ViewModel for a custom view - android

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.

Related

Why is viewModel() used in a composabe and viewModels() in an activity or fragment?

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.

How to run CoroutineScope in RecyclerViewCursorAdapter on ViewHolder inside bindCursor{ }

Note: I had implemented Coroutine Single Scope in RecyclerViewCursorAdapter but the app getting too slow when I change it to runBlocking it's working fine
Is there any way to execute CoroutineScope in ViewHolder inside bindCursor{ } on every scroll without impacting on performance ?
I need to execute multiple queries from the database on every scroll and update the UI accordingly.
I am pretty sure, there is a better way of doing what you are doing than to do it in adapter. I don't think that is correct, maybe you can use a callback via an interface or something.
But to return to your question, you should be easily be able to do this using lifeCycleScope
A LifecycleScope is defined for each Lifecycle object. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed.
Since you have not mentioned whether it is an activity or an fragment that you are using, but the logic and functionality remains same for both. I am going to give you an example for Fragment
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
// Do stuff here
}
}
}
Just like that you can launch coroutines and it will be handled as per the lifecycle, you may need to pass the viewLifeCycleOwner to your Fragment, although as I said that is not recommended, you are better using an interface of sort and then computing in Fragment itself, but you can get the idea.

How to access lifecycleScope of the host fragment from a custom view?

I need to use coroutines inside a custom view. After watching this talk, I believe my best option is to use lifecycleScope as the coroutine scope, so that it will be automatically cancelled when lifecycleowner is destroyed.
However I don't seem to have access to lifecycleScope inside the custom view. According to documentation, we can either have access to it from a lifecycle object as lifecycle.coroutineScopeor from a lifecycleOwner as lifecycleOwner.lifecycleScope. But custom view is not a lifecycle owner. So can I have access to lifecycleScope of the fragment somehow? Or if I can't, which coroutine context should I use instead?
I solved this by implementing LifecycleObserver interface. It was very well explained in lesson 4 of this free course on Udacity how to make lifecycle aware components with LifecycleObserver interface.
I registered the lifecycle of the fragment inside the fragment and inside the custom view, while I get the lifecycle, I used the lifecycle to grap lifecycleScope.
//Inside custom view
fun registerLifecycleOwner(lifecycle: Lifecycle){
lifecycle.addObserver(this)
scope = lifecycle.coroutineScope
}
//Inside fragment
binding.myCustomView.registerLifecycleOwner(lifecycle)
Then inside the custom view, I used it like:
scope.launch{
//Do work
}
EDIT
If you don't need to make your custom view lifecycle aware, you can also simply pass the scope to your custom view from the activity/fragment
fun setScope(lifecycleScope: CoroutineScope) {
scope = lifecycleScope
}

MVVM architecture with custom view

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.

Share ViewModel between fragments that are in different Activity

I have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample

Categories

Resources