Kotlin Flow pour Android: mise en route

Online Coding Courses for Kids

Kotlin Flow est une nouvelle API de traitement de flux développée par JetBrains, l’entreprise derrière la langue Kotlin. C’est une mise en œuvre du Spécification du flux réactif, une initiative dont le but est de fournir une norme pour le traitement de flux asynchrone. Jetbrains a construit Kotlin Flow au-dessus de Kotlin Coroutines.

En utilisant Flow pour gérer des flux de valeurs, vous pouvez transformer des données de manière complexe à plusieurs threads, en écrivant juste un petit morceau de code!

Dans ce didacticiel, vous aurez l’occasion de jouer avec plusieurs approches différentes pour gérer les collections, et vous explorerez les capacités de Kotlin Flow en créant une application météo simple. Au cours de ce processus, vous découvrirez:

  • Collectes et flux de données.
  • Appels d’API synchrones et asynchrones.
  • Flux de données chauds et froids.
  • Gestion des exceptions lors du traitement des flux.
Remarque: Ce tutoriel suppose que vous avez une solide connaissance du développement Android. Si vous êtes complètement nouveau sur le sujet, veuillez d’abord consulter notre série Début de développement Android avec Kotlin.

Vous devez également avoir une compréhension de base de Kotlin Coroutines à suivre avec ce tutoriel. Vous pouvez consulter ces tutoriels pour vous familiariser avec Coroutines:

L’exemple d’application de ce didacticiel utilise le modèle architectural MVVM. MVVM signifie Model-View-ViewModel et représente un modèle dans lequel vous mettez à jour l’interface utilisateur via des flux de données réactifs. Si vous n’êtes pas familier avec ce modèle, veuillez consulter notre cours vidéo MVVM sur Android pour vous familiariser avec celui-ci.

Commencer

Dans la première partie du didacticiel, vous découvrirez les concepts de Kotlin Flow, en utilisant l’application Kotlin Playground comme… enfin, une aire de jeux! Vous utiliserez ensuite ce que vous avez appris pour développer une application Android plus robuste qui affiche les données récupérées via les appels d’API.

Téléchargez le matériel pour ce tutoriel en utilisant le Télécharger les documents bouton en haut ou en bas de la page. Pour l’instant, ouvrez le Playground-Starter projet en Android Studio. Ce n’est qu’un projet vide qui servira de terrain de jeu.

Renvoyer plusieurs valeurs

Vous savez probablement que suspension des fonctions peut renvoyer une seule valeur de manière asynchrone. Lorsque vous utilisez des fonctions de suspension, vous n’avez pas à vous soucier du threading, l’API Coroutines le fait pour vous!

Coulerpeut cependant revenir plusieurs valeurs de manière asynchrone et dans le temps. Opérations asynchrones sont des opérations sur lesquelles vous devez attendre, telles que les requêtes réseau. On ne sait jamais combien de temps ces opérations pourraient prendre! Ils peuvent prendre de quelques millisecondes à plusieurs secondes pour obtenir une réponse. Toute opération de longue durée doit être asynchrone car une attente active peut geler vos programmes.

Vous verrez comment le retour de valeurs à l’aide de fonctions de suspension est très différent de l’API Flow, avec quelques exemples!

liste

Ouvert main.kt fichier et ajoutez la fonction suivante:

suspend fun getValues(): List {
  delay(1000)
  return listOf(1, 2, 3)
}

Cette fonction calcule des valeurs et ajoute ces valeurs dans un List. delay() simule une opération de longue durée, comme vous le feriez avec une API distante.

Ajoutez maintenant une fonction pour traiter ces valeurs:

fun processValues() {
  runBlocking {
    val values = getValues()
    for (value in values) {
      println(value)
    }
  }
}

Appel processValues() de main():

fun main() {
  processValues()
}

Générez et exécutez le code, en utilisant l’icône verte «play» à côté de la fonction principale. Vous obtiendrez cette sortie après un délai d’une seconde:

1
2
3

Quand vous appelez getValues(), il renvoie un List avec trois valeurs. Vous utilisez ensuite ces valeurs dans processValues(). Dans un for boucle, vous parcourez le List et imprimer les valeurs.

Une représentation visuelle de la fonction est la suivante:

Diagramme de liste

