Ingénierie chez Sprout : création d'un sélecteur de mois Android

Publié: 2020-06-26

Remarque : cet article était basé sur la version 1.2.0-beta01 de Material Components au 1er juin 2020 .

Au cours de mes trois ans et demi de travail au sein d'une petite équipe Android chez Sprout Social, l'une des principales choses qui me motive à venir travailler tous les jours est la liberté et la confiance de notre entreprise pour résoudre un problème de la manière que nous jugeons la meilleure.

La liberté de rechercher et d'explorer de nombreuses solutions différentes à un problème que nous jugeons nécessaire, tout en tenant compte d'un délai de livraison des mises à jour de produits, nous permet de trouver la meilleure solution pour nos clients et nos logiciels.

L'un de ces défis consistait à créer un composant d'interface utilisateur pour notre nouvelle fonctionnalité de rapport mobile. Ce nouveau composant était un sélecteur de mois, qui permettait à nos utilisateurs de définir une plage de dates pour un rapport d'analyse.

Le point de départ que nous avons choisi était la bibliothèque de composants de matériaux existante. Plutôt que de partir de zéro, cette bibliothèque est activement maintenue et s'aligne sur les spécifications du matériau. Avec cette bibliothèque comme base, nous pourrions probablement réduire la quantité de logique que nous aurions à écrire nous-mêmes.

Dans cet article, j'expliquerai comment nous avons abordé ce processus, certains facteurs uniques dans la création de l'application Sprout Android, quelques "pièges" qui sont apparus (et ont été corrigés) en cours de route, et ce qu'il faut savoir si vous êtes travaillant sur un projet similaire.

Introduction

La version 1.1.0 d'Android Material Components a introduit un nouveau composant d'interface utilisateur Date Picker. L'un des ajouts bienvenus de ce nouveau MaterialDatePicker par rapport à AppCompat CalendarView est la possibilité de sélectionner une plage de dates à l'aide d'une vue Calendrier ou d'un champ de saisie de texte.

L'ancien AppCompat CalendarView n'était pas très flexible. C'était un bon composant pour le cas d'utilisation limité qu'il était censé résoudre ; c'est-à-dire la sélection d'une date unique et de dates minimales et maximales facultatives pour spécifier une limite de plage de dates autorisée.

Le nouveau MaterialDatePicker a été construit avec plus de flexibilité pour permettre l'utilisation de fonctionnalités étendues de comportement. Cela fonctionne à travers une série d'interfaces que l'on pourrait implémenter pour ajuster et modifier le comportement du sélecteur.

Cette modification de comportement est effectuée au moment de l'exécution via un ensemble de fonctions de modèle de générateur sur la classe MaterialDatePicker.Builder .

Cela signifie que nous sommes en mesure d'étendre le comportement de base de ce MaterialDatePicker via des composants d'interface composables.

Remarque : bien qu'il existe un certain nombre de composants différents utilisés par MaterialDatePicker , dans cet article, nous ne couvrirons que le composant de sélection de date.

Sélecteur de plage de dates

L'équipe Sprout Social Android était en train de créer notre section de rapports d'analyse.

Cette nouvelle section permettrait à nos utilisateurs de sélectionner un ensemble de filtres et un ensemble de plages de dates que le rapport couvrirait.

Le MaterialDatePicker est livré avec des composants prédéfinis que nous pourrions exploiter pour accomplir notre cas d'utilisation.

Pour notre cas le plus courant, permettant à un utilisateur de sélectionner une plage de dates, le MaterialDatePicker pré-construit suffirait :

Avec ce bloc de code, nous obtenons un sélecteur de date qui permet aux utilisateurs de sélectionner une plage de dates.

Sélecteur de date mensuel

L'un des rapports Sprout Social qui propose une sélection de dates plus unique est le rapport sur les tendances Twitter.

Ce rapport diffère des autres en ce qu'au lieu d'autoriser n'importe quel type de plage de dates, il applique une sélection d'un seul mois, ce qui signifie qu'un utilisateur ne peut sélectionner que mars 2020 par rapport au 3 mars au 16 mars 2020.

Notre application Web gère cela en utilisant un champ de formulaire déroulant :

Le MaterialDatePicker n'a pas de moyen d'appliquer une telle restriction avec le sélecteur de plage de dates matériel pré-construit discuté dans la section précédente. Heureusement, MaterialDatePicker a été construit avec des parties composables qui nous permettent d'étendre le comportement par défaut pour notre cas d'utilisation particulier.

Comportement de sélection de date

Le MaterialDatePicker exploite un DateSelector comme interface utilisée pour la logique de sélection du sélecteur.

Du Javadoc :

"Interface pour les utilisateurs de {@link MaterialCalendar<S>} pour contrôler la façon dont le calendrier affiche et renvoie les sélections…"

