📜 ⬆️ ⬇️

Dagger2 and architectural component "ViewModel"

ViewModel is a component from a set of libraries called Android Architecture Components , which were presented on Google I / O 2017. ViewModel is designed to store and manage data associated with the view, as well as the ability to “relive” the re-creation of activations (for example, flipping the screen).


On Habré was already a good article devoted to the ViewModel, where you can get acquainted with this topic in more detail.


This article will look at the options for injecting (providing) dependencies to the ViewModel component using Dagger 2. The problem is that getting the ViewModel must be done in a special way, which in turn imposes some restrictions that are associated with providing dependencies to the ViewModel class itself, and providing the ViewModel as a dependency. This article may also be of interest to those interested in the practical application of such Dagger functionality as multibinding.


A special way to get the ViewModel is as follows:


In the beginning, we need to get a ViewModelProvider , which will be associated with an activation or a fragment, and this also determines the lifetime of the ViewModel .


 ViewModelProvider provider = ViewModelProviders.of(<Activity|Fragment>[, ViewModelProvider.Factory]); 

The second parameter is used to specify the factory that will be used to create the ViewModel instance; it is not necessary, if we do not specify it, the default factory will be used. The factory by default supports the creation of instances of classes that are the heirs of the ViewModel (with a constructor without arguments) and classes that are the heirs of the AndroidViewModel (with the designer with one argument - the type Application ).


If we want to create a ViewModel instance with its own arguments in the constructor (which are not supported by the factory default), then we need to implement our own factory.


After we got the ViewModelProvider , we can already get the ViewModel :


 ProductViewModel productVM = provider.get(ProductViewModel.class); 

From the above description follows:



There are different ways to provide dependencies in the ViewModel , and each of the methods has its pros and cons, so several options will be considered.


Default Factory Option


Let's start with the definition of the module and the subcomponent that will be used for the injection into the activit:


 @Module public class ActivityModule { @Provides public ProductViewModel productViewModel(AppCompatActivity activity) { return ViewModelProviders.of(activity).get(ProductViewModel.class); } } 

 @Subcomponent(modules = {ActivityModule.class}) public interface ActivitySubComponent { @Subcomponent.Builder interface Builder { @BindsInstance Builder with(AppCompatActivity activity); ActivitySubComponent build(); } void inject(MainActivity mainActivity); } 

The presence of such a module and a subcomponent allows us to query the model via @Inject instead of ViewModelProviders.of(activity).get(ProductViewModel.class) inside our activit.


When using the default factory, instances of our ViewModel will be created by this factory and we cannot request dependencies in ViewModel through the designer, so we inject dependencies through the component. In order not to litter the root component, we will create a subcomponent specifically for the twist of models.


 @Subcomponent public interface ViewModelSubComponent { @Subcomponent.Builder interface Builder { ViewModelSubComponent build(); } void inject(ProductViewModel productViewModel); } 

Define our root component:


 @Component(modules = {AppModule.class}) @Singleton public interface AppComponent { @Component.Builder interface Builder { @BindsInstance Builder withApplication(Application application); AppComponent build(); } ViewModelSubComponent.Builder viewModelSubComponentBuilder(); ActivitySubComponent.Builder activitySubComponentBuilder(); } 

AppModule - will contain the dependencies that our view models need (for example, ProductDetailsFacade ).


Create an Application that will contain the root component and the subcomponent for the view of models:


 public class App extends Application { private AppComponent appComponent; private ViewModelSubComponent viewModelSubComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAppComponent .builder() .withApplication(this) .build(); viewModelSubComponent = appComponent .viewModelSubComponentBuilder() .build(); } public AppComponent getAppComponent() { return appComponent; } public ViewModelSubComponent getViewModelSubComponent() { return viewModelSubComponent; } } 

