This article has been published exclusively by wechat official account Yugang Shuo (@Ren Yugang).

Writing in the front

Over the past year or so, I’ve been working on creating the simplest framework that the average Android developer can get started with, and I’ve published a number of development tips, culminating in a series of articles called Building MVVM Applications with Kotlin. This involved the use of Dagger2 and ViewModel, and the collision between the two made me think of another very simple possibility for dependency injection, and triggered a series of chemistry that was a match made in heaven.

You can check out the code on Github: github.com/ditclear/Pa…

The way this article is written does not distinguish between MVP and MVVM structures, but provides a less methodical approach to injection.

Before we begin, let’s take a look at Dagger2 and ViewModel.

Dagger2 is a fast dependency injection tool for Android and Java provided by Google. It is the first choice of many Android developers for dependency injection.

But because of its tortuous learning path and high barriers to use, there have been a number of developers who have started and quit, including me.

ViewModel is one of Google’s Jetpack components. It is used to store and manage UI-related data. It abstracts the data logic related to an Activity or Fragment component, and can be adapted to the life cycle of the component. For example, when the screen rotates the Activity to rebuild, the data in the ViewModel is still valid. It also makes it easy for developers to communicate between fragments, between activities and fragments, and share data.

We can get the ViewModel instance with the following code

 mViewModel=ViewModelProviders.of(this,factory).get(PaoViewModel::class.java)
Copy the code

One to provide ViewModelProvider. The instance of the Factory to help build your ViewModel

public interface Factory {
    /**
     * Creates a new instance of the given {@code Class}.
     * <p>
     *
     * @param modelClass a {@code Class} whose instance is requested
     * @param <T>        The type parameter for the ViewModel.
     * @return a newly created ViewModel
     */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
Copy the code

PS: If you are using the MVP structure, just make it inherit from the ViewModel and you should achieve the same effect

Dagger2? Trouble?

First, let’s take a look at how Dagger2 typically works with dependency injection

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code}}Copy the code

This is an example of a Dagger-Android that makes fun of Dagger2 not working and why, and we’ll use it again here.

Dagger Android gives two reasons:

  1. Just copying and pasting the above code will make future refactoring difficult and will make some developers confused about how the Dagger is actually injected (ps: then it gets even harder to understand)
  2. The more important reason is that it requires the injection type (FrombulationActivity) to be aware of its syringe. Even if this is done through an interface rather than a concrete type, it breaks the core principle of dependency injection: a class should not know how to implement dependency injection.

That is to say you even in the heart of the base class (BaseActivity/BaseFragment) its encapsulation, also inevitable need to write getComponent. Inject code (this), but also must be added in the corresponding Component corresponding inject method, Hence the following code:

@ActivityScope
@Subcomponent
interface ActivityComponent {

    fun inject(activity: ArticleDetailActivity)

    fun inject(activity: CodeDetailActivity)

    fun inject(activity: MainActivity)

    fun inject(activity: LoginActivity)

    fun supplyFragmentComponentBuilder(a):FragmentComponent.Builder

}

@FragmentScope
@Subcomponent
interface FragmentComponent {

    fun inject(fragment: ArticleListFragment)
    fun inject(fragment: CodeListFragment)

    fun inject(fragment: CollectionListFragment)

    fun inject(fragment: MyCollectFragment)

    fun inject(fragment: HomeFragment)

    fun inject(fragment: RecentFragment)

    fun inject(fragment: SearchResultFragment)

    fun inject(fragment: RecentSearchFragment)

    fun inject(fragment: MyArticleFragment)

    @Subcomponent.Builder
    interface Builder {

        fun build(a): FragmentComponent
    }
}
Copy the code

The goal might just be to automatically inject your ViewModel or Presenter object, and your directory structure might look like this

The generated file after build will look like this

And then I’m going to solve those problems with a dagger-Android, okay?

Yes and no, maybe dagger-Android solves these problems, but it is inherently more complex than Dagger2, solving these problems introduces other problems, Android developers are not all Google developers, they can’t all have this logic and quality, After practicing, I decided it was better to move on to other di frameworks.

I just want to inject my ViewModel or Presenter. Simple development. Why bother?

Of course not, maybe we don’t need a Dagger Android, Dagger2 itself can do that.

When Dagger2 meets the ViewModel

Cooperate with the ViewModel components, we don’t need so trouble, but also don’t need to consider where injection, added in the Component/Activity/fragments at sixes and sevens inject and @ inject () method.

We just need a few files

How to do?

Through @Binds and @Intomap

The roles of @Sharing and @provider are not very different, the difference being that @provider requires a specific implementation to be shared, while @Sharing simply tells Dagger2 who is shared by whom, for example

	@Provides
	fun provideUserService(retrofit: Retrofit) :UserService 	=retrofit.create(UserService::class.java)


    @Binds
    abstract fun bindCodeDetailViewModel(viewModel: CodeDetailViewModel):ViewModel


Copy the code

@intomap allows Dagger2 to inject multiple element dependencies into the Map.

ViewModelModule ** Created by ditClear on 2018/8/17. */
@Module
abstract class ViewModelModule{

	// ...
    
    @Binds
    @IntoMap
    @ViewModelKey(CodeDetailViewModel::class)
    abstract fun bindCodeDetailViewModel(viewModel: CodeDetailViewModel):ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class) //key
    abstract fun bindMainViewModel(viewModel: MainViewModel):ViewModel
 
    / /...
    // Provide the factory class for the ViewModel
     @Binds
    abstract fun bindViewModelFactory(factory:APPViewModelFactory): ViewModelProvider.Factory
}
Copy the code

