Dagger 2 Scopes, where to place Presenters? - android

what would it be the best practice about placing Presenters in a Scope?
Could we have Presenters on #Singleton or #AppScope without any problem?
Should they be placed in an #ActivityScope in order to destroy them each time the activity is destroyed?

what would it be the best practice about placing Presenters in a Scope?
Usually a presenter should be in some scope. Not placing it in any scope will lead to problems, as every time you request a presenter it would create a new one.
Which scope you choose mostly depends on your programming style, but the most common would probably be #PerActivity, as a scope that follows the lifecycle of the Activity. (the same way you can use something like #PerFragment with Fragments and their lifecycle)
Could we have Presenters on #Singleton or #AppScope without any problem?
Yes and no. Longer living objects referencing shorter lived ones (e.g. a #Singleton object that references an activity-lifecycled one) usually is not a good practice that might lead to memory leaks.
You can avoid these issues by properly adding / removing your shorter lived objects (e.g. add in onCreate, remove in onDestroy) or using WeakReference.
Some programmers will keep their presenters as #Singleton or in some similar fashion and swap views, but again this depends on how you prefer your code. It will work, but you must make sure what objects you reference and to clean up afterwards.
Should they be placed in an #ActivityScope in order to destroy them each time the activity is destroyed?
This is by far the easiest option, since you have no problem referencing the Activity or anything else that depends on it. You most likely won't have to worry about memory leaks or other issues this way.
In the end its your code and you have to do what works best for you.

Related

Hilt: Why is ActivityRetainedScoped vs ViewModelScoped

I'm trying to understand why is ActivityRetainedScoped introduced for DI in Hilt. It looks to me that the scope is identical to what ViewModelScoped should do. I was under the impression that scoped worked like this:
AppScope (singleton) > ViewModelScope > ActivityScope > ViewScope > ...
But this graphic kinda hints that ViewModel and Activity scopes are... siblings?
According to the docs:
"ActivityRetainedComponent lives across configuration changes, so it
is created at the first Activity#onCreate() and destroyed at the last
Activity#onDestroy()."
Well, so does the view model, no?
I'm pretty sure view models survive config changes (that's the whole point if having them in the first place)
What is ActivityRetainedScoped? How is it different from VM scope? Why does google likes over complicating things that should be conceptually simple
https://developer.android.com/training/dependency-injection/hilt-android
Well, even tho ActivityRetainedScope and ViewModelScope are Siblings and one could think that makes them the same, there are in fact not.
Well, so does the view model, no? I'm pretty sure view models survive config changes (that's the whole point if having them in the first place)
Well yes, but actually no. A Viewmodel does survive configuration changes, but only of its scoped lifecyleowner. So let's think of the following scenario:
You have two dependencies, one is ActivtyRetainedScoped and the other one is viewmodelscoped.
When you now inject the viewmodeldependency inside the viewmodel and the lifecycleowner of the viewmodel is an activity, then you are right, both the ActivtyRetainedScope and the ViewmodelScope would not make any difference.
But now let's assume the lifecycleowner is a fragment, in this case, the viewmodelscoped dependency would "die" when you navigate out of the fragment and the activtyretainedscope dependency would outlive the viewmodelscoped one.
I hope I could explain the difference between them both. Kinda hard with those "scopes" etc. when English is not your native language. Also, I am not 100% if this is the correct answer

How to share ViewModel scope between a particular set of Fragments, without using NavGraphs or scoping it to the activity?

