MVVM - How to share a single repository class across multiple ViewModels - android

I have multiple viewmodels that access a single repository (one activity and the rest fragments).
AdminActivityViewModel
AdminListUsersViewModel
AdminUserTransactionsViewModel
... and a few more
My AdminRepo class has multiple constructors so that I can pass callback methods from the ViewModel
public AdminRepo(Application application, AdminActivityCallback callback) {
this.callback = callback;
BaseApplication baseApplication = (BaseApplication) application;
RetrofitClient client = baseApplication.getRetrofitClient();
adminService = client.getRetrofit().create(AdminService.class);
SharedPrefManager sharedPref = SharedPrefManager.getInstance(application);
AuthHeader authHeader = new AuthHeader(
sharedPref.getIdToken(),
sharedPref.getIdClient(),
sharedPref.getUserEmail()
);
client.setAuthHeader(authHeader);
}
public AdminRepo(Application application, AdminListUsersCallback callback) {
//Exact same code in constructor as above ^
}
public AdminRepo(Application application, AdminUserTransactionsCallback callback) {
//Exact same code in constructor as above ^
}
And in each of the ViewModels I am creating an instance of the AdminRepo (which is probably a bad practice) but I don't know how I can improve this.
public class AdminActivityViewModel extends AndroidViewModel implements AdminActivityCallback
public AdminActivityViewModel(#NonNull Application application) {
super(application);
repo = new AdminRepo(application, this);
}
How can I design my AdminRepo and ViewModels so that they share exactly one repository without creating an expensive AdminRepo class everytime?
I have considered making my AdminRepo class a singleton with a .getInstance() method, but I'm getting SO MANY contradicting posts on how Repository classes SHOULD NOT be static or a singleton, which makes me extremely confused on what I SHOULD do.
https://stackoverflow.com/a/7464235/11110509

