Android MVVM实现

版权归作者所有,转发请注明出处:https://www.jianshu.com/p/a945c992a494

前言

开发需要多思多想,万万不可做代码的搬运工,应用程序不仅应该满足UI、UX需求,还需要易于理解,易于维护,方便测试,拥有更多的灵活性,尽量兼顾多种场景,少留隐患

mvvm.png

1.MVVM

我们有很多常用的架构,比如MVC,MVP,MVVM等,先简单的介绍Android平台下这几种架构以及区别

MVC

Model: 数据模型以及各种数据类
View: 所展示的视图,布局文件以及View
Controller: 控制器ActivityFragment等,在其中控制各种View与数据的绑定逻辑以及事件处理以及与Android系统的交互等
优点: 简单,开发时基本不需要思考,可以快速的开发编写出所要的效果
缺点: Controller代码量庞大,所有业务都在其中,后期维护成本较大,与系统交互的代码在其中,业务逻辑在其中,与视图绑定交互的业务也在其中

MVP

Model: 数据模型以及各种数据类
View: 所展示的视图,布局文件以及View,以及提供UI接口
Presenter: 负责完成ViewModel间交互的业务逻辑,主要有两个部分,操作UI以及业务逻辑进行数据操作
优点: Activity只是处理View的加载以及实现UI Interface 进行View的更新,以及与系统进行交互比如事件绑定等,业务逻辑会抽离出来去到Presenter层,降低了Controller层的复杂程度
缺点: 对View的依赖程度相对较大,View层的变更会影响ActivityPresenter以及UI接口,并且当业务程度相对很复杂的时候也会导致Presenter层相对复杂,代码量巨大

MVVM

Model: 数据模型以及各种数据类
View: XMLViewActivity/Fragment,指责为View的绘制以及与Android系统层进行交互和通信
ViewModel: 拥有视图绑定器,充当ModelView的双向绑定,以及业务逻辑
优点: ViewModel只会持有数据绑定起以及业务逻辑,不会持有UI组件,UI的处理只会在View层,在MVP中Presenter不仅要处理业务逻辑还需要持有UI Interface负责UI的控制,ViewModel其中的业务逻辑部分由于没有UI的耦合很容易进行单元测试和修改
缺点: 前期需要多项准备以搭建完备的MVVM架构,并且由于需要使用到ViewModelLiveDataDataBinding等技术需要知识储备,并且由于我们是使用DataBinding,以及ViewModelLiveData自动生成去进行UI和数据的绑定,如果出现异常相对不好排查

2.代码的分层

mike_mvvm.png

根据Clean Architecture的设计方式,外圈依赖内圈,我们可以将代码分为以下模块

UI模块: 负责负责View的绘制,以及与Android系统的交互,UI层依赖于ViewModel层持有ViewModel的对象

ViewModel模块: 负责持有数据绑定以及UsecaseViewModel不会依赖于UI模块,这意味着它不应该持有View对象或者上下文对象

Usecase模块: 只负责核心业务模块,它依赖于Repository模块

Repository: 存储区模块会处理数据操作。它们会提供一个干净的 API,以便应用的其余部分可以轻松检索该数据。数据更新时,它们知道从何处获取数据以及进行哪些 API 调用。您可以将存储区视为不同数据源(如持久性模型、网络服务和缓存)之间的媒介,它只负责数据的获取以及刷新,不关心数据如何使用

3.MVVM的数据流

mike_mvvm_data_flow.png

当我们点击按钮事件会在UI层触发,然后UI层会调用ViewModel去进行通信,ViewModel会调用Usecase以获取对应的结果然后更新ViewModel中的LiveData,在Usecase中会与Repository进行交互获取数据,Repository会与对应的WebService交互以获取对应的数据

