Questions about the responsbilities of each component in Android Architecture Components - android

I've been using MVP for a long time now and I'm starting to transfer to a hybrid state between MVP and MVVM
In detail my apps will go like this:
Each Activity has a 0 to x Fragments that represent its views
Each Fragment will request the Activity's ViewModel so that they can retrieve data using LiveData
The Activity will have a seperate ViewModel which will act as the presenter. On creation that ViewModel will be injected with the Activity's ViewModel with the LiveData so that it can update the UI as needed
The presenter will get the messages sent to the data ViewModel and send the results back to it
My questions:
Could holding a reference to the data ViewModel in the presenter ViewModel cause a memory leak or adverse effects such as memory leaks?
Where should business logic be? in the presenter or in the model part?
For example, let's say I have a list of items and the user long presses one to edit them, what part of this architecture should be responsible for checking if the user has permission to do this and either let them edit the item or show an error message?
Is there a way for the Fragments to only get part of the Activity's ViewModel?
For example , assuming the activity has 3 Fragments under it, and one ViewModel to cater to them
Can I use something like:
class MainViewModel : ViewModel() , IFragmentA, IFragmentB, IFragmentC
and then when I try to get the ViewModel in the fragments I can write something like:
lateinit var viewModel: IFragmentA
override fun onAttach(context: Context?) {
super.onAttach(context)
vm = ViewModelProviders.of(context as AppCompatActivity).get(IFragmentA::class.java)
}
note:I know the above code does not work , what I am asking is if there is a way for something similar to this could work
Is the correct way to send back messages to the activity SingleEvents?
For example, if the user tries to delete an entry , and I wish for them to enter a password, would the flow be:
The Fragment sends the message to delete to its ViewModel
The ViewModel passes it on to the Presenter
The Presenter decides that it needs password verification before moving on
The presenter sets the value of a SingleEvent in ViewModel
The ViewModel notifies the event's subscribers (in this case the MainActivity) that they should show a dialog asking for a password
Thank you for any help you can provide

I have recently ported one of my app from MVP to MVVM architecture. it doesn't matter whether you do it partially or completely, you are moving towards something great and clean and you are going to like it.
Before checking the answer please have a look at this MVVM architecture diagram and some of it's dos and don'ts
Let's look at the roles of each classes here.
Activity/Fragment:
-Listen to MutableLiveData Obeservers and set Data to the views, no other logics here.
ViewModel
user input Validations (username, password empty or null checks)
set your mutableLive
ask repository to start a task network or localdatastorage(sqlite), with callbacks.
Repository
cache required data.
should not hold any reference to ViewModel this will create a circular dependency.
Decides what to do - whether to make network call or load data from local storage. manipulation of the data received goes here(business logic).
use the callback received from ViewModel to update data to viewModel, strictly no direct communication.
RemoteDataSource
makes a network call and gives the data received back to the repository.
LocalDataSource
handles all SQLite related stuff and gives the requested data through callbacks.
there is a todo app sample project from google which uses MVVM. please refer it, it will be very helpful.
No presenter - check user inputs on viewmodel and communicate forth using repository and back using MutableLiveData.
Do your business logic in Repository, consider it more like a model in mvp pattern.
You can have single viewModel for your activity and its fragments. All your fragments communicate through one viewModel. So Each Fragment will only react to the LiveDataObserver it listens to.
There is actually an example of this use case in the Google sample project for MVVM.
AddEditTaskActivity.java
public static AddEditTaskViewModel obtainViewModel(FragmentActivity activity) {
// Use a Factory to inject dependencies into the ViewModel
ViewModelFactoryfactory= ViewModelFactory.getInstance(activity.getApplication());
return ViewModelProviders.of(activity, factory).get(AddEditTaskViewModel.class);
}
AddEditTaskFragment.java
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.addtask_frag, container, false);
if (mViewDataBinding == null) {
mViewDataBinding = AddtaskFragBinding.bind(root);
}
mViewModel = AddEditTaskActivity.obtainViewModel(getActivity());
mViewDataBinding.setViewmodel(mViewModel);
mViewDataBinding.setLifecycleOwner(getActivity());
setHasOptionsMenu(true);
setRetainInstance(false);
return mViewDataBinding.getRoot();
}
Password Verification Flow:
fragment ask the ViewModel to deleteEntry.
Ask repository to decide whether verification is necessary, with the data which we already have or communicating with the local data source.
ViewModel receives a callback from Repository saying verification needed, ViewModel updates the respective MutableLiveData showVerification.postValue(true);
As the activity is listening to the showVerificationObserver, it shows the verification UI.
Hope it helps.

Related

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)

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.

how to use viewmodel singleton for activities?

