Plus je lisais et regardais les rapports sur les coroutines à Kotlin, plus j'admirais cet outil de langage. Leur version stable a été récemment publiée dans Kotlin 1.3, ce qui signifie qu'il est temps de commencer la plongée et d'essayer les coroutines en action en utilisant mon code RxJava existant comme exemple. Dans cet article, nous nous concentrerons sur la façon de prendre les requêtes réseau existantes et de les convertir en remplaçant RxJava par des coroutines.

Franchement, avant d'essayer les coroutines, je pensais qu'elles étaient très différentes de ce qu'elles étaient. Cependant, le principe de base de la corutine inclut les mêmes concepts que ceux auxquels nous sommes habitués dans les écoulements réactifs RxJava. À titre d'exemple, prenons une configuration RxJava simple pour créer une demande réseau à partir de l'une de mes applications:
- Définissez l'interface réseau pour la mise à niveau à l'aide de l'adaptateur Rx ( retrofit2: adapter-rxjava2 ). Les fonctions renverront des objets à partir d'un framework Rx, tel que Single ou Observable . (Les fonctions sont utilisées ici et non les méthodes, car on suppose que l'ancien code a également été écrit en Kotlin. Eh bien, ou converti à partir de Java via Android Studio).
- Nous appelons une certaine fonction d'une autre classe (par exemple, un référentiel ou une activité).
- Nous déterminons pour les threads sur quel Scheduler ils seront exécutés et retournons le résultat (méthodes .subscribeOn () et .observeOn () ).
- Nous enregistrons le lien vers l'objet pour la désinscription (par exemple, dans CompositeObservable).
- Abonnez-vous au flux d'événements.
- Se désinscrire du flux en fonction des événements du cycle de vie de l'activité.
Il s'agit de l'algorithme de base pour travailler avec Rx (sans tenir compte des fonctions de mappage et des détails des autres manipulations de données). Quant à la corutine, le principe ne change pas grand-chose. Le même concept, seule la terminologie change.
- Nous définissons l'interface réseau pour le Retrofit en utilisant l' adaptateur pour coroutine . Les fonctions renverront des objets différés de l'API Corutin.
- Nous appelons ces fonctions à partir d'une autre classe (par exemple, un référentiel ou une activité). La seule différence: chaque fonction doit être marquée comme suspendue .
- Définissez le répartiteur qui sera utilisé pour la coroutine.
- Nous enregistrons le lien vers l'objet Job pour la désinscription.
- Exécutez coroutine de toutes les manières possibles.
- Nous annulons les coroutines en fonction des événements du cycle de vie de l'activité.
Comme vous pouvez le voir sur les séquences ci-dessus, le processus d'exécution de Rx et Corutin est très similaire. Si nous ne prenons pas en compte les détails de l'implémentation, cela signifie que nous pouvons maintenir l'approche que nous avons - nous remplaçons seulement certaines choses pour rendre notre implémentation coroutine-friendly.

La première étape que nous devons prendre est de permettre à Retrofit de renvoyer des objets différés . Les objets différés sont des futures non bloquants qui peuvent être annulés si nécessaire. Ces objets sont essentiellement un travail coroutine, qui contient la valeur du travail correspondant. L'utilisation du type différé nous permet de mélanger la même idée que Job, avec en plus la possibilité d'obtenir des états supplémentaires, tels que le succès ou l'échec - ce qui le rend idéal pour les demandes réseau.
Si vous utilisez Retrofit avec RxJava, vous utilisez probablement RxJava Call Adapter Factory. Heureusement, Jake Worton a écrit son équivalent pour coroutine .
Nous pouvons utiliser cet adaptateur d'appel dans le générateur Retrofit, puis implémenter notre interface Retrofit de la même manière qu'avec RxJava:
private fun makeService(okHttpClient: OkHttpClient): MyService { val retrofit = Retrofit.Builder() .baseUrl("some_api") .client(okHttpClient) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() return retrofit.create(MyService::class.java) }
Regardons maintenant l'interface MyService, qui est utilisée ci-dessus. Nous devons remplacer les types observables retournés par Deferred dans l'interface Retrofit. Si c'était comme ça:
@GET("some_endpoint") fun getData(): Observable<List<MyData>>
Maintenant, nous le remplaçons par:
@GET("some_endpoint") fun getData(): Deferred<List<MyData>>
Chaque fois que nous appelons getData (), l'objet Deferred nous revient - un analogue de Job pour les requêtes réseau. Auparavant, nous appelions en quelque sorte cette fonction avec RxJava:
override fun getData(): Observable<List<MyData>> { return myService.getData() .map { result -> result.map { myDataMapper.mapFromRemote(it) } } }
Dans ce flux RxJava, nous appelons notre fonction utilitaire, puis appliquons l'opération de mappage de l'API RxJava avec le mappage ultérieur des données renvoyées par la demande à quelque chose utilisé dans la couche d'interface utilisateur. Cela changera un peu lorsque nous utiliserons une implémentation avec des coroutines. Pour commencer, notre fonction doit être suspendue (différée), afin de faire une opération paresseuse à l'intérieur du corps de la fonction. Et pour cela, la fonction appelante doit également être différée. Une fonction différée n'est pas bloquante et peut être contrôlée après son appel initial. Vous pouvez le démarrer, le mettre en pause, le reprendre ou l'annuler.
override suspend fun getData(): List<MyData> { ... }
Maintenant, nous devons appeler notre fonction d'utilité. À première vue, nous faisons la même chose, mais nous devons nous rappeler que nous obtenons maintenant différé au lieu d' Observable .
override suspend fun getData(): List<MyData> { val result = myService.getData() ... }
En raison de ce changement, nous ne pouvons plus utiliser la chaîne d'opérations de carte de l'API RxJava. Et même à ce stade, les données ne sont pas disponibles pour nous - nous avons seulement une instance différée. Maintenant, nous devons utiliser la fonction wait () afin d'attendre le résultat de la requête et ensuite continuer à exécuter le code à l'intérieur de la fonction:
override suspend fun getData(): List<MyData> { val result = myService.getData().await() ... }
À ce stade, nous obtenons la demande complétée et les données de celle-ci disponibles pour utilisation. Par conséquent, nous pouvons maintenant effectuer des opérations de mappage:
override suspend fun getData(): List<MyData> { val result = myService.getData().await() return result.map { myDataMapper.mapFromRemote(it) } }
Nous avons pris notre interface Retrofit avec la classe appelante et utilisé des coroutines. Maintenant, nous voulons appeler ce code à partir de notre activité ou de fragments et utiliser les données que nous avons obtenues du réseau.
Dans notre activité, nous allons commencer par créer un lien vers Job, dans lequel nous pouvons affecter notre opération coroutine, puis l'utiliser pour contrôler, par exemple, l'annulation d'une demande, lors d'un appel onDestroy () .
private var myJob: Job? = null override fun onDestroy() { myJob?.cancel() super.onDestroy() }
Maintenant, nous pouvons affecter quelque chose à la variable myJob. Regardons notre demande avec coroutines:
myJob = CoroutineScope(Dispatchers.IO).launch { val result = repo.getLeagues() withContext(Dispatchers.Main) { //do something with result } }
Dans cet article, je ne voudrais pas me plonger dans les répartiteurs ou effectuer des opérations dans les coroutines, car c'est un sujet pour d'autres articles. En bref, ce qui se passe ici:
- Créez une instance de CoroutineScope en utilisant IO Dispatcher comme paramètre. Ce répartiteur est utilisé pour effectuer des opérations d'E / S de blocage, telles que des requêtes réseau.
- Nous lançons notre coroutine avec la fonction de lancement - cette fonction lance une nouvelle coroutine et renvoie un lien vers une variable de type Job.
- Ensuite, nous utilisons le lien vers notre référentiel pour recevoir des données en effectuant une demande réseau.
- Au final, nous utilisons le répartiteur principal pour effectuer le travail sur le thread d'interface utilisateur. Ici, nous pouvons montrer les données reçues aux utilisateurs.
Dans le prochain article, l'auteur promet de creuser un peu plus dans les détails, mais le matériel actuel devrait être suffisant pour commencer à étudier les coroutines.
Dans cet article, nous avons remplacé l'implémentation RxJava des réponses Retrofit par des objets Deferred de l'API Corutin. Nous appelons ces fonctions pour recevoir des données du réseau, puis les afficher dans notre activité. J'espère que vous avez vu le peu de changements que vous devez effectuer pour commencer avec les coroutines et que vous avez apprécié la simplicité de l'API, en particulier lors de la lecture et de l'écriture de code.
Dans les commentaires sur le post original, j'ai trouvé une requête traditionnelle: afficher tout le code. Par conséquent, j'ai fait une application simple qui, au démarrage, reçoit un horaire de trains avec l'API Yandex.Horaires et l'affiche dans RecyclerView. Lien: https://github.com/AndreySBer/RetrofitCoroutinesExample
Je voudrais également ajouter que les coroutines semblent être un remplacement inférieur pour RxJava, car elles n'offrent pas un ensemble équivalent d'opérations pour la synchronisation des threads. À cet égard, il vaut la peine d'examiner la mise en œuvre de ReactiveX pour Kotlin: RxKotlin .
Si vous utilisez Android Jetpack, j'ai également trouvé un exemple avec Retrofit, coroutines, LiveData et MVVM: https://codinginfinite.com/kotlin-coroutine-call-adapter-retrofit/