MVVM 架构,ViewModel 和LiveData 第二部分(译)

2,512 阅读6分钟
原文链接: dimon94.github.io

原文:MVVM architecture, ViewModel and LiveData (Part 2)

在Google I / O期间,Google推出了包含LiveDataViewModelarchitecture components ,这有助于使用MVVM模式开发Android应用程序。 本文讨论这些组件如何为遵循MVVM的Android应用程序提供服务。

在本系列的第一篇文章中,我们讨论了这些组件如何为遵循MVVM的Android应用程序提供服务。 在第二篇文章中,我们将回答在依赖注入的第一篇文章结尾处提出的其中一个问题。

本文假定您具有Dagger的基本知识,因为我们将专注于在MVVM示例中设置最新的Dagger版本(版本2.11)以实现依赖注入。

如果您需要关于Dagger 2.11的基本信息,请查看Dagger用户指南

配置Dagger 2.11

首先,让我们将Dagger 2.11依赖添加到我们的MVVM Sample。

指定Dagger版本2.11

project.ext {
    // … 
    dagger_version = "2.11"
}
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
compile "com.google.dagger:dagger:$project.dagger_version"
compile "com.google.dagger:dagger-android:$project.dagger_version"
compile "com.google.dagger:dagger-android-support:$project.dagger_version"

annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"

Dagger 2.11项目设置

下图显示了本示例中的主Dagger 2.11设置。

Dagger 2.11 setup in MVVM Sample

我们主要有以下Dagger App类/接口:

  1. AppModule是一个Dagger模块,负责在应用程序级别提供单例服务,例如GitHubServiceProjectViewModelFactory
  2. AppComponent负责注入AppModule
  3. ViewModelSubComponent是创建View Model实例的子组件。
  4. MainActivityModuleFragmentBuildersModule是Activity和Fragment实例提供程序。
  5. Injectable只是可注射Fragment的标记接口。
  6. AppInjector是一个辅助类,用于在实现Injectable接口时自动注入Fragments。

现在,让我们进入这个设置中每个Dagger项目的细节。

创建 View Model SubComponent

以下代码片断显示了ViewModelSubComponent接口,该接口负责创建ViewModel实例:

@Subcomponent
public interface ViewModelSubComponent {
    @Subcomponent.Builder
    interface Builder {
        ViewModelSubComponent build();
    }

    ProjectListViewModel projectListViewModel();
    ProjectViewModel projectViewModel();
}

请注意,ViewModelSubComponent将被ProjectViewModelFactory调用以获取ViewModel实例。

但什么是ProjectViewModelFactory

下一节回答这个问题。

创建自定义View Model Factory

ProjectViewModelFactory是一个扩展ViewModelProvider.Factory以便将ViewModel实例提供给使用者Fragment类的工厂。

以下代码片段显示了ProjectViewModelFactory,它是一个扩展ViewModelProvider.Factory的自解释Factory实现:

@Singleton
public class ProjectViewModelFactory implements ViewModelProvider.Factory {
    private final ArrayMap<Class, Callable<? extends ViewModel>> creators;

    @Inject
    public ProjectViewModelFactory(ViewModelSubComponent viewModelSubComponent) {
        creators = new ArrayMap<>();
        
        // View models cannot be injected directly because they won't be bound to the owner's
        // view model scope.
        creators.put(ProjectViewModel.class, () -> viewModelSubComponent.projectViewModel());
        creators.put(ProjectListViewModel.class, () -> viewModelSubComponent.projectListViewModel());
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Callable<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class, Callable<? extends ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("Unknown model class " + modelClass);
        }
        try {
            return (T) creator.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

现在,让我们看下一节中的主要应用程序模块。

创建App模块

AppModule是一个Dagger模块,负责在应用程序级别为消费者提供单例服务,例如GitHubServiceProjectViewModelFactory。 以下代码片段显示了AppModule类:

@Module(subcomponents = ViewModelSubComponent.class)
class AppModule {
    @Singleton @Provides
    GitHubService provideGithubService() {
        return new Retrofit.Builder()
                .baseUrl(GitHubService.HTTPS_API_GITHUB_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(GitHubService.class);
    }

    @Singleton
    @Provides
    ViewModelProvider.Factory provideViewModelFactory(
            ViewModelSubComponent.Builder viewModelSubComponent) {

        return new ProjectViewModelFactory(viewModelSubComponent.build());
    }
}

这里需要注意的一点是,不要忘记通过在@Module注解的subcomponents参数中指定ViewModelSubComponentAppModule

创建 Injectable和 AppInjector

可注入接口只是一个普通的空白标记接口,如下所示:

public interface Injectable {
  
}

Injectable将由可注射的Fragment实施。

为了在实现Injectable接口时自动注入片段,将创建以下AppInjector助手类,以在onFragmentCreated()上注入片段实例,如下所示:

public class AppInjector {
    private AppInjector() {}

    public static void init(MVVMApplication mvvmApplication) {
        DaggerAppComponent.builder().application(mvvmApplication)
                .build().inject(mvvmApplication);

        mvvmApplication
                .registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
                    @Override
                    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                        handleActivity(activity);
                    }

                    // Other methods are omitted for simplification …
                });
    }