Google states to use SharedViewModel in case of communication between Fragments by scoping it to the activity.
In a Single Activity Application, this means that the activity will be littered with ViewModels which might not be needed anymore and they will stay there for the whole lifecycle. Considering some example like a extended sign up flow or something considering a few screens.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
I came up with the following solution, I want to know how viable or how bad is it and is there any better way around?
Considering I have two Fragments called ExampleOneFragment and ExampleTwoFragment for sake of simplicity and I want them to have a shared scope without actually scoping it to the activity. Let's say I want to update text view in ExampleOneFragment from ExampleTwoFragment so I create a SharedViewModel like this for both
For ExampleOneFragment it will be:
private val mSharedViewModel by lazy {
ViewModelProvider(this).get(SharedViewModel::class.java)
}
And For ExampleTwoFragment I came up with this:
private val mSharedViewModel by lazy {
ViewModelProvider(supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this).get(SharedViewModel::class.java)
}
This seems to be working, but I don't know what kind of issues can this cause.
Some Other Solutions are which I found:
According to #mikhehc here We could actually create our very own ViewModelStore instead. This will allow us granular control to what scope the ViewModel have to exist. But I don't understand how to make it work for Fragments?
Secondly, is the hacky way of scoping it to the activity still, but clearing it out via dummy viewmodel by using same key which I found here
Could anyone guide me what is the right approach? I cannot shift to NavGraphs since it is an already up and running project and scoping to activity just feels wrong. Thanks.
This seems to be working, but I don't know what kind of issues can this cause.
This code will only work if:
An ExampleOneFragment is created first, always
It is added to the same FragmentManager that ExampleTwoFragment uses, via a tag of ExampleOneFragment.TAG, always
If either of those assumptions fail, you will wind up with separate viewmodel instances, because supportFragmentManager().findFragmentByTag(ExampleOneFragment.TAG) ?: this will resolve to this.
But I don't understand how to make it work for Fragments?
You would use it the way it is shown in that answer, or by anything else that accepts a ViewModelStoreOwner. In this line:
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
You would get myApp from your Fragment as:
val myApp = requireContext().application as MyApp
Personally, I think the solution that you pointed to is very risky. The ViewModelStore lives for the entire lifetime of the process and is never cleared. You will wind up sharing your viewmodel across everything, and everything done by that viewmodel is leaked. The concept of creating a custom ViewModelStoreOwner is fine, but you should be doing something to tie the scope of that owner to the relevant lifetime for the viewmodels that it stores. The answer tries to dance around that in its last paragraph; too many developers will ignore this and run into problems.
One way recommended was using a parent Fragment as scope, but that is only possible if there is a parent fragment. It can be just different Fragments.
Your app is not written in a way to make automatic ViewModelStoreOwner management available "out of the box", then.
In the end, you are looking for a ViewModelStoreOwner to be shared between these two fragments. In your first solution, you are trying to hack that by using the ViewModelStoreOwner from one of those fragments, which only works if you can reliably choose which fragment that is. In the solution you pointed to in that other answer, you are trying to hack that by intentionally leaking a ViewModelStoreOwner.
There may be other approaches that you could consider, depending on your circumstances. For example, there may be some option with your dependency inversion framework (Dagger/Hilt, Koin, etc.) to rig up a ViewModelStoreOwner that is tied to a specific pair of fragment instances.

remove references from memory

I'm using dagger 2 for dependency injection in my android app project.
and the question is how to remove references from component when the activity dies ?
I've read some documents about custom scopes and i've created a custom scope called #ForActivity, so references that have this annotation on them will remove when activity dies, but they don't.
any suggestions on this problem ?
If your component shares the lifecycle of the Activity—that means create the component in onCreate, store it in the Activity itself—it will be garbage collected along with the Activity at the end of the Activities lifetime.
If on the other hand you put a component that references the Activity in some way in a static variable, or some other longer lived object, you will create a memory leak. This is the only thing to keep in mind.
Nothing will be magically "removed" since Dagger just generates POJOs that handle object creation for you. Usually it is enough to just let the GC do its job.
Scopes just group dependencies and define relationships, but in the end your component is just an object that holds more objects. The Garbage Collector will remove it along with the Activity and everything else if you don't create memory leaks as mentioned above.
I recently also gave 2 detailed answers about scopes / activities / scoped objects, where you find more concrete examples on how to work with scopes / activites:
Dagger 2 Scopes, where to place Presenters?
Dagger 2 with MVP, avoid creating extra presenter object on view recreation

Dagger2 scopes and activity lifecycle

