一個Jetpack結合MVVM的快速開發框架

🐔🐔🐔JetPackMvvm

  • 基於MVVM模式集成谷歌官方推薦的JetPack組件庫:LiveData、ViewModel、Lifecycle、Navigation組件
  • 使用kotlin語言,添加大量拓展函數,簡化代碼
  • 加入Retrofit網絡請求,協程,幫你簡化各種操作,讓你快速請求網絡

演示Demo

已用該庫重構了我之前的玩安卓項目,利用Navigation組件以單Activity+Fragment架構編寫,優化了很多代碼,對比之前的mvp項目,開發效率與舒適度要提高了不少,想看之前MVP的項目可以去 https://github.com/hegaojian/WanAndroid

效果圖展示

項目效果圖

APK下載:

1.如何集成

  • 1.1 在root’s build.gradle中加入Jitpack倉庫
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  • 1.2 在app’s build.gradle中添加依賴
dependencies {
  ...
  implementation 'me.hegj:JetpackMvvm:1.1.0'
}
  • 1.3 在app’s build.gradle中,android 模塊下開啓DataBinding(如果你不想用DataBinding,請忽略這一步)
android {
    ...
    dataBinding {
        enabled = true 
    }
}

2.繼承基類

一般我們項目中都會有一套自己定義的符合業務需求的基類 BaseActivity/BaseFragment,所以我們的基類需要繼承本框架的Base類BaseVmDbActivity/BaseVmDbFragment

Activity:

abstract class BaseActivity<VM : BaseViewModel, DB : ViewDataBinding> : BaseVmDbActivity<VM, DB>() {
     /**
     * 當前Activity綁定的視圖佈局Id abstract修飾供子類實現
     */
    abstract override fun layoutId(): Int
    /**
     * 當前Activityc創建後調用的方法 abstract修飾供子類實現
     */
    abstract override fun initView(savedInstanceState: Bundle?)

    /**
     * 創建liveData數據觀察
     */
    override override fun createObserver()


    /**
     * 打開等待框 在這裏實現你的等待框展示
     */
    override fun showLoading(message: String) {
       ...
    }

    /**
     * 關閉等待框 在這裏實現你的等待框關閉
     */
    override fun dismissLoading() {
       ...
    }
}

Fragment:

abstract class BaseFragment<VM : BaseViewModel,DB:ViewDataBinding> : BaseVmDbFragment<VM,DB>() {
    /**
     * 當前Fragment綁定的視圖佈局Id abstract修飾供子類實現
     */
    abstract override fun layoutId(): Int
   
    abstract override fun initView(savedInstanceState: Bundle?)

    /**
     * 懶加載 只有當前fragment視圖顯示時纔會觸發該方法 abstract修飾供子類實現
     */
    abstract override fun lazyLoadData()

    /**
     * 創建liveData數據觀察 懶加載之後纔會觸發
     */
    override override fun createObserver()
  
    /**
     * Fragment執行onViewCreated後觸發的方法 
     */
    override fun initData() {

    }
    
   /**
     * 打開等待框 在這裏實現你的等待框展示
     */
    override fun showLoading(message: String) {
       ...
    }

    /**
     * 關閉等待框 在這裏實現你的等待框關閉
     */
    override fun dismissLoading() {
       ...
    }
}

3.編寫一個登錄功能

  • 3.1 編寫fragment_login.xml界面後轉換成 databind 佈局(鼠標停在根佈局,Alt+Enter 點擊提示 Convert to data binding layout即可)
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/tools">
    <data>
       
    </data>
    <LinearLayout>
       ....
    </LinearLayout>
 </layout>   
  • 3.2 創建LoginViewModel類繼承BaseViewModel
class LoginViewModel(application: Application) : BaseViewModel(application) {
  
}
  • 3.3 創建LoginFragment 繼承基類傳入相關泛型,第一個泛型爲你創建的LoginViewModel,第二個泛型爲ViewDataBind,保存fragment_login.xml後databinding會生成一個FragmentLoginBinding類。(如果沒有生成,試着點擊Build->Clean Project)