4.MVVM的实现所需要的组件

  • Dagger2: 依赖注入框架,基于上述的结构,UI需要依赖ViewModelViewModel需要依赖UsecaseUsecase需要依赖RepositoryRepository需要依赖WebService或其他数据源,可以使用Dagger去管理依赖关系
  • RxJava2/RxAndroid: 使用观察者模式的异步框架,由于层级之间在依赖的基础上需要进行通信,上层可以使用观察者的方式拿到并处理下层的数据流,也可以给予观察数据流编写对应的单元测试
  • Okhttp3/Retrofit2: Android端主流网络框架,使用Retrofit也可以返回一个可观察对象配合RxJava使用,使我们的业务链更清晰
  • ViewModel : 官方提供的类,以注重生命周期的方式存储和管理界面相关数据,ViewModel模块的主要职责就是负责数据绑定以及业务操作,View层会持有ViewModel对象从而监听数据的变化或者调用其中的Usecase,我们也可以将ViewModel中的数据绑定器直接绑定到xml视图中实现双向绑定

5.MVVM的实现

基于上述使用MVVM实现一个登录案例

mike_mvvm_login.png

添加依赖

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.core:core-ktx:1.3.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    //Dagger2
    implementation 'com.google.dagger:dagger-android:2.35.1'
    implementation 'com.google.dagger:dagger-android-support:2.35.1'
    kapt 'com.google.dagger:dagger-android-processor:2.35.1'
    kapt "com.google.dagger:dagger-compiler:2.35.1"
    //OkHttp3
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
    //retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    //RxJava
    implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    //lifecycle
    implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"

在Module的Gradle文件中声明

apply plugin: 'kotlin-kapt'

Dagger2配置

创建全局的NetWorkModule

@Module
class NetWorkModule {

    @Provides
    @Singleton
    fun provideGson(): Gson {
        return GsonBuilder().setLenient().create()
    }

    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        val okHttpBuilder = OkHttpClient.Builder()
        okHttpBuilder.addInterceptor(HttpLoggingInterceptor())
        return okHttpBuilder.build()
    }

    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://www.test.com")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build()
    }

}

创建全局的Component,完成之后使用Android Studio中的Rebuild Project,生成对应的DaggerAppComponent

@Singleton
@Component(
    modules = [
        AndroidSupportInjectionModule::class,
        NetWorkModule::class]
)
interface AppComponent : AndroidInjector {

    @Component.Builder
    interface Builder {
        fun build(): AppComponent

        @BindsInstance
        fun application(application: Application): Builder
    }

}

创建Application,继承自DaggerApplication,并将其配置到AndroidManifest.xml

class LoginMVVMApplication : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector {
        return DaggerAppComponent.builder().application(this).build()
    }
}

创建LoginActivity

class LoginActivity : DaggerAppCompatActivity() {
}

创建Login package,配置LoginActivity的依赖,将LoginActivityBuilder在AppComponent中声明

@Module
abstract class LoginActivityBuilder {

    @ContributesAndroidInjector(modules = [LoginActivityModule::class, LoginViewModelModule::class])
    @ActivityScope
    abstract fun loginActivity(): LoginActivity
}
@Module
class LoginActivityModule {

}

创建Entity

data class UseInfo(val name: String)

创建Repository相关

创建ApiService

interface ApiService {
    @POST("/api/login")
    fun login(): Single
}

创建接口LoginRepo,以及实现类LoginRepoImpl,将ApiService依赖注入其中

interface LoginRepo {
    fun login(): Single
}
class LoginRepoImpl @Inject constructor(private val apiService: ApiService) : LoginRepo {

    override fun login(): Single {
//        return Single.create {
//            Thread.sleep(3000)
//            it.onSuccess(UseInfo("Mike"))
//        }
        return apiService.login()
    }

}

将LoginRepo 添加到LoginActivityModule,后续在UseCase中将会注入此依赖

@Module
class LoginActivityModule {

    @Provides
    @ActivityScope
    fun provideLoginRepo(loginRepoImpl: LoginRepoImpl): LoginRepo {
        return loginRepoImpl
    }
}