Vous remarquerez que MaterialDatePicker.Builder.dateRangePicker() renvoie une instance de générateur de RangeDateSelector , que nous avons utilisée dans l'exemple ci-dessus.

Cette classe est un sélecteur prédéfini qui implémente DateSelector .

Brainstorming sur un comportement de sélection de date mensuelle

Pour notre cas d'utilisation, nous voulions un moyen pour nos utilisateurs de sélectionner un mois entier comme plage de dates sélectionnée ; ex. mai 2020, avril 2020, etc.

Nous pensions que le RangeDateSelector pré-construit référencé ci-dessus nous avait permis d'atteindre la majeure partie du chemin. Le composant permettait à un utilisateur de sélectionner une plage de dates et d'appliquer une limite [inférieure, supérieure] .

La seule chose qui manquait était un moyen d'imposer une sélection pour sélectionner automatiquement le mois entier. Le comportement par défaut de RangeDateSelector permet à l'utilisateur de sélectionner une date de début et une date de fin.

Nous voulions un comportement tel que lorsqu'un utilisateur sélectionne un jour du mois, le sélecteur sélectionne automatiquement le mois entier comme plage de dates.

La solution que nous avons décidée était d'étendre le RangeDateSelector , puis de remplacer le comportement de sélection du jour pour sélectionner automatiquement le mois entier à la place.

Heureusement, il existe une fonction que nous pouvons remplacer à partir de l'interface DateSelector appelée : select(selection: Long) .

Cette fonction sera invoquée lorsqu'un utilisateur sélectionne un jour dans le sélecteur, le jour sélectionné étant passé en millisecondes UTC à partir de l'époque.

Implémentation d'un comportement de sélection de date mensuelle

L'implémentation s'est avérée être la partie la plus simple, puisque nous avons une fonction claire que nous pouvons remplacer pour obtenir le comportement que nous voulons.

La logique de base sera celle-ci :

  1. L'utilisateur sélectionne un jour.
  2. La fonction select() est invoquée avec le jour sélectionné en millisecondes UTC longues à partir de l'époque.
  3. Trouvez le premier et le dernier jour du mois à partir du jour donné qui nous est transmis.
  4. Appelez super.select(1st of month) et super.select(last day of month)
  5. Le comportement parent de RangeDateSelector doit fonctionner comme prévu et sélectionner le mois comme plage de dates.

Mettre tous ensemble

Maintenant que nous avons notre Custom MonthRangeDateSelector , nous pouvons configurer notre MaterialDatePicker .

Pour aller plus loin dans l'exemple, nous pouvons traiter le résultat de la sélection comme suit :

Le résultat ressemblera à ceci :

Pièges

Il y avait juste un problème majeur qui rendait difficile l'obtention de cette solution.

Les principaux composants utilisés pour construire notre MonthRangeDateSelector étaient la classe RangeDateSelector et l'interface DateSelector . La version de la bibliothèque utilisée dans cet article (1.2.0-beta01) restreignait la visibilité de ces deux fichiers, pour décourager leur extension ou leur implémentation.

En conséquence, bien que nous ayons pu compiler avec succès notre nouveau MonthRangeDateSelector , le compilateur a affiché un avertissement très effrayant pour nous décourager de le faire :

Une façon de masquer cet avertissement du compilateur consiste à ajouter un @Suppress("RestrictedApi") comme ceci :

Cette expérience illustre comment, même si la bibliothèque de composants matériels a fourni de nouveaux composants formidables à la communauté des développeurs Android, il s'agit toujours d'un travail en cours.

Une grande partie de cette bibliothèque est l'ouverture aux commentaires de la communauté Android ! Après avoir découvert cette restriction de visibilité des composants, j'ai ouvert un problème sur le projet Github, et même ouvert un PR pour y remédier tout de suite.

Cette boucle de rétroaction ouverte entre l'équipe des composants matériels et la communauté Android engendre une excellente collaboration et des résultats pour tout le monde.

Conclusion

Le nouveau MaterialDatePicker dispose d'excellentes fonctionnalités prêtes à l'emploi qui couvriront probablement la plupart des cas d'utilisation de la sélection de date.

Cependant, la meilleure partie de celui-ci par rapport à quelque chose comme AppCompat CalendarView est qu'il est construit de manière composable. Par conséquent, il peut être facilement étendu et modifié pour des cas d'utilisation spécifiques, alors qu'il serait beaucoup plus difficile d'accomplir de telles choses dans le CalendarView .

Remerciement spécial

J'aimerais mettre en évidence certaines personnes qui ont aidé à évaluer cet article par des pairs :

  • Nick Rout (Github)
  • Mike Wolfson (Github)
  • Ryan Phillips (LinkedIn)
  • Lucas Moellers (Github)
  • Mit Patel (LinkedIn)