class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
    
    /**
     *  當前fragment綁定的佈局
     */
    override fun layoutId() = R.layout.fragment_login
    
    /**
     *  初始化操作
     */
    override fun initView(savedInstanceState: Bundle?) {
        ...
    }
    
    /**
     *  fragment 懶加載
     */
    override fun lazyLoadData() { 
        ...
    }
}

4.網絡請求(Retrofit+協程)

  • 4.1 新建請求配置類繼承 BaseNetworkApi 示例:
class NetworkApi : BaseNetworkApi() {

   companion object {
         
        val instance: NetworkApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { NetworkApi() }

        //雙重校驗鎖式-單例 封裝NetApiService 方便直接快速調用
        val service: ApiService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            instance.getApi(ApiService::class.java, ApiService.SERVER_URL)
        }
    }
   
    /**
     * 實現重寫父類的setHttpClientBuilder方法,
     * 在這裏可以添加攔截器,可以對 OkHttpClient.Builder 做任意你想要做的騷操作
     */
    override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
        builder.apply {
            //示例:添加公共heads,可以存放token,公共參數等, 注意要設置在日誌攔截器之前,不然Log中會不顯示head信息
            addInterceptor(MyHeadInterceptor())
            // 日誌攔截器
            addInterceptor(LogInterceptor())
            //超時時間 連接、讀、寫
            connectTimeout(10, TimeUnit.SECONDS)
            readTimeout(5, TimeUnit.SECONDS)
            writeTimeout(5, TimeUnit.SECONDS)
        }
        return builder
    }

    /**
     * 實現重寫父類的setRetrofitBuilder方法,
     * 在這裏可以對Retrofit.Builder做任意騷操作,比如添加GSON解析器,protobuf等
     */
    override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
        return builder.apply {
            addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
            addCallAdapterFactory(CoroutineCallAdapterFactory())
        }
    }
}
  • 4.2如果你請求服務器返回的數據有基類(沒有可忽略這一步)例如:
{
    "data": ...,
    "errorCode": 0,
    "errorMsg": ""
}

該示例格式是 玩Android Api返回的數據格式,如果errorCode等於0 請求成功,否則請求失敗
作爲開發者的角度來說,我們主要是想得到脫殼數據-data,且不想每次都判斷errorCode==0請求是否成功或失敗
這時我們可以在服務器返回數據基類中繼承BaseResponse,實現相關方法:

data class ApiResponse<T>(var errorCode: Int, var errorMsg: String, var data: T) : BaseResponse<T>() {

    // 這裏是示例,wanandroid 網站返回的 錯誤碼爲 0 就代表請求成功,請你根據自己的業務需求來編寫
    override fun isSucces() = errorCode == 0

    override fun getResponseCode() = errorCode

    override fun getResponseData() = data

    override fun getResponseMsg() = errorMsg

}
  • 4.3 在ViewModel中發起請求,所有請求都是在viewModelScope中啓動,請求會發生在IO線程,最終回調在主線程上,當頁面銷燬的時候,請求會統一取消,不用擔心內存泄露的風險,框架做了2種請求使用方式

1、將請求數據包裝給ResultState,在Activity/Fragment中去監聽ResultState拿到數據做處理

class RequestLoginViewModel(application: Application) : BaseViewModel(application) {

  //自動脫殼過濾處理請求結果,自動判斷結果是否成功
    var loginResult = MutableLiveData<ResultState<UserInfo>>()
    
  //不用框架幫脫殼
    var loginResult2 = MutableLiveData<ResultState<ApiResponse<UserInfo>>>()
    
  fun login(username: String, password: String){
   //1.在 Activity/Fragment的監聽回調中拿到已脫殼的數據(項目有基類的可以用)
        request(
            { HttpRequestManger.instance.login(username, password) }, //請求體
            loginResult,//請求的結果接收者,請求成功與否都會改變該值,在Activity或fragment中監聽回調結果,具體可看loginActivity中的回調
            true,//是否顯示等待框,,默認false不顯示 可以默認不傳
            "正在登錄中..."//等待框內容,可以默認不填請求網絡中...
        )
        
   //2.在Activity/Fragment中的監聽拿到未脫殼的數據,你可以自己根據code做業務需求操作(項目沒有基類的可以用)
        requestNoCheck(
          {HttpRequestManger.instance.login(username,password)},
          loginResult2,
          true,
          "正在登錄中...") 
}


class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
    