创建UseCase相关
创建接口LoginUseCase,实现类LoginUseCaseImpl注入依赖LoginRepo,并将其添加到LoginActivityModule

interface LoginUseCase {
    fun login(): Single
}
class LoginUseCaseImpl @Inject constructor(private val loginRepo: LoginRepo) : LoginUseCase {

    override fun login(): Single {
        return loginRepo.login()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
    }
}
@Module
class LoginActivityModule {

    @Provides
    @ActivityScope
    fun provideLoginRepo(loginRepoImpl: LoginRepoImpl): LoginRepo {
        return loginRepoImpl
    }

    @Provides
    @ActivityScope
    fun provideLoginUseCase(loginUseCaseImpl: LoginUseCaseImpl): LoginUseCase {
        return loginUseCaseImpl
    }
}

创建ViewModel相关
创建ViewModelFactory,用于生成ViewModel示例,提供在Activity中生成ViewModel的函数ViewModelProvider.Factory.obtainViewModel,将ViewModelFactory添加到LoginActivityModule

class ViewModelFactory @Inject constructor(var viewModels: MutableMap, Provider>) :
    ViewModelProvider.Factory {

    override fun  create(modelClass: Class): T {
        @Suppress("UNCHECKED_CAST")
        return viewModels[modelClass]?.get() as T
    }
}

inline fun  ViewModelProvider.Factory.obtainViewModel(activity: FragmentActivity): VM =
    ViewModelProvider(activity, this)[VM::class.java]
@Provides
@ActivityScope
fun provideFactory(viewModels:MutableMap, Provider>): ViewModelProvider.Factory {
    return ViewModelFactory(viewModels)
}

创建ViewModelKey

@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass)

创建LoginViewModel以及LoginViewModelImpl

abstract class LoginViewModel : ViewModel() {
    abstract val useCase: LoginUseCase
    abstract val showProgress: MutableLiveData
    abstract val userInfo: MutableLiveData
    abstract fun login()
}
class LoginViewModelImpl @Inject constructor(override val useCase: LoginUseCase) :
    LoginViewModel() {

    override val showProgress: MutableLiveData = MutableLiveData(false)

    override val userInfo: MutableLiveData = MutableLiveData()

    private val compositeDisposable = CompositeDisposable()

    override fun login() {
        showProgress.postValue(true)
        useCase.login()
            .doAfterTerminate {
                showProgress.postValue(false)
            }
            .subscribe({
                userInfo.value = it
            }, {
                //TODO Error
            }).also {
                compositeDisposable.add(it)
            }
    }

    override fun onCleared() {
        compositeDisposable.clear()
    }
}

创建LoginViewModelModule提供ViewModel依赖,并添加到LoginActivityBuilder中

@Module
abstract class LoginViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(LoginViewModel::class)
    internal abstract fun bindLoginViewModel(viewModel: LoginViewModelImpl): ViewModel
}

完善LoginActivity代码以及布局文件
暂时先不使用DataBinding,后续添加
布局文件




    

完善LoginActivity代码,调用ViewModel业务,监听LiveData刷新UI

class LoginActivity : DaggerAppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.atctivity_login)
        loginViewModel = viewModelFactory.obtainViewModel(this)
        loginViewModel.userInfo.observe(this, Observer {
            Toast.makeText(this, "Login complete!", Toast.LENGTH_LONG).show()
        })
        val progress = findViewById(R.id.loading)
        loginViewModel.showProgress.observe(this, Observer {
            progress.visibility = if (it) View.VISIBLE else View.INVISIBLE
        })


        findViewById

代码结构:


结构.PNG

代码连接
https://github.com/huangyiCode/android_mvvm

待续
使用DataBinding优化视图的绑定
单元测试的执行

欢迎关注Mike的

Android 知识整理

你可能感兴趣的:(Android MVVM实现)