I have an Android Activity that I'm using Dagger2 to inject a Presenter into. I'd like my Presenter to be capable of holding state even if a configuration change occurs.
For instance, I'm going to use the Presenter to kick off a network call and if the user rotates the device while the network call is in-flight I'd like to be able to receive the response after the device finishes its rotation and not have to restart the call.
I'm getting tripped up because if I scope the instance of Presenter to the Activity's life, then isn't there a chance that the Presenter would be garbage collected when the Activity goes through onDestroy() during a configuration change? My other thought was to use a scope that is valid during the life of the application. However, if I do that how do I ensure that my Presenter can be garbage collected once the Activity has been destroyed for good (not due to a config. change, but something like the back button being pressed)?
Is there a way to ensure that my Presenter will survive an Activity's configuration change and also not be leaked for the life of the Application?
I would strongly advice against trying to implement this approach.
You're effectively trying to use DI framework in order to support Activity specific life-cycle flow, although DI frameworks are not intended to be used like this.
I recently answered another similar question in which OP tried to share state in View-Model between different Activities. Although use cases are not identical, the general pattern is the same - attempt to delegate flow control responsibilities to DI framework, which is not a good idea.
The best approach in your case (IMHO) would be to store the current state before rotation, re-instantiate the presenter upon rotation, and then restore its state.
How you store the state during rotation depends on what exactly you're trying to preserve:
If you need to preserve UI related state (selections, texts, position of elements, etc.) then you can use the usual onSaveInstanceState() and onRestoreInstanceState() callbacks
If you need to preserve some business related state (ongoing network requests, data, data modifications, etc.) then encapsulate this logic in a business class (e.g. SomeBusinessUseCaseManager) and inject this class from Application wide component with a scope.
You can find a detailed review of Dagger's scopes here.
More information about DI in Android can be found here.
According to this article about Custom Scopes:
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
In short - scopes give us “local singletons” which live as long as scope itself.
Just to be clear - there are no #ActivityScope or #ApplicationScope annotations provided by default in Dagger 2. It’s just most common usage of custom scopes. Only #Singleton scope is available by default (provided by Java itself), and the point is using a scope is not enough(!) and you have to take care of component that contains that scope. This mean keeping a reference to it inside Application class and reuse it when Activity changes.
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
You can take a look at this sample project:
http://github.com/mmirhoseini/marvel
and this article:
https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21
to get more familiar with MVP and learn how dagger scope works.

Do I need to use a WeakReference for circular dependency?

We're talking Android here. We've modeled our architecture using Model-View-Presenter approach, and we hit an issue regarding weak references.
Quick Info:
Presenter handles logic, network calls, etc.
Views handles showing data on screen, displaying loading bars, etc.
The Activity/Fragment classes hold a reference to the presenter.
The presenter classes hold a reference to the view classes.
Our view clases are actually interfaces usually implemented by Activity/Fragment. This means a circular dependency between the Activity/Fragment -> presenter and presenter -> view (an Activity/Fragment). For this reason, we made the presenter hold a weak reference to the view (Activity/Fragment).
Today we needed to use 2 views in the same Activity (to display different model-data), so we didn't implements it on the Activity but created 2 anonymous class. This ended up in the presenter losing the view's references (because it's a weak reference).
Now we're evaluating 2 posibilities:
We don't really need the presenters to hold a WeakReference on the View cause this type of circular dependency won't leak memory.
Instead of using anonymous classes, we hold a references on the Activity (so both presenter and activity have a reference to view) just for the sake of it not getting deallocated (which feels smelly).
Which one is it, do we need the presenter to hold the view as a weak reference?
The answer is 1 - you don't need a WeakReference in the presenter in this case. I'm using the same pattern successfully. No memory leaks occur - when the activity gets GCed the presenter goes with it. But there might be other types of problems - if you keep somewhere (for example in AsyncTask) hard reference to the presenter.
As Galya already pointed, you don't need WeakReference for on Presenter because it will be destroyed when Activity destroyed. But if I right understand your problem, you try to "fix" MVP pattern. Maybe a VIPER pattern could help you?

Categories

Resources