C’est bien pour trois valeurs. Mais pas si cela prend beaucoup de temps pour calculer ces valeurs. Dans ce cas, vous devez attendre tout des valeurs à calculer. Si chaque valeur prend une seconde, vous attendriez des heures pour des milliers de valeurs!

Il est très inefficace car il ajoute un délai supplémentaire au traitement des données. Idéalement, vous souhaitez commencer à traiter chaque élément de la liste dès qu’il sera disponible. Les séquences vous permettre de le faire.

Séquence

Les séquences sont très similaires aux listes. Mais contrairement aux listes, elles sont paresseusement évalué. Cela signifie qu’ils produisent des valeurs lorsque vous les parcourez, au lieu de les produire tous en même temps. Refactor getValues() retourner un Sequence:

suspend fun getValues(): Sequence = sequence {
  Thread.sleep(250)
  yield(1)
  Thread.sleep(250)
  yield(2)
  Thread.sleep(250)
  yield(3)
}

Thread.sleep(250) simule un retard lors du calcul de chaque valeur.

Générez et exécutez le projet. Vous obtiendrez la même sortie qu’auparavant, mais cette fois, vous n’aurez pas à attendre toutes les valeurs. Vous les produirez et les consommerez, un par un:

1
2
3

Maintenant, au lieu d’attendre tous les articles, processValues() consomme chaque article comme getValues() le produit.

Utilisation des séquences Iterators sous le capot et bloquer en attendant le prochain article. Cela fonctionne lorsque vous retournez une liste simple, mais que se passe-t-il si votre application doit communiquer avec un API de streaming?

Canal

Les API de streaming sont presque l’opposé exact des API REST. Lorsque vous communiquez avec une API REST, vous faites une demande et l’API envoie une réponse. Une API de streaming fonctionne différemment. Il se connecte à un client et écoute en permanence de nouvelles informations, au fil du temps. Twitter, par exemple, fournit une API de streaming que vous pouvez utiliser pour diffuser des tweets en temps réel.

Vous pouvez utiliser Sequences pour les flux synchrones. Mais vous avez besoin d’une solution différente pour les flux asynchrones.

Pour les flux asynchrones, vous pouvez utiliser Channels de Kotlin Coroutines. Conceptuellement, vous pouvez considérer les canaux comme des tuyaux. Vous envoyez des articles via un canal et recevez une réponse via l’autre. Cependant, un canal représente un chaud flux de valeurs. Encore une fois, les flux chauds commencent immédiatement à produire des valeurs.

Et cela introduit un autre ensemble de défis.

Flux chauds contre froids

Un canal, qui est un flux chaud, produira des valeurs même s’il ne les écoute pas de l’autre côté. Et si vous n’écoutez pas le flux, vous perdez des valeurs.

Dans le diagramme suivant, getValues() émet les articles via un canal. processValues() reçoit 1, 2, 3, puis il arrête d’écouter les éléments. La chaîne produit toujours les éléments, même lorsque personne n’écoute:

Diagramme de séquençage

En pratique, vous pouvez utiliser un canal pour avoir une connexion réseau ouverte. Mais cela peut entraîner des fuites de mémoire. Ou vous pourriez oublier de vous abonner à une chaîne et «perdre» des valeurs.

Les flux chauds poussent les valeurs même lorsque personne ne les consomme. cependant, courants froids, commencez à pousser les valeurs uniquement lorsque vous commencez à collecter!

Et Kotlin Flow est une implémentation de flux froids, propulsée par Kotlin Coroutines!

Les bases de Kotlin Flow

Le flux est un flux qui produit des valeurs de manière asynchrone. De plus, Flow utilise des coroutines en interne. Et à cause de cela, il bénéficie de tous les avantages de simultanéité structurée.

Avec la simultanéité structurée, les coroutines vivent pendant une durée limitée. Cette fois est liée à la CoroutineScope vous démarrez vos coroutines.

Lorsque vous annulez la portée, vous libérez également toutes les coroutines en cours d’exécution. Les mêmes règles s’appliquent également à Kotlin Flow. Lorsque vous annulez la portée, vous disposez également du flux. Vous n’avez pas besoin de libérer de la mémoire manuellement! :]

