
RxPMãšä»ã®ãã¬ãŒã³ããŒã·ã§ã³ãã¿ãŒã³ãšã®æ¯èŒã«é¢ããæåŸã®èšäºã® 6ãæåŸã Jeevuzãšç§ã¯ã€ãã«RxPMã©ã€ãã©ãªïŒ ãã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãã¿ãŒã³ã®äºåŸçãªå®è£
ïŒãå°å
¥ããæºåãã§ããŸããã ã©ã€ãã©ãªã®äž»èŠã³ã³ããŒãã³ããç°¡åã«ç¢ºèªããŠããããã®äœ¿çšæ¹æ³ã瀺ããŸãããã
ãŸããäžè¬çãªã¹ããŒã ãèŠãŠã¿ãŸãããã

- PresentationModelã¯ããã¥ãŒã®ç¶æ
ãä¿åããUIã€ãã³ãã«å¿çããŠããã¥ãŒã®ã¢ãã«ãšç¶æ
ãå€æŽããŸãã
- ç¶æ
ã®å€æŽã«ãµãã¹ã¯ã©ã€ãã衚瀺ãããŠãŒã¶ãŒã¢ã¯ã·ã§ã³ãPresentationModelã«éä¿¡ããŸãã
- ã¢ãã«ã¯ãããžãã¹ããžãã¯ãããŒã¿ã¹ãã¬ãŒãžãããã³æ€çŽ¢ãé ãããŠããã¬ã€ã€ãŒã§ãã
ã©ã€ãã©ãªã®äž»èŠã³ã³ããŒãã³ãã«ç§»ããŸãããã
éœéåºç
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 {
è«ççãªè³ªåã¯ã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 {
ãŸãã 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ã€ã®ã³ãŒã«ããã¯ããããŸãã
onCreate
æåã®äœææã«åŒã³åºãããRxãã§ãŒã³ãšãã€ã³ãç¶æ
ãåæåããã®ã«é©ããå Žæã§ããonBind
ãã¥ãŒãPresentationModelã«ãã€ã³ããããšãã«åŒã³åºãããŸããonUnbind
ãã¥ãŒãPresentationModelããonUnbind
ãšãã«åŒã³åºãããŸããonDestroy
-PresentationModelã¯äœæ¥ãå®äºããŸãã ãªãœãŒã¹ã解æŸããã®ã«é©ããå Žæã
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() {
ãšã©ãŒãåä¿¡ããããšããã§ãŒã³ã¯äœæ¥ãçµäºããã¢ã¯ã·ã§ã³ã¯åŠçãããªããªãããã retry
æŒç®åã«æ³šæããŠãã ããã retry
ã¯ããšã©ãŒãçºçããå Žåã«ãã§ãŒã³ãåretry
ãŸãã ãã ãã Stateãããã§ãŒã³ãéå§ããå Žåã¯äœ¿çšããªãããã«æ³šæããŠãã ããã
PMVIEW
PresentationModelãèšèšããããšãããã¯ãã¥ãŒã«ãã€ã³ãããããã ãã«æ®ããŸãã
ã©ã€ãã©ãªã«ã¯ã PmSupportFragment
ãå®è£
ããããã®åºæ¬ã¯ã©ã¹PmSupportActivity
ã PmSupportFragment
ããã³PmController
ïŒ Conductorãã¬ãŒã ã¯ãŒã¯ã®ãŠãŒã¶ãŒçšïŒãæ¢ã«å®è£
ãããŠããŸãã ãããããAndroidPmView
ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããå¿
èŠãªã³ãŒã«ããã¯ã察å¿ããããªã²ãŒãã«ã¹ããŒããŸããããã«ãããPresentationModelã©ã€ããµã€ã¯ã«ãå¶åŸ¡ãããç»é¢ã®å転äžã«æ£ããä¿åãããããšãä¿èšŒãããŸãã
PmSupportFragment
ã2ã€ã®å¿
é ã¡ãœããã®ã¿ãå®è£
ããŸãã
providePresentationModel
-PresentationModelã®äœææã«åŒã³åºãããŸããonBindPresentationModel
ãã®ã¡ãœããã§ã¯ãPresentationModelããããã£ã«ãã€ã³ãããå¿
èŠããããŸãïŒ RxBindingãšbindTo
æ¡åŒµæ©èœã䜿çšããŸãïŒã
class DataFragment : PmSupportFragment<DataPresentationModel>() { override fun providePresentationModel() = DataPresentationModel(DataModel()) override fun onBindPresentationModel(pm: DataPresentationModel) { pm.inProgress.observable.bindTo(swipeRefreshLayout.refreshing()) pm.data.observable.bindTo {
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ã«ããæ©èœã®å®å
šãªãªã¹ãã«ã¯ã»ã©é ãã§ãã
PresentationModel
ã®åºæ¬å®è£
ã- ç»é¢å転äžã®
PresentationModel
ä¿åã - ã©ã€ããµã€ã¯ã«åŠçããµãã¹ã¯ãªãã·ã§ã³ãããã³ãµãã¹ã¯ãªãã·ã§ã³è§£é€ã
- Conductorãå«ã
PmView
ãå®è£
ããããã®åºæ¬ã¯ã©ã¹ã State
ã Action
ã Command
ãInputControl
ã CheckContol
ã ClickControl
ãbindTo
ããã³ãã®ä»ã®äŸ¿å©ãªæ¡åŒµæ©èœãbindTo
ããŠããããã£ããã€ã³ãããŸãã- Googleãããã§äœæ¥ããããã®åºæ¬ã¯ã©ã¹ã
ã©ã€ãã©ãªã¯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ã«é¢ããããããŒã¯ãè¡ããŸãã æ¥ãŠè©±ããŠãã ããã