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

作者: 木木玩Android | 来源:发表于2020-08-28 17:00 被阅读0次

    原文链接:https://juejin.im/post/6865596056567676942

    一、简介

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

    注:

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

    二、依赖配置

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

      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

      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.

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

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

      @HiltAndroidApp
      class AppKtApplication : SampleApplication()
      复制代码
      
    3. AppModule(重点)

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

      @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

      interface WeatherService {
      
          @GET("free/day")
          suspend fun getWeather(@QueryMap maps: Map<String, @JvmSuppressWildcards Any>): WeatherBean
      }
      复制代码
      
    2. Repository

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

      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处理
      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注解
      ViewModelProvider(this).get(WeatherViewModel::class.java)
      复制代码
      

    五、公共代码

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

    1. BaseViewModel

      public class BaseViewModel extends ViewModel {
      
          /**
          * 加载窗状态
          */
          public final MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
      
          /**
          * 通用网络请求异常
          */
          public final MutableLiveData<Throwable> networkError = new MutableLiveData<>();
      }
      复制代码
      
    2. BaseMvvmActivity

      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 其实这里可以写一下默认处理方式,可以在业务模块写网络异常处理
          }
      }
      复制代码
      

    六、源码与参考链接

    相关文章

      网友评论

        本文标题:基于Hilt+Retrofit+协程的MVVM模式探索

        本文链接:https://www.haomeiwen.com/subject/jkglsktx.html