当Dagger2撞上ViewModel

本文已受权 微信公众号 玉刚说 (@任玉刚)独家发布。java

写在前面

过去一年多的时间里,我一直在致力于打造一个最简单,并能让普通Android开发者都能快速上手的框架,并陆续发表了多篇开发心得,最终汇总为了《使用Kotlin构建MVVM应用程序》系列文章。其中就涉及到Dagger2和ViewModel的使用,这二者之间的碰撞令我想到了另外一种十分简单的去进行依赖注入的可能,并引起了一系列的化学反应,能够说是天做之合。android

能够在Github上查看相关代码:github.com/ditclear/Pa…git

本文的写法不区分MVP仍是MVVM结构,只是提供了一种不那么循序渐进的注入方式。github

开始以前,咱们先来了解一下Dagger2和ViewModel。缓存

Dagger2是由Google提供的一个适用于Android和Java的快速的依赖注入工具,是现今众多Android开发者进行依赖注入的首选。微信

但因为其曲折的学习路线和较高的使用门槛,因而出现了一批又一批从入门到放弃的开发者,固然也包括我。app

ViewModel是Google的Jetpack组件中的一个。它是用来存储和管理UI相关的数据,将一个Activity或Fragment组件相关的数据逻辑抽象出来,并能适配组件的生命周期,如当屏幕旋转Activity重建后,ViewModel中的数据依然有效。它还能够帮助开发者轻易实现 FragmentFragment 之间, ActivityFragment 之间的通信以及共享数据框架

咱们能够经过如下的代码来获取ViewModel实例ide

mViewModel=ViewModelProviders.of(this,factory).get(PaoViewModel::class.java)
复制代码

其中要提供一个ViewModelProvider.Factory的实例来帮助构建你的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);
}
复制代码

PS:若是你使用的是MVP结构,那么只须要让其继承自ViewModel,也应该能达到相同的效果

Dagger2?麻烦?

首先,咱们先来看看Dagger2一般的依赖注入的方式

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
  }
}
复制代码

这是Dagger-Android用来吐槽Dagger2不行的示例,并给出了缘由,这里咱们也拿来用一次。

Dagger-Android给出了两点理由:

  1. 只是复制粘贴上面的代码会让之后的重构比较困难,还会让一些开发者不知道Dagger究竟是如何进行注入的(ps:而后就更不易理解了)
  2. 更重要的缘由是:它要求注射类型(FrombulationActivity)知道其注射器。 即便这是经过接口而不是具体类型完成的,它打破了依赖注入的核心原则:一个类不该该知道如何实现依赖注入。

也就是说你就算是在基类(BaseActivity/BaseFragment)中将其封装一下,也无可避免的须要写getComponent.inject(this)这样的代码,并且还必须在对应的Component中添加相应的inject方法,因而便有了如下的代码:

@ActivityScope
@Subcomponent
interface ActivityComponent {

    fun inject(activity: ArticleDetailActivity)

    fun inject(activity: CodeDetailActivity)

    fun inject(activity: MainActivity)

    fun inject(activity: LoginActivity)

    fun supplyFragmentComponentBuilder():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(): FragmentComponent
    }
}
复制代码

而目的也许就只是为了自动注入你的ViewModel或者Presenter对象,而后你的目录结构可能就会下图通常

而build以后生成的文件将会是这样的

而后就要用Dagger-Android来解决这些问题

是也不是,可能Dagger-Android解决了这些问题,可是它自己就比Dagger2更复杂,解决了这些问题,却引入了其它的问题,Android开发者并不是都是Google开发者,不可能都具有这样强的逻辑和素质,实践以后我以为还不如转向其它依赖注入的框架。

我只是想注入一下个人ViewModel或Presenter,简简单单的开发,有必要这么麻烦吗?

固然不是,也许咱们并不须要Dagger-Android,Dagger2自己就能作到。

当Dagger2赶上ViewModel

配合ViewModel组件,咱们根本不须要这么麻烦,并且也根本不须要再考虑注入到哪里去,在Component/Activity/Fragment中添加乱七八糟的inject()方法和@Inject

咱们只须要几个文件就好

怎么作?

经过@Binds@IntoMap

@Binds 和 @Provider的做用相差不大,区别在于@Provider须要写明具体的实现,而@Binds只是告诉Dagger2谁是谁实现的,好比

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


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


复制代码

