Android Sunflower + Dagger2

2019/06/24

       这篇文章主要介绍Sunflower项目加入Dagger2依赖注入框架的过程,也是我对Dagger框架学习和理解加深的一个过程。

       这里就不再对Dagger中@Component, @Subcomponent, @Module, @Binds, @Provides, @Inject, @Singleton等注解的作用和用法做过多的介绍了,大家可以参考官方文档及大量的第三方博客。
       主要介绍一下使用Dagger2过程中几次改进及对应的理解。

Version Description
v1.3.0 improve performance and a elegant way to inject params like intent of activity
v1.2.0 another simple and better way to inject ViewModel
v1.1.0 using dagger2 as di, add LongClickListener to delete plant
v1.0.0 android architecture in Java

v1.1.0版本

       v1.1.0版本是参照Google官方示例GithubBrowserSample写的。按照其中的DI注入流程从Kotlin改写为Java。
       在Kotlin改写成Java的过程中也遇到了不少问题。比如@IntoMap@MapKeyMultibindings问题,查了很多博客,都没有说清楚。最后又去仔细查看官方文档,最终解决了问题。所以一定要对官方文档足够重视!不要图快,跳过官方文档,只看一些第三方的快速集成的方法。在看其他第三方资料之前,最好先把官方文档认真读一遍,会减少很多疑惑,变相的节省宝贵的时间。
       如果你是从项目一开始就用Dagger作为依赖注入框架,在掌握了基本使用方法后,一般不会出什么问题;如果你像我一样把之前的项目改成使用Dagger依赖注入,那一定要认真仔细的检查是否所有的依赖都注入@Inject了,是否所有的Module都加入了Component等等。
       我就是因为忘记把ViewModleModule加入到Component中,导致一直报错。我以为是我把Kotlin转成Java过程中出了问题,所以查了大量的资料。不过虽然浪费了不少时间,但也加深了我对Dagger的理解以及Kotlin和Java之间的异同。也催生了v1.2.0版本。
       我们先来看看v1.1.0版本是怎么做的。

主要代码片段:
ViewModelFactory:

@Singleton
public class ViewModelProviderFactory extends ViewModelProvider.NewInstanceFactory {
    Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
    @Inject
    public ViewModelProviderFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        return (T) creator.get();
    }
}

ViewModelKey:

@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

ViewModelModule:

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(GardenPlantingListViewModel.class)
    abstract ViewModel bindGardenPlantingListViewModel(GardenPlantingListViewModel gardenPlantingListViewModel);
    @Binds
    @IntoMap
    @ViewModelKey(PlantDetailViewModel.class)
    abstract ViewModel bindPlantDetailViewModel(PlantDetailViewModel plantDetailViewModel);
    @Binds
    @IntoMap
    @ViewModelKey(PlantListViewModel.class)
    abstract ViewModel bindPlantListViewModel(PlantListViewModel plantListViewModel);
    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelProviderFactory factory);
}

v1.2.0版本

       上面提到的,在查阅资料的过程中,看到了一种ViewModel简洁的注入方式,Android ViewModel injections revisited。所以就按照这样的方法重新写了ViewModel的Factory。这就是v1.2.0的由来。

主要代码片段:
ViewModelFactory:

@Singleton
public class ViewModelSimpleFactory<VM extends ViewModel> extends ViewModelProvider.NewInstanceFactory {
    private Lazy<VM> viewModel;
    @Inject
    public ViewModelSimpleFactory(Lazy<VM> viewModel) {
        this.viewModel = viewModel;
    }
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return (T) viewModel.get();
    }
}

ViewModel:

public class GardenPlantingListViewModel extends ViewModel {
    
    @Inject
    public GardenPlantingListViewModel(GardenPlantingRepository gardenPlantingRepository) {
    }
}

       这样就好了,是不是比v1.1.0中的方法简洁了很多。剩下的你只需要在Activity或Fragment中注入使用就好了。

Activity or Fragment:

public class GardenFragment extends Fragment implements Injectable {
    @Inject
    public ViewModelSimpleFactory<GardenPlantingListViewModel> factory;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //...
        GardenPlantingListViewModel viewModel = ViewModelProviders.of(this, factory).get(GardenPlantingListViewModel.class);
        //...
    }
}

v1.3.0版本

       上面完成了基本的依赖注入,但有一种情况,参数是动态变化的。比如说依赖于Activity或Fragment传递的intent,即每次都是不同的。这种情况如何实现呢?
       在v1.1.0v1.2.0版本中,采用了折中的办法。就是先不注入这个参数,在拿到这个实例后,通过setter进行设置。如:
