聽說你已經會了MVP,MVC,MVVP那么MVI在向你招手
是什么
Model-View-Intent是安卓最新的設計模式。它的靈感來自于于André Staltz的Cycle.js ,并且被 Hannes Dorfmann帶到安卓世界。
Model-View-Intent
你可能看過Model在其他的設計模式比如MVC,MVP或者MVVP。但是MVI的Model和其他設計模式的完全不一樣:
- Model 代表一種狀態(數據的顯示,你的控件的可視或者隱藏,RecyclerView的滑動位置等等)。Model在MVI中比其他的設計模式更加的形式化。你應用的一個頁面可能包含一個或多個Model對象。Model在一個Domain層被定義和管理。
- View 代表一個定義一系列用戶動作的可觀察對象的接口和一個渲染方法
-
Intent 不是
android.content.Intent
!這個Intent
簡單的說是一種意圖,或者說一種動作,或者說一種用戶與APP交互產生的命令。對于每一個用戶動作(意圖/命令)被View分發,被Presenter
觀察(是的,MVI也是有Presenter
的,是不是應該改名叫MVIP,啊哈)。
MVI的整體流程圖
這張圖描述了MVI模式的響應,和數據的流動方向。我們的Model是被Domain層管理和維護的,用來對用戶的某種意圖/動作/命令,做出反應的。只要有新的Model被創建,那么,意味著我們的View肯定要被更新。
為什么
這種模式,打開了開發安卓的新思路。我們可以將整個項目按照用戶的操作/命令/動作來設計APP。
如何做
使用到的依賴
MVI模式快速開發的依賴
//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庫來構建MVI。這個庫可以讓我們關注程序設計的藍圖,例如MVI的內容和業務邏輯,而不是處理棘手RxJava API和內存管理。
網絡請求依賴
//OKHTTP
compile "com.squareup.okhttp3:okhttp:$okhttpVersion"
compile "com.squareup.okio:okio:$okioVersion"
結構
data
domain
mvi
其中data是用來進行數據請求的
domain用來管理Model
mvi用來管理View的
實現過程
MVI層
實現View
在MVI模式中,我們的View是由兩部分構成的。在上面我們也說過了,就是一系列用戶動作的可觀察對象的接口和一個渲染方法
interface WeatherView:MvpView {
//請求天氣意圖
fun getWeatherIntent():Observable<Unit>
//將請求的結果渲染到UI上
fun renderToUi(state:GetWeatherState)
}
其中state,將在我們的Domain層定義
實現Presenter
在MVI模式中,Presenter是Domain和View層交互的橋梁,在這個例子中,我們需要將獲取天氣請求的意圖/動作/命令,與獲取天氣數據綁定起來
class WeatherPresenter:MviBasePresenter<WeatherView,GetWeatherState>() {
//綁定意圖
override fun bindIntents() {
val getWeatherInfo=
intent (WeatherView::getWeatherIntent)
.subscribeOn(Schedulers.io())
.switchMap{GetWeatherData.getWeather()}
.doOnNext { Log.d("狀態",it.toString()) }
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(getWeatherInfo,WeatherView::renderToUi)
}
}
Domain層
在Domain層,我們用來實現Model,在這里例子中,我們只要完成一個Model,也就是天氣請求的Model。請求天氣這個Model下,有三種狀態:1.加載狀態2.數據獲取狀態3.錯誤狀態
具體代碼
sealed class GetWeatherState {
object LoadingState:GetWeatherState()
data class DataState(val weatherData:String):GetWeatherState()
data class ErrorState(val error:Throwable):GetWeatherState()
}
獲取數據的具體方法
object GetWeatherData {
fun getWeather():Observable<GetWeatherState>{
return WeatherRepository.loadWeatherInfoJson() //在Data層實現
.map<GetWeatherState>{GetWeatherState.DataState(it)}
.startWith(GetWeatherState.LoadingState)
.onErrorReturn { GetWeatherState.ErrorState(it) }
}
}
Data層
數據請求的具體實現,我們這里,就是獲取天氣數據的獲取
object WeatherRepository {
private val URL = "http://www.dg121.com/index.php/portal/share/hour24"http://東莞市天氣數據公共接口
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())
}
}
最后的工作
實現MainActivity,MainActivity需要繼承自MviActivity<WeatherView, WeatherPresenter>()
,并且需要實現我們的View即WeatherView
class MainActivity : MviActivity<WeatherView, WeatherPresenter>(), WeatherView {
//將意圖與按鈕點擊關聯起來,只要按鈕點擊,那么就相當于發送這個意圖
override fun getWeatherIntent()=get_weather_info_btn.clicks()
//創建一個Presenter
override fun createPresenter()= WeatherPresenter()
override fun renderToUi(state: GetWeatherState) =//根據不同的狀態,來選擇不同的函數,實現不同的展示
when(state){
is GetWeatherState.LoadingState->renderLoadingUi() //加載狀態的UI
is GetWeatherState.DataState->renderDataUi(state) //渲染數據時的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
}
}
其中的關鍵點,我都已經注釋了。
效果展示
源碼github
Have Fun!