聽說你已經(jīng)會了MVP,MVC,MVVP那么MVI在向你招手
是什么
Model-View-Intent是安卓最新的設計模式泵琳。它的靈感來自于于André Staltz的Cycle.js ,并且被 Hannes Dorfmann帶到安卓世界嵌溢。
Model-View-Intent
你可能看過Model在其他的設計模式比如MVC,MVP或者MVVP。但是MVI的Model和其他設計模式的完全不一樣:
- Model 代表一種狀態(tài)(數(shù)據(jù)的顯示趟脂,你的控件的可視或者隱藏确虱,RecyclerView的滑動位置等等)。Model在MVI中比其他的設計模式更加的形式化赦邻。你應用的一個頁面可能包含一個或多個Model對象啸蜜。Model在一個Domain層被定義和管理坑雅。
- View 代表一個定義一系列用戶動作的可觀察對象的接口和一個渲染方法
-
Intent 不是
android.content.Intent
!這個Intent
簡單的說是一種意圖,或者說一種動作衬横,或者說一種用戶與APP交互產(chǎn)生的命令裹粤。對于每一個用戶動作(意圖/命令)被View分發(fā),被Presenter
觀察(是的蜂林,MVI也是有Presenter
的蛹尝,是不是應該改名叫MVIP后豫,啊哈)。
MVI的整體流程圖
這張圖描述了MVI模式的響應突那,和數(shù)據(jù)的流動方向挫酿。我們的Model是被Domain層管理和維護的,用來對用戶的某種意圖/動作/命令愕难,做出反應的早龟。只要有新的Model被創(chuàng)建,那么猫缭,意味著我們的View肯定要被更新葱弟。
為什么
這種模式,打開了開發(fā)安卓的新思路猜丹。我們可以將整個項目按照用戶的操作/命令/動作來設計APP芝加。
如何做
使用到的依賴
MVI模式快速開發(fā)的依賴
//MVI需要的依賴
// Mosby
compile "com.hannesdorfmann.mosby3:mvi:$mosbyVersion"
// RxBinding
compile "com.jakewharton.rxbinding2:rxbinding-kotlin:$rxBindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-support-v4-kotlin:$rxBindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-appcompat-v7-kotlin:$rxBindingVersion"
// RxJava and RxAndroid
compile "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
compile "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
為什么使用mosby庫
使用Mosby庫來構(gòu)建MVI。這個庫可以讓我們關(guān)注程序設計的藍圖射窒,例如MVI的內(nèi)容和業(yè)務邏輯藏杖,而不是處理棘手RxJava API和內(nèi)存管理。
網(wǎng)絡請求依賴
//OKHTTP
compile "com.squareup.okhttp3:okhttp:$okhttpVersion"
compile "com.squareup.okio:okio:$okioVersion"
結(jié)構(gòu)
data
domain
mvi
其中data是用來進行數(shù)據(jù)請求的
domain用來管理Model
mvi用來管理View的
實現(xiàn)過程
MVI層
實現(xiàn)View
在MVI模式中脉顿,我們的View是由兩部分構(gòu)成的蝌麸。在上面我們也說過了,就是一系列用戶動作的可觀察對象的接口和一個渲染方法
interface WeatherView:MvpView {
//請求天氣意圖
fun getWeatherIntent():Observable<Unit>
//將請求的結(jié)果渲染到UI上
fun renderToUi(state:GetWeatherState)
}
其中state艾疟,將在我們的Domain層定義
實現(xiàn)Presenter
在MVI模式中来吩,Presenter是Domain和View層交互的橋梁,在這個例子中蔽莱,我們需要將獲取天氣請求的意圖/動作/命令弟疆,與獲取天氣數(shù)據(jù)綁定起來
class WeatherPresenter:MviBasePresenter<WeatherView,GetWeatherState>() {
//綁定意圖
override fun bindIntents() {
val getWeatherInfo=
intent (WeatherView::getWeatherIntent)
.subscribeOn(Schedulers.io())
.switchMap{GetWeatherData.getWeather()}
.doOnNext { Log.d("狀態(tài)",it.toString()) }
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(getWeatherInfo,WeatherView::renderToUi)
}
}
Domain層
在Domain層,我們用來實現(xiàn)Model盗冷,在這里例子中兽间,我們只要完成一個Model,也就是天氣請求的Model正塌。請求天氣這個Model下,有三種狀態(tài):1.加載狀態(tài)2.數(shù)據(jù)獲取狀態(tài)3.錯誤狀態(tài)
具體代碼
sealed class GetWeatherState {
object LoadingState:GetWeatherState()
data class DataState(val weatherData:String):GetWeatherState()
data class ErrorState(val error:Throwable):GetWeatherState()
}
獲取數(shù)據(jù)的具體方法
object GetWeatherData {
fun getWeather():Observable<GetWeatherState>{
return WeatherRepository.loadWeatherInfoJson() //在Data層實現(xiàn)
.map<GetWeatherState>{GetWeatherState.DataState(it)}
.startWith(GetWeatherState.LoadingState)
.onErrorReturn { GetWeatherState.ErrorState(it) }
}
}
Data層
數(shù)據(jù)請求的具體實現(xiàn)恤溶,我們這里乓诽,就是獲取天氣數(shù)據(jù)的獲取
object WeatherRepository {
private val URL = "http://www.dg121.com/index.php/portal/share/hour24"http://東莞市天氣數(shù)據(jù)公共接口
fun loadWeatherInfoJson(): Observable<String> {
return Observable.create(ObservableOnSubscribe<String> { e ->
val okHttpClient = OkHttpClient()
val request = Request.Builder()
.url(URL)
.build()
val call = okHttpClient.newCall(request)
try {
val response = call.execute()
e.onNext(response.body()!!.string())
e.onComplete()
} catch (ex: IOException) {
e.onError(ex)
}
}).subscribeOn(Schedulers.io())
}
}
最后的工作
實現(xiàn)MainActivity,MainActivity需要繼承自MviActivity<WeatherView, WeatherPresenter>()
,并且需要實現(xiàn)我們的View即WeatherView
class MainActivity : MviActivity<WeatherView, WeatherPresenter>(), WeatherView {
//將意圖與按鈕點擊關(guān)聯(lián)起來咒程,只要按鈕點擊鸠天,那么就相當于發(fā)送這個意圖
override fun getWeatherIntent()=get_weather_info_btn.clicks()
//創(chuàng)建一個Presenter
override fun createPresenter()= WeatherPresenter()
override fun renderToUi(state: GetWeatherState) =//根據(jù)不同的狀態(tài),來選擇不同的函數(shù)帐姻,實現(xiàn)不同的展示
when(state){
is GetWeatherState.LoadingState->renderLoadingUi() //加載狀態(tài)的UI
is GetWeatherState.DataState->renderDataUi(state) //渲染數(shù)據(jù)時的UI
is GetWeatherState.ErrorState->renderErrorUi(state) //出錯后的UI
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
private fun renderErrorUi(state: GetWeatherState.ErrorState) {
progress_bar.visible=false
info.text=state.error.message
}
private fun renderDataUi(state: GetWeatherState.DataState) {
progress_bar.visible=false
info.text=state.weatherData
}
private fun renderLoadingUi() {
progress_bar.visible=true
}
}
其中的關(guān)鍵點稠集,我都已經(jīng)注釋了奶段。
效果展示
源碼github
Have Fun!