Il existe quelques similitudes entre Kotlin Flow, LiveData et RxJava. Tous offrent un moyen de mettre en œuvre le modèle d’observateur dans votre code.

  • Données en direct est un simple détenteur de données observables. Il est préférable de stocker l’état de l’interface utilisateur, comme les listes d’éléments. C’est facile à apprendre et à travailler. Mais cela ne fournit pas beaucoup plus que cela.
  • RxJava est un outil très puissant pour les flux réactifs. Il possède de nombreuses fonctionnalités et une pléthore d’opérateurs de transformation. Mais il a une courbe d’apprentissage abrupte!
  • Couler se situe quelque part entre LiveData et RxJava. C’est très puissant mais aussi très facile à utiliser! L’API Flow ressemble même beaucoup à RxJava!

Kotlin Flow et RxJava sont tous deux des implémentations du Spécification du flux réactif.

Cependant, Flow utilise des coroutines en interne et ne possède pas certaines des fonctionnalités de RxJava. En partie parce qu’il n’a pas besoin de certaines fonctionnalités, et en partie parce que certaines fonctionnalités sont encore en cours de développement!

Remarque: Dans la version Kotlin 1.3.0, les API Flow principales et les opérateurs de base sont stables. Les API qui ne sont pas stables ont des annotations @ExperimentalCoroutinesApi ou @FlowPreview.

Maintenant que vous avez assez de théorie, il est temps de créer votre premier Flow!

Constructeurs de flux

Aller vers main.kt dans le projet de démarrage.

Vous allez commencer par créer un flux simple. Pour créer un flux, vous devez utiliser un générateur de flux. Vous allez commencer par utiliser le générateur le plus simple – flow { ... }. Ajoutez le code suivant ci-dessus main():

val namesFlow = flow {
  val names = listOf("Jody", "Steve", "Lance", "Joe")
  for (name in names) {
    delay(100)
    emit(name)
  }
}

Assurez-vous d’ajouter les importations de la kotlinx.coroutines paquet.

Ici, vous utilisez flow() pour créer un flux à partir d’un bloc lambda suspendu. À l’intérieur du bloc, vous déclarez names et l’assigner à une liste de noms.

Ensuite, vous avez utilisé un for boucle pour parcourir la liste des noms et émettre chaque nom après un petit délai. Le Flow utilise emit() envoyer des valeurs aux consommateurs.

Il existe d’autres générateurs de flux que vous pouvez utiliser pour une déclaration de flux simple. Par exemple, vous pouvez utiliser flowOf() pour créer un flux à partir d’un ensemble fixe de valeurs:

val namesFlow = flowOf("Jody", "Steve", "Lance", "Joe")

Ou vous pouvez convertir diverses collections et séquences en un flux:

val namesFlow = listOf("Jody", "Steve", "Lance", "Joe").asFlow()

Opérateurs de flux

De plus, vous pouvez utiliser des opérateurs pour transformer des flux, comme vous le feriez avec des collections ou des séquences. Il existe deux types d’opérateurs disponibles dans le Flow – intermédiaire et Terminal.

Opérateurs intermédiaires

Revenir à main.kt et ajoutez le code suivant à main():

fun main() = runBlocking {
  namesFlow
      .map { name -> name.length }
      .filter { length -> length < 5 }
    
  println()
}

Ici, vous avez utilisé le flux de noms précédent et vous y avez appliqué deux opérateurs intermédiaires:

  • map transforme chaque valeur en une autre valeur. Ici, vous avez transformé les valeurs de nom en leur longueur.
  • filter sélectionne des valeurs qui remplissent une condition. Ici, vous avez choisi des valeurs inférieures à cinq.

La chose importante à noter ici est le bloc de code à l'intérieur de chacun de ces opérateurs. Ces blocs de code peuvent appeler des fonctions de suspension! Vous pouvez donc également retarder dans ces blocs. Ou vous pouvez appeler d'autres fonctions de suspension!

Ce qui se passe avec le Flow est visible sur l'image ci-dessous:

Diagramme du filtre de débit

Le flux émettra des valeurs une par une. Vous appliquez ensuite chaque opérateur à chacune des valeurs, une fois de plus, une à la fois. Et enfin, lorsque vous commencez à consommer des valeurs, vous les recevez dans le même ordre.

Construisez et exécutez en cliquant sur le bouton de lecture à côté de la fonction principale.

