Développer une app Jetpack Compose pour des smartphones pliables

Material3 - Adaptive UI

Aujourd'hui peut être considéré comme l'essor des smartphones pliables, car un nombre croissant de personnes les achètent. De nombreuses entreprises telles que Samsung, Huawei, etc... Fabriquent désormais des smartphones pliables. L'objectif d'un téléphone pliable est de pouvoir toucher plusieurs écrans séparément, mais aussi d'avoir une interface utilisateur adaptative.

Une interface utilisateur adaptative (également connue sous le nom d' AUI ) est une interface utilisateur (UI) qui adapte, c'est-à-dire modifie, sa mise en page et ses éléments aux besoins de l'utilisateur ou du contexte et est également modifiable par chaque utilisateur.

Nous pouvons nous demander comment développer une application avec Jetpack Compose pour les smartphones pliables. Grâce à Jetpack Compose, il est possible de développer des applications pour les appareils pliables avec les guidelines de Material3. Pour ce faire, il faut configurer votre environnement de travail, puis s'adapter aux différentes tailles d'écran et finalement suivre les informations relatives à la disposition des fenêtres.

Configuration de l'environnement de travail

Il existe différents éléments qu'un développeur devra configurer pour travailler sur des smartphones pliables avec Jetpack Compose, tels qu'un émulateur pliable et ses dépendances de projet.

Configuration de l'émulateur pliable

Voici les différentes étapes à suivre pour mettre en place un émulateur pliable :

  • Ouvrir device manager.
  • Cliquer sur create device.
  • Choisir un émulateur pliable (ex: 7.6 Fold-in with outer display or 6.7 Horizontal Fold-in) et son image système et cliquer sur finish.

Félicitations ! Vous avez maintenant un nouvel appareil exécutable dans votre  device manager 😀.

Dépendances

Les dépendances suivantes doivent être implémentées pour fonctionner sur les appareils pliables :

// 1
implementation "androidx.compose.material3:material3:<m3 version>"
// 2
implementation "androidx.compose.material3:material3-window-size-class:<m3 version>"
// 3
implementation "androidx.window:window:<window version>"
//4
implementation "com.google.accompanist:accompanist-adaptive:<accompanist version>"
  1. material3 invoque des composants Material3 qui peuvent être utilisés dans le code de l'application.
  2. material3-window-size-class fait appel à WindowSizeClass (points d'arrêt sur les dimensions de la fenêtre)
  3. window fait appel à librairie Window Manager pour obtenir un flux d'informations sur la disposition de la fenêtre.
  4. accompanist-adaptive une librairie fournissant une collection d'utilitaires pour les mises en page adaptatives telles que TwoPane, un composant d'interface utilisateur qui positionne exactement deux emplacements sur l'écran.

Vous devrez également mettre à jour kotlinCompilerExtensionVersion et org.jetbrains.kotlin.android.

// build.gradle app 
composeOptions { kotlinCompilerExtensionVersion "1.4.0" } 
// build.gradle project 
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false

Les dépendances suivantes seront utilisées pour appeler un ensemble de classes différentes. Ces classes vous donneront les outils nécessaires pour créer des composants réactifs pour différentes tailles d'écran.

Adaptation aux différentes tailles d'écrans

Pour s'adapter aux différentes tailles d'écran, il existe quelques règles utiles à suivre. La classe WindowSizeClass propose des méthodes pour gérer les window breakpoints (points de rupture). Material3 est livré avec des règles de mise en page réactives. Examinons-les.

Window Size Class

Les classes de taille de fenêtre sont un ensemble de points d'arrêt de fenêtre d'affichage définis qui permettent de concevoir, de développer et de tester des mises en page d'applications responsives et adaptatives. Les points d'arrêt ont été choisis spécifiquement pour équilibrer la simplicité de la mise en page et la flexibilité qui permet d'optimiser votre application dans des cas spécifiques..

WindowSizeClass permet d'ajuster à la fois la largeur et la hauteur et offre trois points d'arrêt possibles : Compact, Medium et Expanded.

Width Window Size Class
Height Window Size Class

Ces points d'arrêt permettent au développeur de se concentrer sur la mise en page en fonction des appareils et/ou des pages particulières de l'application. Ils permettent d'utiliser efficacement l'espace disponible. Pour plus de détails sur la mise en œuvre, vous pouvez consulter la page Window Size Classes (Classes de taille de fenêtre).

Mise en page réactive

L'objectif d'une mise en page réactive est d'adapter l'interface utilisateur à la taille de la fenêtre. Pour créer une mise en page réactive dans Jetpack Compose, nous pouvons utiliser les points de rupture des fenêtres. Deux méthodes sont disponibles pour calculer la taille de la fenêtre dans Android Jetpack Compose :

// Calculates the window's [WindowSizeClass] for the provided [activity].
fun calculateWindowSizeClass(activity: Activity): WindowSizeClass

// Calculates [WindowSizeClass] for a given [size]. Should be used for testing purposes only
fun WindowSizeClass.calculateFromSize(size: DpSize): WindowSizeClass

Néanmoins, il faut se méfier de ces méthodes car elles sont encore expérimentales. La méthode calculateWindowSizeClass est la seule recommandée. WindowSizeClass contient une WindowWidthSizeClass et une WindowHeightSizeClass, représentant respectivement la largeur et la hauteur de la fenêtre. Chaque classe aura ses propres instances de Compact, Medium et Expanded.  En fonction de la largeur et de la hauteur de l'écran, il est facile d'organiser le code pour avoir une mise en page réactive. Je conseille la création d'une sealed class Dimensions et d'une classe composable WindowResponsiveDimensions comme ci-dessous** **:

/**
 * Declare which window dimension to use [WindowWidthSizeClass] or [WindowHeightSizeClass].
 */
sealed class Dimensions() {
	object Width: Dimensions()
	object Height: Dimensions()
}
/**
 * Will decide which composable to call with [windowSizeClass] and [dimensions].
 */
@Composable
fun WindowResponsiveDimensions(
    windowSizeClass: WindowSizeClass,
    dimensions: Dimensions = Dimensions.Width,
    compact: @Composable () -> Unit,
    medium: @Composable () -> Unit = compact,
    expanded: @Composable () -> Unit = medium,
) {
    when(dimensions) {
        is Dimensions.Width -> {
            when(windowSizeClass.widthSizeClass) {
                WindowWidthSizeClass.Compact -> compact()
                WindowWidthSizeClass.Medium -> medium()
                WindowWidthSizeClass.Expanded -> expanded()
            }
        }
        is Dimensions.Height -> {
            when(windowSizeClass.heightSizeClass) {
                WindowHeightSizeClass.Compact -> compact()
                WindowHeightSizeClass.Medium -> medium()
                WindowHeightSizeClass.Expanded -> expanded()
            }
        }
    }
}

En fin de compte, en suivant également les guidelines recommandées par Material3, nous sommes en mesure de créer des fenêtres de mise en page réactives. Pour plus de précisions, consultez Layout - Material3. La mise en page responsive doit s'adapter à tous les types d'appareils, même pliables. Les informations sur la mise en page des fenêtres aideront à créer une mise en page réactive pour chaque appareil.

Informations sur la disposition des fenêtres

Android est capable de gérer les informations relatives aux fenêtres à l'aide d'un auditeur. Grâce aux informations sur les fenêtres, nous pouvons adapter nos fenêtres personnalisées aux changements de l'environnement. Nous verrons d'abord comment suivre les informations sur les fenêtres, et ensuite quels sont les états de pliage.

Suivi de l'information sur les fenêtres

Sous Android, la bibliothèque WindowManager est utilisée pour suivre les informations relatives aux fenêtres. Les informations sur la fenêtre renvoient une liste de DisplayFeature. Cette liste fournit les dimensions de la fenêtre, mais peut également être interprétée comme une FoldingFeature, une sous-classe directe de DisplayFeature. Pour plus de détails, consultez les pages suivantes :

Dans Jetpack Compose, nous trouvons la méthode suivante :

@Composable
public fun calculateDisplayFeatures(activity: Activity): List<DisplayFeature>

Il permet de calculer la liste des DisplayFeature à partir de l'activité donnée en utilisant la bibliothèque WindowManager. Elle crée un produceState pour mettre à jour la liste lorsque des changements sont invoqués. Vous avez besoin de cette liste pour utiliser la méthode TwoPane. Elle utilise les six paramètres suivants :

  • first : le contenu composable pour l'écran le plus à gauche ou le plus en haut de notre appareil en fonction du SplitResult de TwoPaneStrategy.
  • second : la même chose que pour le premier paramètre, mais le contenu sera placé dans la partie la plus à droite ou la plus en bas.
  • strategy : la stratégie qui contrôle la disposition de la mise en page.
  • displayFeatures : la liste des DisplayFeatures.
  • foldAwareConfiguration : le type de plis à éviter. Une FoldAwareConfiguration contient 3 instances
  • AllFolds : par défaut, la configuration sélectionnée prend en compte les plis verticaux et horizontaux.
  • HorizontalFoldsOnly : ne prend en compte que les plis horizontaux, en divisant le contenu verticalement.
  • VerticalFoldsOnly : ne prend en compte que les plis verticaux, en divisant le contenu horizontalement.
  • modifier : un modifier optionnel qui s'appliquera à l'ensemble de la fenêtre et non pas à chaque côté de l'écran.

Voici un exemple d’usage de la méthode :

TwoPane(
        first = {
	// a composable method
        },
        second = {
	// a composable method
        },
        strategy = VerticalTwoPaneStrategy(.5f),
        displayFeatures = displayFeatures,
        foldAwareConfiguration = FoldAwareConfiguration.AllFolds,
        modifier = Modifier.fillMaxSize(),
    )

Pour plus de détails, lire l'article Jetpack Compose Accompanist TwoPane. La méthode doit être utilisée en fonction de l'état de pliage de l'appareil.

États pliables

Une FoldingFeature représente les différents états et facteurs de pliage disponibles. Elle décrit un pli dans l'affichage dynamique ou la charnière entre deux panneaux d'affichage physique. Elle contient l'Orientation, l'OcclusionType et le State de notre fenêtre. Ces éléments permettent de connaître la position de votre appareil (fermé, à moitié ouvert, ouvert) avec des informations supplémentaires (angle de la charnière, orientation). Je recommande de créer deux méthodes pour trouver la position de l'appareil :

    fun isFlatWithSeparatePanels(foldingFeature: FoldingFeature?): Boolean {
        return foldingFeature?.state == FoldingFeature.State.FLAT && foldingFeature.isSeparating
    }

    fun isHalfOpened(foldingFeature: FoldingFeature?): Boolean {
        return foldingFeature?.state == FoldingFeature.State.HALF_OPENED
    }

Vous pouvez également créer une fonction composable qui appellera différents contenus composables en fonction de la position de l'appareil :

@Composable
fun WindowResponsive(
    foldingFeature: FoldingFeature?,
    normal: @Composable () -> Unit,
    bookPosture: @Composable () -> Unit = {},
    separating: @Composable () -> Unit = normal,
) {
    when {
        Tools.isBookPosture(foldingFeature) -> bookPosture()
        Tools.isSeparating(foldingFeature) -> separating()
        else -> normal()
    }
}

Ces méthodes doivent être utilisées après avoir obtenu la caractéristique de pliage. Pour ce faire, vous devez filtrer l'instance de FoldingFeature dans la liste de DisplayFeature :

displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()

En fin de compte, avec une combinaison de TwoPane, de FoldingFeature et de mise en page réactive, nous avons différents composants adaptés aux changements de mise en page de la fenêtre. Nous verrons les résultats dans la section suivante.

Résultat sur les dispositifs pliables

Pour présenter les résultats obtenus dans un dispositif pliable, j'ai développé une application de lecteur vidéo. Les principales caractéristiques de cette application sont d'afficher une liste de vidéos et de permettre à l'utilisateur de lire une vidéo. Chaque vidéo possède un titre, une vignette, un sous-titre et une description. Les commentaires générés sont attachés à la vidéo. L'utilisateur peut également ajouter des commentaires pour une vidéo donnée. L'utilisateur peut sélectionner une vidéo dans la liste de lecture. Le projet est accessible sur GitLab avec plus de détails sur le README. Les images suivantes sont le résultat du projet.

Appareil Flat Separate Half Opened
7.6 Fold-in with outer display 7.6 flat separate 7.6 half opened
Le mode portrait utilise les deux écrans. Le premier est destiné à la vidéo, à sa description et aux commentaires. Le deuxième est consacré à la liste de lecture des vidéos. Half opened utilise les deux écrans. Le premier représente la vidéo en plein écran. Le deuxième est divisé par deux verticalement, une partie affichant la description de la vidéo et les commentaires et l'autre la liste de lecture de la vidéo.
6.7 Horizontal Fold-in 6.7 flat separate 6.7 half opened
En mode portrait, il est traité comme un appareil normal. Half opened utilise les deux écrans. Le premier représente la vidéo en plein écran. Le deuxième est composé de trois volets, l'un affichant la description de la vidéo, l'autre les commentaires et le dernier la liste de lecture de la vidéo.

L'équipe de Google a fourni d'autres exemples, tels que Reply (application des e-mails de la boîte de réception) avec le guide pour les étapes suivantes dans ce lien.

Conclusion

Les smartphones pliables sont encore un concept très récent. Les dernières versions d'Android Studio proposent des émulateurs pliables capables d'avoir une perspective 3D de l'émulateur, mais aussi de simuler différentes poses de l'appareil. Une grande partie du code est encore expérimentale et il y a des mises à jour fréquentes, mais il s'agit encore d'une version alpha. Consultez la page accompanist-adaptive pour plus de détails. Très peu d'ingénieurs mobiles développent des applications prenant en compte les appareils pliables. La principale difficulté à laquelle il faut faire face lors du développement pour les téléphones pliables est la lenteur des émulateurs. Les IDE disponibles n'offrent pas encore une expérience de développement confortable et fluide pour ce nouveau défi dans le développement mobile. La recherche d'exemples, d'explications et d'extraits est également un autre défi pour les développeurs. Nous pouvons nous demander quel sera l'impact si les équipes de développement ne prennent pas en compte assez rapidement les smartphones pliables qui pourraient arriver sur le marché. Il est fort probable que la disposition graphique des applications existantes ne sera pas en mesure de s'adapter aux contraintes des appareils pliables. La conséquence directe à court terme sera la nécessité de mettre à jour le code des applications existantes. Pour mettre à jour l'interface utilisateur des applications existantes, les développeurs peuvent suivre un tutoriel pour migrer vers Material3 et utiliser des points de rupture de fenêtre, et également suivre les différentes mises à jour fournies par accompanist-adaptive.

Adaptive user interface - Wikipedia
Support different screen sizes | Android Developers