    private static void handleActivity(Activity activity) {
        if (activity instanceof HasSupportFragmentInjector) {
            AndroidInjection.inject(activity);
        }
        if (activity instanceof FragmentActivity) {
            ((FragmentActivity) activity).getSupportFragmentManager()
                    .registerFragmentLifecycleCallbacks(
                            new FragmentManager.FragmentLifecycleCallbacks() {
                                @Override
                                public void onFragmentCreated(FragmentManager fm, Fragment fragment,
                                                              Bundle savedInstanceState) {
                                    if (fragment instanceof Injectable) {
                                        AndroidSupportInjection.inject(fragment);
                                    }
                                }
                            }, true);
        }
    }
}

有一点需要注意,AppInjector.init()将在应用程序启动时调用(正如我们将在自定义应用程序类部分中展示的那样)。

创建 Activity 和 Fragment Modules

以下代码片段显示了Fragments Dagger模块:

@Module
public abstract class FragmentBuildersModule {
    @ContributesAndroidInjector
    abstract ProjectFragment contributeProjectFragment();

    @ContributesAndroidInjector
    abstract ProjectListFragment contributeProjectListFragment();
}

从Dagger 2.10开始,@ContributesAndroidInjector轻松将活动和片段附加到匕首图上。 以下代码片断显示了MainActivityModule

@Module
public abstract class MainActivityModule {
    @ContributesAndroidInjector(modules = FragmentBuildersModule.class)
    abstract MainActivity contributeMainActivity();
}

现在,我们来看看Dagger 2.11设置中的最后一项,即AppComponent

创建AppComponent

下一个代码片段显示了AppComponent接口。

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        MainActivityModule.class
})
public interface AppComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance Builder application(Application application);
        AppComponent build();
    }
    void inject(MVVMApplication mvvmApplication);
}

有一件重要的事情要注意,除了包括AppModuleMainActivityModule之外,我们还根据官方文档向AppComponent添加了AndroidSupportInjectionModule,该文档声明有必要确保所有必要的绑定都可用。 AndroidSupportInjectionModule是dagger-android中的一个内置模块:

github.com/google/dagg…

更新存储库层实现

现在,我们完成了设置Dagger 2.11,让我们更新我们现有的应用程序代码,以便利用Dagger依赖注入。

ProjectRepository不再需要手动创建GitHubService服务实例,它所需要做的就是在它的GitHubService实例的构造函数中使用@Inject,如下所示:

@Singleton
public class ProjectRepository {
    private GitHubService gitHubService;

    @Inject
    public ProjectRepository(GitHubService gitHubService) {
        this.gitHubService = gitHubService;
    }

    // Other methods here are omitted for simplicity …
}

更新ViewModel层实现

更新ViewModel图层也是必要的,以避免在此图层内手动从ProjectRepository创建实例。

以下代码片段显示了ProjectViewModel的一个示例,该示例使用@inject注释来注入ApplicationProjectRepository实例:

public class ProjectViewModel extends AndroidViewModel {
    private static final String TAG = ProjectViewModel.class.getName();
    private static final MutableLiveData ABSENT = new MutableLiveData();
    {
        //noinspection unchecked
        ABSENT.setValue(null);
    }

    private final LiveData<Project> projectObservable;
    private final MutableLiveData<String> projectID;

    public ObservableField<Project> project = new ObservableField<>();

    @Inject
    public ProjectViewModel(@NonNull ProjectRepository projectRepository, @NonNull Application application) {
        super(application);

        this.projectID = new MutableLiveData<>();

        projectObservable = Transformations.switchMap(projectID, input -> {
            if (input.isEmpty()) {
                return ABSENT;
            }

            return projectRepository.getProjectDetails("Google", projectID.getValue());
        });
    }

    // Code is omitted for simplicity …
}

更新视图实现(Fragments和主要的Activity)

更新视图层也是必要的,以避免在该图层内手动创建ViewModel类的实例。

以下代码片段显示了ProjectFragment的一个示例:

public class ProjectFragment extends LifecycleFragment implements Injectable {

    @Inject
    ViewModelProvider.Factory viewModelFactory;


    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        final ProjectViewModel viewModel = ViewModelProviders.of(this, viewModelFactory)
                .get(ProjectViewModel.class);

        // …
    }

    // …
}

这里需要注意的一些重点:

  1. 现在每个Fragment都必须实现可注入接口。
  2. Fragment应该引用ViewModelProvider.Factory以获取ViewModel实例。

创建定制 Application类

最后,我们的自定义application类代码如下所示:

public class MVVMApplication extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        AppInjector.init(this);
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
}

这里需要注意两点:

  1. Application必须实现HasActivityInjector,并@Inject DispatchingAndroidInjector 才能从activityInjector()方法返回。
  2. 在Application类的onCreate()中,我们初始化AppInjector以便在实现Injectable接口时自动注入Fragments。

源代码

在GitHub中检查更新的应用程序的源代码,随意随意分叉和更新,如你所愿:

github.com/hazems/mvvm…

下一步是什么

在本文后,您现在有足够的信息来使用Google Architectural components创建自己的MVVM应用程序。 希望我能为本系列的下一篇文章留有余地,内容包括以下主题:

  1. 错误处理。
  2. 在此演示中添加更多功能,以了解Room如何促进SQLite数据操作,以及如何实现有效的缓存。
  3. 单元测试。
  4. 我们在哪里可以将Rx用于此架构?
  5. 我们如何使用Kotlin简化这个实现? (旅程真的有很多有趣的东西 : ))。

如果你喜欢这篇文章,我感谢你的支持与他人分享这篇文章,让伟大的Android社区知道。