ã³ã«ãŒãã³ã¯ãéåæã³ãŒãå®è¡ã®ããã®åŒ·åãªããŒã«ã§ãã ãããã¯äžŠè¡ããŠåäœããäºãã«éä¿¡ãããªãœãŒã¹ãã»ãšãã©æ¶è²»ããŸããã æããããšãªããã³ã«ãŒãã³ãçç£ã«å°å
¥ã§ããããã«æãããŸãã ããããæããããã圌ãã¯å¹²æžããŸãã
Vladimir Ivanovã®
AppsConfã«é¢ããã¬ããŒãã¯ãæªéã¯ããã»ã©ã²ã©ãã¯ãªããã³ã«ãŒãã³ãããã«äœ¿çšã§ãããšããäºå®ã«é¢ãããã®ã§ãã
è¬æŒè
ã«ã€ã㊠ïŒVladimir IvanovïŒ
dzigoro ïŒã¯ã
EPAMã§7幎ã®çµéšãæã€äž»èŠãªAndroidéçºè
ã§ããããœãªã¥ãŒã·ã§ã³ã¢ãŒããã¯ãã£ãReact NativeãiOSéçºã奜ãã§ã
Google Cloud Architectã®èšŒææžãæã£ãŠã
ãŸã ã
èªãã ãã®ã¯ãã¹ãŠçµéšãšããŸããŸãªç 究ã®ææç©ã§ãããããä¿èšŒãªãã§ãã®ãŸãŸã䜿ããã ããã
ã³ã«ãŒãã³ãKotlinããã³RxJava
詳现ã«ã€ããŠã¯ãã³ã«ãã³ã®çŸåšã®ã¹ããŒã¿ã¹ã¯ãªãªãŒã¹ã«ãããããŒã¿çãæ®ããŠããŸãã
Kotlin 1.3ããªãªãŒã¹ãããã³ã«ãŒãã³ãå®å®ããŠãããšå®£èšãããäžçã«å¹³åãããããããŸããã
æè¿ãã³ã«ãŒãã³ã䜿çšããŠãã人ã
ã«é¢ããTwitterã§èª¿æ»ãå®æœããŸããã
- é£ç©äžã®ã³ã«ãŒãã³ã®13ïŒ
ã ãã¹ãŠé 調ã§ãã
- 25ïŒ
ã¯ããããããžã§ã¯ãã§è©ŠããŠã¿ãŠãã ããã
- 24ïŒ
-Kotlinãšã¯ïŒ
- 38ïŒ
ã®RxJavaã®å€§éšåã¯ã©ãã«ã§ããããŸãã
çµ±èšã¯å¹žãã§ã¯ãããŸããã
RxJava㯠ãéçºè
ããã䜿çšããã¿ã¹ã¯ã«ã¯
è€éãããããŒã«ã ãš
æããŸãã ã³ã«ãŒãã³ã¯ãéåææäœã®å¶åŸ¡ã«ããé©ããŠããŸãã
以åã®ã¬ããŒãã§ãRxJavaããKotlinã®ã³ã«ãŒãã³ã«ãªãã¡ã¯ã¿ãªã³ã°ããæ¹æ³ã«ã€ããŠè©±ããŸããã®ã§ãããã«ã€ããŠè©³ããã¯èª¬æããŸããããèŠç¹ã®ã¿ãæãåºããŠãã ããã
ãªãã³ã«ãŒãã³ã䜿çšããã®ã§ããïŒ
RxJavaã䜿çšããå Žåãéåžžã®å®è£
äŸã¯æ¬¡ã®ããã«ãªãããã§ãã
interface ApiClientRx { fun login(auth: Authorization) : Single<GithubUser> fun getRepositories (reposUrl: String, auth: Authorization) : Single<List<GithubRepository>> }
ã€ã³ã¿ãŒãã§ã€ã¹ããããŸããããšãã°ãGitHubã¯ã©ã€ã¢ã³ããäœæãããã®ããã®ããã€ãã®æäœãå®è¡ããããšããŸãã
- ãã°ã€ã³ãŠãŒã¶ãŒã
- GitHubãªããžããªã®ãªã¹ããååŸããŸãã
ã©ã¡ãã®å Žåããé¢æ°ã¯GitHubUserãŸãã¯GitHubRepositoryã®ãªã¹ããšããåäžã®ããžãã¹ãªããžã§ã¯ããè¿ããŸãã
ãã®ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
ã³ãŒãã¯æ¬¡ã®ãšããã§ãã
private fun attemptLoginRx () { showProgress(true) compositeDisposable.add(apiClient.login(auth) .flatMap { user -> apiClient.getRepositories(user.repos_url, auth) } .map { list -> list.map { it.full_name } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { showProgress(false) } .subscribe( { list -> showRepositories(this, list) }, { error -> Log.e("TAG", "Failed to show repos", error) } )) }
-compositeDisposableã䜿çšããŠãã¡ã¢ãªãªãŒã¯ãçºçããªãããã«ããŸãã
-æåã®ã¡ãœãããžã®åŒã³åºããè¿œå ããŸãã
-䟿å©ãªæŒç®åãããšãã°
flatMapã䜿çšããŠãŠãŒã¶ãŒãååŸããŸãã
-ãªããžããªã®ãªã¹ããååŸããŸãã
-BoilerplateãäœæããŠãé©åãªã¹ã¬ããã§
å®è¡ãããããã«ããŸãã
-ãã¹ãŠã®æºåãæŽã£ããããã°ã€ã³ããŠãããŠãŒã¶ãŒã®ãªããžããªã®ãªã¹ãã衚瀺ããŸãã
RxJavaã³ãŒãã®åé¡ïŒ- è€éã ç§ã®æèŠã§ã¯ãã³ãŒãã¯2ã€ã®ãããã¯ãŒã¯åŒã³åºããšUIã«äœãã衚瀺ãããšããåçŽãªã¿ã¹ã¯ã«ã¯è€éãããŸãã
- ãã€ã³ããããŠããªãã¹ã¿ãã¯ãã¬ãŒã¹ã ã¹ã¿ãã¯ãã¬ãŒã¹ã¯ãèšè¿°ããã³ãŒããšã»ãšãã©é¢ä¿ãããŸããã
- ãªãœãŒã¹ã®æµªè²» ã RxJavaã¯å
éšã§å€ãã®ãªããžã§ã¯ããçæããããã©ãŒãã³ã¹ãäœäžããå¯èœæ§ããããŸãã
ããŒãžã§ã³0.26ããåã®ã³ã«ãŒãã³ãšåãã³ãŒãã¯ã©ããªããŸããïŒ0.26ã§ã¯ãAPIãå€æŽãããŠãããçç£ã«ã€ããŠè©±ããŠãããšããã§ãã 補åã«0.26ãé©çšããããšã¯ã§ããŠããŸããããçŸåšäœæ¥äžã§ãã
ã³ã«ãŒãã³ã䜿çšãããšãã€ã³ã¿ãŒãã§ã€ã¹ã倧å¹
ã«å€æŽãããŸã ã é¢æ°ã¯ãSinglesããã³ãã®ä»ã®ãã«ããŒãªããžã§ã¯ããè¿ãã®ãåæ¢ããŸãã 圌ãã¯ããã«ããžãã¹ãªããžã§ã¯ããè¿ããŸãïŒGitHubUserãšGitHubRepositoryã®ãªã¹ãã GitHubUserããã³GitHubRepositoryé¢æ°ã«ã¯ã
äžæåæ¢ä¿®é£ŸåããããŸãã ãµã¹ãã³ãã¯ã»ãšãã©äœãããå¿
èŠããªããããããã¯è¯ãããšã§ãã
interface ApiClient { suspend fun login(auth: Authorization) : GithubUser suspend fun getRepositories (reposUrl: String, auth: Authorization) : List<GithubRepository> }
ãã®ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
ããã§ã«äœ¿çšããŠããã³ãŒããèŠããšãRxJavaãšæ¯èŒããŠå€§å¹
ã«å€æŽãããŠããŸãã
private fun attemptLogin () { launch(UI) { val auth = BasicAuthorization(login, pass) try { showProgress(true) val userlnfo = async { apiClient.login(auth) }.await() val repoUrl = userlnfo.repos_url val list = async { apiClient.getRepositories(repoUrl, auth) }.await() showRepositories( this, list.map { it -> it.full_name } ) } catch (e: RuntimeException) { showToast("Oops!") } finally { showProgress(false) } } }
-
ã³ã«ãŒãã³ãã«ããŒasyncãåŒã³åºããå¿çãåŸ
ã£ãŠ
userlnfoãååŸããã¡ã€ã³ã¢ã¯ã·ã§ã³ãå®è¡ãããŸãã
-ãã®ãªããžã§ã¯ãã®ããŒã¿ã䜿çšããŸãã
-å¥ã®
éåæåŒã³åºããè¡ãã
awaitãåŒã³åºããŸãã
ãã¹ãŠãéåæã®äœæ¥ãçºçããŠããªãããã«èŠããåã«ã³ãã³ããæžã蟌ãã ãã§å®è¡ãããŸãã æåŸã«ãUIã§è¡ãå¿
èŠã®ããããšãè¡ããŸãã
ã³ã«ãŒãã³ãåªããŠããã®ã¯ãªãã§ããïŒ- ãã®ã³ãŒãã¯èªã¿ãããã§ãã äžè²«ããŠããããã«æžãããŠããŸãã
- ã»ãšãã©ã®å Žåããã®ã³ãŒãã®ããã©ãŒãã³ã¹ã¯RxJavaãããåªããŠããŸãã
- ãã¹ããæžãã®ã¯éåžžã«ç°¡åã§ãããå°ãåŸã§èª¬æããŸãã
暪ã«2ã¹ããã
å°ãè±ç·ããŸãããããŸã è°è«ãå¿
èŠãªããšãããã€ããããŸãã
ã¹ããã1. withContext vs launch / async
ã³ã«ãŒãã³ãã«ããŒéåæã«å ããŠ
ã ã³ã«ãŒãã³ãã«ããŒwithContextããããŸãã
èµ·åãŸãã¯
éåæã§æ°ãã
Coroutineã³ã³ããã¹ããäœæã
ãŸãã ãããã¯å¿
ãããå¿
èŠã§ã¯ãããŸããã ã¢ããªã±ãŒã·ã§ã³å
šäœã§äœ¿çšããCoroutineã³ã³ããã¹ããããå Žåããããåäœæããå¿
èŠã¯ãããŸããã æ¢åã®ãã®ãåçŽã«åå©çšã§ããŸãã ãããè¡ãã«ã¯ãã³ã«ãŒãã³ãã«ããŒwithContextãå¿
èŠã«ãªããŸãã æ¢åã®ã³ã«ãŒãã³ã³ã³ããã¹ããåã«åå©çšããŸãã 2ã3åé«éã«ãªããŸãããä»ã§ã¯åççãªåé¡ã§ã¯ãããŸããã æ£ç¢ºãªæ°å€ãèå³æ·±ãå Žåããã³ãããŒã¯ãšè©³çŽ°ãå«ã
stackoverflow㮠質åããããŸãã
äžè¬çãªã«ãŒã«ïŒæå³çã«é©åããå Žæã§withContextã䜿çšããããšã¯ééããããŸããã ãã ããè€æ°ã®ç»åãããŒã¿ã®æçãªã©ã䞊åèªã¿èŸŒã¿ãå¿
èŠãªå Žåã¯ãasync / awaitãéžæããŸãã
ã¹ããã2.ãªãã¡ã¯ã¿ãªã³ã°
æ¬åœã«è€éãªRxJavaãã§ãŒã³ããªãã¡ã¯ã¿ãªã³ã°ãããšã©ããªããŸããïŒ ç§ã¯æ¬çªã§ããã«ééããŸããïŒ
observable1.getSubject().zipWith(observable2.getSubject(), (t1, t2) -> {
ã€ãã³ããã¹ã«å¥ã®äœããéä¿¡ããå
ãžãããŒã« ãžãããš
å¯äœçšã䌎ãã
ãããªãã¯ãµããžã§ã¯ãã®è€éãªãã§ãŒã³ããããŸããã å°ãªããšããã¿ã¹ã¯ã¯ã€ãã³ããã¹ãåãé€ãããšã§ããã ç§ã¯1æ¥ãéãããŸããããåé¡ã解決ããããã«ã³ãŒãããªãã¡ã¯ã¿ãªã³ã°ã§ããŸããã§ããã
ãã¹ãŠãæšãŠãŠã4æéã§ã³ã«ãŒãã³ã®ã³ãŒããæžãçŽããšããæ£ãã決å®ãå€æããŸãã ã
以äžã®ã³ãŒãã¯ç§ãæã«å
¥ãããã®ãšéåžžã«äŒŒãŠããŸãïŒ
try { val firstChunkJob = async { call1 } val secondChunkJob = async { call2 } val thirdChunkJob = async { call3 } return Result( firstChunkJob.await(), secondChunkJob.await(), thirdChunkJob.await()) } catch (e: Exception) {
-1ã€ã®ã¿ã¹ã¯ã2çªç®ãš3çªç®ã®ã¿ã¹ã¯ã«å¯ŸããŠéåæãå®è¡ããŸãã
-çµæãåŸ
ã£ãŠããã¹ãŠããªããžã§ã¯ãã«å
¥ããŸãã
-å®äºïŒ
è€éãªãã§ãŒã³ããããã³ã«ãŒãã³ãããå Žåã¯ããªãã¡ã¯ã¿ãªã³ã°ããã ãã§ãã æ¬åœã«éãã§ãã
éçºè
ãprodã§ã³ã«ãŒãã³ã䜿çšã§ããªãã®ã¯ãªãã§ããïŒ
ç§ã®æèŠã§ã¯ãéçºè
ãšããŠãç§ãã¡ã¯çŸåšãäœãæ°ãããã®ãæããã ãã§ã³ã«ãŒãã³ã®äœ¿çšã劚ããããŠããŸãã
- ã©ã€ããµã€ã¯ã« ã ã¢ã¯ãã£ãã㣠ããã©ã°ã¡ã³ãã®ã©ã€ããµã€ã¯ã«ãã©ããããã¯ããããŸããã ãããã®å Žåã«ã³ã«ãŒãã³ã䜿çšããã«ã¯ã©ãããã°ããã§ããïŒ
- ã³ã«ãã³ã䜿çšããçç£ã«ãããæ¥ã
ã®è€éãªã¿ã¹ã¯ã解決ããçµéšã¯ãããŸããã
- ååãªããŒã«ããããŸããã RxJavaåãã«å€æ°ã®ã©ã€ãã©ãªãšé¢æ°ãäœæãããŠããŸãã ããšãã°ã RxFCM ã RxJavaèªäœã«ã¯å€ãã®æŒç®åããããŸãããããã¯è¯ãããšã§ãããã³ã«ãŒãã³ã¯ã©ãã§ããããïŒ
- ã³ã«ãŒãã³ããã¹ãããæ¹æ³ã¯æ¬åœã«ç解ããŠããŸããã
ãããã®4ã€ã®äžå®ãåãé€ããšãå€ã¯éãã«ç ããçç£ã§ã³ã«ãŒãã³ã䜿çšã§ããŸãã
ãã€ã³ãããšã«ã¿ãŸãããã
1.ã©ã€ããµã€ã¯ã«ç®¡ç
- ã³ã«ãŒãã³ã¯ã 䜿ãæšãŠãŸãã¯AsyncTaskãšããŠãªãŒã¯ããå¯èœæ§ããããŸãã ãã®åé¡ã¯æåã§è§£æ±ºããå¿
èŠããããŸãã
- ã©ã³ãã ãªNULLãã€ã³ã¿ãŒäŸå€ãåé¿ããã«ã¯ãã³ã«ãŒãã³ãåæ¢ããå¿
èŠããããŸãã
ãããŠ
Thread.stopïŒïŒã«ç²ŸéããŠããŸããïŒ ããªããããã䜿çšããå Žåããã®åŸã¯é·ããããŸããã
JDK 1.1ã§ã¯ãç¹å®ã®ã³ãŒããååŸããã³åæ¢ããããšã¯äžå¯èœã§ãããæ£ããå®äºããä¿èšŒã¯ãªããããã¡ãœããã¯ããã«å»æ¢ããããšå®£èšãããŸããã ã»ãšãã©ã®å Žåã
ã¡ã¢ãªç Žæã®ã¿ãçºçã
ãŸã ã
ãããã£ãŠã
Thread.stopïŒïŒã¯æ©èœããŸãã ã ãã£ã³ã»ã«ã¯å調çã§ããå¿
èŠããããŸããã€ãŸãããã£ã³ã»ã«ããããšãç¥ãããã«å察åŽã®ã³ãŒããå¿
èŠã§ãã
RxJavaã§ã¹ããããé©çšããæ¹æ³ïŒ
private val compositeDisposable = CompositeDisposable() fun requestSmth() { compositeDisposable.add( apiClientRx.requestSomething() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> {}) } override fun onDestroy() { compositeDisposable.dispose() }
RxJavaã§ã¯CompositeDisposableã
䜿çšããŸã ã
-ãã©ã°ã¡ã³ããŸãã¯ãã¬ãŒã³ã¿ãŒã®ã¢ã¯ãã£ããã£ã«å€æ°
compositeDisposableãè¿œå ããŸããããã§ã¯ãRxJavaã䜿çšããŸãã
-onDestro yã§
Disposeãè¿œå
ãããšããã¹ãŠã®äŸå€ã
èªåçã«æ¶ããŸãã
ã³ã«ãŒãã³ãšã»ãŒåãåçïŒ
private val job: Job? = null fun requestSmth() { job = launch(UI) { val user = apiClient.requestSomething() ⊠} } override fun onDestroy() { job?.cancel() }
ç°¡åãªã¿ã¹ã¯ã®äŸãèããŠã¿ãŸãããã
éåžžã
ã³ã«ãŒãã³ãã«ããŒã¯
ãžã§ããè¿ããå Žåã«ãã£ãŠã¯
Deferredãè¿ããŸãã
-ç§ãã¡ã¯ãã®ä»äºãèŠããããšãã§ããŸãã
-
ãlaunchã ã³ã«ãŒãã³ãã«ããŒã³ãã³ããæå®ããŸãã ããã»ã¹ãéå§ãããäœããèµ·ãããå®è¡ã®çµæãèšæ¶ãããŸãã
-ä»ã«äœãæž¡ããªãå Žåããèµ·åãã¯æ©èœãéå§ãããžã§ããžã®ãªã³ã¯ãè¿ããŸãã
-ãžã§ãã¯èšæ¶ãããŠãããonDestroyã§
ããã£ã³ã»ã«ããšèšããšããã¹ãŠãæ£åžžã«æ©èœããŸãã
ã¢ãããŒãã®åé¡ã¯äœã§ããïŒ åãžã§ãã«ã¯ãã£ãŒã«ããå¿
èŠã§ãã ãžã§ãããŸãšããŠãã£ã³ã»ã«ããã«ã¯ããžã§ãã®ãªã¹ããç¶æããå¿
èŠããããŸãã ãã®ã¢ãããŒãã¯ã³ãŒãã®éè€ãæããŸãããããããªãã§ãã ããã
è¯ããã¥ãŒã¹ã¯ã
CompositeJobãš
Lifecycle-aware jobãšãã 代æ¿æ段ãããããšã§ãã
CompositeJobã¯compositeDisposableã®é¡äŒŒç©ã§ãã 次ã®ããã«ãªããŸã
ã private val job: CompositeJob = CompositeJob() fun requestSmth() { job.add(launch(UI) { val user = apiClient.requestSomething() ... }) } override fun onDestroy() { job.cancel() }
-1ã€ã®ãã©ã°ã¡ã³ãã«å¯ŸããŠ1ã€ã®ãžã§ããéå§ããŸãã
ããã¹ãŠã®
ãžã§ããCompositeJobã«å
¥ããŠã
ãjob.cancelïŒïŒfor everyoneïŒããšããã³ãã³ã
ãå®è¡ããŸããããã®ã¢ãããŒãã¯ãã¯ã©ã¹å®£èšãã«ãŠã³ãããã«4è¡ã§ç°¡åã«å®è£
ã§ããŸãã
Class CompositeJob { private val map = hashMapOf<String, Job>() fun add(job: Job, key: String = job.hashCode().toString()) = map.put(key, job)?.cancel() fun cancel(key: String) = map[key]?.cancel() fun cancel() = map.forEach { _ ,u -> u.cancel() } }
次ã®ãã®ãå¿
èŠã§ãã
-æååããŒã§
ããããã
-ãžã§ãã
è¿œå ããã¡ãœãããè¿œå ããŸãã
-ãªãã·ã§ã³ã®
ããŒãã©ã¡ãŒã¿ã
åããžã§ãã«åãããŒã䜿çšããå Žåã¯ããé¡ãããŸãã ããã§ãªãå Žåã
hashCodeãåé¡ã解決ããŸãã æž¡ãããããã«ãžã§ããè¿œå ããåãããŒã§ä»¥åã®ãžã§ãããã£ã³ã»ã«ããŸãã ã¿ã¹ã¯ãéå°ã«å®è¡ããå Žåã以åã®çµæã¯èå³ããããŸããã ç§ãã¡ã¯ããããã£ã³ã»ã«ããåã³é転ããŸãã
ãã£ã³ã»ã«ã¯ç°¡åã§ããããŒã§ãžã§ããååŸããŠãã£ã³ã»ã«ããŸãã ãããå
šäœã®2åç®ã®ãã£ã³ã»ã«ã§ã¯ããã¹ãŠããã£ã³ã»ã«ãããŸãã ãã¹ãŠã®ã³ãŒãã¯4è¡ã§30åã§èšè¿°ãããæ©èœããŸãã æžããããªãå Žåã¯ãäžã®äŸãã芧ãã ããã
ã©ã€ããµã€ã¯ã«å¯Ÿå¿ãžã§ã
Androidã©ã€ããµã€ã¯ã« ã
ã©ã€ããµã€ã¯ã«ã®ææè
ããŸãã¯
ãªãã¶ãŒããŒã䜿çšããŸãããïŒ
ã¢ã¯ãã£ããã£ãš
ãã©ã°ã¡ã³ãã«ã¯ç¹å®ã®ç¶æ
ããããŸãã ãã€ã©ã€ãïŒ
äœæã éå§ ã
åé ã ç¶æ
ã«ã¯ããŸããŸãªé·ç§»ããããŸãã
LifecycleObserverã䜿çšãããšããããã®é·ç§»ã«ãµãã¹ã¯ã©ã€ãããé·ç§»ã®1ã€ãçºçãããšãã«äœããå®è¡ã§ããŸãã
ããã¯éåžžã«ç°¡åã«èŠããŸãïŒ
public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void connectListener() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void disconnectListener() { ⊠} }
ã¡ãœããã®ãã©ã¡ãŒã¿ãŒã䜿çšããŠæ³šéãåæãããšã察å¿ããé·ç§»ã§åŒã³åºãããŸãã ã³ã«ãŒãã³ã«ãã®ã¢ãããŒãã䜿çšããŠãã ããïŒ
class AndroidJob(lifecycle: Lifecycle) : Job by Job(), LifecycleObserver { init { lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun destroy() { Log.d("AndroidJob", "Cancelling a coroutine") cancel() } }
-AndroidJobåºæ¬ã¯ã©ã¹ãäœæã§ããŸãã
-
ã©ã€ããµã€ã¯ã«ãã¯ã©ã¹ã«è»¢éããŸãã
-LifecycleObserverã€ã³ã¿ãŒãã§ãŒã¹ããžã§ããå®è£
ããŸãã
å¿
èŠãªãã®ïŒ
-ã³ã³ã¹ãã©ã¯ã¿ãŒã§ããªãã¶ãŒããŒãšããŠã©ã€ããµã€ã¯ã«ã«è¿œå ããŸãã
-ON_DESTROYãŸãã¯ãã®ä»ã®èå³ã®ãããã®ã賌èªããŸãã
-ON_DESTROYã§ãã£ã³ã»ã«ããŸãã
-ãã©ã°ã¡ã³ãã§1ã€ã®
parentJobã
ååŸããŸãã
-ã³ã³ã¹ãã©ã¯ã¿ãŒã®
Joyãžã§ããŸãã¯ã¢ã¯ãã£ããã£ãã©ã°ã¡ã³ãã®
ã©ã€ããµã€ã¯ã«ãåŒã³åºããŸãã éãã¯ãããŸããã
-ãã®
parentJobã
parentãšããŠ
æž¡ããŸãã
å®æããã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
private var parentJob = AndroidJob(lifecycle) fun do() { job = launch(UI, parent = parentJob) {
芪ããã£ã³ã»ã«ãããšããã¹ãŠã®åã³ã«ãŒãã³ããã£ã³ã»ã«ããããã©ã°ã¡ã³ãã«äœããæžãå¿
èŠããªããªããŸãã ãã¹ãŠãèªåçã«è¡ãããON_DESTROYã¯äžèŠã§ãã äž»ãª
ãã®ã¯ã
parent = parentJobãæž¡ãããšãå¿ããªãã§ãã ããã
䜿çšããå Žåãããªãã¯ããªãã匷調ããåçŽãªãªã³ãèŠåãæžãããšãã§ããŸãïŒããããããªãã¯ããªãã®èŠªãå¿ããŸããïŒã
ãš
ã©ã€ããµã€ã¯ã«ç®¡çãæŽçãããŸããã ãããç°¡åãã€å¿«é©ã«è¡ããããŒã«ãããã€ããããŸãã
æ¬çªç°å¢ã§ã®è€éãªã·ããªãªãšéèŠãªã¿ã¹ã¯ã«ã€ããŠã¯ã©ãã§ããïŒ
2.è€éãªãŠãŒã¹ã±ãŒã¹
è€éãªã·ããªãªãšéèŠãªã¿ã¹ã¯ã¯æ¬¡ã®ãšããã§ãã
-
æŒç®å -RxJavaã®è€éãªæŒç®åïŒflatMapããããŠã³ã¹ãªã©
-
ãšã©ãŒåŠç-è€éãªãšã©ãŒåŠçã
try..catchã ãã§ãªããããšãã°ãã¹ããããŠããŸãã
-
ãã£ãã·ã¥ ã¯éèŠãªã¿ã¹ã¯ã§ãã æ¬çªç°å¢ã§ã¯ããã£ãã·ã¥ã«ééããã³ã«ãŒãã³ã䜿çšããŠãã£ãã·ã¥ã®åé¡ãç°¡åã«è§£æ±ºããããŒã«ãå
¥æããããšèããŸããã
ç¹°ãè¿ã
ã³ã«ãŒãã³ã®æŒç®åãèãããšããæåã®ãªãã·ã§ã³ã¯
repeatWhenïŒïŒã§ããã
äœããããŸãããããCorutinãå
éšã®ãµãŒããŒã«å°éã§ããªãã£ãå Žåã¯ãäœããã®ææ°é¢æ°çãªãã©ãŒã«ããã¯ã§äœåºŠãåè©Šè¡ããŸãã ãããããæ¥ç¶ãäžååã§ãããããæäœãæ°åç¹°ãè¿ãããšã§ç®çã®çµæãåŸãããšãã§ããŸãã
ã³ã«ãŒãã³ã䜿çšãããšããã®ã¿ã¹ã¯ãç°¡åã«å®è£
ã§ããŸãã
suspend fun <T> retryDeferredWithDelay( deferred: () -> Deferred<T>, tries: Int = 3, timeDelay: Long = 1000L ): T { for (i in 1..tries) { try { return deferred().await() } catch (e: Exception) { if (i < tries) delay(timeDelay) else throw e } } throw UnsupportedOperationException() }
ãªãã¬ãŒã¿ãŒã®å®è£
ïŒ
-圌ã¯
æ®ã眮ããåããŸãã
-ãã®ãªããžã§ã¯ããååŸããã«ã¯ã
asyncãåŒã³åºãå¿
èŠããããŸãã
-Deferredã®ä»£ããã«ããµã¹ãã³ããããã¯ãšäžè¬çã«
ãµã¹ãã³ãé¢æ°ã®äž¡æ¹ãæž¡ãããšãã§ã
ãŸãã-forã«ãŒã-ã³ã«ãŒãã³ã®çµæãåŸ
ã£ãŠããŸãã äœããçºçããç¹°ãè¿ãã«ãŠã³ã¿ãŒã䜿ãæããããŠããªãå Žåã¯ã
Delayãä»ããŠåè©Šè¡ããŠãã ããã ããã§ãªãå Žåã¯ããããã
é¢æ°ã¯ç°¡åã«ã«ã¹ã¿ãã€ãºã§ããŸããææ°é¢æ°çãªé
延ãèšå®ããããç¶æ³ã«å¿ããŠé
延ãèšç®ããã©ã ãé¢æ°ãæž¡ããŸãã
ããã䜿çšããŠãããã¯åäœããŸãïŒ
ãžãã
ãŸãããããã«é »ç¹ã«ééããŸãã ããã§ãããã¹ãŠãç°¡åã§ãã
suspend fun <T1, T2, R> zip( source1: Deferred<T1>, source2: Deferred<T2>, zipper: BiFunction<T1, T2, R>): R { return zipper.apply(sourcel.await(), source2.await()) } suspend fun <T1, T2, R> Deferred<T1>.zipWith( other: Deferred<T2>, zipper: BiFunction<T1, T2, R>): R { return zip(this, other, zipper) }
-
ãžãããŒã䜿çšããŠãDeferredã§åŸ
æ©ããŸãã
-Deferredã®ä»£ããã«ãwithContextã§suspendé¢æ°ãšã³ã«ãŒãã³ãã«ããŒã䜿çšã§ããŸãã å¿
èŠãªã³ã³ããã¹ããäŒããŸãã
ããã¯åã³æ©èœãããã®ææãåãé€ããŠãããããšãé¡ã£ãŠããŸãã
ãã£ãã·ã¥
RxJavaã䜿çšããŠå®çšŒåç°å¢ã§ãã£ãã·ã¥ãå®è£
ããŠããŸããïŒ RxCacheã䜿çšããŸãã
å·ŠåŽã®å³ïŒ
Viewããã³
ViewModel ã å³åŽã«ã¯ããŒã¿ãœãŒã¹ããããŸãããããã¯ãŒã¯ã³ãŒã«ãšããŒã¿ããŒã¹ã§ãã
äœãããã£ãã·ã¥ãããå Žåããã£ãã·ã¥ã¯ããŒã¿ã®å¥ã®ãœãŒã¹ã«ãªããŸãã
ãã£ãã·ã¥ã®çš®é¡ïŒ
- ãããã¯ãŒã¯ã³ãŒã«ã®ãããã¯ãŒã¯ãœãŒã¹ ã
- ã¡ã¢ãªå
ãã£ãã·ã¥ ã
- ãã£ãã·ã¥ãã¢ããªã±ãŒã·ã§ã³ã®åèµ·åã«èããããããã«ãæå¹æéãèšå®ãããæ°žç¶ãã£ãã·ã¥ããã£ã¹ã¯ã«ä¿åããŸãã
3çªç®ã®ã±ãŒã¹ã§ã¯ãåçŽã§ããªããã£ããª
ãã£ãã·ã¥ãäœæããŸãã ã³ã«ãŒãã³ãã«ããŒwithContextãåã³å©ãã«ãªããŸãã
launch(UI) { var data = withContext(dispatcher) { persistence.getData() } if (data == null) { data = withContext(dispatcher) { memory.getData() } if (data == null) { data = withContext(dispatcher) { network.getData() } memory.cache(url, data) persistence.cache(url, data) } } }
-withContextã䜿çšããŠåæäœãå®è¡ããããŒã¿ãæ¥ãŠãããã©ããã確èªããŸãã
-
æ°žç¶æ§ããã®ããŒã¿ãæ¥ãªãå Žåãããªãã¯
memory.cacheãããããååŸããããšããŠããŸãã
-memory.cacheãååšããªãå Žåã¯ã
ãããã¯ãŒã¯ãœãŒã¹ã«é£çµ¡ããŠããŒã¿ãååŸããŸãã ãã¡ããããã¹ãŠã®ãã£ãã·ã¥ã«å
¥ããããšãå¿ããªãã§ãã ããã
ããã¯ããªãåå§çãªå®è£
ã§ãããå€ãã®è³ªåããããŸããã1ã€ã®å Žæã«ãã£ãã·ã¥ãå¿
èŠãªå Žåããã®æ¹æ³ã¯æ©èœããŸãã å®çšŒåã¿ã¹ã¯ã®å Žåããã®ãã£ãã·ã¥ã¯ååã§ã¯ãããŸããã ãã£ãšè€éãªãã®ãå¿
èŠã§ãã
Rxã«ã¯RxCacheããããŸã
ãŸã RxJavaã䜿çšããŠããå Žåã¯ãRxCacheã䜿çšã§ããŸãã ãŸã 䜿çšããŠããŸãã
RxCacheã¯ç¹å¥ãªã©ã€ãã©ãªã§ãã ããŒã¿ããã£ãã·ã¥ããã©ã€ããµã€ã¯ã«ã管çã§ããŸãã
ããšãã°ããã®ããŒã¿ã¯15ååŸã«æå¹æéãåãããšèšããŸããããã®æéã®åŸããã£ãã·ã¥ããããŒã¿ãéä¿¡ããã«ãæ°ããããŒã¿ãéä¿¡ããŠãã ãããã
ã©ã€ãã©ãªã¯ãããŒã ã宣èšçã«ãµããŒããããšããç¹ã§çŽ æŽãããã§ãã 宣èšã¯ã
Retrofitã§è¡ãããšãšéåžžã«äŒŒãŠããŸãã
public interface FeatureConfigCacheProvider { @ProviderKey("features") @LifeCache(duration = 15, timeUnit = TimeUnit.MINUTES) fun getFeatures( result: Observable<Features>, cacheName: DynamicKey ): Observable<Reply<Features>> }
-CacheProviderããããšèšããŸãã
-ã¡ãœãããéå§ãã
LifeCacheã®æå¹æé㯠15åã§
ãããšèšããŸãã ãããå©çšå¯èœã«ãªãããŒã¯
æ©èœã§ãã
-Observable <Replyãè¿ããŸã
ãReplyã¯ããã£ãã·ã¥ãæäœããããã®è£å©ã©ã€ãã©ãªãªããžã§ã¯ãã§ãã
䜿ãæ¹ã¯ãšãŠãç°¡åã§ãïŒ
val restObservable = configServiceRestApi.getFeatures() val features = featureConfigCacheProvider.getFeatures( restObservable, DynamicKey(CACHE_KEY) )
-Rxãã£ãã·ã¥ãã
RestApiã«ã¢ã¯ã»ã¹ããŸãã
-CacheProviderã«åãæ¿ããŸãã
-圌ã«ObservableãäžããŸãã
-ã©ã€ãã©ãªèªäœãäœããã¹ãããå€æããŸãããã£ãã·ã¥ã«è¡ããã©ãããæéããªããªã£ãå Žåã¯
Observableã«åãæ¿ããŠå¥ã®æäœãå®è¡ããŸãã
ã©ã€ãã©ãªã®äœ¿çšã¯éåžžã«äŸ¿å©ã§ãã³ã«ãŒãã³ã«ã€ããŠãåæ§ã®ãã®ãå
¥æããããšæããŸãã
éçºäžã®ã³ã«ãŒãã³ãã£ãã·ã¥
EPAMå
éšã§ã¯ãRxCacheã®ãã¹ãŠã®æ©èœãå®è¡ãã
Coroutine Cacheã©ã€ãã©ãªãäœæããŠããŸãã æåã®ããŒãžã§ã³ãäœæãã瀟å
ã§å®è¡ããŸããã æåã®ãªãªãŒã¹ãå
¬éãã次第ãTwitterã«æçš¿ããŸãã 次ã®ããã«ãªããŸãã
val restFunction = configServiceRestApi.getFeatures() val features = withCache(CACHE_KEY) { restFunction() }
äžæé¢æ°
getFeaturesããããŸãã é¢æ°ããããã¯ãšããŠç¹å¥ãªé«æ¬¡é¢æ°
withCacheã«æž¡ããŸã ããã
ã«ãã ãäœãããå¿
èŠããããã
ããããŸãã
ããããã宣èšé¢æ°ããµããŒãããããã«åãã€ã³ã¿ãŒãã§ãŒã¹ãäœæããã§ãããã
ãšã©ãŒåŠç
åçŽãªãšã©ãŒåŠçã¯å€ãã®å Žåãéçºè
ã«ãã£ãŠçºèŠãããéåžžã¯éåžžã«ç°¡åã«è§£æ±ºãããŸãã è€éãªãã®ããªãå Žåã¯ãcatchã§
äŸå€ããã£ããããããã§èµ·ãã£ãããšã確èªãããã°ã«æžã蟌ã¿ããšã©ãŒããŠãŒã¶ãŒã«è¡šç€ºããŸãã UIã§ã¯ããããç°¡åã«è¡ãããšãã§ããŸãã
åçŽãªå Žåããã¹ãŠãåçŽã§ããããšãæåŸ
ãããŸã-ã³ã«ãŒãã³ã䜿çšãããšã©ãŒåŠçã¯
try-catch-finallyãä»ããŠè¡ãããŸãã
æ¬çªç°å¢ã§ã¯ãåçŽãªå Žåã«å ããŠã次ã®ãã®ããããŸãã
-ãã¹ãããã
try-catch ã
-ããŸããŸãªçš®é¡ã®
äŸå€ ã
-ãããã¯ãŒã¯ãŸãã¯ããžãã¹ããžãã¯ã®ãšã©ãŒã
-ãŠãŒã¶ãŒãšã©ãŒã 圌ã¯åã³äœãééã£ãããšããããã¹ãŠã®ããã«ããããšã§ããã
ãã®ããã«æºåããå¿
èŠããããŸãã
2ã€ã®è§£æ±ºçããããŸã
ãCoroutineExceptionHandlerãš
Resultã¯ã©ã¹ã®ã¢ãããŒãã§ãã
ã³ã«ãŒãã³äŸå€ãã³ãã©ãŒ
ããã¯ããšã©ãŒã®è€éãªã±ãŒã¹ãåŠçããããã®ç¹å¥ãªã¯ã©ã¹ã§ãã
ExceptionHandlerã䜿çšãããšã
äŸå€ãåŒæ°ãšããŠãšã©ãŒãšããŠåãåãããããåŠçã§ããŸãã
éåžžãè€éãªãšã©ãŒãã©ã®ããã«åŠçããŸããïŒ
ãŠãŒã¶ãŒãäœããæŒãããããã¿ã³ãæ©èœããªãã£ãã 圌ã¯äœãããŸããããªãã£ãããèšãããããç¹å®ã®ã¢ã¯ã·ã§ã³ã«åããå¿
èŠããããŸããã€ã³ã¿ãŒããããWi-Fiããã§ãã¯ããåŸã§è©Šãããã¢ããªã±ãŒã·ã§ã³ãåé€ããŠãäºåºŠãšäœ¿çšããªãã§ãã ããã ããããŠãŒã¶ãŒã«èšãã®ã¯éåžžã«ç°¡åã§ãïŒ
val handler = CoroutineExceptionHandler(handler = { , error -> hideProgressDialog() val defaultErrorMsg = "Something went wrong" val errorMsg = when (error) { is ConnectionException -> userFriendlyErrorMessage(error, defaultErrorMsg) is HttpResponseException -> userFriendlyErrorMessage(Endpoint.EndpointType.ENDPOINT_SYNCPLICITY, error) is EncodingException -> "Failed to decode data, please try again" else -> defaultErrorMsg } Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show() })
-ããã©ã«ãã¡ãã»ãŒãžïŒãäœããããããïŒããéããäŸå€ãåæããŸãã
-ããã
ConnectionExceptionã§ããå ŽåããªãœãŒã¹ããããŒã«ã©ã€ãºãããã¡ãã»ãŒãžãåãåããŸããã人ãWi-Fiããªã³ã«ãããšãåé¡ã¯ãªããªããŸãã ä¿èšŒããŸããã
-
ãµãŒããŒãäœãééã£ãŠãããšèšã£ãå Žåãã¯ã©ã€ã¢ã³ãã«ããã°ã¢ãŠãããŠå床ãã°ã€ã³ãããããã¢ã¹ã¯ã¯ã§ãããè¡ããªããä»ã®åœã§ãããè¡ããããŸãã¯ãç³ãèš³ãããŸããããåå¿ã ç§ã«ã§ããããšã¯ãåã«äœããããŸããããªãã£ããšèšãã ãã§ããã
-ãããå®å
šã«
ç°ãªãééãã§ããå Žåãããšãã°
ã¡ã¢ãªäžè¶³ã®å Žåããäœãåé¡ãçºçããŸãããããããªããããšèšããŸãã
-ãã¹ãŠã®ã¡ãã»ãŒãžã衚瀺ãããŸãã
CoroutineExceptionHandlerã«æžã蟌ããã®ã¯ã
ã³ã«ãŒãã³ãå®è¡ããã®ãšåã
Dispatcherã§å®è¡ãããŸãã ãããã£ãŠããèµ·åãUIã³ãã³ããæå®ãããšããã¹ãŠãUIã§å®è¡ãããŸãã åå¥ã®
ãã£ã¹ãããã¯å¿
èŠãããŸãããããã¯éåžžã«äŸ¿å©ã§ãã
䜿ãæ¹ã¯ç°¡åã§ãïŒ
launch(uiDispatcher + handler) { ... }
ãã©ã¹æŒç®åããããŸãã ã³ã«ãŒãã³ã³ã³ããã¹ãã§
ãã³ãã©ãŒãè¿œå ãããšããã¹ãŠãæ©èœããŸããããã¯éåžžã«äŸ¿å©ã§ãã ããããã°ãã䜿çšããŸããã
çµæã¯ã©ã¹
åŸã§ãCoroutineExceptionHandlerãæ¬ èœããŠããå¯èœæ§ãããããšã«æ°ä»ããŸããã ã³ã«ãŒãã³ã®äœæ¥ã«ãã£ãŠåœ¢æãããçµæã¯ãç°ãªãéšåããã®è€æ°ã®ããŒã¿ã§æ§æãããããè€æ°ã®ç¶æ³ãåŠçãããã§ããŸãã
çµæã¯ã©ã¹ã®ã¢ãããŒãã¯ããã®åé¡ã«å¯ŸåŠããã®ã«åœ¹ç«ã¡ãŸãã
sealed class Result { data class Success(val payload: String) : Result() data class Error(val exception: Exception) : Result() }
-ããžãã¹ããžãã¯ã§ã
Resultã¯ã©ã¹ãéå§ããŸãã
-
å°å°æžã¿ãšããŠããŒã¯ããŸãã
-ã¯ã©ã¹ãããä»ã®2ã€ã®ããŒã¿ã¯ã©ã¹ãç¶æ¿ããŸãïŒ
Successãš
Errorã§ãã
-
æåã§ã¯ãã³ã«ãŒãã³å®è¡ã®çµæãšããŠçæãããããŒã¿ã転éããŸãã
-
ãšã©ãŒã§äŸå€ãè¿œå ããŸãã
ãã®åŸã次ã®ããã«ããžãã¹ããžãã¯ãå®è£
ã§ããŸãã
override suspend fun doTask(): Result = withContext(CommonPool) { if ( !isSessionValidForTask() ) { return@withContext Result.Error(Exception()) } ⊠try { Result.Success(restApi.call()) } catch (e: Exception) { Result.Error(e) } }
Coroutine context â Coroutine builder withContex .
, :
â , error. .
â RestApi -.
â ,
Result.Success .
â ,
Result.Error .
- , ExceptionHandler .
Result classes , . Result classes, ExceptionHandler try-catch.
3.
, .
unit- , , . unit-.
, . , unit-, 2 :
- Replacing context . , ;
- Mocking coroutines . .
Replacing context
presenter:
val login() { launch(UI) { ⊠} }
,
login , UI-. , ,
. , , unit-.
:
val login (val coroutineContext = UI) { launch(coroutineContext) { ... } }
â login coroutineContext. , . Kotlin , UI .
â Coroutine builder Coroutine Contex, .
unit- :
fun testLogin() { val presenter = LoginPresenter () presenter.login(Unconfined) }
â
LoginPresenter login - , , Unconfined.
â
Unconfined , , . .
Mocking coroutines
â .
Mockk unit-. unit- Kotlin, . suspend-
coEvery -.
login
githubUser :
coEvery { apiClient.login(any()) } returns githubUser
Mockito-kotlin , â . , , :
given { runBlocking { apiClient.login(any()) } }.willReturn (githubUser)
runBlocking .
given- , .
Presenter :
fun testLogin() { val githubUser = GithubUser('login') val presenter = LoginPresenter(mockApi) presenter.login (Unconfined) assertEquals(githubUser, presenter.user()) }
â -, ,
GitHubUser .
â LoginPresenter API, . .
â
presenter.login Unconfined , Presenter , .
! .
- Rx- . . , RxJava RxJava. - â , .
- . , . Unit- â , , , . â welcome!
- . , , , , . .
䟿å©ãªãªã³ã¯
ãã¥ãŒã¹
30 Mail.ru . , .
AppsConf , .
, , , .
youtube- AppsConf 2018 â :)