    /** 注意,在by lazy中使用getViewModel一定要使用泛型,雖然他提示不報錯,但是你不寫是不行的 */
    private val requestLoginRegisterViewModel: RequestLoginRegisterViewModel by lazy { getViewModel<RequestLoginRegisterViewModel>() }
    
    /**
     *  當前fragment綁定的佈局
     */
    override fun layoutId() = R.layout.fragment_login
    
    /**
     *  初始化操作
     */
    override fun initView(savedInstanceState: Bundle?) {
        ...
    }
    
    /**
     *  fragment 懶加載
     */
    override fun lazyLoadData() { 
        ...
    }
    
    override fun createObserver(){
      //脫殼
       requestLoginRegisterViewModel.loginResult.observe(viewLifecycleOwner,
            Observer { resultState ->
                parseState(resultState, {
                    //登錄成功 打印用戶
                    it.username.logd()
                }, {
                    //登錄失敗(網絡連接問題,服務器的結果碼不正確...異常都會走在這裏)
                    showMessage(it.errorMsg)
                })
            })
    
       //不脫殼
       requestLoginRegisterViewModel.loginResult2.observe(viewLifecycleOwner, Observer {resultState ->
               parseState(resultState,{
                   if(it.errorCode==0){
                       //登錄成功 打印用戶名
                       it.data.username.logd()
                   }else{
                       //登錄失敗
                       showMessage(it.errorMsg)
                   }
               },{
                   //請求發生了異常
                   showMessage(it.errorMsg)
               })
           })
   } 
}

2、 直接在當前ViewMdel中拿到請求結果

class RequestLoginViewModel(application: Application) : BaseViewModel(application) {
    
  fun login(username: String, password: String){
   //1.拿到已脫殼的數據(項目有基類的可以用)
     request({HttpRequestManger.instance.login(username,password)},{
             //請求成功 已自動處理了 請求結果是否正常
             it.username.logd()
         },{
             //請求失敗 網絡異常,或者請求結果碼錯誤都會回調在這裏
             it.errorMsg.logd()
         },true,"正在登錄中...")
        
   //2.拿到未脫殼的數據,你可以自己根據code做業務需求操作(項目沒有基類的可以用)
       requestNoCheck({HttpRequestManger.instance.login(username,password)},{
            //請求成功 自己拿到數據做業務需求操作
            if(it.errorCode==0){
                //結果正確
                it.data.username.logd()
            }else{
                //結果錯誤
                it.errorMsg.logd()
            }
        },{
            //請求失敗 網絡異常回調在這裏
            it.errorMsg.logd()
        },true,"正在登錄中...")
}

5.獲取ViewModel

  • 5.1我們的activity/fragment會有多個ViewModel,按官方的寫感覺有點累
 val mainViewModel = ViewModelProvider(this,
            ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)

**優化了一下改成了

//在activity中獲取當前Activity級別作用域的ViewModel
 val mainViewModel = getViewModel<MainViewModel>()
 
//在activity中獲取Application級別作用域的ViewModel(注,Application類繼承框架的BaseApp類纔有用)
 val mainViewModel = getAppViewModel<MainViewModel>()

//在fragment中獲取當前Fragment級別作用域的ViewModel
val mainViewModel = getViewModel<MainViewModel>()

//在fragment中獲取父類Activity級別作用域的ViewModel
val mainViewModel = getActivityViewModel<MainViewModel>()

//在fragment中獲取Application級別作用域的ViewModel(注,Application類繼承框架的BaseApp類纔有用)
val mainViewModel = getAppViewModel<MainViewModel>()

6.寫了一些常用的拓展函數

 算了不寫了,這個不重要,想具體看的話可以在
 me.hgj.jetpackmvvm.ext.util
 me.hgj.jetpackmvvm.ext.view
 的包中看,反正你也可以自己寫,按照自己的喜好與需求來

License

 Copyright 2019, hegaojian(何高建)       
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at 
 
       http://www.apache.org/licenses/LICENSE-2.0 

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章