Vous remarquerez que rien ne se passe! En effet, les opérateurs intermédiaires sont froids. Lorsque vous appelez une opération intermédiaire sur un flux, l'opération n'est pas exécutée immédiatement. Au lieu de cela, vous retournez le Flow transformé, qui est encore froid. Les opérations s'exécutent uniquement lorsque vous appelez un opérateur de terminal sur le flux final.

Opérateurs de terminaux

Parce que les flux sont froids, ils ne produisent pas de valeurs tant qu'un opérateur de terminal n'est pas appelé. Les opérateurs de terminaux suspendent les fonctions qui démarrent collection du flux. Lorsque vous appelez un opérateur de terminal, vous appelez tous les opérateurs intermédiaires avec lui:

Un exemple de ce qui se passerait avec les valeurs d'origine, si vous écoutiez un Flow:

Diagramme de fonctionnement du terminal

Lorsque vous commencez à collecter des valeurs, vous en obtenez une à la fois et vous ne bloquez pas en attendant de nouvelles valeurs!

Revenons maintenant à la main.kt déposer et ajouter le collect() opérateur de terminal:

fun main() = runBlocking {
  namesFlow
      .map { name -> name.length }
      .filter { length -> length < 5 }
      .collect { println(it) }

  println()
}

Puisque collect() est une fonction de suspension, elle ne peut être appelée qu'à partir d'une coroutine ou d'une autre fonction de suspension. C'est pourquoi vous enveloppez le code avec runBlocking().

Créez et exécutez le code en cliquant sur le bouton de lecture. Vous obtiendrez la sortie suivante:

4
3

collect() est l'opérateur de terminal le plus élémentaire. Il collecte les valeurs d'un flux et exécute une action avec chaque élément. Dans ce cas, vous imprimez un élément sur la console. D'autres opérateurs de terminaux sont disponibles; vous en apprendrez plus tard dans ce didacticiel.

Vous pouvez consulter le code final dans le Playground-Final projet.

Maintenant que vous connaissez les bases de Flow, passons à notre application météo, où vous verrez Kotlin Flow faire du vrai travail! :]

Flow sur Android

Vous allez maintenant appliquer tout ce que vous avez appris jusqu'à présent dans une application Android! le Sunzoid app est une application météo simple qui affiche des prévisions pour une ville spécifique. Il récupère les données météorologiques du réseau et les stocke dans une base de données pour prendre en charge le mode hors ligne.

Ouvrez le Sunzoid-Starter projet dans Android Studio. Créez et exécutez l'application, et vous verrez un écran vide:

Écran initial Sunzoid

Il y a une icône de recherche dans le coin supérieur gauche. Vous pouvez le toucher pour entrer dans un emplacement spécifique. Si vous faites cela maintenant, rien ne se passera. Mais attendez - vous allez implémenter cette fonctionnalité ensuite!

Il y a une bonne quantité de code dans le projet de démarrage:

Structure du projet Sunzoid

Vous vous concentrerez sur l'utilisation de Kotlin Flow dans l'application. Mais si vous le souhaitez, vous pouvez explorer le code et vous familiariser avec l'application!

Le projet de démarrage suit le guide recommandé par Google pour l'architecture des applications. Vous pouvez trouver le guide sur le site des développeurs Android Documentation:

Architecture recommandée par Google

Copyright 2020 Google LLC

En haut du schéma, il y a une couche d'interface utilisateur qui communique avec le composant d'architecture ViewModel. ViewModel communique avec un référentiel de données. Le référentiel récupère les données du réseau à l'aide de Retrofit. Il stocke les données dans un local Chambre base de données. Enfin, il expose les données de la base de données au ViewModel.

Room et Retrofit, dans leurs dernières versions, prennent en charge Kotlin Coroutines. Le projet de démarrage est configuré pour les utiliser avec des coroutines.

Vous utiliserez Kotlin Flow pour transmettre les données de la base de données au ViewModel. Le ViewModel collectera alors les données. Vous utiliserez également des coroutines et Flow pour implémenter la fonctionnalité de recherche.

Récupérer des données

Vous commencerez par implémenter la logique de récupération des données de prévision. Ouvert HomeActivity.kt. Dans onCreate(), ajoutez un appel à fetchLocationDetails(), juste en dessous initUi():

homeViewModel.fetchLocationDetails(851128)