From this, Dagger2 automatically generates a key Map based on this information. Key is the Class of the ViewModel, and value is the Provider object that provides the instance of the ViewModel. The corresponding ViewModel object can be obtained by using the provider.get() method.

private Map<Class<? extends ViewModel>, Provider<ViewModel>>
    getMapOfClassOfAndProviderOfViewModel() {
  return MapBuilder.<Class<? extends ViewModel>, Provider<ViewModel>>newMapBuilder(7)
      .put(ArticleDetailViewModel.class, (Provider) articleDetailViewModelProvider)
      .put(CodeDetailViewModel.class, (Provider) codeDetailViewModelProvider)
      .put(MainViewModel.class, (Provider) mainViewModelProvider)
      .put(RecentViewModel.class, (Provider) recentViewModelProvider)
      .put(LoginViewModel.class, (Provider) loginViewModelProvider)
      .put(ArticleListViewModel.class, (Provider) articleListViewModelProvider)
      .put(CodeListViewModel.class, (Provider) codeListViewModelProvider)
      .build();
}
Copy the code

These objects are also automatically assembled by Dagger2 for us.

With this in mind, we can easily construct the ViewModel factory class APPViewModelFactory and construct the desired ViewModel.

/** * APPViewModelFactory provides an instance of the ViewModel cache * by injecting the Map directly through Dagger2, fetching the corresponding ViewModel factory class directly through the key, Create by ditClear on 2018/8/17
class APPViewModelFactory @Inject constructor(private val creators:Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory{

    override fun 
        create(modelClass: Class<T>): T {
        // Find the Provider for the corresponding ViewModel by class
        valcreator = creators[modelClass]? :creators.entries.firstOrNull{ modelClass.isAssignableFrom(it.key) }? .value? :throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get(a)as T // Get the ViewModel with get()
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
Copy the code

A new ViewModel instance is constructed when creator.get() is created


public ArticleDetailViewModel get(a) {
return provideInstance(repoProvider, userRepoProvider);
}

public static ArticleDetailViewModel provideInstance(
Provider<PaoRepository> repoProvider, Provider<UserRepository> userRepoProvider) {
return new ArticleDetailViewModel(repoProvider.get(), userRepoProvider.get());
}
Copy the code

At this point, ViewModel is already tied to Dagger2, so how to not write all those annoying inject()?

The answer is to make your Application. Hold your ViewModelProvider Factory as an example, the Talk is being ~

Inject in Application

class PaoApp : Application() {

    @Inject
    lateinit var factory: APPViewModelFactory

    val appModule by lazy { AppModule(this)}override fun onCreate(a) {
        super.onCreate()
        / /...
        DaggerAppComponent.builder().appModule(appModule).build().inject(this)}}Copy the code

Use in activities/fragments

/ / BaseActivity base class
abstract class BaseActivity : AppCompatActivity(), Presenter {
	/ /...
    val factory:ViewModelProvider.Factory by lazy {
        if (application is PaoApp) {
            val mainApplication = application as PaoApp
           return@lazy mainApplication.factory
        }else{
            throw IllegalStateException("application is not PaoApp")}}fun <T :ViewModel> getInjectViewModel (c:Class<T>)= ViewModelProviders.of(this,factory).get(c)


    
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        //....

        initView()
    }
        abstract fun initView(a)
    //....
}
Copy the code

Activities that need to be injected, such as ArticleDetailActivity, do not need to annotate @Inject

class ArticleDetailActivity : BaseActivity() {
	// It is easy to get the ViewModel
	private val mViewModel: ArticleDetailViewModel by lazy { 	getInjectViewModel(ArticleDetailViewModel::class.java)}

	override fun initView(a) {
        // Call the method
   		mViewModel.dosth()
    }
}
Copy the code

Fragment = PaoNet: Master branch = PaoNet: Master branch

Write in the last

Compare this to the usual Dagger2, dagger-Android principle

  • Normal assignment: Manual construction is tedious and time-consuming
viewmodel = ViewModel(Repo(remote,local,prefrence))
Copy the code
  • Normal Dagger2 injection: You need to identify what needs to be injected with @inject in the Activity and add it to the Componentinject(activity)Method, which generates a lot of Java classes
 instance.viewmodel = component.viewmodel
Copy the code
  • Dagger-Android injection: many modules/components need to be written, high threshold, not convenient to use, better not to use
app.map = Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>

activity.viewmodel = app.map.get(activity.class).getComponent().viewmodel
Copy the code
  • Injection of dagger2-ViewModel: There is no need to identify and inject in the ActivityXX_MemberInjectorsJava class, the least changed, pure oneDependent retrieval container.
app.factory = component.AppViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>>)

viewmodel = ViewModelProviders.of(this,app.factory).get(viewmodel.class)
Copy the code

Dagger/Android/Dagger2-viewModel/Dagger2-ViewModel/Dagger2-ViewModel And it’s all injected in the Application. Dagger2-viewmodel doesn’t need to add AndroidInjection.inject(this) code to the Dagger-Android, it’s more like a dependency management container to build the ViewModel, but for me or the MVVM structure I want to build, That was enough.

other

Code address: github.com/ditclear/Pa…

Kotlin construct MVVM application series of use: www.jianshu.com/c/50336d57e…

Jane: www.jianshu.com/p/d3c43b9dd…