Now we can inject dependencies in the ViewModel :


 public class ProductViewModel extends AndroidViewModel { @Inject ProductFacade productFacade; public ProductViewModel(Application application) { super(application); // ,   . //    ((App)application) .getViewModelSubComponent() .inject(this); } //methods } 

Instead of inject, you can use the provide methods on the component.


Inject ViewModel in activit:


 public class MainActivity extends AppCompatActivity { @Inject ProductViewModel productViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ,   . //    ((App) getApplication()) .getAppComponent() .activitySubComponentBuilder() .with(this) .build() .inject(this); } } 

The advantages of this method:



Disadvantages:



Option with own factory


Create a pair of view models, where we will provide dependencies through the constructor:


 public class UserViewModel extends ViewModel { private UserFacade userFacade; @Inject public UserViewModel(UserFacade userFacade) { this.userFacade = userFacade; } //methods } public class UserGroupViewModel extends ViewModel { private UserGroupFacade userGroupFacade; @Inject public UserGroupViewModel(UserGroupFacade userGroupFacade) { this.userGroupFacade = userGroupFacade; } //methods } 

We will create our own key annotation, which we will use to make our view of models into the collection for the use of multibinding. You can read about multibindig here .


 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @MapKey @interface ViewModelKey { Class<? extends ViewModel> value(); } 

Define the module where we will be adding our view models to the collection:


 @Module public abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(UserViewModel.class) abstract ViewModel userViewModel(UserViewModel userViewModel); @Binds @IntoMap @ViewModelKey(UserGroupViewModel.class) abstract ViewModel groupViewModel(UserGroupViewModel groupViewModel); } 

Let's go over to writing our factory for providing ViewModel using multi-siding:


 public class DemoViewModelFactory implements ViewModelProvider.Factory { private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels; @Inject public DemoViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) { this.viewModels = viewModels; } @Override public <T extends ViewModel> T create(Class<T> modelClass) { Provider<ViewModel> viewModelProvider = viewModels.get(modelClass); if (viewModelProvider == null) { throw new IllegalArgumentException("model class " + modelClass + " not found"); } return (T) viewModelProvider.get(); } } 

Provider<ViewModel> allows us to use delayed initialization with a model view, as well as a new instance model view every time.
Without multi-siding, we could have a huge if / else block.


Application class:


 @Component(modules = {AppModule.class, ViewModelModule.class}) @Singleton public interface AppComponent { @Component.Builder interface Builder { @BindsInstance Builder withApplication(Application application); AppComponent build(); } void inject(MainActivity mainActivity); } 

Providing the ViewModel to our activites:


 public class MainActivity extends AppCompatActivity { @Inject DemoViewModelFactory viewModelFactory; UserViewModel userViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((App) getApplication()) .getAppComponent() .inject(this); userViewModel = ViewModelProviders.of(this, viewModelFactory) .get(UserViewModel.class); } } 

The advantages of this method:



Disadvantages:



If we requested the model via @Inject , then we would simply receive the model instance (because it is already in the dependency graph) and it would not be associated with the activation life cycle or fragment and would not be able to “survive” for example, flipping the screen. In order for this to work, we need to create through the factory.
We cannot add a model view to the graph twice we cannot do the following:


 @Module public class ActivityModule { // , .. UserViewModel     //(@Inject  ) @Provides public UserViewModel productViewModel( DemoViewModelFactory viewModelFactory, AppCompatActivity activity) { return ViewModelProviders .of(activity, viewModelFactory) .get(UserViewModel .class); } } 

To circumvent this limitation, you can enter an interface for the model and request a view of the model on the interface:


 @Module public class ActivityModule { @Provides public UserViewModelInterface productViewModel( DemoViewModelFactory viewModelFactory, AppCompatActivity activity) { return ViewModelProviders .of(activity, viewModelFactory) .get(ProductViewModelImplementation.class); } } public class MainActivity extends AppCompatActivity { @Inject UserViewModelInterface userViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((App) getApplication()) .getAppComponent() .activitySubComponentBuilder() .with(this) .build() .inject(this); } } 

At the time of this writing, dagger 2.11 and architectural components of version 1.0.0-alpha9 were used. As you can see, the architectural components at the time of this writing have an alpha version. Perhaps in the future there will be other methods for obtaining a view of the model.


')

Source: https://habr.com/ru/post/337320/


All Articles