kotlin - android - viewModelScope in singleton viewmodel - android

My application using MVVM pattern and want to use viewmodelscope for the coroutine and now I'm facing some problem related to the viewmodelscope
in the apps, there are two fragments and each fragment has a viewmodel. from fragment one navigate to fragment two and at the second fragment, the user will hit API. if the response is false, the user will back to the first fragment again and need to fix their input. and then the user can navigate again to fragment two and hit the API again.
the problem is at the second attempt, apps can't hit the API because the coroutine is already canceled in ondestroy state in viewmodel at the first attempt (it's automatically canceled since it's the behavior of the viewmodelscope itself CMIIW). and since I make all viewmodel singleton then I guess at the second attempt, the canceled viewmodelscope is called again and cant run coroutine (because it's already canceled in the first attempt).
My question is if I keep using the singleton for the viewmodel, is there any other way to reset the viewmodelscope in the second attempt so it can run again to hit API?

I suggest you use single ViewModel for your Activity, then all fragments in this Activity will share it, and this is why Google invented ViewModel.
This viewModel will follows Activity's LifeCycle, and you don't need to make it singleton, just get it in Activity.onCreate().

Related

Android Fragment isAdded returns false and getActivity is null after posting thread in the onResume method

So i am using Navigation in my main activity of my app and i have a fragment which is my start navigation fragment.
In this fragment, after it is created, in my presenter i post a thread to fetch data from the network.
After data have been fetched, i am using the main thread to show the data to my screen.
The first time that the app runs, this is working fine.
However if user opens the drawer and selects again THIS fragment and not another one, fragment is recreated again meaning that it gets destroyed and created from scratch as Navigation Component is designed to do.
However this time, when my presenter posts thread fetching-data-thread and this gets finished and sends the results to the UI, fragment's isAdded() method returns false as well as getActivity is null.
Having that, means that i can't use the Activity Context (getActivity() is null or requireActivity() throws an illegal state exception) and consequently i cannot load images etc since i don't have the context available.
I highlight that this happens when user opens the drawer while this fragment is visible and selects again to navigate to this fragment from the drawer. In case that user navigates to another fragment and then presses the back button everything is ok.
Any idea how to handle this problem?
Fragments are meant to be destroyed, as well as activities.
You can never rely on android framework component lifecycle state, and because of it android architecture components were made. ViewModel, for example, can outlive it's host fragment.
But - viewmodel/presenter/controller is not a right place to perform network request and handle app logic, just because it's not their job (SOLID's S-single responsibility).
There is official guide to app architecture. Simply speaking, you have a layer for android-related code, where you update UI, layer for handling app logic (which is java/kotlin and android framework independent) and layer for requesting/holding data.
So, during creation of your ui class you obtain viewmodel, which has reference to class that handle logic and exposes result to render in ui. Inner layers are persisted - view is not.
So, after testing and searching i found out the origin of the problem i described above.
I am nullifying my presenter's view in onDestroy/onDetach method of my Fragment.
However when the replacement Fragment gets created, this new Fragment is firstly attached to the calling Activity and then, the old one gets destroyed.
Having in mind, that i inject my presenter into the Fragment instance, my presenter will be never null at the time that new Fragment gets attached and consequently, and considering that i create a new instance of my Presenter when it is null, the presenter instance that is being injected into the fragment is not aware of the new 'View' object.
As a result, when the results reach the UI thread through the callback this view object is 'not Added'.

How to execute network request when leaving Fragment?

