Il s'agit d'un référentiel contenant des exemples utiles du monde réel d'utilisation de RxJava avec Android. Il sera généralement dans un état constant de « Work in Progress » (WIP).
J'ai également donné des conférences sur Learning Rx en utilisant de nombreux exemples répertoriés dans ce référentiel.
using
)Une exigence courante consiste à décharger les opérations longues et gourmandes en E/S vers un thread d'arrière-plan (thread non-UI) et à renvoyer les résultats au thread UI/principal, une fois terminé. Il s'agit d'une démonstration de la manière dont les opérations de longue durée peuvent être déchargées vers un thread d'arrière-plan. Une fois l’opération terminée, on revient sur le thread principal. Le tout en utilisant RxJava ! Considérez cela comme un remplacement d'AsyncTasks.
L'opération longue est simulée par un appel Thread.sleep bloquant (puisque cela se fait dans un thread en arrière-plan, notre interface utilisateur n'est jamais interrompue).
Pour vraiment voir cet exemple briller. Appuyez plusieurs fois sur le bouton et voyez comment le clic sur le bouton (qui est une opération de l'interface utilisateur) n'est jamais bloqué car l'opération longue ne s'exécute qu'en arrière-plan.
Ceci est une démonstration de la façon dont les événements peuvent être accumulés à l'aide de l'opération "buffer".
Un bouton est fourni et nous accumulons le nombre de clics sur ce bouton, sur une période de temps, puis crachons les résultats finaux.
Si vous appuyez une fois sur le bouton, vous recevrez un message indiquant que le bouton a été appuyé une fois. Si vous appuyez dessus 5 fois de manière continue en 2 secondes, vous obtenez un seul journal indiquant que vous avez appuyé sur ce bouton 5 fois (contre 5 journaux individuels indiquant "Le bouton a appuyé une fois").
Note:
Si vous recherchez une solution plus infaillible qui accumule des taps "continus" par rapport au seul nombre de taps sur une période de temps, regardez la démo EventBus où une combinaison d'opérateurs publish
et buffer
est utilisée. Pour une explication plus détaillée, vous pouvez également consulter cet article de blog.
Il s'agit d'une démonstration de la manière dont les événements peuvent être avalés de manière à ce que seul le dernier soit respecté. Les boîtes de résultats de recherche instantanée en sont un exemple typique. Lorsque vous tapez le mot "Bruce Lee", vous ne souhaitez pas exécuter de recherches sur B, Br, Bru, Bruce, Bruce, Bruce L ... etc. Mais attendez plutôt intelligemment quelques instants, assurez-vous que l'utilisateur a fini de taper le mot en entier, puis lance un seul appel pour "Bruce Lee".
Lorsque vous tapez dans la zone de saisie, il n'enverra pas de messages de journal à chaque changement de caractère d'entrée, mais sélectionnera uniquement le dernier événement émis (c'est-à-dire l'entrée) et l'enregistrera.
Il s'agit de la méthode anti-rebond/throttleWithTimeout dans RxJava.
Retrofit from Square est une bibliothèque étonnante qui facilite la mise en réseau (même si vous n'avez pas encore fait le saut vers RxJava, vous devriez vraiment y jeter un œil). Cela fonctionne encore mieux avec RxJava et ce sont des exemples frappant l'API GitHub, tirés directement de la conférence du développeur demi-dieu Android Jake Wharton à Netflix. Vous pouvez regarder la conférence sur ce lien. D'ailleurs, ma motivation pour utiliser RxJava était d'assister à cette conférence à Netflix.
(Remarque : vous êtes très susceptible d'atteindre le quota de l'API GitHub assez rapidement, alors envoyez un jeton OAuth en tant que paramètre si vous souhaitez continuer à exécuter ces exemples souvent).
Les vues de mise à jour automatique sont une chose plutôt intéressante. Si vous avez déjà utilisé Angular JS, ils ont un concept plutôt astucieux appelé "liaison de données bidirectionnelle". Ainsi, lorsqu'un élément HTML est lié à un objet modèle/entité, il "écoute" constamment les modifications apportées à cette entité et met automatiquement à jour son état en fonction du modèle. En utilisant la technique de cet exemple, vous pouvez potentiellement utiliser un modèle tel que le modèle de modèle de vue de présentation avec une grande facilité.
Si l’exemple ici est assez rudimentaire, la technique utilisée pour réaliser la double reliure à l’aide d’un Publish Subject
est bien plus intéressante.
Ceci est un exemple d'interrogation à l'aide des planificateurs RxJava. Ceci est utile dans les cas où vous souhaitez constamment interroger un serveur et éventuellement obtenir de nouvelles données. L'appel réseau est "simulé", il impose donc un délai avant de renvoyer une chaîne résultante.
Il existe deux variantes pour cela :
Le deuxième exemple est essentiellement une variante de Exponential Backoff.
Au lieu d'utiliser un RetryWithDelay, nous utilisons ici un RepeatWithDelay. Pour comprendre la différence entre Retry(When) et Repeat(When), je suggérerais le fantastique article de Dan sur le sujet.
Une approche alternative à l’interrogation différée sans l’utilisation de repeatWhen
consisterait à utiliser des observables de retard imbriqués enchaînés. Voir startExecutingWithExponentialBackoffDelay dans l'exemple ExponentialBackOffFragment.
L'intervalle exponentiel est une stratégie dans laquelle, en fonction des commentaires d'une certaine sortie, nous modifions la vitesse d'un processus (généralement en réduisant le nombre de tentatives ou en augmentant le temps d'attente avant de réessayer ou de réexécuter un certain processus).
Le concept prend plus de sens avec des exemples. RxJava rend (relativement) simple la mise en œuvre d’une telle stratégie. Mes remerciements à Mike pour avoir suggéré l'idée.
Supposons que vous ayez une panne de réseau. Une stratégie judicieuse serait de NE PAS réessayer votre appel réseau toutes les secondes. Il serait plutôt intelligent (et non... élégant !) de réessayer avec des délais croissants. Donc vous essayez à la seconde 1 d'exécuter l'appel réseau, pas de dés ? essayez après 10 secondes... négatif ? essayez au bout de 20 secondes, pas de cookie ? essayez après 1 minute. Si cette chose échoue toujours, vous devez abandonner le réseau, yo !
Nous simulons ce comportement en utilisant RxJava avec l'opérateur retryWhen
.
Extrait de code RetryWithDelay
avec l'aimable autorisation :
Regardez également l'exemple d'interrogation où nous utilisons un mécanisme d'intervalle exponentiel très similaire.
Une autre variante de la stratégie d'attente exponentielle consiste à exécuter une opération un nombre de fois donné mais avec des intervalles retardés. Donc, vous exécutez une certaine opération dans 1 seconde, puis vous l'exécutez à nouveau dans 10 secondes, puis vous exécutez l'opération dans 20 secondes. Après un total de 3 fois, vous arrêtez l'exécution.
La simulation de ce comportement est en réalité bien plus simple que le mécanisme de nouvelle tentative précédent. Vous pouvez utiliser une variante de l'opérateur delay
pour y parvenir.
.combineLatest
)Merci à Dan Lew de m'avoir donné cette idée dans le podcast fragmenté - épisode n°4 (vers 4h30).
.combineLatest
vous permet de surveiller l'état de plusieurs observables à la fois de manière compacte et en un seul emplacement. L'exemple illustré montre comment utiliser .combineLatest
pour valider un formulaire de base. Il y a 3 entrées principales pour que ce formulaire soit considéré comme « valide » (un e-mail, un mot de passe et un numéro). Le formulaire deviendra valide (le texte ci-dessous devient bleu :P) une fois que toutes les saisies seront valides. Si ce n’est pas le cas, une erreur s’affiche pour les entrées non valides.
Nous avons 3 observables indépendants qui suivent les modifications de texte/saisie pour chacun des champs du formulaire ( WidgetObservable
de RxAndroid est pratique pour surveiller les modifications de texte). Après qu'un changement d'événement soit remarqué sur les 3 entrées, le résultat est "combiné" et la validité du formulaire est évaluée.
Notez que la fonction Func3
qui vérifie la validité n'intervient qu'après que TOUTES les 3 entrées ont reçu un événement de changement de texte.
La valeur de cette technique devient plus évidente lorsque vous disposez d'un plus grand nombre de champs de saisie dans un formulaire. Le gérer autrement avec un tas de booléens rend le code encombré et difficile à suivre. Mais en utilisant .combineLatest
toute cette logique est concentrée dans un joli bloc de code compact (j'utilise toujours des booléens mais c'était pour rendre l'exemple plus lisible).
Nous avons deux observables sources : un cache disque (rapide) et un appel réseau (frais). Généralement, le disque Observable est beaucoup plus rapide que le réseau Observable. Mais afin de démontrer le fonctionnement, nous avons également utilisé un faux cache disque « plus lent », juste pour voir comment les opérateurs se comportent.
Ceci est démontré à l’aide de 4 techniques :
.concat
.concatEager
.merge
.publish
+ fusion + takeUntilLa 4ème technique est probablement celle que vous souhaitez utiliser à terme mais il est intéressant de parcourir la progression des techniques, pour comprendre pourquoi.
concat
est génial. Il récupère les informations du premier Observable (le cache disque dans notre cas), puis du réseau Observable suivant. Étant donné que le cache disque est probablement plus rapide, tout semble bien et le cache disque est chargé rapidement, et une fois l'appel réseau terminé, nous échangeons les résultats « frais ».
Le problème avec concat
est que l'observable suivant ne démarre même pas tant que le premier observable n'est pas terminé. Cela peut poser problème. Nous voulons que tous les observables démarrent simultanément mais produisent les résultats de la manière que nous attendons. Heureusement, RxJava a introduit concatEager
qui fait exactement cela. Il démarre les deux observables mais met en mémoire tampon le résultat du dernier jusqu'à ce que le premier observable se termine. C'est une option tout à fait viable.
Parfois cependant, vous souhaitez simplement commencer à afficher les résultats immédiatement. En supposant que le premier observable (pour une raison étrange) met très longtemps à parcourir tous ses éléments, même si les premiers éléments du deuxième observable sont descendus sur le fil, il sera forcé d'être mis en file d'attente. Vous ne voulez pas nécessairement "attendre" un observable. Dans ces situations, nous pourrions utiliser l’opérateur merge
. Il entrelace les éléments au fur et à mesure de leur émission. Cela fonctionne très bien et commence à cracher les résultats dès qu'ils sont affichés.
Semblable à l'opérateur concat
, si votre premier observable est toujours plus rapide que le deuxième observable, vous ne rencontrerez aucun problème. Cependant, le problème avec merge
est le suivant : si, pour une raison étrange, un élément est émis par le cache ou est observable plus lentement après l'observable le plus récent/le plus récent, il écrasera le contenu le plus récent. Cliquez sur le bouton « FUSIONNER (DISQUE PLUS LENT) » dans l'exemple pour voir ce problème en action. Les contributions de @JakeWharton et @swankjesse passent à 0 ! Dans le monde réel, cela pourrait être mauvais, car cela signifierait que les nouvelles données seraient remplacées par des données de disque obsolètes.
Pour résoudre ce problème, vous pouvez utiliser la fusion en combinaison avec l'opérateur publish
super astucieux qui intègre un "sélecteur". J'ai écrit sur cette utilisation dans un article de blog mais je dois remercier Jedi JW pour avoir rappelé cette technique. Nous publish
l'observable du réseau et lui fournissons un sélecteur qui commence à émettre à partir du cache disque, jusqu'au moment où l'observable du réseau commence à émettre. Une fois que l’observable réseau commence à émettre, il ignore tous les résultats de l’observable disque. C'est parfait et résout tous les problèmes que nous pourrions avoir.
Auparavant, j'utilisais l'opérateur merge
mais je surmontais le problème de l'écrasement des résultats en surveillant le "resultAge". Consultez l’ancien exemple PseudoCacheMergeFragment
si vous êtes curieux de voir cette ancienne implémentation.
Il s'agit d'un exemple très simple et direct qui vous montre comment utiliser les opérateurs timer
, interval
et delay
de RxJava pour gérer un certain nombre de cas dans lesquels vous souhaitez exécuter une tâche à des intervalles spécifiques. En gros, dites NON aux Android TimerTask
.
Cas démontrés ici :
Il existe des articles de blog qui accompagnent cette démo et qui expliquent bien mieux les détails de cette démo :
Une question fréquemment posée lors de l'utilisation de RxJava sous Android est la suivante : "comment reprendre le travail d'un observable si un changement de configuration se produit (rotation d'activité, changement de langue, etc.) ?".
Cet exemple vous montre une stratégie, à savoir. en utilisant des fragments conservés. J'ai commencé à utiliser des fragments conservés comme "fragments de travail" après avoir lu ce fantastique article d'Alex Lockwood il y a quelque temps.
Appuyez sur le bouton Démarrer et faites pivoter l'écran à votre guise ; vous verrez l'observable continuer là où il s'est arrêté.
Il y a certaines bizarreries concernant la « chaleur » de la source observable utilisée dans cet exemple. Consultez mon article de blog où j'explique les détails.
Depuis, j'ai réécrit cet exemple en utilisant une approche alternative. Bien que l'approche ConnectedObservable
ait fonctionné, elle entre dans le domaine du "multicasting" qui peut être délicat (thread-safety, .refcount etc.). En revanche, les sujets sont beaucoup plus simples. Vous pouvez le voir réécrit en utilisant un Subject
ici.
J'ai écrit un autre article de blog sur la façon de réfléchir aux sujets dans lequel j'entre dans certains détails.
Volley est une autre bibliothèque réseau présentée par Google lors d'IO '13. Un gentil citoyen de github a contribué à cet exemple afin que nous sachions comment intégrer Volley avec RxJava.
J'exploite ici la simple utilisation d'un sujet. Honnêtement, si vos éléments ne descendent pas déjà via un Observable
(comme via une rénovation ou une demande réseau), il n'y a aucune bonne raison d'utiliser Rx et de compliquer les choses.
Cet exemple envoie essentiellement le numéro de page à un sujet, et le sujet gère l'ajout des éléments. Notez l'utilisation de concatMap
et le retour d'un Observable<List>
de _itemsFromNetworkCall
.
Pour commencer, j'ai également inclus un exemple PaginationAutoFragment
, qui "pagine automatiquement" sans que nous ayons besoin d'appuyer sur un bouton. Cela devrait être simple à suivre si vous comprenez comment fonctionne l'exemple précédent.
Voici quelques autres implémentations sophistiquées (même si j'ai aimé les lire, je n'ai pas fini par les utiliser pour mon application réelle car personnellement, je ne pense pas que ce soit nécessaire) :
Le diagramme ascii ci-dessous exprime l’intention de notre prochain exemple avec panache. f1,f2,f3,f4,f5 sont essentiellement des appels réseau qui, une fois effectués, renvoient un résultat nécessaire à un calcul futur.
(flatmap)
f1 ___________________ f3 _______
(flatmap) | (zip)
f2 ___________________ f4 _______| ___________ final output
|
____________ f5 _______|
Le code de cet exemple a déjà été écrit par un certain Mr.skehlet dans les interwebs. Rendez-vous à l’essentiel pour le code. Il est écrit en Java pur (6), donc c'est assez compréhensible si vous avez compris les exemples précédents. Je le ferai à nouveau ici lorsque le temps le permettra ou si je serai à court d'autres exemples convaincants.
Il s'agit d'un exemple simple illustrant l'utilisation de l'opérateur .timeout
. Le bouton 1 terminera la tâche avant la contrainte de délai d'attente, tandis que le bouton 2 forcera une erreur de délai d'attente.
Remarquez comment nous pouvons fournir un observable personnalisé qui indique comment réagir en cas d'exception de délai d'attente.
using
) L'opérateur using
est relativement moins connu et notoirement difficile à utiliser pour Google. C'est une belle API qui permet de configurer une ressource (coûteuse), de l'utiliser puis de s'en débarrasser de manière propre.
L’avantage de cet opérateur est qu’il fournit un mécanisme permettant d’utiliser des ressources potentiellement coûteuses de manière ciblée. en utilisant -> configurer, utiliser et éliminer. Pensez aux connexions DB (comme les instances Realm), aux connexions socket, aux threads verrouillés, etc.
La multidiffusion dans Rx est comme un art sombre. Peu de gens savent comment y parvenir sans souci. Cet exemple présente deux abonnés (sous forme de boutons) et vous permet d'ajouter/supprimer des abonnés à différents moments et de voir comment les différents opérateurs se comportent dans ces circonstances.
L'observable source est un observable de minuterie ( interval
) et la raison pour laquelle cela a été choisi était de choisir intentionnellement un observable sans fin, afin que vous puissiez tester/confirmer si votre expérience de multidiffusion fuira.
J'ai également donné une conférence détaillée sur le multicasting chez 360|Andev. Si vous en avez l'envie et le temps, je vous suggère fortement de regarder d'abord ce discours (en particulier le segment de permutation des opérateurs de multidiffusion), puis de jouer avec l'exemple ici.
Tous les exemples ici ont été migrés pour utiliser RxJava 2.X.
Nous utilisons la bibliothèque Interop de David Karnok dans certains cas car certaines bibliothèques comme RxBindings, RxRelays, RxJava-Math etc. n'ont pas encore été portées vers 2.x.
J'essaie de m'assurer que les exemples ne sont pas trop artificiels mais reflètent un cas d'utilisation réel. Si vous avez des exemples utiles similaires illustrant l'utilisation de RxJava, n'hésitez pas à envoyer une pull request.
Je réfléchis également à RxJava, donc si vous pensez qu'il existe une meilleure façon de faire l'un des exemples mentionnés ci-dessus, ouvrez un problème expliquant comment. Mieux encore, envoyez une pull request.
Le threading Rx est une affaire compliquée. Pour vous aider, ce projet utilise les outils YourKit pour l'analyse.
YourKit prend en charge les projets open source avec des outils innovants et intelligents pour surveiller et profiler les applications Java. YourKit est le créateur de YourKit Java Profiler.
Sous licence Apache, version 2.0 (la « Licence »). Vous pouvez obtenir une copie de la licence à
http://www.apache.org/licenses/LICENSE-2.0
Sauf disposition contraire de la loi applicable ou accord écrit, le logiciel distribué sous la licence est distribué « EN L'ÉTAT », SANS GARANTIE OU CONDITION D'AUCUNE SORTE, expresse ou implicite. Consultez la licence pour connaître la langue spécifique régissant les autorisations et les limitations en vertu de la licence.
Vous acceptez que toutes les contributions à ce référentiel, sous forme de correctifs, de demandes d'extraction, de nouveaux exemples, etc. suivent la licence mentionnée ci-dessus.