If all view models live at the same time then and repository does not keep any state you could share the same instance of repository and provide that instance for each view model. However there's no place for magic here - if you create an instance of repository, you have to keep a reference to that, so you can pass the same object to other view models.
At first you need to make your view model accepting external dependencies (I\d recommend seeing dependency injection pattern)
YourViewModel( /* other dependencies ..., */ AdminRepo adminRepo)
Once you have that, you need to create a view model factory. This post describes it nicely, but long story short: implement ViewModelProvider.Factory that will keep your repository instance and use it in ViewModelProvider to obtain an instance of your view model.
With this setup you will be in control what instance you create and how you create other view models. This could be automated though with the use of dependency injection framework (Koin, dagger, hilt). If you use Dagger or Hilt (recommended by Google), then you can leave your object lifecycle in hands of the framework by providing a proper annotation. Let's try this exaple:
#Singleton
class MyRepository { ...
#HiltViewModel
class MyViewModel {
MyRepository repo;
#Inject MyViewModel(MyRepository repo) { ...
...
This code will make your repository a singleton tied to your application lifecycle. Usually you don't want to do that, because the object will live in the memory even if you move to the screen that deosn't need that. But you could use #ActivityScoped so your repository lives as long the activity.
Now if those view models have different lifecycles and they don't overlap, it would be completely fine to create a new insance for each one of them. You wouldn't want to keep unnecessary objects in the memory while you don't need them anymore.

Related

Inject into arbitrary Logic class that is not instanciated by Hilt

I'm currently migrating an app from anko-sqlite to room as anko has been discontinued a long time ago. In the process of doing so I introduced a repository layer and thought I would try to introduce dependency injection to get references to the repository instances in all my classes.
Prior to this I used a singleton instance that I just shoved into my application classes companion object and accessed that from everywhere.
I managed to inject my repositories into Fragments, Workmanager and Viewmodels. But I do now struggle a bit to understand how they forsaw we should handle this with arbitrary logic classes.
For instance my workmanager worker calls a class that instantiates a list of "jobs" and those need access to the repository. The worker itself actually bearly even does need access to the repositories as all the work is abstracted away from it.
How can I make something like this work
class Job(val someExtraArgINeed: Int) {
#Inject
lateinit var someRepository: SomeRepository // <= this should be injected when the job is instanciated with the constructor that already exists
}
For Activities and so forth we have special annotations #AndroidEntryPoint that makes this work. However, the documentation makes it unclear as to how we should get our instances from any other class that isn't an Android class. I understand that constructor injection is the recommended thing to use. But I do not think it would work here for me without an even more massive refactor required than this already would be.
If I understand your question correctly you can use hilt entry points like this
class CustomClass(applicationContext: Context) {
#EntryPoint
#InstallIn([/*hilt component that provide SomeRepository*/ApplicationComponent]::class)
interface SomeRepositoryEntryPoint {
fun provideSomeRepository(): SomeRepository
}
private val someRepository by lazy {
EntryPointAccessors.fromApplication(applicationContext, SomeRepositoryEntryPoint::class.java).provideSomeRepository()
}
fun doSomething() {
someRepository.doSomething()
}
}

How to get Context in Android MVVM ViewModel

I am trying to implement MVVM pattern in my android app. I have read that ViewModels should contain no android specific code (to make testing easier), however I need to use context for various things (getting resources from xml, initializing preferences, etc). What is the best way to do this? I saw that AndroidViewModel has a reference to the application context, however that contains android specific code so I'm not sure if that should be in the ViewModel. Also those tie into the Activity lifecycle events, but I am using dagger to manage the scope of components so I'm not sure how that would affect it. I am new to the MVVM pattern and Dagger so any help is appreciated!
You can use an Application context which is provided by the AndroidViewModel, you should extend AndroidViewModel which is simply a ViewModel that includes an Application reference.
For Android Architecture Components View Model,
It's not a good practice to pass your Activity Context to the Activity's ViewModel as its a memory leak.
Hence to get the context in your ViewModel, the ViewModel class should extend the Android View Model Class. That way you can get the context as shown in the example code below.
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
It's not that ViewModels shouldn't contain Android specific code to make testing easier, since it's the abstraction that makes testing easier.
The reason why ViewModels shouldn't contain an instance of Context or anything like Views or other objects that hold onto a Context is because it has a separate lifecycle than Activities and Fragments.
What I mean by this is, let's say you do a rotation change on your app. This causes your Activity and Fragment to destroy itself so it recreates itself. ViewModel is meant to persist during this state, so there's chances of crashes and other exceptions happening if it's still holding a View or Context to the destroyed Activity.
As for how you should do what you want to do, MVVM and ViewModel works really well with the Databinding component of JetPack.
For most things you would typically store a String, int, or etc for, you can use Databinding to make the Views display it directly, thus not needing to store the value inside ViewModel.
But if you don't want Databinding, you can still pass the Context inside the constructor or methods to access the Resources. Just don't hold an instance of that Context inside your ViewModel.
What I ended up doing instead of having a Context directly in the ViewModel, I made provider classes such as ResourceProvider that would give me the resources I need, and I had those provider classes injected into my ViewModel
Short answer - Don't do this
Why ?
It defeats the entire purpose of view models
Almost everything you can do in view model can be done in activity/fragment by using LiveData instances and various other recommended approaches.
As others have mentioned, there's AndroidViewModel which you can derive from to get the app Context but from what I gather in the comments, you're trying to manipulate #drawables from within your ViewModel which defeats the purpose MVVM.
In general, the need to have a Context in your ViewModel almost universally suggests you should consider rethinking how you divide the logic between your Views and ViewModels.
Instead of having ViewModel resolve drawables and feed them to the Activity/Fragment, consider having the Fragment/Activity juggle the drawables based on data possessed by the ViewModel. Say, you need different drawables to be displayed in a view for on/off state -- it's the ViewModel that should hold the (probably boolean) state but it's the View's business to select the drawable accordingly.
DataBinding makes it quite easy:
<ImageView
...
app:src="#{viewModel.isOn ? #drawable/switch_on : #drawable/switch_off}"
/>
If you have more states and drawables, to avoid unwieldy logic in the layout file you can write a custom BindingAdapter that translates, say, an Enum value into an R.drawable.* ref, e.g.:
enum class CatType { NYAN, GRUMPY, LOL }
class CatViewModel {
val catType: LiveData<CatType> = ...
// View-tier logic, takes the burden of knowing
// Contexts and R.** refs from the ViewModel
#BindingAdapter("bindCatImage")
fun bindCatImage(view: ImageView, catType: CatType) = view.apply {
val resource = when (value) {
CatType.NYAN -> R.drawable.cat_nyan
CatType.GRUMPY -> R.drawable.cat_grumpy
CatType.LOL -> R.drawable.cat_lol
}
setImageResource(resource)
}
<ImageView
bindCatType="#{vm.catType}"
... />
If you need the Context for some component that you use within your ViewModel -- then, create the component outside the ViewModel and pass it in. You can use DI, or singletons, or create the Context-dependent component right before initialising the ViewModel in Fragment/Activity.
Why bother
Context is an Android-specific thing, and depending on it in ViewModels is unwieldy for unit tests (of course you can use AndroidJunitRunner for android-specific stuff, but it just makes sense to have cleaner code without the extra dependency). If you don't depend on Context, mocking everything for the ViewModel test is easier. So, rule of thumb is: don't use Context in ViewModels unless you have a very good reason to do so.
TL;DR: Inject the Application's context through Dagger in your ViewModels and use it to load the resources. If you need to load images, pass the View instance through arguments from the Databinding methods and use that View context.
The MVVM is a good architecture and It's definitely the future of Android development, but there's a couple of things that are still green. Take for example the layer communication in a MVVM architecture, I've seen different developers (very well known developers) use LiveData to communicate the different layers in different ways. Some of them use LiveData to communicate the ViewModel with the UI, but then they use callback interfaces to communicate with the Repositories, or they have Interactors/UseCases and they use LiveData to communicate with them. Point here, is that not everything is 100% define yet.
That being said, my approach with your specific problem is having an Application's context available through DI to use in my ViewModels to get things like String from my strings.xml
If I'm dealing with image loading, I try to pass through the View objects from the Databinding adapter methods and use the View's context to load the images. Why? because some technologies (for example Glide) can run into issues if you use the Application's context to load images.
Hope it helps!
In Hilt:
#Inject constructor(#ApplicationContext context : Context)
has a reference to the application context, however that contains android specific code
Good news, you can use Mockito.mock(Context.class) and make the context return whatever you want in tests!
So just use a ViewModel as you normally would, and give it the ApplicationContext via the ViewModelProviders.Factory as you normally would.
You should not use Android related objects in your ViewModel as the motive of using a ViewModel is to separate the java code and the Android code so that you can test your business logic separately and you will have a separate layer of Android components and your business logic and data ,You should not have context in your ViewModel as it may lead to crashes
I was having trouble getting SharedPreferences when using the ViewModel class so I took the advice from answers above and did the following using AndroidViewModel. Everything looks great now
For the AndroidViewModel
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;
public class HomeViewModel extends AndroidViewModel {
private MutableLiveData<String> some_string;
public HomeViewModel(Application application) {
super(application);
some_string = new MutableLiveData<>();
Context context = getApplication().getApplicationContext();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
some_string.setValue("<your value here>"));
}
}
And in the Fragment
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
public class HomeFragment extends Fragment {
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.fragment_home, container, false);
HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class);
homeViewModel.getAddress().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(#Nullable String address) {
}
});
return root;
}
}
Using Hilt
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Singleton
#Provides
fun provideContext(application: Application): Context = application.applicationContext
}
Then pass it via constructor
class MyRepository #Inject constructor(private val context: Context) {
...
}
This is a way to get Context in to ViewModel
private val context = getApplication<Application>().applicationContext
you can access the application context from getApplication().getApplicationContext() from within the ViewModel. This is what you need to access resources, preferences, etc..
Finally i got the easiest way to get context in viewModel using MVVM. Suppose we need context in viewmodel class so we can go to dependency injection or using ANDROID_VIEW_MODEL instead of using ViewModel. sample is given below.
class SampleViewModel(app: Application) : AndroidViewModel(app){
private val context = getApplication<Application>().applicationContext
val prefManager = PrefManager(context)
//Now we can call any method which is in PrefManager class like
prefManager.getToken()
}
Use the following pattern:
class NameViewModel(
val variable:Class,application: Application):AndroidViewModel(application){
body...
}
The problem with injecting a Context into the ViewModel is that the Context can change at any time, depending on screen rotation, night mode, or system language, and any returned resources can change accordingly.
Returning a simple resource ID causes problems for extra parameters, like getString substitutions.
Returning a high-level result and moving rendering logic to the Activity makes it harder to test.
My solution is to have the ViewModel generate and return a function that is later run through the Activity's Context. Kotlin's syntactic sugar makes this incredibly easy!
ViewModel.kt:
// connectedStatus holds a function that calls Context methods
// `this` can be elided
val connectedStatus = MutableLiveData<Context.() -> String> {
// initial value
this.getString(R.string.connectionStatusWaiting)
}
connectedStatus.postValue {
this.getString(R.string.connectionStatusConnected, brand)
}
Activity.kt // is a Context
override fun onCreate(_: Bundle?) {
connectionViewModel.connectedStatus.observe(this) { it ->
// runs the posted value with the given Context receiver
txtConnectionStatus.text = this.run(it)
}
}
This allows ViewModel to hold all of the logic for calculating the displayed information, verified by unit tests, with the Activity being a very simple representation with no internal logic to hide bugs.
I created it this way:
#Module
public class ContextModule {
#Singleton
#Provides
#Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
And then I just added in AppComponent the ContextModule.class:
#Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
And then I injected the context in my ViewModel:
#Inject
#Named("AppContext")
Context context;

How do I use AndroidInjection class in custom views or other android classes?

My issue with the Android-specific pattern is, if you use their AndroidInjection class, there is no way to members inject other objects besides Activities/Fragments/custom views/adapters, except with the Application Component. This is because you cannot get a reference the the Subcomponent (AndroidInjector) used to inject Activities/Fragments.
This makes injecting Dialogs (if you use DialogFragments).
The AndroidInjection class seems to support just the core Android types.
What follows is not an answer to your question, but an explanation why you shouldn't be asking this question at all.
You should avoid injections into custom Views in general. The reasons for this are listed in this article.
Advantages of using Method Injection in this case [injection into custom Views] are:
Dependencies will need to be propagated from top level component (Activity or Fragment)
Method Injection does not open door to Single Responsibility Principle violation
No dependency on the framework
Better performance
The first advantage might come as a surprise because propagation from
top level component is harder than adding annotation to fields, and
involves more boilerplate code. This is surely a bad thing, right?.
Not in this case. In fact, there are two good aspects associated with
such a propagation of dependencies. First of all, the dependencies
will be visible at the top level component. Therefore, just by looking
at e.g. Fragment‘s fields, the reader of the code will immediately
understand that this Fragment shows images. Such optimizations for
readability makes the system more easily maintainable in the long
term. Secondly, there are not many use cases in which sub-classes of
View need additional dependencies. The fact that you need to actually
work in order to provide these dependencies will give you a bit of
time to think about whether providing them is a good design decision
to start with.
The second advantage is related to collaborative construction. You
might be very experienced software engineer yourself, but you’ll
probably have also less experienced teammates. Or it is possible that
you’ll leave the project one day, and the guy who will take over will
not be as good as you. By injecting one single dependency using a
framework, you basically open a door for other injections. Imagine
that some data from SharedPreferences becomes required in custom View
in order to e.g. fix a bug. One of the less experienced developers
might decide that it is a good approach to inject SharedPreferences
into custom View directly. Doing this violates Single Responsibility
Principle, but that developer might not even be aware of such a
concept. Therefore, in the long term, such injection “backdoors” can
reduce design quality and lead to long debug sessions.
The third advantage of using Method Injection with custom Views is
that you don’t couple the View to dependency injection framework. Just
imagine that few years from now you (or some other poor guy) need to
replace the framework. The fact that you’ll probably have tens of
Activities and Fragments to start with will make your life miserable.
If you’ll have additional tens or hundreds of custom Views to handle,
then it might bring you into suicidal mood.
The last (but not least) advantage is performance. One screen can
contain one Activity, several Fragments and tens of custom Views.
Bootstrapping this number of classes using dependency injection
framework might degrade application’s performance. It is especially
true for reflection based frameworks, but even Dagger carries some
performance cost.
In addition, I advice to avoid the new injection method that involves AndroidInjection class. It is discussed in this video tutorial.
First, you should think over Vasily's answer.
But let's think for a moment how we did this before Dagger Android?
We built a subcomponent from the component that was taken from the Application class. Later, we could use this subcomponent in order to inject fields, for example, of a custom view.
So, we'll try to do the exact same thing now.
Suppose, our aim is to inject MyAdapter class into a MyButton:
public class MyButton extends AppCompatButton {
#Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
...
}
}
And let's make the adapter have a dependency on the activity Context, not application Context:
public class MyAdapter {
#Inject
public MyAdapter(#Named("activity") Context context) {
}
}
Let's start with the custom Application class.
MyApplication.java
public class MyApplication extends DaggerApplication {
#Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
public static MySubcomponent mySubcomponent;
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder()
.create(this);
}
}
AppComponent.java:
#Component(modules = {AndroidSupportInjectionModule.class, ActivityBindingModule.class, AppModule.class})
#Singleton
public interface AppComponent extends AndroidInjector<MyApplication> {
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApplication> {
}
}
AppModule.java
#Module
abstract class AppModule {
#Binds
#Singleton
#Named("app")
abstract Context providesContext(Application application);
}
ActivityBindingModule.java
#Module(subcomponents = MySubcomponent.class)
public abstract class ActivityBindingModule {
#Binds
#IntoMap
#ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MySubcomponent.Builder builder);
}
AndroidSupportInjectionModule.java is shipping with dagger itself. If you do not use classes from support package (i.e. android.support.v4.app.Fragment instead of android.app.Fragment), then use AndroidInjectionModule.java.
MySubcomponent.java
#ActivityScope
#Subcomponent(modules = {SubcomponentModule.class/*, other modules here, if needed */})
public interface MySubcomponent extends AndroidInjector<MainActivity> {
void inject(MyButton button);
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
public abstract MySubcomponent build();
}
}
SubcomponentModule.java
#Module
abstract class SubcomponentModule {
#Binds
#ActivityScope
#Named("activity")
abstract Context toContext(MainActivity activity);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Inject
MySubcomponent subcomponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Will inject `subcomponent` field
AndroidInjection.inject(this);
// Saving this component in a static field
// Hereafter you are taking responsibility of mySubcomponent lifetime
MyApplication.mySubcomponent = subcomponent;
super.onCreate(savedInstanceState);
setContentView(new MyButton(this));
}
}
Having all of these, now here's how MyButton will look like:
public class MyButton extends AppCompatButton {
#Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
MyApplication.mySubcomponent.inject(this);
}
}
I admit that this looks hacky and certainly not an approach to stick to. I'm happy to see a better approach.
This is because you cannot get a reference the the Subcomponent (AndroidInjector) used to inject Activities/Fragments.
You can always just inject the component itself. Just add a field for the component to your Activity / Fragment and let Dagger inject it along with the rest.
// will be injected
#Inject MainActivitySubcomponent component;
The issue of whether the dagger-android classes like AndroidInjector should support injection inside Views or not has been discussed in the following Github issue:
https://github.com/google/dagger/issues/720
Quoting from one of the library authors:
There is both a philosophical point and logistical/implementation point to be made here.
First, it's not fully clear to us that injecting views is the right thing to do. View objects are meant to draw, and not much else. The controller (in a traditional MVC pattern) is the one which can coordinate and pass around the appropriate data to a view. Injecting a view blurs the lines between fragments and views (perhaps a child fragment is really the appropriate construct instead?)
From the implementation perspective, there is also a problem in that there isn't a canonical way to retrieve the View's parent Fragments (if any), or Activity to retrieve a parent component. There have been hacks suggested to build in that relationship, but so far we haven't seen anything that seems to suggest that we could do this correctly. We could just call View.getContext().getApplicationContext() and inject from there, but skipping the intermediate layers without any option for something in between is inconsistent with the rest of our design, and probably confusing to users even if it works.
This reinforces the opinion expressed in Vasily's answer.
To add further, people often seem to want to inject model-layer dependencies inside their custom views. This is a bad idea as it goes against the software engineering principle of separation of concerns.
The correct solution for associating a view and a model is to write an adapter like the adapters for RecyclerView and ListView. You can inject the model-layer dependency at the Fragment or Presenter level and set the adapter there.

