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

一、简介

年初开始我们公司的项目上开始使用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 其实这里可以写一下默认处理方式,可以在业务模块写网络异常处理
        }
    }
    复制代码
    

有用的话,点个赞吧ღ( ´・ᴗ・` )比心

作者:张钦
链接:https://juejin.im/post/6865596056567676942

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335