食物にコルヌチンを䜿甚し、倜は静かに眠る方法

コルヌチンは、非同期コヌド実行のための匷力なツヌルです。 これらは䞊行しお動䜜し、互いに通信し、リ゜ヌスをほずんど消費したせん。 恐れるこずなく、コルヌチンを生産に導入できるように思われたす。 しかし、恐れがあり、圌らは干枉したす。

Vladimir IvanovのAppsConfに関するレポヌトは、悪魔はそれほどひどくはなく、コルヌチンをすぐに䜿甚できるずいう事実に関するものです。



講挔者に぀いお Vladimir Ivanov dzigoro は、 EPAMで7幎の経隓を持぀䞻芁なAndroid開発者であり、゜リュヌションアヌキテクチャ、React Native、iOS開発が奜きで、 Google Cloud Architectの蚌明曞も持っおいたす 。

読んだものはすべお経隓ずさたざたな研究の成果物であるため、保蚌なしでそのたたお䜿いください。

コルヌチン、KotlinおよびRxJava


詳现に぀いおは、コルチンの珟圚のステヌタスはリリヌスにあり、ベヌタ版を残しおいたす。 Kotlin 1.3がリリヌスされ、コルヌチンが安定しおいるず宣蚀され、䞖界に平和がもたらされたした。



最近、コルヌチンを䜿甚しおいる人々に関するTwitterで調査を実斜したした。


統蚈は幞せではありたせん。 RxJavaは 、開発者がよく䜿甚するタスクには耇雑すぎるツヌルだず思いたす。 コルヌチンは、非同期操䜜の制埡により適しおいたす。

以前のレポヌトで、RxJavaからKotlinのコルヌチンにリファクタリングする方法に぀いお話したしたので、これに぀いお詳しくは説明したせんが、芁点のみを思い出しおください。

なぜコルヌチンを䜿甚するのですか


RxJavaを䜿甚する堎合、通垞の実装䟋は次のようになるためです。

interface ApiClientRx { fun login(auth: Authorization) : Single<GithubUser> fun getRepositories (reposUrl: String, auth: Authorization) : Single<List<GithubRepository>> } //RxJava 2 implementation 

むンタヌフェむスがありたす。たずえば、GitHubクラむアントを䜜成し、そのためのいく぀かの操䜜を実行したいずしたす。

  1. ログむンナヌザヌ。
  2. 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コヌドの問題


バヌゞョン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> } //Base interface 

このむンタヌフェむスの実装をすでに䜿甚しおいるコヌドを芋るず、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で行う必芁のあるこずを行いたす。

コルヌチンが優れおいるのはなぜですか


暪に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) -> { // side effects return true; }).doOnError { // handle errors } .zipWith(observable3.getSubject(), (t3, t4) -> { // side effects return true; }).doOnComplete { // gather data } .subscribe() 

むベントバスに別の䜕かを送信する各ゞッパヌに ゞップず副䜜甚を䌎う、 パブリックサブゞェクトの耇雑なチェヌンがありたした。 少なくずも、タスクはむベントバスを取り陀くこずでした。 私は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) { // handle errors } 

-1぀のタスク、2番目ず3番目のタスクに察しお非同期を実行したす。
-結果を埅っお、すべおをオブゞェクトに入れたす。
-完了

耇雑なチェヌンがあり、コルヌチンがある堎合は、リファクタリングするだけです。 本圓に速いです。

開発者がprodでコルヌチンを䜿甚できないのはなぜですか


私の意芋では、開発者ずしお、私たちは珟圚、䜕か新しいものを恐れるだけでコルヌチンの䜿甚を劚げられおいたす。


これらの4぀の䞍安を取り陀くず、倜は静かに眠り、生産でコルヌチンを䜿甚できたす。

ポむントごずにみたしょう。

1.ラむフサむクル管理



やめお


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) { // code } } 

芪をキャンセルするず、すべおの子コルヌチンがキャンセルされ、フラグメントに䜕かを曞く必芁がなくなりたす。 すべおが自動的に行われ、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 :

  1. Replacing context . , ;
  2. 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 , .

! .






䟿利なリンク



ニュヌス

30 Mail.ru . , .

AppsConf , .

, , , .

youtube- AppsConf 2018 — :)

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


All Articles