fetchLocationDetails() accepte un cityId comme argument. Pour l'instant, vous passerez l'ID codé en dur. Vous ajouterez plus tard une fonction de recherche qui vous permettra de rechercher un emplacement spécifique.

Générez et exécutez le projet. Vous ne verrez toujours rien à l'écran:

Écran vide Sunzoid

Mais cette fois, l'application a récupéré les données de prévision et les a enregistrées dans la base de données Room! :]

Chambre et flux

Dans la salle 2.1, la bibliothèque a ajouté le support coroutine pour les opérations ponctuelles. La salle 2.2 a ajouté la prise en charge de Flow pour les requêtes observables. Cela vous permet d'être averti chaque fois que vous ajoutez ou supprimez des entrées dans la base de données.

Dans l'implémentation actuelle, seul l'utilisateur peut déclencher l'extraction de données. Mais vous pouvez facilement implémenter une logique qui planifie et met à jour la base de données toutes les trois heures, par exemple. En faisant cela, vous vous assurez que votre interface utilisateur est à jour avec les dernières données. Vous utiliserez Kotlin Flow pour être averti de chaque modification du tableau.

Connexion à la base de données
Ouvert ForecastDao.kt et ajoutez un appel à getForecasts(). Cette méthode renvoie Flow>:

@Query("SELECT * FROM forecasts_table")
fun getForecasts(): Flow>

getForecasts() renvoie les données de prévision pour une ville spécifique de forecasts_table. Chaque fois que les données de ce tableau changent, la requête s'exécute à nouveau et Flow émet de nouvelles données.

Ensuite, ouvrez WeatherRepository.kt et ajoutez une fonction appelée getForecasts:

fun getForecasts(): Flow>

Ensuite, ajoutez l'implémentation à WeatherRepositoryImpl.kt:

override fun getForecasts() =
    forecastDao
      .getForecasts()
      .map { dbMapper.mapDbForecastsToDomain(it) }

Cette méthode utilise le forecastDao pour obtenir des données de la base de données. La base de données renvoie le modèle de base de données. C'est une bonne pratique pour chaque couche de l'application de travailler avec son propre modèle. En utilisant map(), vous convertissez le modèle de base de données en Forecast modèle de domaine.

Ouvert HomeViewModel.kt et ajouter forecasts, ainsi:

//1
val forecasts: LiveData> = weatherRepository
    //2
    .getForecasts()
    //3
    .map {
      homeViewStateMapper.mapForecastsToViewState(it)
    }
    //4
    .asLiveData()

Il se passe quelques choses ici:

  1. Tout d'abord, vous déclarez forecasts du LiveData> type. le Activity observera les changements forecasts. forecasts aurait pu être du Flow> type, mais LiveData est préférable lors de la mise en œuvre de la communication entre View et ViewModel. Ceci est dû au fait LiveData a la gestion du cycle de vie interne!
  2. Ensuite, référence weatherRepository pour obtenir le flux de données de prévision.
  3. Appelez ensuite map(). map() convertit les modèles de domaine en ForecastViewState modèle, qui est prêt pour le rendu.
  4. Enfin, convertissez un Flow à LiveData, en utilisant asLiveData(). Cette fonction provient de la bibliothèque AndroidX KTX pour Lifecycle et LiveData.

Préservation du contexte et contre-pression

La collecte d'un flux se produit toujours dans le contexte de la coroutine parent. Cette propriété de Flow est appelée préservation du contexte. Mais vous pouvez toujours changer le contexte lors de l'émission d'éléments. Pour changer le contexte des émissions, vous pouvez utiliser flowOn().

Vous pourriez avoir un scénario dans lequel le flux produit des événements plus rapidement que le collecteur ne peut les consommer. Dans les flux réactifs, cela s'appelle contre-pression. Kotlin Flow prend en charge la contre-pression hors de la boîte car il est basé sur des coroutines. Lorsque le consommateur est dans un état suspendu ou est occupé à effectuer un travail, le producteur le reconnaîtra. Il ne produira aucun élément pendant cette période.

Observer les valeurs

Enfin, ouvrez HomeActivity.kt et observer forecasts de initObservers():

homeViewModel.forecasts.observe(this, Observer {
  forecastAdapter.setData(it)
})

Chaque fois que les prévisions changent dans la base de données, vous recevrez de nouvelles données dans l'Observateur et les afficherez sur l'interface utilisateur.

