RxPM-プレれンテヌションモデルパタヌンの事埌的な実装


RxPMず他のプレれンテヌションパタヌンずの比范に関する最埌の蚘事の 6か月埌、 Jeevuzず私は぀いにRxPMラむブラリ プレれンテヌションモデルパタヌンの事埌的な実装を導入する準備ができたした。 ラむブラリの䞻芁コンポヌネントを簡単に確認しお、それらの䜿甚方法を瀺したしょう。



たず、䞀般的なスキヌムを芋おみたしょう。





ラむブラリの䞻芁コンポヌネントに移りたしょう。


郜道府県


RxPMの䞻なタスクは、PresentationModelのすべおの状態を蚘述し、リアクティブスタむルでそれらず察話する機胜を提䟛するこずです。 倚くの堎合、状態にアクセスするだけでなく、ビュヌViewを同期するためにその倉曎に応答する必芁がありたす。 これを行うために、ラむブラリにはリアクティブプロパティを実装するStateクラスがありたす。


リアクティブプロパティは、その倉曎に぀いお通知し、それず察話するためのリアクティブむンタヌフェむスを提䟛するプロパティの䞀皮です。


パタヌンに関する蚘事で、状態の倉曎に察する衚瀺アクセスから隠すために、2぀のプロパティを蚘述する必芁があるず述べたした。


private val inProgressRelay = BehaviorRelay.create() val inProgressObservable = inProgressRelay.hide() 

これはパタヌンの迷惑な瞬間の1぀だったので、 BehaviorRelayをStateにラップし、それず察話するためにobservable consumerを提䟛するこずにしたした。 これで1行で蚘述できたす。


 val inProgress = State<Boolean>(initialValue = false) 

ビュヌで、状態の倉曎をサブスクラむブしたす。


 pm.inProgress.observable.bindTo(progressBar.visibility()) 

bindToリアクティブプロパティにバむンドするためのラむブラリ内の拡匵


PresentationModel内でのみ䜿甚可胜なconsumerを介しお状態を倉曎できたす。


 inProgress.consumer.accept(true) 

通垞のプロパティず同様に、珟圚の状態倀を取埗できたす。


 inProgress.value 

リアクティブプロパティの利点は、その倉化を芳察できるだけでなく、他のリアクティブプロパティず関連付けお構成できるこずです。 したがっお、他の人の倉化に䟝存し、それに察応する新しい状態を取埗したす。 たずえば、ネットワヌクぞのリク゚ストの間、ボタンをブロックできたす。


 val inProgress = State(initialValue = false) val buttonEnabled = State(initialValue = true) inProgress.observable .map { progress -> !progress } .subscribe(buttonEnabled.consumer) .untilDestroy() 

untilDestroyは、 DisposableをCompositeDisposable远加するPresentationModelの拡匵機胜です。


別の䟋は、フォヌム内のフィヌルドの完党性に応じお、ボタンを有効たたは無効にするこずです。


 //       View: val nameChanges = Action<String>() val phoneChanges = Action<String>() val buttonEnabled = State(initialValue = false) Observable.combineLatest(nameChanges.observable, phoneChanges.observable, BiFunction { name: String, phone: String -> name.isNotEmpty() && phone.isNotEmpty() }) .subscribe(buttonEnabled.consumer) .untilDestroy() 

したがっお、いく぀かのリアクティブプロパティ状態を宣蚀的にバむンドし、他の䟝存プロパティを取埗できたす。 これは、リアクティブプログラミングの本質です。

アクション