而@IntoMap则可让Dagger2将多个元素依赖注入到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
 
    //...
    //提供ViewModel的工厂类
     @Binds
    abstract fun bindViewModelFactory(factory:APPViewModelFactory): ViewModelProvider.Factory
}
复制代码

经过这些,Dagger2会根据这些信息自动生成一个关键的Map。key为ViewModel的Class,value则为提供ViewModel实例的Provider对象,经过provider.get()方法就能够获取到相应的ViewModel对象。

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();
}
复制代码

而这些对象也是由Dagger2帮咱们自动组装的。

DaggerAppComponent

有了这些,咱们就能够很方便的去构造ViewModel的工厂类APPViewModelFactory,并构造到所需的ViewModel。

/** * 页面描述:APPViewModelFactory 提供ViewModel 缓存的实例 * 经过Dagger2将Map直接注入,经过key直接获取到相应的ViewModel的工厂类,进而new 出所需的ViewModel实例 * Created by ditclear on 2018/8/17. */
class APPViewModelFactory @Inject constructor(private val creators:Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory{

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        //经过class找到相应ViewModel的Provider
        val creator = creators[modelClass]?:creators.entries.firstOrNull{
            modelClass.isAssignableFrom(it.key)
        }?.value?:throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T //经过get()方法获取到ViewModel
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
复制代码

当creator.get()时则会去构造新的ViewModel实例

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

public static ArticleDetailViewModel provideInstance( Provider<PaoRepository> repoProvider, Provider<UserRepository> userRepoProvider) {
return new ArticleDetailViewModel(repoProvider.get(), userRepoProvider.get());
}
复制代码

到这里,ViewModel与Dagger2已经紧密联系起来,那如何不去写那么多恼人的inject()呢?

答案就是让你的Application持有你的ViewModelProvider.Factory实例,Talk is Cheap~

在Application中进行注入

class PaoApp : Application() {

    @Inject
    lateinit var factory: APPViewModelFactory

    val appModule by lazy { AppModule(this) }

    override fun onCreate() {
        super.onCreate()
        //...
        DaggerAppComponent.builder().appModule(appModule).build().inject(this)
    }
}
复制代码

在Activity/Fragment之中使用

//基类BaseActivity
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()
    //....
}
复制代码

须要进行注入的Activity,好比ArticleDetailActivity就不须要再写@inject之类的注解

class ArticleDetailActivity : BaseActivity() {
	//很方便就可获取到ViewModel
	private val mViewModel: ArticleDetailViewModel by lazy { 	getInjectViewModel(ArticleDetailViewModel::class.java) }

	override fun initView() {
        //调用方法
   		mViewModel.dosth()
    }
}
复制代码

Fragment相同的道理,具体能够查看【PaoNet : Master分支】相应的代码。

写在最后

咱们能够和一般的Dagger二、Dagger-Android的原理比较一下

  • 普通的赋值:手动构造,十分繁琐,浪费时间
viewmodel = ViewModel(Repo(remote,local,prefrence))
复制代码
  • 一般的Dagger2注入:须要在Activity中用@Inject标识哪些须要被注入,并在Component中添加inject(activity)方法,会生成不少java类,有些繁琐
instance.viewmodel = component.viewmodel
复制代码
  • Dagger-Android的注入:须要编写不少module,component,门槛高,不方便使用,还不如不用
app.map = Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>

activity.viewmodel = app.map.get(activity.class).getComponent().viewmodel
复制代码
  • Dagger2-ViewModel的注入:不须要在Activity中标识和inject,不会生成各类XX_MemberInjectors的java类,修改时改动最少,纯粹的一个依赖检索容器
app.factory = component.AppViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>>)

viewmodel = ViewModelProviders.of(this,app.factory).get(viewmodel.class)
复制代码

对比Dagger-Android和Dagger2-ViewModel,二者都是间接经过Map来进行注入,不过一个的key是Class<Activity>,一个是Class<ViewModel>,并且都是在Application中inject一下。而Dagger2-ViewModel不须要向Dagger-Android那样添加AndroidInjection.inject(this)代码,更像是一个用来构造ViewModel的依赖管理容器,但对于我或者我但愿打造的MVVM结构来讲,这便已经足够了。

其它

代码地址:github.com/ditclear/Pa…

《使用Kotlin构建MVVM应用程序系列》 :www.jianshu.com/c/50336d57e…

简书:www.jianshu.com/p/d3c43b9dd…

相关文章
相关标签/搜索