0%

基于Hilt+Retrofit+协程的MVVM模式探索

一、简介

年初开始我们公司的项目上开始使用MVVM与Jetpack,但是我们并没有使用Kotlin,最近想学习一下Kotlin的协程,所以写了个Demo,然后就寻思写篇博客。最开始并没有想用hilt,感觉最近挺火的就试了一下~

注:

  1. hilt木有考虑多模块情况
  2. 没有在生产项目中使用过~
  3. 主要说了用法,基础知识很少讲,不熟悉的可以看下最下面的参考文章,讲的比较详细。

二、依赖配置

  1. 根目录build(hilt需要加一个依赖)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ext {
    kotlin_version = '1.4.0'
    hilt_version = '2.28.3-alpha'
    }
    dependencies {
    ...
    // hilt
    classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
  2. 模块build

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    apply plugin: 'dagger.hilt.android.plugin'

    dependencies {
    ...
    // 协程
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

    // hilt
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
    implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02"
    kapt "androidx.hilt:hilt-compiler:1.0.0-alpha02"

    }

三、Hilt

  1. 使用Arouter遇到的一个坑

    arguments后面不能能用=,要用+=!!!,要不然会提示[Hilt] Processing did not complete. See error above for details.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    defaultConfig {
    ...

    javaCompileOptions {
    annotationProcessorOptions {
    // fix hilt
    arguments += [AROUTER_MODULE_NAME: project.getName()]
    }
    }
    }
  2. Application

    @HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。

    1
    2
    @HiltAndroidApp
    class AppKtApplication : SampleApplication()
  3. AppModule(重点)

    这里与Dagger2类似,@Provides注解的方法命名规则(好像)是provide+返回值类名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @Module
    @InstallIn(ApplicationComponent::class)
    object AppModule {

    @Provides
    fun provideWeatherService(retrofit: Retrofit): WeatherService = retrofit.create(WeatherService::class.java)

    @Singleton
    @Provides
    fun provideRetrofit(okHttp: OkHttpClient): Retrofit {
    return Retrofit.Builder()
    .baseUrl(Constants.BASE_URL) // 设置OkHttpclient
    .client(okHttp) // RxJava2
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // 字符串
    .addConverterFactory(ScalarsConverterFactory.create()) // Gson
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    }

    @Provides
    fun provideOkHttpClient(): OkHttpClient {
    val builder = OkHttpClient.Builder()
    if (BuildConfig.DEBUG) {
    // OkHttp日志拦截器
    builder.addInterceptor(HttpLoggingInterceptor())
    builder.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
    override fun log(message: String) {
    val strLength: Int = message.length
    var start = 0
    var end = 2000
    for (i in 0..99) {
    //剩下的文本还是大于规定长度则继续重复截取并输出
    if (strLength > end) {
    Log.d("okhttp", message.substring(start, end))
    start = end
    end += 2000
    } else {
    Log.d("okhttp", message.substring(start, strLength))
    break
    }
    }
    }

    }).setLevel(HttpLoggingInterceptor.Level.BODY))
    }
    return builder.build()
    }
    }

四、Hilt+协程

  1. ServiceApi

    Retrofit2.6开始原生支持suspend

    1
    2
    3
    4
    5
    interface WeatherService {

    @GET("free/day")
    suspend fun getWeather(@QueryMap maps: Map<String, @JvmSuppressWildcards Any>): WeatherBean
    }
  2. Repository

    WeatherService是通过hilt注入的,使用时不需要传构造参数

    1
    2
    3
    4
    5
    6
    7
    class WeatherRepository @Inject constructor(
    private val mClient: WeatherService
    ) {

    suspend fun getWeather(map: Map<String, Any>) = mClient.getWeather(map)

    }
  3. ViewModel

    1. WeatherRepository是通过hilt注入的,使用时不需要传构造参数,但是要给使用的Activiti添加一个@AndroidEntryPoint注解
    2. block: suspend () -> Unit是一个高阶函数
    3. viewModelScope来自androidx.lifecycle:c:2.2.0,他会替我们处理协程的生命周期
    4. isLoadingnetworkError是在BaseViewModel中定义的MutableLiveData,会在BaseMvvmActivityBaseMvvmFragment中处理Loading窗与异常,也可以在当前Activity重写,具体请看Demo
    5. 我这里没有处理服务器返回错误,直接通过DataBinding展示到页面上了,需要的话可以先判断一下,如果返回错误可以使用networkErrorpost一个自定义ServerExceptionActivity处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class WeatherViewModel @ViewModelInject constructor(
    private val repository: WeatherRepository
    ) : BaseViewModel() {

    val weatherBean = MutableLiveData<WeatherBean>()

    fun loadWeather() {

    isLoading.postValue(true)

    val map: Map<String, Any> = HashMap<String, Any>()

    launch({
    weatherBean.postValue(repository.getWeather(map))
    }, {
    LogUtils.e(it)
    networkError.postValue(it)
    }, {
    isLoading.postValue(false)
    })
    }

    private fun launch(block: suspend () -> Unit, error: suspend (Throwable) -> Unit, complete: suspend () -> Unit) = viewModelScope.launch {
    try {
    block()
    } catch (e: Throwable) {
    error(e)
    } finally {
    complete()
    }
    }
    }
  4. Activity

    1. 刚刚说过了WeatherRepository是通过hilt注入的,使用时不需要传构造参数,但是要给使用的Activiti添加一个@AndroidEntryPoint注解
    1
    ViewModelProvider(this).get(WeatherViewModel::class.java)

五、公共代码

我的Base代码是用Java写的,简单写一下供大家参考~

  1. BaseViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class BaseViewModel extends ViewModel {

    /**
    * 加载窗状态
    */
    public final MutableLiveData<Boolean> isLoading = new MutableLiveData<>();

    /**
    * 通用网络请求异常
    */
    public final MutableLiveData<Throwable> networkError = new MutableLiveData<>();
    }
  2. BaseMvvmActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public abstract class BaseMvvmActivity<V extends ViewBinding, VM extends BaseViewModel> extends BaseActivity<V> {

    protected VM mVm;

    @Override
    protected void initViewModel() {
    mVm = getViewModel();

    mVm.isLoading.observe(this, isLoading -> {
    if (isLoading) {
    showProgress();
    } else {
    hideProgress();
    }
    });
    mVm.networkError.observe(this, this::commonNetworkErrorListener);
    }

    /**
    * 获取ViewModel
    */
    protected abstract VM getViewModel();

    /**
    * 通用网络异常回掉
    */
    protected void commonNetworkErrorListener(Throwable throwable) {
    // TODO 其实这里可以写一下默认处理方式,可以在业务模块写网络异常处理
    }
    }

六、源码与参考链接