Difference in the use of scopes in Dagger 2

I use Dagger 2 to perform Dependency Inversion rule in my app. I was just looking on Clean Architecture example by Fernando Cejas and I had a question - what is difference between the two approaches presented below:
If I mark the class like so:
#Singleton // or #PerActivity or #PerFragment, nevermind
public class UserDataStoreFactory {
private final Context context;
private final UserCache
}
Or if I create a module, where I define a provide-method and add this module into the any component (PerActivity, PerFragment and so on, nevermind)
#Module
public class SomeModule {
#Provides
#Singleton // or #PerActivity or #PerFragment, nevermind
UserDataStoreFactory providesUserDataStoreFactory (Context context, UserCache userCache) {
return new UserDataStoreFactory(context, userCache)
}
}
The two approaches are the same: Both will allow your UserDataStoreFactory to be injected throughout your application in the Singleton (or #PerActivity/#PerFragment/nevermind) scope.
The former approach, marking the class with the scope, will only work if the class has an #Inject-annotated constructor. The second approach does not require that constructor annotation, but does also require additional boilerplate code that is subject to change when the constructor arguments change. This makes the first approach more resilient to dependency changes on UserDataStoreFactory, even though they both achieve the same end result in your graph; however, the first approach may only be possible if the class is code you can change, or that is otherwise structured for scoped dependency injection.

Dagger 2 Custom Scope for each Fragment (or Activity etc...)

I've looked at a couple different articles which seem to suggest two different ways of doing custom scoping in Dagger 2:
MVP Presenters that Survive Configuration Changes Part-2 (Github repo):
Uses unique custom scopes for each fragment, e.g. #Hello1Scope and #Hello2Scope for Hello1Fragment and Hello2Fragment respectively
Tasting Dagger 2 on Android:
Uses a single custom scope for all fragments, e.g. #PerFragment.
From what I understand, it seems that, as in method 2, it should be okay to have a single scope defined that can be used for all fragments (i.e., #PerFragment). In fact (please correct me if I'm wrong), it seems like the name of the custom scope is irrelevant, and it's only where the subcomponent is created (i.e. in Application, Activity, or Fragment) that matters.
Is there any use case for defining a unique scope for each fragment such as in case 1?
After reading the answer by #vaughandroid, and What determines the lifecycle of a component (object graph) in Dagger 2? I think I understand custom scopes well enough to answer my own question.
First, here are a couple rules when dealing with components, modules, and scoping annotations in dagger2.
A Component must have a (single) scope annotation (e.g. #Singleton or #CustomScope).
A Module does not have a scope annotation.
A Module Method may have a (single) scope that matches its Component or no scope, where:
Scoped: means a single instance is created for each instance of the component.
Unscoped: mean a new instance is created with each inject() or provider call
NOTE: Dagger2 reserves #Singleton for the root Component (and it's modules) only. Subcomponents must use a custom scope, but the functionality of that scope is exactly the same as #Singleton.
Now, to answer the question: I would say create a new named scope for each conceptually different scope. For example, create a #PerActivity, #PerFragment, or #PerView annotation that indicates where the component should be instantiated, and thus indicating its lifetime.
Note: this is a compromise between two extremes. Consider the case of a root component and n subcomponents you will need:
at least 2 annotations (#Singleton and #SubSingleton), and
at most n+1 annotations (#Singleton, #SubSingleton1, ... #SubSingletonN).
Example:
Application:
/** AppComponent.java **/
#Singleton
#Component( modules = AppModule.class )
public interface AppComponent{
void inject(MainActivity mainActivity);
}
/** AppModule.java **/
#Module
public class AppModule{
private App app;
public AppModule(App app){
this.app = app;
}
// For singleton objects, annotate with same scope as component, i.e. #Singleton
#Provides #Singleton public App provideApp() { return app; }
#Provides #Singleton public EventBus provideBus() { return EventBus.getDefault(); }
}
Fragment:
/** Fragment1Component.java **/
#PerFragment
#Component( modules = {Fragment1Module.class}, dependencies = {AppComponent.class} )
public interface Fragment1Component {
void inject(Fragment1 fragment1);
}
/** Fragment1Module.java **/
#Module
public class Fragment1Module {
// For singleton objects, annotate with same scope as component, i.e. #PerFragment
#Provides #PerFragment public Fragment1Presenter providePresenter(){
return new Fragment1Presenter();
}
}
/** PerFragment.java **/
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerFragment {}
Your understanding is correct. The named scopes allow you to communicate intention, but they all work the same way.
For scoped provider methods, each Component instance will create 1 instance of the provided object.
For unscoped provider methods, each Component instance will create a new instance of the provided object whenever it needs to inject it.
The lifetime of the Component instance is important though. 2 different instances of the same component will provide different object instances, even scoped ones.
Scope names should indicate the lifetime of the provided object (which matches that of the Component instance) so #PerFragment makes much more sense to me.
From a quick look at the "MVP Presenters..." tutorial, it's not clear to me exactly what the author's intention is with having separate scopes. Since the names are just throwaway ones, I wouldn't read too much into it.

Categories

Resources