1. Introduction
Dans l'atelier de programmation précédent, vous avez commencé à transformer l'application Reply pour la rendre adaptative en utilisant des classes de taille de fenêtre et en implémentant la navigation dynamique. Ces fonctionnalités sont essentielles et constituent la première étape dans le processus de création d'applications pour toutes les tailles d'écran. Si vous n'avez pas suivi l'atelier de programmation Créer une application adaptative avec la navigation dynamique, nous vous recommandons vivement de le faire.
Dans cet atelier de programmation, vous allez vous appuyer sur un concept que vous avez étudié pour implémenter une mise en page adaptative dans votre application. La mise en page que vous allez implémenter fait partie des mises en page standards, c'est-à-dire un ensemble de formats couramment utilisés pour les grands écrans. Vous découvrirez également d'autres outils et techniques de test qui vous aideront à créer rapidement des applications robustes.
Conditions préalables
- Vous avez terminé l'atelier de programmation Créer une application adaptative avec la navigation dynamique.
- Vous maîtrisez la programmation Kotlin, y compris les classes, les fonctions et les conditions.
- Vous maîtrisez les classes
ViewModel
. - Vous maîtrisez les fonctions
Composable
. - Vous savez comment créer des mises en page avec Jetpack Compose.
- Vous savez comment exécuter des applications sur un appareil ou un émulateur.
- Vous savez comment utiliser l'API
WindowSizeClass
.
Points abordés
- Comment créer une mise en page adaptative de modèle sous forme de liste avec Jetpack Compose
- Comment créer des aperçus pour différentes tailles d'écran
- Comment tester le code pour plusieurs tailles d'écran
Objectifs de l'atelier
- Vous allez continuer à mettre à jour l'application Reply pour qu'elle s'adapte à toutes les tailles d'écran.
La version finale de l'application ressemblera à ceci :
Ce dont vous avez besoin
- Un ordinateur avec accès à Internet, un navigateur Web et Android Studio
- Un accès à GitHub
Télécharger le code de démarrage
Pour commencer, téléchargez le code de démarrage :
Vous pouvez également cloner le dépôt GitHub du code :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git $ cd basic-android-kotlin-compose-training-reply-app $ git checkout nav-update
Vous pouvez parcourir le code dans le dépôt GitHub Reply
.
2. Aperçus pour différentes tailles d'écran
Créer des aperçus pour différentes tailles d'écran
Dans l'atelier de programmation Créer une application adaptative avec la navigation dynamique, vous avez appris à utiliser des composables d'aperçu pour faciliter le processus de développement. Dans le cas d'une application adaptative, il est recommandé de créer plusieurs aperçus pour afficher l'application sur différentes tailles d'écran. Cela vous permet de voir vos modifications sur toutes les tailles d'écran en même temps. Les aperçus servent également de documentation pour que les autres développeurs qui examinent votre code puissent vérifier la compatibilité de votre application avec différentes tailles d'écran.
Auparavant, il n'y avait qu'un seul aperçu compatible avec l'écran de format compact. Vous en ajouterez d'autres par la suite.
Pour ajouter des aperçus pour les écrans de taille moyenne et étendus, procédez comme suit :
- Ajoutez un aperçu pour les écrans de taille moyenne en définissant une valeur
widthDp
moyenne dans le paramètre d'annotationPreview
et en spécifiant la valeurWindowWidthSizeClass.Medium
en tant que paramètre pour le composableReplyApp
.
MainActivity.kt
...
@Preview(showBackground = true, widthDp = 700)
@Composable
fun ReplyAppMediumPreview() {
ReplyTheme {
Surface {
ReplyApp(windowSize = WindowWidthSizeClass.Medium)
}
}
}
...
- Ajoutez un autre aperçu pour les écrans étendus en définissant une valeur
widthDp
élevée dans le paramètre d'annotationPreview
et en spécifiant la valeurWindowWidthSizeClass.Expanded
en tant que paramètre pour le composableReplyApp
.
MainActivity.kt
...
@Preview(showBackground = true, widthDp = 1000)
@Composable
fun ReplyAppExpandedPreview() {
ReplyTheme {
Surface {
ReplyApp(windowSize = WindowWidthSizeClass.Expanded)
}
}
}
...
- Générez l'aperçu pour afficher ce qui suit :
3. Implémenter la mise en page de contenu adaptative
Présentation de la vue détaillée
Vous remarquerez peut-être que sur les écrans étendus, le contenu semble étiré et qu'il n'exploite pas correctement l'espace disponible à l'écran.
Vous pouvez améliorer cette mise en page en appliquant l'une des mises en page standards. Il s'agit de compositions d'écran de grande taille qui servent de points de départ pour la conception et l'implémentation. Trois mises en page sont disponibles pour vous aider à organiser les éléments courants d'une application, d'une vue de liste, d'un panneau d'assistance et d'un flux. Chaque mise en page prend en compte les cas d'utilisation et les composants courants pour répondre aux attentes et aux besoins des utilisateurs concernant la façon dont l'application s'adapte aux tailles d'écran et aux points d'arrêt.
Pour l'application Reply, nous allons implémenter la vue détaillée, car elle est idéale pour parcourir des contenus et afficher rapidement des détails. Dans la mise en page de vue détaillée, vous allez créer un autre volet à côté de l'écran de la liste de diffusion pour afficher les détails des e-mails. Cette mise en page vous permet d'utiliser l'écran disponible pour présenter davantage d'informations aux utilisateurs et améliorer la productivité de votre application.
Implémenter la vue détaillée
Pour implémenter une vue détaillée pour les écrans étendus, procédez comme suit :
- Pour représenter différents types de mise en page de contenu, créez une classe
Enum
pour différents types de contenu surWindowStateUtils.kt
. Utilisez la valeurLIST_AND_DETAIL
lorsque l'écran étendu est utilisé etLIST_ONLY
dans le cas contraire.
WindowStateUtils.kt
...
enum class ReplyContentType {
LIST_ONLY, LIST_AND_DETAIL
}
...
- Déclarez la variable
contentType
surReplyApp.kt
et affectez la variablecontentType
appropriée pour différentes tailles de fenêtre afin de déterminer la sélection du type de contenu approprié en fonction de la taille de l'écran.
ReplyApp.kt
...
import com.example.reply.ui.utils.ReplyContentType
...
val navigationType: ReplyNavigationType
val contentType: ReplyContentType
when (windowSize) {
WindowWidthSizeClass.Compact -> {
...
contentType = ReplyContentType.LIST_ONLY
}
WindowWidthSizeClass.Medium -> {
...
contentType = ReplyContentType.LIST_ONLY
}
WindowWidthSizeClass.Expanded -> {
...
contentType = ReplyContentType.LIST_AND_DETAIL
}
else -> {
...
contentType = ReplyContentType.LIST_ONLY
}
}
...
Vous pouvez ensuite utiliser la valeur contentType
pour créer un embranchement différent pour les mises en page dans le composable ReplyAppContent
.
- Dans
ReplyHomeScreen.kt
, ajoutezcontentType
en tant que paramètre au composableReplyHomeScreen
.
ReplyHomeScreen.kt
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReplyHomeScreen(
navigationType: ReplyNavigationType,
contentType: ReplyContentType,
replyUiState: ReplyUiState,
onTabPressed: (MailboxType) -> Unit,
onEmailCardPressed: (Email) -> Unit,
onDetailScreenBackPressed: () -> Unit,
modifier: Modifier = Modifier
) {
...
- Transmettez la valeur
contentType
au composableReplyHomeScreen
.
ReplyApp.kt
...
ReplyHomeScreen(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = { mailboxType: MailboxType ->
viewModel.updateCurrentMailbox(mailboxType = mailboxType)
viewModel.resetHomeScreenStates()
},
onEmailCardPressed = { email: Email ->
viewModel.updateDetailsScreenStates(
email = email
)
},
onDetailScreenBackPressed = {
viewModel.resetHomeScreenStates()
},
modifier = modifier
)
...
- Ajoutez
contentType
en tant que paramètre pour le composableReplyAppContent
.
ReplyHomeScreen.kt
...
@Composable
private fun ReplyAppContent(
navigationType: ReplyNavigationType,
contentType: ReplyContentType,
replyUiState: ReplyUiState,
onTabPressed: ((MailboxType) -> Unit),
onEmailCardPressed: (Email) -> Unit,
navigationItemContentList: List<NavigationItemContent>,
modifier: Modifier = Modifier
) {
...
- Transmettez la valeur
contentType
aux deux composablesReplyAppContent
.
ReplyHomeScreen.kt
...
ReplyAppContent(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
}
} else {
if (replyUiState.isShowingHomepage) {
ReplyAppContent(
navigationType = navigationType,
contentType = contentType,
replyUiState = replyUiState,
onTabPressed = onTabPressed,
onEmailCardPressed = onEmailCardPressed,
navigationItemContentList = navigationItemContentList,
modifier = modifier
)
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
isFullScreen = true,
onBackButtonClicked = onDetailScreenBackPressed,
modifier = modifier
)
}
}
...
Nous allons afficher soit la liste complète et l'écran détaillé lorsque contentType
est LIST_AND_DETAIL
, soit le contenu de l'e-mail sous forme de liste uniquement lorsque contentType
est LIST_ONLY
.
- Dans
ReplyHomeScreen.kt
, ajoutez une instructionif/else
sur le composableReplyAppContent
pour afficher le composableReplyListAndDetailContent
lorsque la valeurcontentType
estLIST_AND_DETAIL
et pour afficher le composableReplyListOnlyContent
sur la brancheelse
.
ReplyHomeScreen.kt
...
Column(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.inverseOnSurface)
) {
if (contentType == ReplyContentType.LIST_AND_DETAIL) {
ReplyListAndDetailContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
)
} else {
ReplyListOnlyContent(
replyUiState = replyUiState,
onEmailCardPressed = onEmailCardPressed,
modifier = Modifier.weight(1f)
.padding(
horizontal = dimensionResource(R.dimen.email_list_only_horizontal_padding)
)
)
}
AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
ReplyBottomNavigationBar(
currentTab = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList
)
}
}
...
- Supprimez la condition
replyUiState.isShowingHomepage
pour afficher un panneau de navigation permanent, car l'utilisateur n'a pas besoin d'accéder à la vue détaillée s'il utilise la vue étendue.
ReplyHomeScreen.kt
...
if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER) {
PermanentNavigationDrawer(
drawerContent = {
PermanentDrawerSheet(Modifier.width(dimensionResource(R.dimen.drawer_width))) {
NavigationDrawerContent(
selectedDestination = replyUiState.currentMailbox,
onTabPressed = onTabPressed,
navigationItemContentList = navigationItemContentList,
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.background(MaterialTheme.colorScheme.inverseOnSurface)
.padding(dimensionResource(R.dimen.drawer_padding_content))
)
}
}
) {
...
- Exécutez votre application en mode Tablette pour afficher l'écran ci-dessous :
Améliorer les éléments d'interface utilisateur pour la vue détaillée
Votre application affiche actuellement un volet de détails sur l'écran d'accueil pour les écrans étendus.
Cependant, l'écran contient des éléments superflus, tels que le bouton "Retour", l'en-tête et des marges intérieures supplémentaires, car il a été conçu pour un écran de détails autonome. Vous pourrez améliorer cela par la suite en effectuant un simple ajustement.
Pour améliorer l'écran de détails de la vue étendue, procédez comme suit :
- Dans
ReplyDetailsScreen.kt
, ajoutez une variableisFullScreen
en tant que paramètreBoolean
au composableReplyDetailsScreen
.
Cela vous permet de différencier le composable lorsqu'il est utilisé de manière autonome et lorsqu'il est utilisé dans l'écran d'accueil.
ReplyDetailsScreen.kt
...
@Composable
fun ReplyDetailsScreen(
replyUiState: ReplyUiState,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
isFullScreen: Boolean = false
) {
...
- Dans le composable
ReplyDetailsScreen
, encapsulez le composableReplyDetailsScreenTopBar
avec une instructionif
pour qu'il ne s'affiche que lorsque l'application est en mode plein écran.
ReplyDetailsScreen.kt
...
LazyColumn(
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.inverseOnSurface)
.padding(top = dimensionResource(R.dimen.detail_card_list_padding_top))
) {
item {
if (isFullScreen) {
ReplyDetailsScreenTopBar(
onBackPressed,
replyUiState,
Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.detail_topbar_padding_bottom))
)
)
}
...
Vous pouvez à présent ajouter une marge intérieure. La marge intérieure requise pour le composable ReplyEmailDetailsCard
varie selon que vous l'utilisez ou non en plein écran. Lorsque vous utilisez ReplyEmailDetailsCard
avec d'autres composables sur l'écran étendu, une marge intérieure supplémentaire provenant d'autres composables est ajoutée.
- Transmettez la valeur
isFullScreen
au composableReplyEmailDetailsCard
. Transmettez un modificateur avec une marge intérieure horizontale deR.dimen.detail_card_outer_padding_horizontal
en cas d'affichage en plein écran. Dans le cas contraire, transmettez un modificateur avec une marge intérieure de fin deR.dimen.detail_card_outer_padding_horizontal
.
ReplyDetailsScreen.kt
...
item {
if (isFullScreen) {
ReplyDetailsScreenTopBar(
onBackPressed,
replyUiState,
Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(R.dimen.detail_topbar_padding_bottom))
)
)
}
ReplyEmailDetailsCard(
email = replyUiState.currentSelectedEmail,
mailboxType = replyUiState.currentMailbox,
isFullScreen = isFullScreen,
modifier = if (isFullScreen) {
Modifier.padding(horizontal = dimensionResource(R.dimen.detail_card_outer_padding_horizontal))
} else {
Modifier.padding(end = dimensionResource(R.dimen.detail_card_outer_padding_horizontal))
}
)
}
...
- Ajoutez une valeur
isFullScreen
en tant que paramètre au composableReplyEmailDetailsCard
.
ReplyDetailsScreen.kt
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ReplyEmailDetailsCard(
email: Email,
mailboxType: MailboxType,
modifier: Modifier = Modifier,
isFullScreen: Boolean = false
) {
...
- Dans le composable
ReplyEmailDetailsCard
, n'affichez le texte d'objet de l'e-mail que lorsque l'application n'est pas en mode plein écran, car la mise en page en plein écran affiche déjà l'objet de l'e-mail comme en-tête. Si elle est affichée en plein écran, ajoutez un espace vide d'une hauteur deR.dimen.detail_content_padding_top
.
ReplyDetailsScreen.kt
...
Column(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.detail_card_inner_padding))
) {
DetailsScreenHeader(
email,
Modifier.fillMaxWidth()
)
if (isFullScreen) {
Spacer(modifier = Modifier.height(dimensionResource(R.dimen.detail_content_padding_top)))
} else {
Text(
text = stringResource(email.subject),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
modifier = Modifier.padding(
top = dimensionResource(R.dimen.detail_content_padding_top),
bottom = dimensionResource(R.dimen.detail_expanded_subject_body_spacing)
),
)
}
Text(
text = stringResource(email.body),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
DetailsScreenButtonBar(mailboxType, displayToast)
}
...
- Dans
ReplyHomeScreen.kt
, à l'intérieur du composableReplyHomeScreen
, transmettez une valeurtrue
pour le paramètreisFullScreen
lorsque vous créez le composableReplyDetailsScreen
en tant qu'élément autonome.
ReplyHomeScreen.kt
...
} else {
ReplyDetailsScreen(
replyUiState = replyUiState,
isFullScreen = true,
onBackPressed = onDetailScreenBackPressed,
modifier = modifier
)
}
...
- Exécutez l'application en mode Tablette pour afficher la mise en page suivante :
Ajuster le comportement de retour pour la vue détaillée
Avec les écrans étendus, il n'est pas nécessaire d'accéder au composable ReplyDetailsScreen
. Vous souhaitez plutôt que l'application se ferme lorsque l'utilisateur sélectionne le bouton "Retour". Il convient donc d'ajuster le gestionnaire de retour.
Modifiez le gestionnaire de retour en transmettant la fonction activity.finish()
en tant que paramètre onBackPressed
du composable ReplyDetailsScreen
à l'intérieur du composable ReplyListAndDetailContent
.
ReplyHomeContent.kt
...
import android.app.Activity
import androidx.compose.ui.platform.LocalContext
...
val activity = LocalContext.current as Activity
ReplyDetailsScreen(
replyUiState = replyUiState,
modifier = Modifier.weight(1f),
onBackPressed = { activity.finish() }
)
...
4. Vérifier la compatibilité avec différentes tailles d'écran
Consignes relatives à la qualité des applications sur grand écran
Pour garantir une expérience optimale et cohérente aux utilisateurs d'Android, vous devez créer et tester vos applications en accordant une attention toute particulière à la qualité. Consultez les Consignes fondamentales relatives à la qualité des applications pour savoir comment améliorer la qualité de votre application.
Pour créer une application de qualité pour tous les facteurs de forme, consultez les Consignes relatives à la qualité des applications sur grand écran. Votre application doit également répondre aux exigences de niveau 3 : applications compatibles avec un grand écran.
Évaluer manuellement l'aptitude de votre application à s'afficher sur un grand écran
Vous trouverez, dans les consignes relatives à la qualité des applications, les recommandations et procédures de test nécessaires pour vérifier la qualité de votre application. Penchons-nous sur un exemple de test correspondant à l'application Reply.
Conformément aux consignes relatives à la qualité des applications ci-dessus, l'application doit conserver ou restaurer son état après un changement de configuration. Des instructions sont également disponibles pour tester les applications, comme illustré ci-dessous :
Pour tester manuellement la continuité et la configuration de l'application Reply, procédez comme suit :
- Exécutez l'application Reply sur un appareil de taille moyenne ou, si vous utilisez l'émulateur redimensionnable, en mode pliable déplié.
- Assurez-vous que la rotation automatique est activée sur l'émulateur.
- Faites défiler la liste de diffusion vers le bas.
- Cliquez sur une fiche d'e-mail. Par exemple, ouvrez l'e-mail envoyé par Ali.
- Faites pivoter l'appareil pour vérifier que l'e-mail choisi correspond toujours à celui sélectionné en mode Portrait. Dans cet exemple, l'e-mail envoyé par Ali est toujours affiché.
- Faites à nouveau pivoter l'appareil en mode Portrait pour vérifier que l'application affiche toujours le même e-mail.
5. Ajouter un test automatisé pour les applications adaptatives
Configurer le test pour la taille d'écran compacte
Dans l'atelier de programmation Tester l'application Cupcake, vous avez appris à créer des tests d'interface utilisateur. Voyons maintenant comment créer des tests spécifiques pour différentes tailles d'écran.
Dans l'application Reply, vous utilisez des éléments de navigation différents en fonction des tailles d'écran. Par exemple, lorsque l'utilisateur affiche l'écran étendu, vous vous attendez à voir un panneau de navigation permanent. Il est utile de créer des tests pour vérifier l'existence de divers éléments de navigation, comme la barre de navigation inférieure, le rail de navigation et le panneau de navigation pour différentes tailles d'écran.
Pour créer un test dans le but de vérifier l'existence d'un élément de navigation inférieure dans un écran de format compact, procédez comme suit :
- Dans le répertoire de test, créez une classe Kotlin nommée
ReplyAppTest.kt
. - Dans la classe
ReplyAppTest
, créez une règle de test en utilisantcreateAndroidComposeRule
et en transmettantComponentActivity
comme paramètre de type.ComponentActivity
est utilisé pour accéder à une activité vide au lieu deMainActivity
.
ReplyAppTest.kt
...
class ReplyAppTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
...
Pour différencier les éléments de navigation dans les écrans, ajoutez testTag
dans le composable ReplyBottomNavigationBar
.
- Définissez une ressource de chaîne pour Navigation Bottom (Navigation inférieure).
strings.xml
...
<resources>
...
<string name="navigation_bottom">Navigation Bottom</string>
...
</resources>
- Ajoutez le nom de chaîne en tant qu'argument
testTag
pour la méthodetestTag
duModifier
dans le composableReplyBottomNavigationBar
.
ReplyHomeScreen.kt
...
val bottomNavigationContentDescription = stringResource(R.string.navigation_bottom)
ReplyBottomNavigationBar(
...
modifier = Modifier
.fillMaxWidth()
.testTag(bottomNavigationContentDescription)
)
...
- Dans la classe
ReplyAppTest
, créez une fonction de test pour tester un écran de format compact. Définissez le contenu decomposeTestRule
avec le composableReplyApp
, puis transmettezWindowWidthSizeClass.Compact
en tant qu'argumentwindowSize
.
ReplyAppTest.kt
...
@Test
fun compactDevice_verifyUsingBottomNavigation() {
// Set up compact window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact
)
}
}
- Confirmez l'existence de l'élément de navigation inférieure avec la balise de test. Appelez la fonction d'extension
onNodeWithTagForStringId
surcomposeTestRule
, transmettez la chaîne de navigation inférieure et appelez la méthodeassertExists()
.
ReplyAppTest.kt
...
@Test
fun compactDevice_verifyUsingBottomNavigation() {
// Set up compact window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Compact
)
}
// Bottom navigation is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_bottom
).assertExists()
}
- Exécutez le test et assurez-vous qu'il réussit.
Configurer le test pour les écrans de taille moyenne et étendus
Maintenant que vous avez créé un test pour l'écran de format compact, nous allons créer les tests correspondants pour les écrans de taille moyenne et étendus.
Pour créer des tests dans le but de vérifier l'existence d'un rail de navigation et d'un panneau de navigation permanent pour les écrans de taille moyenne et étendus, procédez comme suit :
- Définissez une ressource de chaîne pour le rail de navigation qui sera utilisée, par la suite, comme balise de test.
strings.xml
...
<resources>
...
<string name="navigation_rail">Navigation Rail</string>
...
</resources>
- Transmettez la chaîne en tant que balise de test via
Modifier
dans le composablePermanentNavigationDrawer
.
ReplyHomeScreen.kt
...
val navigationDrawerContentDescription = stringResource(R.string.navigation_drawer)
PermanentNavigationDrawer(
...
modifier = Modifier.testTag(navigationDrawerContentDescription)
)
...
- Transmettez la chaîne en tant que balise de test via
Modifier
dans le composableReplyNavigationRail
.
ReplyHomeScreen.kt
...
val navigationRailContentDescription = stringResource(R.string.navigation_rail)
ReplyNavigationRail(
...
modifier = Modifier
.testTag(navigationRailContentDescription)
)
...
- Ajoutez un test pour vérifier qu'il existe un élément de rail de navigation dans les écrans de taille moyenne.
ReplyAppTest.kt
...
@Test
fun mediumDevice_verifyUsingNavigationRail() {
// Set up medium window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Medium
)
}
// Navigation rail is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_rail
).assertExists()
}
- Ajoutez un test pour vérifier qu'il existe un élément de panneau de navigation dans les écrans étendus.
ReplyAppTest.kt
...
@Test
fun expandedDevice_verifyUsingNavigationDrawer() {
// Set up expanded window
composeTestRule.setContent {
ReplyApp(
windowSize = WindowWidthSizeClass.Expanded
)
}
// Navigation drawer is displayed
composeTestRule.onNodeWithTagForStringId(
R.string.navigation_drawer
).assertExists()
}
- Utilisez un émulateur de tablette ou un émulateur redimensionnable en mode Tablette pour exécuter le test.
- Exécutez tous les tests et vérifiez qu'ils réussissent.
Tester un changement de configuration sur un écran de format compact
Un changement de configuration est un événement courant dans le cycle de vie d'une application. Cela se produit, par exemple, lorsque vous passez du mode Portrait au mode Paysage. Dans ce cas, il est important de vérifier que votre application conserve son état. Vous allez ensuite créer des tests qui simulent un changement de configuration afin de vérifier que votre application conserve son état sur un écran de format compact.
Pour tester un changement de configuration sur l'écran de format compact :
- Dans le répertoire de test, créez une classe Kotlin nommée
ReplyAppStateRestorationTest.kt
. - Dans la classe
ReplyAppStateRestorationTest
, créez une règle de test en utilisantcreateAndroidComposeRule
et en transmettantComponentActivity
comme paramètre de type.
ReplyAppStateRestorationTest.kt
...
class ReplyAppStateRestorationTest {
/**
* Note: To access to an empty activity, the code uses ComponentActivity instead of
* MainActivity.
*/
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
}
...
- Créez une fonction de test pour vérifier qu'un e-mail est toujours sélectionné dans l'écran de format compact après un changement de configuration.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
}
...
Pour tester un changement de configuration, vous devez utiliser StateRestorationTester
.
- Configurez
stateRestorationTester
en transmettantcomposeTestRule
comme argument àStateRestorationTester
. - Utilisez
setContent()
avec le composableReplyApp
et transmettezWindowWidthSizeClass.Compact
en tant qu'argumentwindowSize
.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
}
...
- Vérifiez qu'un troisième e-mail s'affiche dans l'application. Utilisez la méthode
assertIsDisplayed()
surcomposeTestRule
pour rechercher le texte du troisième e-mail.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
}
...
- Accédez à l'écran des détails de l'e-mail en cliquant sur son objet. Utilisez la méthode
performClick()
pour naviguer.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
}
...
- Vérifiez que le troisième e-mail est bien affiché dans l'écran des détails. Vérifiez l'existence du bouton "Retour" pour confirmer la présence de l'application dans l'écran de détails et vérifiez que le texte du troisième e-mail est bien affiché.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
}
...
- Simulez un changement de configuration à l'aide de
stateRestorationTester.emulateSavedInstanceStateRestore()
.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
}
...
- Vérifiez à nouveau que le troisième e-mail est bien affiché dans l'écran des détails. Vérifiez l'existence du bouton "Retour" pour confirmer la présence de l'application dans l'écran de détails et vérifiez que le texte du troisième e-mail est bien affiché.
ReplyAppStateRestorationTest.kt
...
@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup compact window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Compact) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Open detailed page
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that it shows the detailed screen for the correct email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
// Verify that it still shows the detailed screen for the same email
composeTestRule.onNodeWithContentDescriptionForStringId(
R.string.navigation_back
).assertExists()
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertExists()
}
...
- Exécutez le test avec un émulateur de téléphone ou un émulateur redimensionnable en mode Téléphone.
- Vérifiez que le test réussit.
Tester un changement de configuration sur l'écran étendu
Pour tester un changement de configuration sur l'écran étendu en simulant le changement et en transmettant la classe WindowWidthSizeClass appropriée, procédez comme suit :
- Créez une fonction de test pour vérifier qu'un e-mail est toujours sélectionné dans l'écran de détails après un changement de configuration.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
}
...
Pour tester un changement de configuration, vous devez utiliser StateRestorationTester
.
- Configurez
stateRestorationTester
en transmettantcomposeTestRule
comme argument àStateRestorationTester
. - Utilisez
setContent()
avec le composableReplyApp
et transmettezWindowWidthSizeClass.Expanded
en tant qu'argumentwindowSize
.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
}
...
- Vérifiez qu'un troisième e-mail s'affiche dans l'application. Utilisez la méthode
assertIsDisplayed()
surcomposeTestRule
pour rechercher le texte du troisième e-mail.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
}
...
- Sélectionnez le troisième e-mail sur l'écran de détails. Utilisez la méthode
performClick()
pour sélectionner l'e-mail.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
...
}
...
- Vérifiez que le troisième e-mail est bien affiché sur l'écran de détails en utilisant
testTag
sur cet écran et en recherchant le texte sur ses éléments enfants. Avec cette méthode, vous êtes sûr de trouver le texte dans la section des détails et non dans la liste de diffusion.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
...
}
...
- Simulez un changement de configuration à l'aide de
stateRestorationTester.emulateSavedInstanceStateRestore()
.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
...
}
...
- Vérifiez à nouveau que le troisième e-mail est bien affiché sur l'écran de détails à la suite d'un changement de configuration.
ReplyAppStateRestorationTest.kt
...
@Test
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
// Setup expanded window
val stateRestorationTester = StateRestorationTester(composeTestRule)
stateRestorationTester.setContent { ReplyApp(windowSize = WindowWidthSizeClass.Expanded) }
// Given third email is displayed
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)
).assertIsDisplayed()
// Select third email
composeTestRule.onNodeWithText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].subject)
).performClick()
// Verify that third email is displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
// Simulate a config change
stateRestorationTester.emulateSavedInstanceStateRestore()
// Verify that third email is still displayed on the details screen
composeTestRule.onNodeWithTagForStringId(R.string.details_screen).onChildren()
.assertAny(hasAnyDescendant(hasText(
composeTestRule.activity.getString(LocalEmailsDataProvider.allEmails[2].body)))
)
}
...
- Exécutez le test avec un émulateur de tablette ou un émulateur redimensionnable en mode Tablette.
- Vérifiez que le test réussit.
Utiliser des annotations pour regrouper les tests pour différentes tailles d'écran
Comme vous avez pu le constater dans les sections précédentes, certains tests ont échoué lorsqu'ils étaient exécutés sur des appareils ayant une taille d'écran incompatible. Bien qu'il soit possible d'exécuter les tests un par un à l'aide d'un appareil approprié, il se peut que cette méthode ne soit pas adaptée s'il y a de nombreux scénarios de test.
Une solution consiste à créer des annotations indiquant les tailles d'écran sur lesquelles le test peut s'exécuter et à configurer le test annoté pour les appareils appropriés.
Pour exécuter un test basé sur la taille d'écran, procédez comme suit :
- Dans le répertoire de test, créez le fichier
TestAnnotations.kt
, qui contient trois classes d'annotation :TestCompactWidth
,TestMediumWidth
etTestExpandedWidth
.
TestAnnotations.kt
...
annotation class TestCompactWidth
annotation class TestMediumWidth
annotation class TestExpandedWidth
...
- Utilisez les annotations sur les fonctions de test pour les écrans de format compact en plaçant l'annotation
TestCompactWidth
après l'annotation de test dansReplyAppTest
etReplyAppStateRestorationTest
.
ReplyAppTest.kt
...
@Test
@TestCompactWidth
fun compactDevice_verifyUsingBottomNavigation() {
...
ReplyAppStateRestorationTest.kt
...
@Test
@TestCompactWidth
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
...
- Utilisez les annotations sur les fonctions de test pour les écrans de taille moyenne en plaçant l'annotation
TestMediumWidth
après l'annotation de test dansReplyAppTest
.
ReplyAppTest.kt
...
@Test
@TestMediumWidth
fun mediumDevice_verifyUsingNavigationRail() {
...
- Utilisez les annotations sur les fonctions de test pour les écrans étendus en plaçant l'annotation
TestExpandedWidth
après l'annotation de test dansReplyAppTest
etReplyAppStateRestorationTest
.
ReplyAppTest.kt
...
@Test
@TestExpandedWidth
fun expandedDevice_verifyUsingNavigationDrawer() {
...
ReplyAppStateRestorationTest.kt
...
@Test
@TestExpandedWidth
fun expandedDevice_selectedEmailEmailRetained_afterConfigChange() {
...
Pour que le test réussisse, configurez-le de telle sorte qu'il exécute uniquement les tests annotés avec TestCompactWidth
.
- Dans Android Studio, sélectionnez Run > Edit Configurations… (Exécuter > Modifier les configurations) .
- Renommez le test Compact Test (Test compact), puis choisissez d'exécuter le test All in Package (Tous dans le package).
- Cliquez sur les points de suspension (…) à droite du champ Instrumentation arguments (Arguments d'instrumentation).
- Cliquez sur le bouton Plus (
+
), puis ajoutez les paramètres supplémentaires : annotation avec la valeur com.example.reply.test.TestCompactWidth.
- Exécutez les tests avec un émulateur compact.
- Vérifiez que seuls les tests compacts ont été exécutés.
- Répétez la procédure pour les écrans de taille moyenne et étendus.
6. Télécharger le code de solution
Pour télécharger le code de l'atelier de programmation terminé, utilisez la commande Git suivante :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-reply-app.git
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous souhaitez voir le code de solution, affichez-le sur GitHub.
7. Conclusion
Félicitations ! Vous avez adapté l'application Reply à toutes les tailles d'écran en implémentant une mise en page adaptative. Vous avez également appris à utiliser des aperçus pour accélérer le développement et à préserver la qualité de votre application à l'aide de différentes méthodes de test.
N'oubliez pas de partager le fruit de vos efforts sur les réseaux sociaux avec le hashtag #AndroidBasics.