MyApp need hold a User object in whole context,A,B,C activities'xml use this User object,when A edit User,I want B and C notifyChange,how to deal this problem with databinding,livedata and viewModel?
Formerly I make User.class extend BaseObservable,but POJO will be very troublesome and must not be a null,sometimes User maybe null such as not login.
Now I Change to use LiveData, make Pojo simple and not extend BaseObservable,but when A edit,B and C not work,I think i need ABC use same viewModel instance in memory,but this will cause viewModel's onClear() trigger manytimes.
Another way is to have one singleton repository to hold your user data and each viewModel can have that repository and share the same data between activities.
Based on this part of the documentation:
https://developer.android.com/topic/libraries/architecture/livedata#extend_livedata
The fact that LiveData objects are lifecycle-aware means that you can share them between multiple activities, fragments, and services. To keep the example simple, you can implement the LiveData class as a singleton as follows:
You can create a singleton for your view model like I did here:
companion object{
private lateinit var instance: ViewModelProfile
#MainThread
fun getInstance(userId: String): ViewModelProfile{
instance = if(::instance.isInitialized) instance else ViewModelProfile(userId)
return instance
}
}
Then I call it and get instance anywhere like this:
val profileVModel = ViewModelProfile.getInstance(user.uid)
If you want to share common ViewModel between ABC activities, then it is suggested to keep them as 3 fragments in a single Activity, create ViewModel of that Activity which can be shared among all three fragments A, B, and C.
Also what you are trying to achieve with activities is like this, suppose you have done some operation in activity A, if you want Activity B and C to get notified about them then they need to be running to get notified, which won't be happening, so instead you should use Intent or Bundle to pass needed information when the activity get started.
Updated
There are other ways as well to achieve similar kind of functionality like,
Event Bus
RxJava Subjects (refer this)
Android State by Evernote (refer this)
This will allow you to have application level access of state, which can be accessed by any Activity or Fragment

Architecture Components refresh LiveData

I'm struggling with the correct way of refreshing data on the master-detail view using Architecture Components. I have a single-top master activity that displays a list of favourite movies. When I go to details view, add/remove movie from favourites and close the details view the master view's the data stays unsync. I initialize view model in the onCreate method:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
moviesViewModel = ViewModelProviders
.of(this, viewModelFactory)
.get(MoviesViewModel.class);
moviesViewModel.getMovies(FAVOURITES).observe(this, this::show);
}
View Model is also quite straight forward:
#NonNull
LiveData<Resource<List<Movie>>> getMovies(Criterion criterion) {
movieRepository.fetchBy(criterion)
.doOnSubscribe(it -> moviesLiveData.setValue(Resource.loading()))
.subscribe(
movies -> moviesLiveData.setValue(Resource.success(movies)),
throwable -> moviesLiveData.setValue(Resource.error(throwable))
);
The simplest solution would be to force to refresh the view every time it becomes active:
#Override
public void onResume() {
super.onResume();
moviesViewModel.getMovies(FAVOURITES).observe(this, this::show);
}
But I don't really like this approach since it will result in a refresh when screen orientation changes and also when an activity comes from the background.
It's also possible to start details activity for result and refresh data only when it has changed, but I also don't think this is how it should be done this way in the reactive approach.
Moreover, I was thinking about subscribing for database changes in the cotent resolver and updating the Flowable with new content everytime data changes, but I'm convinced whether it will work since when it the change occurs the live data observer (master view) is in pause mode so it will not be notified, am I right?
There are a few ways you can manage refreshing your master view.
You could use a shared ViewModel for this situation. If both the master and detail view are fragments, all you have to do is use the same viewmodel, passing in the activity or the containing fragment as the scope, eg ViewModelProviders.of(parentFragment!!, viewModelFactory). Then your moviesLiveData can be observed by both master and detail pages. **Note that the latest version of Navigation allows you more finely defined scopes by tying the ViewModel to a specific nav graph.
If you are using activities for your pages, you can pass the movies results information back to the master page via startActivityForResult, setResult in the detail screen, and retrieve the information in onActivityResult
You can cache the result of the network request somewhere other than the viewmodel. Typically you might have a Singleton repository that retrieves your data. You could use LiveData in the same pattern as with ViewModels.
You can also observe your database as you suggest. Don't worry about it being in pause mode! That's the point of LiveData, it stores the latest update and delivers it again whenever something observes it.
You could use livedata also for the database in order to update changes from db -> vm -> ui

How to connect View and ViewModel with respect to screen rotation?

One promise of the ViewModel is, that it survives cases like rotation of the screen. I still try to figure out how to organise this in practice.
On certain events of the model the View should update. There are two major options:
The ViewModel updates the View.
The View observes the ViewModel and updates itself.
In the first case the ViewModel needs a link to the View. I could inject the View into the ViewModel, yet my feeling is it would be better to inject the VieModel into the View.
What is the better style to join them?
Then after rotation the onCreate() method is called again triggering initialisations of the ViewModel a second time. I need to check for this else I am in danger to register listeners to the actual model twice and thrice and similar issues. I may even need to clean up relations to the old view first.
This checking feels kind of unclean. I would expect a dedicated API for this in the ViewModel, if this would be a standard practice. Without I have the feeling to be on the wrong track.
What are good patterns to deal with this in a clean standard way?
Soo.. You don't really have to connect the ViewModel and the Activity/Fragment "with respect to screen rotation", you get that for free - that's one of the perks.
The official documentation is really good.
You connect a ViewModel to your view in onCreate() by something like
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
updateUI()
});
}
}
And while it is true as you say that orientation change will trigger onCreate() again, it's not true that this will create a new ViewModel. The MyViewModel is only created the first time around in onCreate. Re-created activities receive the same MyViewModel instance created by the first activity. This is even true for different fragments/activities referencing the same ViewModel.
You should never ever inject a view into the ViewModel. It's the equivalent of drowning puppies. If you need a context in the ViewModel, extend AndroidViewModel instead (and pass it the Application).
What you do is that you create a ViewModel that holds all state. And handles fetching data from network or disk or what not. All that is not UI related goes in to the ViewModel (as a rule of thumb). All view updating stuff goes into the activity/fragment.
A ViewModel for the example above might look like
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
This often means that click events should probably be passed down into the ViewModel so that it can massage the data. And the view will just react to the updated (massaged) data.

Categories

Resources