Créez et exécutez l'application. Maintenant, l'écran d'accueil affiche les données de prévision! :]

Sunzoid avec données prévisionnelles

Toutes nos félicitations! Vous avez implémenté la communication entre plusieurs couches de votre application à l'aide de Flow et LiveData!

Annulation

Dans HomeViewModel.kt, vous observez la forecasts. Vous avez remarqué que vous n'arrêtez jamais d'observer. Combien de temps cela est-il observé, alors?

Dans ce cas, la collection Flow démarre lorsque LiveData devient actif. Puis si LiveData devient inactif avant la fin du flux, la collecte de flux est annulée.

L'annulation intervient après un délai programmé sauf si LiveData redevient actif avant ce délai. Le retard par défaut déclenchant l'annulation est de 5000 millisecondes. Vous pouvez personnaliser la valeur du délai d'expiration si nécessaire. Le délai existe pour gérer les cas tels que les modifications de configuration Android.

Si LiveData redevient actif après l'annulation, la collection Flow redémarre.

Des exceptions

Les flux peuvent se terminer avec une exception si un émetteur ou un code à l'intérieur des opérateurs lève une exception. catch() les blocs gèrent les exceptions dans Flows. Tu peux le faire impérativement ou déclarativement. UNE try-catch bloc du côté du collecteur est un exemple de impératif approche.

C'est impératif car ils interceptent toutes les exceptions qui se produisent dans l'émetteur ou dans l'un des opérateurs.

Vous pouvez utiliser catch() gérer les erreurs déclarativement au lieu. Déclaratif signifie ici que vous déclarer la fonction pour gérer les erreurs. Et vous le déclarez dans le Flow lui-même, et non try-catch bloquer.

Ouvert HomeViewModel.kt et accédez à forecasts. Ajouter catch() juste avant map(). Pour simuler des erreurs dans le flux, lève une exception de map():

val forecasts: LiveData> = weatherRepository
    .getForecasts()
    .catch {
      // Log Error
    }
    .map {
      homeViewStateMapper.mapForecastsToViewState(it)
      throw Exception()
    }
    .asLiveData()

Créez et exécutez l'application. Vous remarquerez que l'application se bloque! catch() ne capture que les exceptions en amont. Autrement dit, il intercepte les exceptions de tous les opérateurs au-dessus de la capture. catch() ne détecte aucune exception qui se produit après l'opérateur.

Maintenant bouge catch() au dessous de map():

val forecasts: LiveData> = weatherRepository
    .getForecasts()
    .map {
      homeViewStateMapper.mapForecastsToViewState(it)
      throw Exception()
    }
    .catch {
      // Log Error
    }
    .asLiveData()

Créez et exécutez à nouveau l'application. Maintenant, vous verrez un écran vide:

Sunzoid Thrown Exception

Ceci est un exemple de transparence d'exception, où vous pouvez séparer la gestion des exceptions qui se produisent dans le flux de la collection de valeurs. Vous êtes également transparent sur les exceptions, car vous ne cachez aucune erreur, vous les gérez explicitement dans un opérateur!

Avant de continuer, supprimez la ligne qui lève une exception de map().

Recherche de lieux

Jusqu'à présent, votre application a affiché une prévision pour un emplacement codé en dur. Vous allez maintenant implémenter la fonctionnalité de recherche! Cela permettra aux utilisateurs de rechercher un emplacement spécifique à l'aide de coroutines et de Flow. Au fur et à mesure que l'utilisateur tape dans la zone de recherche, l'application effectue une recherche pour chaque lettre saisie et met à jour le résultat de la recherche.

Dans HomeActivity.kt, un écouteur est déjà associé à la vue de recherche. Lorsque l'utilisateur modifie le texte de la requête, l'application envoie la nouvelle valeur à queryChannel dans HomeViewModel.kt. HomeViewModel.kt utilise un BroadcastChannel comme un pont pour passer le texte de la vue au ViewModel. offer() passe le texte et ajoute de manière synchrone l'élément spécifié au canal.

Interrogation des emplacements

Ajoutez maintenant la logique de consommation des événements du canal en tant que Flow:

private val _locations = queryChannel
    //1
    .asFlow()
    //2
    .debounce(SEARCH_DELAY_MILLIS)
    //3
    .mapLatest {
      if (it.length >= MIN_QUERY_LENGTH) {
        getLocations(it)
      } else {
        emptyList()
      }
    }
    //4
    .catch {
      // Log Error
    }