PlantDetailViewModel:

public class PlantDetailViewModel extends ViewModel {
    PlantRepository plantRepository;
    private GardenPlantingRepository gardenPlantingRepository;
    private String plantId;
    @Inject
    public PlantDetailViewModel(PlantRepository plantRepository, GardenPlantingRepository gardenPlantingRepository) {
        this.plantRepository = plantRepository;
        this.gardenPlantingRepository = gardenPlantingRepository;
    }

    public void setPlantId(String plantId) {
        this.plantId = plantId;
    }
}

PlantDetailFragment:

public class PlantDetailFragment extends Fragment implements Injectable {

    @Inject
    public ViewModelSimpleFactory<PlantDetailViewModel> factory;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        String plantId = PlantDetailFragmentArgs.fromBundle(getArguments()).getPlantId();

        PlantDetailViewModel plantDetailViewModel = ViewModelProviders.of(this, factory).get(PlantDetailViewModel.class);
        plantDetailViewModel.setPlantId(plantId);
    }
}

       这种方式不太让我满意,所以我又查找资料。找到了两种方式:

  1. 创建带构造函数的module
    比如我们构建一个PlantDetailModule:
     @Module
     public class PlantDetailModule(){
         String plantId;
         public PlantDetailModule(String plantId) {
             this.plantId = plantId;
         }
    
         @Provides
         PlantDetailViewModel providePlantDetailViewModel(PlantRepository plantRepository, GardenPlantingRepository gardenPlantingRepository) {
             return new PlantDetailViewModel(plantRepository, gardenPlantingRepository, plantId)
         }
     }
    

    PlantDetailViewModel:

     public class PlantDetailViewModel extends ViewModel {
         PlantRepository plantRepository;
         private GardenPlantingRepository gardenPlantingRepository;
         private String plantId;
            
         public PlantDetailViewModel(PlantRepository plantRepository, GardenPlantingRepository gardenPlantingRepository, String plantId) {
             this.plantRepository = plantRepository;
             this.gardenPlantingRepository = gardenPlantingRepository;
             this.plantId = plantId;
         }
     }
    

    Activity:

     public class MainActivity extends AppCompatActivity {
     
         @Inject
         PlantDetailViewModel viewModel;
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             //...
             DaggerAppComponent.builder()
                     .plantDetailModule(new PlantDetailModule("apple"))
                     .build()
                     .inject(this);
             //...
         }
     }
    

           这种方式显然很复杂,而且也不符合Dagger的最佳使用方式:module最好是无状态的,static的。详细可查看另一篇文章Dagger2 Guidelines

  2. 使用AssistedInject库
           AssistedInject是square开发的配合Dagger使用的一个依赖注入库。好消息是Dagger在未来也会集成这个库。
    使用方法:
    首先,添加依赖:
     compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
     annotationProcessor 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
    

    第二,使用注解@AssistedInject代替@Inject, 为变量前添加@Assisted注解, 使用@AssistedInject.Factory注解添加一个Factory接口.
    PlantDetailViewModel:

     public class PlantDetailViewModel extends ViewModel {
         @AssistedInject
         public PlantDetailViewModel(PlantRepository plantRepository, GardenPlantingRepository gardenPlantingRepository,@Assisted String plantId) {
         }
         @AssistedInject.Factory
         public interface Factory {
             PlantDetailViewModel create(String plantId);
         }
     }
    

    第三, 创建一个module或者加入一个已经存在的module,使得上面创建Factory接口加入graph中。
    GardenActivityModule:

     @AssistedModule
     @Module(includes = {AssistedInject_GardenActivityModule.class})
     abstract class GardenActivityModule {
         //...
     }
    

    第三,在Activity or Fragement:

     public class PlantDetailFragment extends Fragment implements Injectable {
         @Inject
         public PlantDetailViewModel.Factory factory2;
         @Override
         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
             String plantId = PlantDetailFragmentArgs.fromBundle(getArguments()).getPlantId();
             PlantDetailViewModel plantDetailViewModel = factory2.create(plantId);
         }
     }
    

           其中,第一和第三步只需要做一次。也就是说除了第一次,其他注入你只需要写一个Foctory接口。所以这还算是一个比较优雅的解决方案。

       但是,这个对一般的带变量参数的注入是个完美的解决方案。但对ViewModel来说,它应该由ViewModelProvider.Factory提供。我暂时还想不出比较好的解决方法。如果你有更好的方案,欢迎你联系我。

       欢迎Star&Follow!

Post Directory