é·ãéãç§ã¯RxPMãã¿ãŒã³ã«ã€ããŠèããŠãããããããã¯ã·ã§ã³ãã§ãããããŸãé©çšãããããŠããŸããã ç§ã¯ãã®ãããã¯ã«ã€ããŠæåã«Mobiusãšè©±ãäºå®ã§ããããããã°ã©ã å§å¡äŒã¯æåŠããã®ã§ãAndroidã³ãã¥ããã£ãšæ°ãããã¿ãŒã³ã®ããžã§ã³ãå ±æããããã®èšäºãä»å ¬éããŠããŸãã
誰ããMVPãšMVVMã«ç²ŸéããŠããŸãããMVVMããã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãã¿ãŒã³ã®è«ççãªéçºã§ããããšãç¥ã£ãŠãã人ã¯ã»ãšãã©ããŸããã çµå±ãMVVMãšPMã®å¯äžã®éãã¯ãèªåããŒã¿ãã€ã³ãã£ã³ã°ïŒ databinding ïŒã§ãã
ãã®èšäºã§ã¯ããªã¢ã¯ãã£ãããã³ã°ã䜿çšãããã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãã¿ãŒã³ã«çŠç¹ãåœãŠãŸãã 誀ã£ãŠRxMVVMãšåŒã¶äººãããŸãããããã¯ãã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãã³ãã¬ãŒãã®ä¿®æ£ã§ãããããRxPMãšåŒã¶ã®ãæ£ããã§ãããã
ãã®ãã¿ãŒã³ã¯ã Rxã䜿çšãããããžã§ã¯ãã§äœ¿çšãããšäŸ¿å©ã§ããããã«ãããã¢ããªã±ãŒã·ã§ã³ã®å¿çæ§ãåäžããããã§ãã ãŸããä»ã®ãã¿ãŒã³ã®åé¡ã¯å€ããããŸããã æ¬¡ã®å³ã¯ããã¬ãŒã³ããŒã·ã§ã³ãã³ãã¬ãŒãã®ããŸããŸãªãªãã·ã§ã³ãšåé¡ã瀺ããŠããŸãã
RxPMãã¿ãŒã³ã®èª¬æã«é²ãåã«ãæã人æ°ã®ããMVPïŒããã·ããã¥ãŒïŒãšMVVMãèŠãŠã¿ãŸãããã ãã¹ãŠã®ãã¿ãŒã³ãšãã®éãã®è©³çްãªèª¬æã¯ãåã®èšäºã«èšèŒãããŠããŸã ã
MVP vs PM vs MVVM
ãã¿ãŒã³ã®äžè¬çãªãã¿ãŒã³ã¯ããã€ã¢ã°ã©ã ã®åœ¢åŒã§è¡šãããšãã§ããŸãã
äžèŠãããšãããåºæ¬çãªéãã¯ãªãããã«æãããããããŸããã ããããããã¯äžèŠããã ãã§ãã éãã¯ãå販æ¥è ã®è²¬ä»»ãšããã¥ãŒãšã®éä¿¡æ¹æ³ã«ãããŸãã ã¢ãã«ã¯ãã¹ãŠã®ãã¿ãŒã³ã§åãã«èŠããŸãã ãã®èšèšã¯è€éã§åºç¯ãªãããã¯ã§ãããããããã§ã¯èª¬æããŸããã æãäžè¬çãªãã¿ãŒã³ã§ããããã·ããã¥ãŒã®MVPããå§ããŸãããã ãã®äž»ãªåé¡ãèæ ®ããŠãã ããã
MVP
åŸæ¥ã®MVPã§ã¯ãUIã®ç¶æ ãç¶æããã³åŸ©å ãã責任ã¯Viewã«ãããŸãã Presenterã¯ã¢ãã«ã®å€æŽã®ã¿ãç£èŠããã€ã³ã¿ãŒãã§ã€ã¹ãä»ããŠãã¥ãŒãæŽæ°ããŸããå察ã«ãViewããã³ãã³ããåãåããã¢ãã«ã倿ŽããŸãã
ãã ããã¢ãã«ã®ããŒã¿ç¶æ ã«å ããŠãè€éãªã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããå ŽåãããŒã¿ã«é¢é£ããªã远å ã®UIç¶æ ããããŸãã ããšãã°ãç»é¢ã§åŒ·èª¿è¡šç€ºãããŠãããªã¹ãã¢ã€ãã ããå ¥åãã©ãŒã ã«å ¥åãããŠããããŒã¿ãããŠã³ããŒãããã»ã¹ã®é²è¡ç¶æ³ã«é¢ããæ å ±ããããã¯ãŒã¯ãžã®èŠæ±ãªã©ã§ãã ãã¥ãŒã®UIç¶æ ã®åŸ©å ãšä¿åã¯ããã¥ãŒããæ»ã¬ãåŸåãããããã倧ããªåé¡ã§ãã ãŸãããããã¯ãŒã¯èŠæ±ã«é¢ããæ å ±ã®è¡šç€ºã¯ãåºæ¬çã«ä¿åã§ããŸããã ãã¥ãŒããã¬ãŒã³ã¿ãŒããåæãããŠããéãããªã¯ãšã¹ãã¯ããããäœããã®çµæã§çµäºããŸãã
ãããã£ãŠãUIã®ç¶æ ã埩å ããäœæ¥ã¯ãã¬ãŒã³ã¿ãŒã«éä¿¡ãããŸãã ããã«ã¯ããã¬ãŒã³ã¿ãŒã®çŸåšã®ç¶æ ã«è¿œå ã®ããŒã¿ãšãã©ã°ãä¿åãããã¥ãŒãã¢ã¿ããããããã³ã«ãããåçããå¿ èŠããããŸãã
2çªç®ã®åé¡ã¯ãããšãã°ç»é¢ãå転ããŠãããšããªã©ãViewããã¬ãŒã³ã¿ãŒãããã€ã§ãåæã§ãããšããåãæ¡ä»¶ã«èµ·å ããŸãã ãããã£ãŠããã¬ãŒã³ã¿ãŒã®Viewã€ã³ã¿ãŒãã§ã€ã¹ãžã®ãªã³ã¯ã¯ãªã»ãããããŸãã ãããã£ãŠããã¥ãŒãæŽæ°ããå¿
èŠãããå Žåã¯ãåžžã«null
ãã§ãã¯ãå®è¡ããå¿
èŠããããŸãã ããã¯éåžžã«é¢åã§ãã³ãŒããç
©éã«ãªããŸãã
3çªç®ã®åé¡ïŒViewã€ã³ã¿ãŒãã§ãŒã¹ã¯ã§ããã ã銬鹿ããŠããã¯ããªã®ã§ãViewã€ã³ã¿ãŒãã§ãŒã¹ãããçšåºŠè©³çްã«èšè¿°ããå¿ èŠããããŸãã ãŸãããã¬ãŒã³ã¿ãŒã¯ããã¥ãŒãç®çã®ç¶æ ã«ããããã«å€ãã®ã¡ãœãããåŒã³åºãå¿ èŠããããŸãã ããã«ãããã³ãŒãã®éãå¢ããŸãã
ååŸ
Martin Fowlerã説æãã Presentation ModelãšåŒã°ããå¥ã®ãã¿ãŒã³ããããŸãã ãã®ãã¿ãŒã³ã®æ¬è³ªã¯ãããã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ããšåŒã°ãããUIã®ç¶æ ãæ ŒçŽããUIããžãã¯ãå«ãç¹å¥ãªã¢ãã«ãå°å ¥ãããããšã§ãã PresentationModelã¯ãGUIãã¬ãŒã ã¯ãŒã¯ã«äŸåããªãæœè±¡çãªãã¬ãŒã³ããŒã·ã§ã³ãšèŠãªãããå¿ èŠããããŸãã PresentationModelã¯ç¶æ ãããããã£ãšããŠä¿åããViewã«ãã£ãŠèªã¿åãããŠç»é¢ã«è¡šç€ºãããŸãã ãã®ãã¿ãŒã³ã®äž»ãªåé¡ã¯ãPresentationModelãšViewç¶æ ã®åæã§ãã ãObserverããã¿ãŒã³ãé©çšããããšã§ããããèªåã§åŠçããå¿ èŠããããŸãã ã»ãšãã©ã®å ŽåãUIå šäœãæŽæ°ããªãããã«ãåããããã£ã®å€æŽã远跡ããå¿ èŠããããŸãã éå±ã§ç¹°ãè¿ãã®å€ãã³ãŒãããããããããŸãã
MVVM
ãæ°ã¥ããããããŸããããMVVMã¯ãã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ã«éåžžã«ãã䌌ãŠããŸãã é©ãã¹ãããšã§ã¯ãããŸãããããã¯ãã®éçºã ããã§ãã PresentationModelã®ã¿ãViewModelãšåŒã°ããViewModelãšViewç¶æ ã®åæã¯ãèªåããŒã¿ãã€ã³ãã£ã³ã°ãã€ãŸãããŒã¿åã«ãã£ãŠå®è¡ãããŸãã ãããããã®ãã¿ãŒã³ã«ã¯æ¬ é¥ããªãããã§ã¯ãããŸããã ããšãã°ãã¢ãã¡ãŒã·ã§ã³ããçŽç²ã«ãå®è£ ããããã³ãŒããããã¥ãŒã䜿çšããŠäœããå®è¡ãããããã®ã¯åé¡ã§ãã ãã®ããšã«ã€ããŠã¯ ãååã®Jeevuzã® èšäºã ã芧ãã ãã ã
RxPMã«ã€ããŠè°è«ããçèãå§ãããšãããã®ãã¿ãŒã³ã¯MVVMã«ã€ããŠå¥œããªããšãã€ãŸãViewã®ã€ã³ã¿ãŒãã§ãŒã¹ãšããŠã®ViewModelã®æŠå¿µãå ŒãåããŠããããåæã«äž»ãªæ¬ ç¹ã§ããå察æ§ãå«ãã§ããªãããšã«æ°ä»ããŸããã ããŒã¿ãã€ã³ãã£ã³ã°ããªããããããã¯è«ççã§ãã ããããåæã«ãRxã䜿çšãããã€ã³ãã¯ãDatabinding Libraryã䜿çšããèªåãã€ã³ããããããã»ã©è€éã§ã¯ãªããåæã«ãªã¢ã¯ãã£ãã¢ããªã±ãŒã·ã§ã³ã§ã®äœ¿çšã«ãéåžžã«é©ããŠããŸãã
ãã®çµæãRxPMã¯ç¶æ ã®åé¡ã解決ããŸãã ç§ã®èšäºã®ã«ãŒããã¯ãã¥ãŒããèŠããŠããŸããïŒ ç¶æ ã¯ãã£ãŒã«ãã®ã»ãããŸãã¯ã¢ã¯ã·ã§ã³ã®ã»ããã®ããããã§èšè¿°ã§ããããšã説æããŸããããããã£ãŠãRxPMã¯ããã2ã€ã®ã¡ãœãããè峿·±ãæ¹æ³ã§çµã¿åãããŸãããµãã¹ã¯ãªãã·ã§ã³ã§ïŒããããã¯åæã«ãã¢ã¯ã·ã§ã³ãã§ãã ãããŠãããã¯ã°ã©ãŠã³ãã§ïŒãã¥ãŒãååšããªãéã«ïŒçºçããã€ãã³ãã¯ããµãã¹ã¯ãªãã·ã§ã³äžã«å°çããããšãããããŸããã ãããïŒ
ããããäžèšã®ãã¹ãŠã®ãã¿ãŒã³ã®æãéèŠã§æ±ºå®çãªæ¬ ç¹ã¯ãViewãšä»²ä»è ã®çžäºäœçšãåœä»€çãªã¹ã¿ã€ã«ã§å®è¡ãããããšã§ãã äžæ¹ãç®æšã¯ãªã¢ã¯ãã£ãã¢ããªã±ãŒã·ã§ã³ãäœæããããšã§ãã UIã¬ã€ã€ãŒã¯ãç¹ã«åçã€ã³ã¿ãŒãã§ã€ã¹ã§ã¯ãããŒã¿ãããŒã®ããªã倧ããªãœãŒã¹ã§ãããã¢ãã«ã§ã®éåæäœæ¥ã«ã®ã¿Rxã䜿çšããã®ã¯ç¡æå³ã§ãã
ãªã¢ã¯ãã£ããã¬ãŒã³ããŒã·ã§ã³ã¢ãã«
ãã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãã¿ãŒã³ã®äž»ãªåé¡ã¯ããã¬ãŒã³ããŒã·ã§ã³ã¢ãã«ãšãã¥ãŒã®éã®ç¶æ ã®åæã§ããããšã¯æ¢ã«ããã£ãŠããŸãã æããã«ã倿Žå¯èœãªããšãéç¥ã§ããããããã£ã§ããç£èŠå¯èœãªããããã£ã䜿çšããå¿ èŠããããŸãã RxJavaã¯ãã®åé¡ã解決ããã®ã«åœ¹ç«ã¡ãåæã«ãªã¢ã¯ãã£ãã¢ãããŒãã®ãã¹ãŠã®å©ç¹ãåŸãããšãã§ããŸãã
ãŸãããã¿ãŒã³ã¹ããŒã ãèŠãŠã¿ãŸããããæ¬¡ã«ãå®è£
ã®è©³çްãçè§£ããŸãã
ãããã£ãŠãRxPMã®éèŠãªèŠçŽ ã¯ãªã¢ã¯ãã£ãããããã£ã§ãã Rx-propertyã®åœ¹å²ã®æåã®åè£ã¯BehaviorSubjectã§ãã æåŸã®å€ãä¿åãããµãã¹ã¯ã©ã€ããããã³ã«å€ãæž¡ããŸãã
äžè¬ã«ã ãµããžã§ã¯ãã¯æ¬è³ªçã«äžæã§ããäžæ¹ã§ããããã¯Observableã®æ¡åŒµã§ããã仿¹ã§ã¯ãObserverã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããŸãã ã€ãŸãã ãµããžã§ã¯ãããã¥ãŒã®çºä¿¡ããŒã¿ã¹ããªãŒã ãšããŠäœ¿çšã§ããPresentationModelã§ã¯çä¿¡ããŒã¿ã¹ããªãŒã ã®ã³ã³ã·ã¥ãŒãã«ãªããŸãã
ãããã 被éšè ã«ã¯ç§ãã¡ã«åãå ¥ããããªãæ¬ é¥ããããŸãã Observableã³ã³ãã©ã¯ãã§ã¯ã onCompleteããã³onErrorã€ãã³ãã§çµäºã§ããŸã ã ãããã£ãŠã ãµããžã§ã¯ãã倱æããäœãã«ãµãã¹ã¯ã©ã€ããããå Žåããã§ãŒã³å šäœã忢ããŸãã ãã¥ãŒã¯ã€ãã³ãã®åä¿¡ã忢ããå床ãµãã¹ã¯ã©ã€ãããå¿ èŠããããŸãã ããã«ãRxããããã£ã¯å®çŸ©äžã onCompleteããã³onErrorã€ãã³ããéä¿¡ã§ããŸãããããã¯ããã¥ãŒã®åãªãããŒã¿ïŒç¶æ ïŒãœãŒã¹ã§ããããã§ãã ããã§ãJake Whartonã圌ã®RxRelayã©ã€ãã©ãªã§æå©ã«æ¥ãŸãã 圌ãªãã§ç§ãã¡ã¯äœãããŸããïŒ ãªã¬ãŒ ããšèšèŒãããæ¬ é¥ãæ¬ ããŠããŸãã
æŠåšåº«ã«ã¯ãããã€ãã®ãµãã¯ã©ã¹ããããŸãã
BehaviorRelay-æåŸã«åä¿¡ããå€ãä¿åãããµãã¹ã¯ã©ã€ããããã³ã«éä¿¡ããŸãã ç¶æ ã®ä¿åãšå€æŽã«æé©ã§ãã
PublishRelayã¯åãªã泚ç®ã«å€ãããã®ã§ãã Viewã®ã³ãã³ããŸãã¯ã€ãã³ãã«é©ããŠããŸãã ããšãã°ããã€ã¢ãã°ã衚瀺ããããã¢ãã¡ãŒã·ã§ã³ãéå§ãããããŸãã Viewããã®ã³ãã³ãïŒã€ãã³ãïŒã®åä¿¡ã«ã䜿çšãããŸãã
- ReplayRelay-åä¿¡ãããã¹ãŠã®ã¢ã€ãã ããããã¡ã«ä¿åããè³Œèªæã«ãã¹ãŠåçããŸãã éåžžã«ãŸãã«ãã䜿çšãããŸããããè€åæ¡ä»¶ã«åœ¹ç«ã€å ŽåããããŸãã æç»ã®äŸãæãæµ®ãã³ãŸãïŒç·ãåŒããæ¬¡ã«åãåŒããŸãã
ãã ãã Relayã®è¡šç€ºã¢ã¯ã»ã¹ãçŽæ¥æäŸããããšã¯ã§ããŸããã 誀ã£ãŠããããã£ã«å€ãå ¥åããããViewããã³ãã³ããåä¿¡ããããã«èšèšãããRelayããµãã¹ã¯ã©ã€ããããããå¯èœæ§ãããããã§ãã ãããã£ãŠãããããã£ãObservableãšããŠæç€ºããViewããã®ã€ãã³ããªã¹ããŒãConsumerãšããŠæç€ºããå¿ èŠããããŸãã ã¯ããã«ãã»ã«åã«ã¯ããã«å€ãã®ã³ãŒããå¿ èŠã«ãªããŸãããäžæ¹ã§ãããããã£ã®å Žæãšã³ãã³ãã®å Žæã¯ããã«ããããŸãã PresentationModelïŒpmïŒã®ããŠã³ããŒãã®é²è¡ç¶æ³ã®äŸïŒ
//State private val progress = BehaviorRelay.create<Int>() // property val progressState: Observable<Int> = progress.hide() // , fun progress(): Observable<Int> = progress.hide() //Action private val downloadClicks = PublishRelay.create<Unit>() // property val downloadClicksConsumer: Consumer<Unit> = downloadClicks // , fun downloadClicks(): Consumer<Unit> = downloadClicks
ç¶æ ãšã¢ã¯ã·ã§ã³ãå®çŸ©ããã®ã§ããã¥ãŒã§ã®ã¿ãããã«ã¢ã¿ããã§ããŸãã ãããè¡ãã«ã¯ãå¥ã®Jake Wortonã©ã€ãã©ãª-RxBindingãå¿ èŠã§ã ã 圌ã¯ãã€å¯ãŸããïŒ
pm.progressState.subscribe { progressBar.progress() } // downloadButton.clicks().subscribe { pm.downloadClicksConsumer } // PM
é©åãªObservableããªãå Žåã¯ã consumer.accept()
åŒã³åºãããšãã§ããŸã-ãŠã£ãžã§ãããªã¹ããŒããçŽæ¥ã
pm.downloadClicksConsumer.accept(Unit)
ãããŠä»ãå®éã«
次ã«ãäžèšã®ãã¹ãŠãããŒãã«åéããäŸã䜿çšããŠåæããŸãã PresentationModelã®èšèšã¯ãæ¬¡ã®æé ã«åé¡ã§ããŸãã
- ãã¥ãŒã«ä¿åããå¿ èŠãããPresentationModelã®ç¶æ ïŒããŒã¿ãããŒãã¹ããŒã¿ã¹ã衚瀺ããå¿ èŠããããšã©ãŒãªã©ïŒã決å®ããŸãã
- ãã¥ãŒã§çºçããå¯èœæ§ã®ããã€ãã³ããå€å¥ããŸãããã¿ã³ã®ã¯ãªãã¯ãå ¥åãã£ãŒã«ããžã®å ¥åãªã©ã
- PresentationModelãäœæããå ŽåãRxãèš±å¯ããããã«ã宣èšåã¹ã¿ã€ã«ã§ç¶æ ãã³ãã³ããããã³ã¢ãã«ãé¢é£ä»ããŸãã
- ãã¥ãŒãPresentationModelã«ãã€ã³ãããŸãã
ããšãã°ãããã¹ãå ã®åèªãèŠã€ããã¿ã¹ã¯ãèããŠã¿ãŸãããã
- æ€çŽ¢ããããã¹ãã®å ¥åãã£ãŒã«ãããããŸãã
- æ€çŽ¢ããåèª/éšåã®å ¥åãã£ãŒã«ãããããŸãã
- ãã¿ã³ãã¯ãªãã¯ããŠãæ€çŽ¢ãéå§ããŸãã
- æ€çŽ¢äžã«é²è¡ç¶æ³ã衚瀺ããŸããä»åã¯ãã¿ã³ããããã¯ããŸãã
- åçãåãåã£ãåŸãèŠã€ãã£ãåèªã®ãªã¹ãã衚瀺ããŸãã
ã€ã³ã¿ã©ã¯ã¿ãŒã®ãã¡ãµãŒãã®èåŸã«æ€çŽ¢ã¢ã«ãŽãªãºã ãé衚瀺ã«ããŸãã
data class SearchParams(val text: String, val query: String) interface Interactor { fun findWords(params: SearchParams): Single<List<String>> } class InteractorImpl : Interactor { override fun findWords(params: SearchParams): Single<List<String>> { return Single .just(params) .map { (text, query) -> text .split(" ", ",", ".", "?", "!", ignoreCase = true) .filter { it.contains(query, ignoreCase = true) } } .subscribeOn(Schedulers.computation()) } }
å ·äœçãªäŸã§ã¯ãSingleãšRxããŸã£ãããªããŠãããŸããŸããããã€ã³ã¿ãŒãã§ã€ã¹ã¯åäžã«ä¿ã¡ãŸãã ç¹ã«å®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ã Retrofitãä»ãããããã¯ãŒã¯ãžã®èŠæ±ããããŸãã
次ã«ãPresentationModelãèšèšããŸãã
ãã¥ãŒã®ç¶æ ïŒèŠã€ãã£ãåèªã®ãªã¹ããèªã¿èŸŒã¿ã¹ããŒã¿ã¹ãæ€çŽ¢ãã¿ã³ã®ã¢ã¯ãã£ããã£ãã©ã°ã ãã¿ã³ã®æå¹ç¶æ ã¯PresentationModelã®ããŒããã©ã°ã«ãã€ã³ãã§ããŸããããã¥ãŒã®å Žåã¯åå¥ã®ããããã£ãæäŸããå¿ èŠããããŸãã ãã¥ãŒã®ããŒããã©ã°ã«ã¹ãããããã ãã§ã¯ã©ãã§ããïŒ ããã§ã¯ãããŒããšæå¹åãšãã2ã€ã®ç¶æ ãããããšã確èªããå¿ èŠããããŸããããã®å ŽåãPresentationModelãããããæ¥ç¶ããŠããããšãäžèŽããŸããã äžè¬çãªå Žåããããã¯ç¬ç«ããŠããå ŽåããããŸãã ããšãã°ããŠãŒã¶ãŒãæå°æåæ°ãå ¥åãããŸã§ãã¿ã³ãããã¯ããå Žåã
ãã¥ãŒããã®ã€ãã³ãïŒããã¹ãã®å ¥åãæ€çŽ¢ã¯ãšãªã®å ¥åããã¿ã³ã®ã¯ãªãã¯ã ããã§ã¯ãã¹ãŠãç°¡åã§ããããã¹ãããã£ã«ã¿ãŒåŠçããããã¹ããšæ€çŽ¢æååã1ã€ã®ãªããžã§ã¯ãSearchParamsã«çµåããŸãã ãã¿ã³ãã¯ãªãã¯ããŠãæ€çŽ¢ã¯ãšãªãäœæããŸãã
ã³ãŒãã§ã¯æ¬¡ã®ããã«ãªããŸãã
class TextSearchPresentationModel { private val interactor: Interactor = InteractorImpl() // --- States --- private val foundWords = BehaviorRelay.create<List<String>>() val foundWordState: Observable<List<String>> = foundWords.hide() private val loading = BehaviorRelay.createDefault<Boolean>(false) val loadingState: Observable<Boolean> = loading.hide() val searchButtonEnabledState: Observable<Boolean> = loading.map { !it }.hide() // -------------- // --- UI-events --- private val searchQuery = PublishRelay.create<String>() val searchQueryConsumer: Consumer<String> = searchQuery private val inputTextChanges = PublishRelay.create<String>() val inputTextChangesConsumer: Consumer<String> = inputTextChanges private val searchButtonClicks = PublishRelay.create<Unit>() val searchButtonClicksConsumer: Consumer<Unit> = searchButtonClicks // --------------- private var disposable: Disposable? = null fun onCreate() { val filteredText = inputTextChanges.filter(String::isNotEmpty) val filteredQuery = searchQuery.filter(String::isNotEmpty) val combine = Observable.combineLatest(filteredText, filteredQuery, BiFunction(::SearchParams)) val requestByClick = searchButtonClicks.withLatestFrom(combine, BiFunction<Unit, SearchParams, SearchParams> { _, params: SearchParams -> params }) disposable = requestByClick .filter { !isLoading() } .doOnNext { showProgress() } .delay(3, TimeUnit.SECONDS) // .flatMap { interactor.findWords(it).toObservable() } .observeOn(AndroidSchedulers.mainThread()) .doOnEach { hideProgress() } .subscribe(foundWords) } fun onDestroy() { disposable?.dispose() } private fun isLoading() = loading.value private fun showProgress() = loading.accept(true) private fun hideProgress() = loading.accept(false) }
ãã¥ãŒã®åœ¹å²ã§ã¯ããã©ã°ã¡ã³ãããããŸãã
class TextSearchFragment : Fragment() { private val pm = TextSearchPresentationModel() private var composite = CompositeDisposable() private lateinit var inputText: EditText private lateinit var queryEditText: EditText private lateinit var searchButton: Button private lateinit var progressBar: ProgressBar private lateinit var resultText: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true // pm.onCreate() } // ... onCreateView override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // ... init widgets onBindPresentationModel() } fun onBindPresentationModel() { // --- States --- pm.foundWordState .subscribe { if (it.isNotEmpty()) { resultText.text = it.joinToString(separator = "\n") } else { resultText.text = "Nothing found" } } .addTo(composite) pm.searchButtonEnabledState .subscribe(searchButton.enabled()) .addTo(composite) pm.loadingState .subscribe(progressBar.visibility()) .addTo(composite) // --------------- // --- Ui-events --- queryEditText .textChanges() .map { it.toString() } .subscribe(pm.searchQueryConsumer) .addTo(composite) inputText .textChanges() .map { it.toString() } .subscribe(pm.inputTextChangesConsumer) .addTo(composite) searchButton.clicks() .subscribe(pm.searchButtonClicksConsumer) .addTo(composite) //------------------ } fun onUnbindPresentationModel() { composite.clear() } override fun onDestroyView() { super.onDestroyView() onUnbindPresentationModel() } override fun onDestroy() { super.onDestroy() pm.onDestroy() } } // RxKotlin /** * Add the disposable to a CompositeDisposable. * @param compositeDisposable CompositeDisposable to add this disposable to * @return this instance */ fun Disposable.addTo(compositeDisposable: CompositeDisposable): Disposable = apply { compositeDisposable.add(this) }
GitHubã§å®å šãªäŸãèŠãããšãã§ããŸãã
ãŸãšãããš
æ°ããRxPMãã¿ãŒã³ã«åºäŒããä»ã®ãã¬ãŒã³ããŒã·ã§ã³ãã³ãã¬ãŒãã®æ¬ ç¹ã調ã¹ãŸããã ããããMVPãšMVVMãRxPMãããæªããŸãã¯åªããŠãããšã¯ã£ãããšèšãããã¯ãããŸããã ç§ã¯ãå€ãã®äººãšåæ§ã«ããã®ã·ã³ãã«ããšççŽãããMVPã倧奜ãã§ãã ãŸããã¬ã€ã¢ãŠãå ã®ã³ãŒãã¯ã¢ããã¥ã¢ã§ãããMVVMã¯èªåããŒã¿ãã€ã³ãã£ã³ã°ã«é©ããŠããŸãã
ããããåçãªUIãåããææ°ã®ã¢ããªã±ãŒã·ã§ã³ã«ã¯ãå€ãã®ã€ãã³ãã³ãŒããšéåæã³ãŒãããããŸãã ãããã£ãŠãç§ã®éžæã¯ããªã¢ã¯ãã£ãã¢ãããŒããšRxPMã«åŸããŠããŸãã Jake Wortonã®ãã¬ãŒã³ããŒã·ã§ã³ããã®èšèãåŒçšããŸãããªãç§ãã¡ã®ã¢ããªã±ãŒã·ã§ã³ã¯ãªã¢ã¯ãã£ãã§ãªããã°ãªããªãã®ã§ããããã
ã·ã¹ãã å šäœãåæçã«ã¢ãã«åã§ããªãéããåäžã®éåæãœãŒã¹ã¯åœä»€åããã°ã©ãã³ã°ãäžæããŸãã
ã·ã¹ãã å šäœãåæçã«ã·ãã¥ã¬ãŒãã§ããªãå ŽåãéåæãœãŒã¹ã1ã€ã§ãåœä»€åããã°ã©ãã³ã°ãäžæããŸãã
ãã¡ãããRxPMã«ã¯é·æãšçæã®äž¡æ¹ããããŸãã
é·æïŒ
- Observableã®ãªã¢ã¯ãã£ããã§ãŒã³ãå£ãããã¢ãã«ãããã¥ãŒãžããŸãã¯ãã®éã«ã¹ãã¬ããããããšã¯ã§ããŸããã ããã«ãããViewãšã®å¿ é ã®çžäºäœçšããªããªããŸãã
- PresentationModelã®ããžãã¯ã®å®£èšçãªèª¬æã
- PresentationModelã¯Viewã®æœè±¡åã§ãããç¹å®ã®ãŠã£ãžã§ããã«é¢é£ä»ããããŠããŸããã
- ãã¥ãŒãæ·»ä»ãããŠãããã©ãããå¿é ããå¿ èŠã¯ãããŸããã Rx-propertyã®å€ã倿Žããã ãã§ãã ãã¥ãŒã¯ããµãã¹ã¯ã©ã€ããããšèªåçã«ç¶æ ãåãåããŸãã
- ãã¥ãŒããã€ãã³ãããªã¢ã¯ãã£ãã¹ã¿ã€ã«ã§ååŸããŸããRxæŒç®åã䜿çšããŠãã£ã«ã¿ãªã³ã°ãçµåãªã©ãè¡ããšäŸ¿å©ã§ãã
çæïŒ
- ãã€ã³ãã£ã³ã°ã³ãŒããèšè¿°ããå¿ èŠããããŸãããRxã§ã¯ç°¡åã§ãã
- Relayãã«ãã»ã«åããView as Observable and ConsumerãšããŠè¡šç€ºããå¿ èŠããããŸãã ããã¯ä»ã®ãšããç§ãæ©ãŸãå¯äžã®ãã®ã§ãã
- Rxã®è±å¯ãã ãã€ãã¹ãšãã©ã¹ã®äž¡æ¹ãšèããããšãã§ããŸãã åå¿æ§ã¯å¥ã®ãã©ãã€ã ã§ãããããRxã®äœ¿çšãéå§ãããšãã«ããããããããå Žæã§äœ¿çšããªããã°ãªããªãããšã¯é©ãããšã§ã¯ãããŸããã Javaã§ããã°ã©ãã³ã°ããå Žåãå€ãã®ãªããžã§ã¯ããã ãŸã人ã¯ããŸããããããOOPãã©ãã€ã ã§ãã
ããã¯ããããå®å šãªãªã¹ãã§ã¯ãããŸããã ããªããèŠãè³åŠäž¡è«ãã³ã¡ã³ãã«æžããŠãã ãããããªãã®æèŠãç¥ãããšã¯è峿·±ãã§ãããã
ãããã£ãŠãRxã«èªä¿¡ãããããªã¢ã¯ãã£ãã¢ããªã±ãŒã·ã§ã³ãäœæããå Žåãdatabindingã䜿çšããMVPããã³MVVMã«ããããããŠããå Žåã¯ãRxPMã詊ããŠãã ããã ãŸããããªãããã§ã«å¿«é©ã§ããã°ãç§ã¯ããªãã説åŸããŸãã;ïŒ
PS
æŽç·ŽãããAndroidéçºè ã¯ãã©ã€ããµã€ã¯ã«ã«ã€ããŠãããã³ããŒããŒã·ã§ã³äžã®PresentationModelã®ä¿åã«ã€ããŠã¯äœãèšããªãã£ãããšã«æãæ°ã¥ããã§ãããã ãã®åé¡ã¯ãã®ãã¿ãŒã³ã«åºæã®ãã®ã§ã¯ãªããåå¥ã«èæ ®ããå¿ èŠããããŸãã ç§ã®èšäºã§ã¯ããã¿ãŒã³ã®æ¬è³ªã«çŠç¹ãåœãŠãããšæããŸãããMVPãMVVMãšæ¯èŒããå Žåã®é·æãšçæã§ãã ãŸãã åæ¹åã®ããŒã¿ãã€ã³ãã£ã³ã° ãRxPMã®ã³ã³ããã¹ãã§ã®ç»é¢éã®ããã²ãŒã·ã§ã³ãªã©ãéèŠãªãããã¯ã¯ã«ããŒãããŠããŸããã§ããã æ¬¡ã®èšäºã§ã¯ã Jeevuzãšäžç·ã«ãå®éã®ãããžã§ã¯ãã§RxPMã®äœ¿çšãéå§ããæ¹æ³ã«ã€ããŠèª¬æããã¢ããªã±ãŒã·ã§ã³ãç°¡çŽ åããã©ã€ãã©ãªãœãªã¥ãŒã·ã§ã³ã玹ä»ããŸãã