Voici ce qui se passe dans ce bloc de code:

  1. Tout d'abord, l'appel à asFlow convertit le Channel dans une Flow.
  2. Prochain, debounce() attend que les valeurs cessent d'arriver pendant une période donnée. Ceci est utilisé pour éviter de traiter chaque lettre unique tapée par les utilisateurs. Les utilisateurs tapent généralement plusieurs lettres d'affilée. Vous n'avez pas besoin de faire une demande de réseau jusqu'à ce que l'utilisateur arrête de taper. Cela garantit que vous n'effectuez l'appel d'API qu'après 500 millisecondes sans aucune saisie!
  3. Ensuite, mapLatest() effectue l'appel d'API et renvoie les résultats de l'emplacement. Si le flux d'origine émet une nouvelle valeur alors que l'appel d'API précédent est toujours en cours, mapLatest() garantit que le calcul du bloc précédent est annulé. mapLatest() effectue l'appel API uniquement si la requête de recherche contient au moins deux caractères.
  4. Finalement, catch() gère les erreurs.

Ajouter locations à HomeViewModel.kt. Cela vous permet d'observer de l'activité:

val locations = _locations.asLiveData()

Ici, vous utilisez asLiveData() pour collecter des valeurs à partir du flux d'origine et ajouter les transformer en un LiveData exemple.

Ouvert HomeActivity.kt et supprimez l'appel de onCreate() à homeViewModel.fetchLocationDetails(). Au lieu d'observer locations de initObservers():

private fun initObservers() {
  homeViewModel.locations.observe(this, Observer {
    locationAdapter.setData(it)
  })

  ...
}

Encore une fois, créez et exécutez l'application. Saisissez maintenant une requête de recherche. Vous verrez les options générées à partir de votre requête:

Sunzoid avec requête de localisation

Allez-y et appuyez sur l'une des options. L'écran d'accueil affichera les données de prévision pour un nouvel emplacement sélectionné.

Pourquoi Kotlin Flow

Il existe déjà d'autres implémentations de la spécification de flux réactif, telles que RxJava. Alors pourquoi utiliser Kotlin Flow? L'une des raisons est que vous ne pouvez pas utiliser des bibliothèques spécifiques à la JVM comme RxJava dans les projets multi-plateformes Kotlin. Mais Flow fait partie du langage Kotlin, il est donc idéal à utiliser dans les projets multi-plateformes Kotlin.

De plus, Kotlin Flow a moins d'opérateurs, mais ils sont beaucoup plus simples. Un seul opérateur peut gérer à la fois la logique synchrone et asynchrone car le bloc de code que les opérateurs acceptent peut être suspendu!

Et Kotlin Flow est interopérable avec d'autres flux réactifs et coroutines. Puisqu'il est construit au-dessus des coroutines, il offre tous les avantages de la concurrence structurée et de l'annulation. En combinaison avec des fonctions de suspension, il produit une API simple, lisible et compréhensible. Il prend également en charge la contre-pression hors de la boîte.

Où aller en partant d'ici?

Vous pouvez télécharger le projet final en utilisant le Télécharger les documents en haut ou en bas de ce didacticiel.

Vous avez créé une application qui utilise Kotlin Flow pour la communication entre les couches d'application. Au cours de ce processus, vous avez appris ce qui est si spécial à propos de Kotlin Flow et en quoi il diffère des solutions existantes. Vous avez également utilisé des générateurs de flux pour créer un flux. Vous vous êtes familiarisé avec les opérateurs de base pour transformer un flux de données. Et enfin, vous avez appliqué des opérateurs de terminaux pour consommer les valeurs!

Pour acquérir des connaissances plus approfondies sur Kotlin Flow, consultez notre cours vidéo Kotlin Flow: Mise en route. Vous en apprendrez beaucoup plus sur les constructeurs, les opérateurs et l'annulation de Flow. Vous apprendrez également à travailler avec des flux, à modifier son contexte, à ajouter des tampons, à combiner plusieurs flux et à gérer les exceptions!

Nous espérons que vous avez apprécié ce tutoriel! Si vous avez des questions ou des commentaires à partager, veuillez vous joindre au forum de discussion ci-dessous. :]

Close Menu