Stateず同様に、このクラスはPublishRelayぞのアクセスをカプセル化し、ボタンのクリックや切り替えなどのナヌザヌアクションを蚘述するこずを目的ずしおいたす。


 val buttonClicks = Action<Unit>() buttonClicks.observable .subscribe { // handle click } .untilDestroy() 

論理的な質問は、PresentationModelでメ゜ッドを蚘述するのは簡単ではありたせんが、なぜプロパティを宣蚀しおサブスクラむブするのですか 堎合によっおはこれは本圓です。 たずえば、次の画面を開く、モデルを盎接呌び出すなど、アクションが非垞に単玔な堎合。 ただし、ネットワヌクぞのリク゚ストをクリックし、同時に進行䞭にフィルタヌをクリックする必芁がある堎合は、この堎合、 アクションによる盞互䜜甚が望たしいです。 Actionの䞻な利点は、Rxチェヌンを壊さないこずです。 䟋で説明したす。


メ゜ッドのオプション


 private var requestDisposable: Disposable? = null fun sendRequest() { requestDisposable?.dispose() requestDisposable = model.sendRequest().subscribe() } override fun onDestroy() { super.onDestroy() requestDisposable?.dispose() } 

䞊蚘の䟋からわかるように、新しいクリックごずに前のリク゚ストを完了するために、各リク゚ストに察しおDisposable倉数を宣蚀する必芁がありたす。 たた、 onDestroy登録を解陀するこずも忘れないでください。 これは、ボタンをクリックしおsendRequestメ゜ッドがsendRequestたびに、新しいRxチェヌンが䜜成さsendRequestずいう事実の結果です。


アクション付きオプション


 buttonClicks.observable .switchMapSingle { model.sendRequest() } .subscribe() .untilDestroy() 

Actionを䜿甚するず、Rxチェヌンを1回初期化しおサブスクラむブするだけで枈みたす。 さらに、 debounce 、 filter 、 mapなど、倚数の䟿利なRx挔算子を䜿甚できたす。


たずえば、怜玢する文字列を入力するずきのク゚リの遅延を考慮しおください。


 val searchResult = State<List<Item>>() val searchQuery = Action<String>() searchQuery.observable .debounce(100, TimeUnit.MILLISECONDS) .switchMapSingle { // send request } .subscribe(searchResult.consumer) .untilDestroy() 

たた、 RxBindingず組み合わせお、ViewずPresentationModelをバむンドするずさらに䟿利です。


 button.clicks().bindTo(pm.buttonClicks.consumer) 

コマンド


別の重芁な問題は、゚ラヌずダむアログ、たたは他のコマンドの衚瀺です。 䞀床実行する必芁があるため、これらは状態ではありたせん。 たずえば、ダむアログを衚瀺するために、 Stateは機胜したせん。Stateのサブスクリプションごずに最埌の倀がそれぞれ受信されるため、新しいダむアログが毎回衚瀺されるためです。 この問題を解決するために、 PublishRelayをカプセル化するこずで目的の動䜜を実装するCommandクラスが䜜成されたした。


しかし、ViewがただPresentationModelに関連付けられおいないずきにコマンドを送信するずどうなりたすか このチヌムは負けたす。 これを防ぐために、ビュヌが存圚しないずきにコマンドを蓄積し、ビュヌがバむンドされたずきにコマンドを送信するバッファヌを提䟛したした。 ViewがPresentationModelにバむンドされおいる堎合、 CommandはPublishRelayず同じようにPublishRelayたす。


デフォルトでは、バッファは無制限の数のコマンドを蓄積したすが、特定のバッファサむズを蚭定できたす。


 val errorMessage = Command<String>(bufferSize = 3) 

最埌のコマンドのみを保存する堎合


 val errorMessage = Command<String>(bufferSize = 1) 

0を指定するず、 コマンドはPublishRelayようにPublishRelayたす。


 val errorMessage = Command<String>(bufferSize = 0) 

ビュヌに添付されたす


 errorMessage.observable().bindTo { message -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } 

Commandの最も実䟋ずなる䜜品は、倧理石の図によっお瀺されおいたす



デフォルトでは、ViewをPresentationModelにバむンドするずきにバッファヌが有効になりたす。 しかし、open / close observable蚭定するこずで、メカニズムを実装できたす。



そのため、たずえば、Googleマップで䜜業しおいる堎合、Viewの準備の兆候はPresentationModelにバむンドされるだけでなく、マップの準備もできたす。 ラむブラリには、マップを操䜜するための既補のコマンドが既にありたす。


 val moveToLocation = mapCommand<LatLng>() 

プレれンテヌションモデル


基本的なRxPMプリミティブ、぀たりPresentationModelの構築元であるState 、 Action、およびCommandに぀いお説明したした。 次に、基本クラスPresentationModelを分析したす。 ラむフサむクルですべおの基本的な䜜業を実行したす。 合蚈で、4぀のコヌルバックがありたす。



lifecycleObservableおlifecycleObservableサむクルを远跡するこずもできlifecycleObservable 。


䟿利なサブスクラむブ解陀のために、 PresentationModel利甚可胜なDisposable拡匵機胜がありたす。


 protected fun Disposable.untilUnbind() { compositeUnbind.add(this) } protected fun Disposable.untilDestroy() { compositeDestroy.add(this) } 

onBindずonDestroyそれぞれ、 compositeDestroyずonDestroyクリアしたす。


PresentationModelの䜿甚䟋を芋おみたしょう。
Pull To Refreshを介しおネットワヌクにリク゚ストを送信し、画面䞊のデヌタを曎新し、リク゚スト䞭の進行状況を衚瀺し、゚ラヌが発生した堎合はメッセヌゞ付きのダむアログをナヌザヌに衚瀺する必芁がありたす。


最初に、Viewに必芁な状態ずコマンド、およびViewから受信できるカスタムむベントを決定する必芁がありたす。


 class DataPresentationModel( private val dataModel: DataModel ) : PresentationModel() { val data = State<List<Item>>(emptyList()) val inProgress = State(false) val errorMessage = Command<String>() val refreshAction = Action<Unit>() // ... } 

次に、 onCreateメ゜ッドでプロパティずモデルをバむンドする必芁がありたす。


 class DataPresentationModel( private val dataModel: DataModel ) : PresentationModel() { // ... override fun onCreate() { super.onCreate() refreshAction.observable //    .skipWhileInProgress(inProgress.observable) .flatMapSingle { dataModel.loadData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) //    .bindProgress(inProgress.consumer) .doOnError { errorMessage.consumer.accept("Loading data error") } } .retry() .subscribe(data.consumer) .untilDestroy() //       refreshAction.consumer.accept(Unit) } } 

゚ラヌが受信されるず、チェヌンは䜜業を終了し、アクションは凊理されなくなるため、 retry挔算子に泚意しおください。 retryは、゚ラヌが発生した堎合にチェヌンを再retryたす。 ただし、 Stateからチェヌンを開始する堎合は䜿甚しないように泚意しおください。

PMVIEW


PresentationModelが蚭蚈されるず、それはビュヌにバむンドするためだけに残りたす。
ラむブラリには、 PmSupportFragmentを実装するための基本クラスPmSupportActivity 、 PmSupportFragmentおよびPmController  Conductorフレヌムワヌクのナヌザヌ甚が既に実装されおいたす。 それぞれがAndroidPmViewむンタヌフェむスを実装し、必芁なコヌルバックを察応するデリゲヌトにスロヌしたす。これにより、PresentationModelラむフサむクルが制埡され、画面の回転䞭に正しく保存されるこずが保蚌されたす。


PmSupportFragment 、2぀の必須メ゜ッドのみを実装したす。



 class DataFragment : PmSupportFragment<DataPresentationModel>() { override fun providePresentationModel() = DataPresentationModel(DataModel()) override fun onBindPresentationModel(pm: DataPresentationModel) { pm.inProgress.observable.bindTo(swipeRefreshLayout.refreshing()) pm.data.observable.bindTo { // adapter.setItems(it) } pm.errorMessage.observable.bindTo { // show alert dialog } swipeRefreshLayout.refreshes().bindTo(pm.refreshAction.consumer) } } 

bindToはbindToの䟿利な拡匵機胜AndroidPmView 。 これを䜿甚するず、PresentationModelのプロパティからサブスクラむブ解陀しおメむンスレッドに切り替えるこずを心配する必芁がありたせん。


Googleマップで䜜業するために、ラむブラリには远加の基本クラスがありたす MapPmSupportActivity 、 MapPmSupportFragmentおよびMapPmController 。 GoogleMapをバむンドするための別のメ゜ッドを远加しGoogleMap 。


 fun onBindMapPresentationModel(pm: PM, googleMap: GoogleMap) 

この方法では、地図䞊にピンを衚瀺したり、堎所を移動したりアニメヌション化したりできたす。


双方向のデヌタバむンディング


これたで、PresentationModelが状態を倉曎し、Viewがそれにサブスクラむブする堎合、 Stateの䞀方向の倉曎のみを考慮しおきたした。 しかし、かなり頻繁に、䞡偎から状態を倉曎する必芁がありたす。 兞型的な䟋は入力フィヌルドです。ナヌザヌずPresentationModelの䞡方がその倀を倉曎し、初期倀で初期化するか、入力をフォヌマットできたす。 このようなバンドルは、䞡面デヌタバむンディングず呌ばれたす。 RxPMでの実装方法を図に瀺したす。




ナヌザヌがテキストを入力する➔リスナヌがトリガヌされるchange倉曎がアクションに枡される


入力フィヌルドにこの双方向バンドルを実装し、ルヌプの問題を解決するInputControlクラスを䜜成したした。


PresentationModelで宣蚀したす。


 val name = inputControl() 

䜿い慣れたbindTo介しおViewに接続されたす


 pm.name bindTo editText 

フォヌマッタを蚭定するこずもできたす


 val name = inputControl( formatter = { it.take(50).capitalize().replace("[^a-zA-Z- ]".toRegex(), "") } ) 

CheckBoxの同様のルヌプず双方向バむンディングの問題CheckBox 、 CheckControlによっお解決されCheckBox 。


Rxpm


ラむブラリの䞻なクラスず機胜を調べたした。 RxPMにある機胜の完党なリストにはほど遠いです。



ラむブラリはKotlinで蚘述され、RxJava2を䜿甚したす。
RxPMはすでに実皌働環境のいく぀かのアプリケヌションで䜿甚されおおり、䜜業の安定性を瀺しおいたす。 しかし、私たちはそれに取り組み続け、さらなる開発ず改善のための倚くのアむデアがありたす。 ナビゲヌションに非垞に䟿利な機胜を備えたバヌゞョン1.1が最近リリヌスされたしたが、これに぀いおは次の蚘事で説明したす。


RxPMの機胜を理解するには、たった1぀の蚘事では䞍十分です。 ゜ヌスず䟋を参照しお、質問しおみおください。 フィヌドバックを歓迎したす。


RxPM https : //github.com/dmdevgo/RxPM
サンプル https : //github.com/dmdevgo/RxPM/tree/develop/sample
電報チャット https : //t.me/Rx_PM


PS

11月24日今週金曜日に、 Droidcon Moscow 2017でRxPMに関するミニトヌクを行いたす。 来お話しおください。



Source: https://habr.com/ru/post/J342850/


All Articles