How to conveniently perform a network request when exiting one Fragment using Coroutines and ViewModel?
The workflow is: user enters a Fragment A containing list of Boxes' names. He picks one and is navigated to Fragment B where he can scan barcodes, which are added into chosen Box (in memory). When scanned all required barcodes he clicks Back button and in this moment mentioned Box with barcodes should be sent to server (serialized into JSON of course).
ViewModelScope of course is not a solution since Fragment's B ViewModel is cleared. I tried using WorkManager, but I can't figure out how to do it conveniently - it requires passing a Context reference to ViewModel (which I would like avoid in order to make ViewModel clean from Android's platform references and make unit tests simple) and passing a Box using Data object.
Is there any better solution?
You can use the host Activity ViewModel from the the Fragment for shared actions. So in your case, make the network request from the host Activity ViewModel. So the network request will continue executing while you navigate form Fragment A to B, and the data will be preserved on the Activity ViewModel, hence you can access the value from any Fragment hosted by the same Activity.
You can get the shared ViewModel by the following method
activity?.let {
sharedViewModel = ViewModelProviders.of(it).get(SharedViewModel::class.java)
}
Bonus Point
Even though the OP does not want to use ViewModel scoped to the Graph , it is considered as more perfect approach. Here is how you can get a ViewModel specific to a Graph
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

Should we Use ViewModels to communicate between 2 different fragments or bundles in single activity App Architecture?

Scenario 1 - If we use ViewModels to communicate between fragments, then the ViewModel has to be created by activity reference and hence going to stay there in memory until the activity is destroyed.
Scenario 2 - In master-detail flow ViewModel makes our life easier but again the memory usage issue is there.
Scenario 3 - We have viewModelScope in the new version of arch library to cancel jobs with Fragment/Activity lifecycles, but if ViewModel is created with activity reference then it's going to stay there until activity is destroyed. Hence the job can still be executing and fragment is already gone.
You can use ViewModels to communication between two different fragments(aka SharedViewmodels) because it's simple but it's not perfect.
As you know the SharedViewModels must be alive until the first joint parent (activity or fragment) is alive.
but wait ...
What is the purpose of ViewModels?
Are we create ViewModels just for communication between fragments? Absolutely not.
Is the use of ViewModels against the purpose of ViewModels? no, I say it's not perfect use them for communication between fragments but if you have a small project you can use them because it's simple.
So what can I do instead of using ViewModels for communication between fragments? You can build independent UI components and use them for communication between fragments.
What is the advantage of building this weird independent UI components? In this way, each component has its own ViewModel (or Presenter) and they haven't any parent/child relation; Instead, they updated from the same business logic or in reactive programming they are just observing the same Model.
What is the meaning of building independent UI components and how to build them? if you are already familiar with reactive programming, I recommend reading this amazing blog post by Hannes Dorfmann; If you don't, I simply propose using EventBus library for communication between fragments but You will soon realize that too much usage of this library leads to spaghetti code.
Scenarios
If the fragments are not part of a flow/group, then don't share the ViewModel, just pass some id/data to the new fragment, create its own viewmodel, and query the data for the fragment from its own viewmodel.
If the fragments are part of some flow/group (cart/checkout/booking flow, multi-screen registration process, viewpager fragments, etc) and the logic is common enough, then share the viewmodels between the fragments. In single-activity architecture, I put these flow/process in its own root parent fragment that acts as a host and is used to create the scope of the viewmodel. For example:
MainActivity ->
-> RootAuthFragment
-> SplashFragment (replace with below)
-> LoginFragment (add to backstack with below or onsuccess login go to MainFragment)
-> SignupFragment (onsuccess go to Main)
-> MainFragment (replace with RootAuthFragment)
In the above scenario, you can share the viewmodel between login and signup screens with RootAuthFragment's scope. If you have a multi-screen signup process, then you could move that into separate root fragment and create a separate viewmodel for the signup flow.
Bundle vs ViewModels:
Bundles are used to pass some values. So, use it just for that. I use bundles to usually pass primitive data types or enums and based on that I query the actual data from the viewmodel (through android room or retrofit) or if the data objects are small enough, I make them parcelable and just pass that.
If you have a shared ViewModel and it's becoming a god class and does a lot of different things, then it means those fragments need separate ViewModels. Don't share the ViewModel just for data. Share the ViewModel for the common/shared behaviour/data/logic (whichever makes sense for your particular use cases)
I prefer you should use View Models approach if you are using single activity architecture. To justify my answer I will clear your scenarios here.
Scenario 1 - If we use ViewModels to communicate between fragments, then the ViewModel has to be created by activity reference and hence going to stay there in memory until the activity is destroyed.
Scenario 2 - In master-detail flow ViewModel makes our life easier but again the memory usage issue is there.
As for memory you are already holding information into memory there is no escaping there. If you don't need data for stay there then you can clear data from models also but again it will kill the purpose of storing data in the first place.
If you pass data using bundle it's also going to take memory there also.
Scenario 3 - We have viewModelScope in the new version of arch library to cancel jobs with Fragment/Activity lifecycles, but if ViewModel is created with activity reference then it's going to stay there until activity is destroyed. Hence the job can still be executing and fragment is already gone.
That's the main purpose of using view models it will store the last state for user where he left.
As per https://developer.android.com/topic/libraries/architecture/viewmodel states
This approach offers the following benefits:
The activity does not need to do anything, or know anything about this communication.
Fragments don't need to know about each other besides the SharedViewModel contract. If one of the fragments disappears, the other one keeps working as usual.
Each fragment has its own lifecycle, and is not affected by the lifecycle of the other one. If one fragment replaces the other one, the UI continues to work without any problems.

Is it possible to call ViewModel methods from fragments through an Activity or is it a bad practice?

I have an Activity and 4 fragments in it. At first I wanted to do for each fragment of the ViewModel. But the situation is such that I also need ViewModel for Activity. I want to know if it would be an error to make the ViewModel just for the Activity and call the necessary methods from the fragments using getActivity? For example, call getActivity().myViewModel.callMethod() at fragment? Wouldn't this approach be wrong?
Yes, you can use the ViewModel of the Activity at a fragment, but not like that getActivity().myViewModel.callMethod(). It should be like this
YourViewModel viewModel = ViewModelProviders.of(getActivity()).get(YourViewModel.class);
And it's a good practice to share data between fragments.
Official doc of Google says
That way, when the fragments each get the ViewModelProvider, they
receive the same SharedViewModel instance, which is scoped to this
activity.
This approach offers the following benefits:
The activity does not need to do anything, or know anything about this
communication.
Fragments don't need to know about each other besides
the SharedViewModel contract. If one of the fragments disappears, the
other one keeps working as usual.
Each fragment has its own lifecycle,
and is not affected by the lifecycle of the other one. If one fragment
replaces the other one, the UI continues to work without any problems.

How to use mvvm pattern using databinding

I am working on an application where there is a log in form.I am bit confused with the pattern as I don't understand as how I will open the new activity as my login is successful.As per my understanding when I click on submit button a method in viewmodel which authenticates will get call and after my successful login I do not know how to navigate it to activity file so that I can call new activity.
Thumb Rule:
No package from android.* should lie in ViewModel. You can ignore package import for ViewModel
Also, you can do it with normal ViewModel as well.
How to proceed?
Lets make it simple. Suppose, you are making Login page.
Lets have below things in ViewModel itself:
Fields of email & password with two-way binding
Form Validation logic
Button Click Event
Api Call
All these things lie in your ViewModel.
Now, your Activity needs to react to the result of your Api Call. So, let your ViewModel have a separate LiveData where T is the type of Response from your Api Call.
For example:
val loginApiStatus = MutableLiveData<LoginResponse>()
And then, let your Activity observe this LiveData. It should be observed in onStart() method of Activity. I will tell you the reason why to observe in onStart().
viewModel.loginApiStatus.observe(this, Observer{ loginResponse->
// respond accordingly
})
Now, once you receive response from Api, simply update the LiveData in your ViewModel as:
loginApiStatus.value = loginResponse // Login Api Response
With this structure, you have total control over handling the Api Response. Even if, your activity goes into background, after launching Api Call, you will still have the state of what happened to that Api call. Now, when you return to Login screen again from background, you start observing the LiveData again (because we are observing the state LiveData in onStart() as I said earlier), and you will get the state to react upon.
Life becomes a lot easier when you start storing states of your View / Fragment / Activity, in your ViewModel itself.
for that, you can use AndroidViewModel which gives Application Context and then using intent you can navigate to the new activity.
You can simply implement a click listener in activity and handle opening new activity from there. As far as I know, ViewModel should only be used to persist data or do other communication with Repository/model. It should not be used for navigation.
Although if you want to use then you can use AndroidViewModel class which provides a context object that can be used for navigating to another activity/fragment.

Categories

Resources