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

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.

Related

Questions about the responsbilities of each component in Android Architecture Components

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.

Call View from View on MVP

For example i have a button, if user click the button, it just call finish(). Shall i tell the presenter the button is get clicked? :
//--------------HomeView.kt--------------
var presenter: HomePresenter? = null
override fun onCreate(...) {
btBack.setOnClickListener {
presenter.onBackPress()
}
}
private fun onBackPress() {
finish()
}
//--------------HomePresenter.kt--------------
var view : HomeView? = null
private fun onBackPress() {
view?.onBackPress()
}
or can i simplified it just call finish() in its onClickListener()? :
//--------------HomeView.kt--------------
override fun onCreate(...) {
btBack.setOnClickListener {
finish()
}
}
Unless you need some logic to be executed before finishing the activity, tell the presenter, otherwise I do not see much any advantage do this
As with almost all architectural questions, this comes down to personal preferences and how much you like your code to be clean.
Personally, I would suggest to always call the presenter for each interaction on the view (even if they are just one line calls).
this allows you to move all the logic out of the view and into the presenter (since all interactions on the view just pass the call directly to the presenter, without any attached logic)
it makes your whole app much more testable, since you can mock the view from the presenter and test the logic right there
it makes the architecture of your app cleaner, since you always know that calls get send to the presenter for every single interaction
it makes your code more future proof, since you can add new logic right there in the presenter call and do not have to move code around first (or worse: just be lazy and add the logic directly in the view)
Of course I have to admit that it adds quite a lot of boilerplate code, but I think this is worth it.

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

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

Android MVP WeakRefrence

In mvp we save that reference of an activity in a weak reference.
WeakReference<Activity> view = new WeakReference<Activity>(activity);
If we lose the reference. can we get it back?
If you lose the reference to your Activity it means the activity was garbage collected and it doesn't exist anymore. You can't get back what doesn't exist.
Ex. If this happens because of configuration change it means a new activity was created.
You need a way to attach the newly created view to the same presenter.
If you are looking for libraries to help you, take a look at mosby and nucleus.
I don't think you should be saving a reference to an Activity in MVP at all - doesn't matter if it's hard or weak!
I'm assuming you're storing this reference in the Presenter. To really decouple the layers you should create an interface that describes your View (Activity) and use it instead of the activity.
So you'd do:
public interface LoginView {
displayUsernameError(String error);
displayPasswordError(String error);
openMainScreen();
}
Your Activity should implement the interface from above.
public class LoginActivity implements LoginView {
...
}
In your presenter you'd have:
class LoginPresenter {
private LoginView mView;
public LoginPresenter(LoginView view) {
mView = view;
}
public onLoginButtonClicked(String username, char[] password) {
...
mView.openMainScreen();
}
}
Immediate benefits of doing this:
The different layers are really decoupled. You can change your Activity (say you decide to use Fragments instead) without touching your Presenter.
Your presenter is fully testable using JUnit only! No need to use anything fancy to verify your interactions are correct, just plain Mockito to mock the LoginView.
One other point to note - are you sure you want your Presenter to outlive your View? There are some situations when that can't be avoided, but in most cases they have the same life span - when the View is destroyed the Presenter should be as well.
How did you set the reference in the first place?
You should be setting it with a setter method in Activity's onCreate.
This "setter" method is often called "attach" or "bind".
fun attach(view: View) {
this.view = view
}
So when new Activity is created due to configuration change, it will set itself to the presenter again. Note that you might be dealing with a new instance of the presenter too. However, based on your question I believe you want to attach the newly created activity to the same instance of presenter. If you got your presenter scoped right, this will work for both cases :)

Categories

Resources