Blueprint d'une Activity android mettant en avant les espacements à prendre en compte pour le support edge-to-edge de l'API 35.

Migrer vers Android API 35 : guide rapide et minimaliste

Vous devez migrer votre application Android vers l’API 35 ? Ce guide vous explique comment réussir cette mise à jour avec un minimum d’efforts, tout en respectant les exigences Google Play et en garantissant une expérience utilisateur acceptable sur les appareils modernes.

Depuis juillet 2025, Google impose aux développeurs de cibler l’API Android 15 (API 35) ou plus pour publier ou mettre à jour leurs applications. Si, comme moi, vous maintenez une application gratuite et stable, il est essentiel d’optimiser votre temps et d’éviter une refonte complète.

L’arrivée des écrans edge-to-edge, des coins arrondis et des encoches (notch) sur les appareils Android récents pose de nouveaux défis : chevauchement de la status bar, Floating Action Buttons (FAB) difficiles d’accès, contenu masqué par la navigation bar… Ce guide vous aide à adapter rapidement votre interface sans tout réécrire.

Caractéristiques de l’application Aesthetics Workout

Dans le cadre de cet article, je vous propose une étude de cas sur l’application Aesthetics Workout, qui est mature, stable et que je maintiens depuis plusieurs années.

Aesthetics Workout est une application Android de fitness qui permet de suivre ses entraînements, planifier des séances et visualiser ses progrès. Lancée en 2018, elle repose sur Material Design 1.0, et après tant d’années, l’application est stable mais peu adaptée aux exigences modernes.

Au fil des années, j’ai intégré les packages Androidx et Material Components pour assurer la compatibilité, mais j’ai choisi de conserver le design d’origine.

Configurer correctement votre émulateur Android API 35 ou plus

Avant de commencer les tests, il est primordial de configurer un émulateur Android qui reflète les spécificités des appareils modernes pour lesquels les contraintes edge-to-edge sont les plus fortes.

Pour cela, vous avez deux options :

  • Créer un émulateur Android 15 ou plus récent avec un skin adapté aux écrans arrondis et aux encoches comme le Pixel 7 pro,
  • Activer les encoches dans les paramètres des « options pour les développeurs ».

Problèmes rencontrés avec la migration Android API 35

Les premiers tests sur l’émulateur Android 16 (API 36) révèlent plusieurs limites du kit graphique historique qui ne sont pas compatibles avec les exigences modernes :

  • Les boutons flottants d’action sont difficiles à atteindre à cause de la navigation bar.
  • Les encoches (notch) des caméras frontales masquent des éléments de l’interface.
  • L’app bar est chevauchée par la status bar, rendant l’affichage confus.

Pourquoi la refonte complète n’est pas réaliste ?

La documentation officielle Android recommande une refonte de l’interface pour gérer l’edge-to-edge et les écrans arrondis. Mais avec des dizaines d’activités et de fragments, l’effort serait disproportionné pour une application 100 % gratuite et sans revenus publicitaires.

Pour respecter l’API cible Android 15 et offrir une expérience utilisateur correcte, il faudrait prendre en compte les spécificités des nouveaux appareils à plusieurs niveaux :

  • Ajouter un padding au dessus de l’app bar pour éviter le chevauchement.
  • Repositionner les FAB pour les rendre accessibles.
  • Adapter les RecyclerView pour que le contenu ne soit pas masqué par la navigation bar ou l’encoche.
  • Ajuster les BottomSheet pour garantir leur visibilité.

Diagnostic préliminaire

Pour évaluer les modifications nécessaires, j’ai parcouru l’ensemble de l’application et testé la solution la plus simple : ajouter un padding sur le conteneur principal de chaque activité grâce à la méthode setOnApplyWindowInsetsListener. En utilisant ce callback, je demande au système de déterminer les marges internes nécessaires en prenant en compte les encoches WindowInsets.Type.displayCutout() et les barres système WindowInsets.Type.systemBars().

Screen shot de l'interface utilisateur de l'application Aesthetics Workout sur un mobile Android API 35 après ajout du padding.

Comme vous le voyez sur l’image précédente, je me retrouve avec une barre blanche en haut et en bas de l’écran lorsque l’écran est en mode portrait, et un espace blanc à gauche et à droite lorsque l’écran est en mode paysage. Ce n’est pas idéal, mais cela permet de résoudre la majorité des problèmes de chevauchement et de visibilité :

  • Les FAB sont complètement visibles et accessibles.
  • L’app bar est positionnée sous la status bar.

Pour ces activités, il me restera à trouver une solution pour gérer les espacements blancs pour rendre l’interface plus esthétique.

Malheureusement, cette solution ne fonctionne pas pour l’activité la plus complexe (celle de du suivi de l’entraînement en temps réel), car l’interface est trop chargée, et le padding engendre des problèmes avec le BottomSheetBehavior. Même en appliquant l’attribut fitSystemWindows="true", les composants ne s’ajustent pas correctement.
Pour cette dernière, il faudra envisager une refonte partielle de l’interface graphique sans dégrader l’expérience utilisateur.

Appliquer un padding et un background dynamique aux conteneurs roots

Après le diagnostic préliminaire, j’ai constaté qu’en appliquant un padding sur le conteneur root, je règle 95% des problèmes d’accessibilité et de chevauchement des composants graphiques. Il est donc probable que cette solution suffise également pour la majorité de vos activités. Je vous propose donc de découvrir en détail comment procéder.

Étape 1 : Ajouter un padding au conteneur root

Cette première étape est simple et rapide. Elle est d’ailleurs recommandée par la documentation officielle Android. Il suffit d’ajouter un listener pour les WindowInsets dans le conteneur root de chaque activité.

Pour cela, j’ai créer une classe utilitaire EdgeToEdgeUtil qui applique la configuration commune à toutes les activités. Voici le code :

package com.aesthetics.workout.core.graphics.ui;

import android.app.Activity;
import android.view.View;

import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;

public class EdgeToEdgeUtil {

    /**
     * Configure activity to have same appearance of non edge to edge device.
     *
     * @param activity The current activity.
     * @param rootViewId The root view id.
     */
    public static void configureEdgeToEdgeForStandardActivity(Activity activity, int rootViewId) {
        // Setup fitSystemWindows to false
        WindowCompat.setDecorFitsSystemWindows(activity.getWindow(), false);

        // Apply padding to root view
        View rootView = activity.findViewById(rootViewId);
        ViewCompat.setOnApplyWindowInsetsListener(
                rootView,
                (v, insets) -> {
                    // Get system bar inserts (status bar and navigation bar)
                    Insets inset = insets.getInsets(
                            WindowInsetsCompat.Type.systemBars()
                                    | WindowInsetsCompat.Type.displayCutout()
                    ); // <1>

                    // Apply padding to root view
                    v.setPadding(inset.left, inset.top, inset.right, inset.bottom);

                    // Return CONSUMED to prevent child view to manage insets
                    return insets;
                }
        );
    }
    
}
1

On récupère les _window insets_ pour les barres système et les encoches grâce à la combinaison des deux flags.

Étape 2 : Ajouter un background dynamique

Un background dynamique pour reproduire le rendu classique

En appliquant un padding, selon la configuration de l’appareil, vous obtiendrez un espace blanc ou noir (selon le thème) au niveau des bordures de l’écran lorsque l’écran est en mode portrait ou paysage (en haut et en bas, ou à gauche et à droite selon l’orientation de l’écran). Pour éviter cet effet inesthétique, la solution la plus simple que j’ai trouvé consiste à appliquer un background dynamique.

Dans la plupart des cas, le conteneur root de vos activités est un CoordinatorLayout ou ConstraintLayout. Il contient généralement un AppBarLayout en haut, éventuellement un BottomNavigationView en bas, et un composant central (FrameLayout, RecyclerView, etc.). En définissant un background dynamique sur le conteneur root, il est possible d’attribuer une couleur spécifique dans les zones de padding, tout en conservant la couleur principale du thème sur le contenu central.

Cette approche permet de régler les effets indésirables du padding et de retrouver un rendu conforme aux standards Material Design 1 :

  • Une status bar légèrement plus sombre que l’app bar.
  • Une navigation bar adaptée (blanche ou noire selon l’appareil).

C’est d’ailleurs le rendu que j’avais sur l’émulateur Android 14 en ciblant l’API 34 (voir l’image suivante).

Screen shot de l'interface utilisateur de l'application Aesthetics Workout sur un mobile Android API 30

Créer un background dynamique

L’objectif est de créer un background dynamique qui utilise la couleur de fond @color/colorPrimaryDark pour la zone supérieure (status bar) et @android:color/white ou @android:color/black pour les zones latérales et inférieures. Pour cela, la solution la plus souple consiste à générer un BitmapDrawable créé dynamiquement à partir des informations de l’écran, des window insets et des couleurs du thème.

package com.aesthetics.workout.core.graphics.ui;
 
 import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
 import android.view.View;
 
+import androidx.core.content.ContextCompat;
 import androidx.core.graphics.Insets;
// ...
 
+import com.aesthetics.workout.R;
+
 public class EdgeToEdgeUtil {
 
+    public static Drawable getBackgroundDrawable(Activity activity, Insets insets) {
+        // Compute screen size
+        DisplayMetrics dm = new DisplayMetrics();
+        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+        // Create bitmap
+        Bitmap bitmap = Bitmap.createBitmap(
+                dm.widthPixels,
+                dm.heightPixels,
+                Bitmap.Config.ARGB_8888
+        );
+        Canvas canvas = new Canvas(bitmap);
+
+        // Draw background
+        canvas.drawColor(Color.TRANSPARENT);
+
+        Paint paint = new Paint();
+        paint.setStyle(Paint.Style.FILL);
+
+        // Draw black rectangles
+        //  - left
+        paint.setColor(Color.BLACK);
+        canvas.drawRect(0, 0, insets.left, dm.heightPixels, paint);
+        //  - right
+        canvas.drawRect(
+                dm.widthPixels - insets.right, 0, dm.widthPixels, dm.heightPixels, paint
+        );
+        //  - bottom
+        canvas.drawRect(
+                insets.left, dm.heightPixels - insets.bottom, dm.widthPixels - insets.right, dm.heightPixels, paint
+        );
+
+        // Draw top
+        paint.setColor(ContextCompat.getColor(activity, R.color.colorPrimaryDark));
+        canvas.drawRect(insets.left, 0, dm.widthPixels - insets.right, insets.top, paint);
+
+        return new BitmapDrawable(activity.getResources(), bitmap);
+    }
+
     /**
      * Configure activity to have same appearance of non edge to edge device.
 // ...

Lorsque vous appliquez ce drawable comme background du conteneur root, seules les zones de padding seront colorées, tandis que le contenu central conservera la couleur principale du thème (centre transparent). Cela permet d’obtenir un rendu graphique conforme aux précédentes versions.

Appliquer le background dynamique

Pour appliquer ce background dynamique, nous devons obligatoirement le faire via le code Java, car les XML de layout attendent un drawable statique. J’ai donc choisi de l’intégré à la classe utilitaire EdgeToEdgeUtil que j’ai créée précédemment. Voici comment procéder :

 // ...
                     // Apply padding to root view
                     v.setPadding(inset.left, inset.top, inset.right, inset.bottom);
 
+                    // Configure background on root view to simulate hold device style using dynamic background
+                    ViewCompat.setBackground(rootView, getBackgroundDrawable(activity, inset));
+
                     // Return CONSUMED to prevent child view to manage insets
                     return insets;
                 }
 // ...

Les mauvaises surprises de la migration vers Android API 36

Comme mentionné précédemment, j’ai choisi de cibler l’API 36 pour anticiper les futures exigences de Google Play. Cependant, cette migration a révélé un problème inattendu : la fin du support du onBackPressed() sur les appareils utilisant la nouvelle API de navigation.

Le système de navigation a évolué pour supporter les gestes de navigation modernes, ce qui a rendu l’implémentation de la navigation arrière plus complexe. En effet, le comportement par défaut de mon émulateur Android 16 ne permet plus d’utiliser onBackPressed() pour gérer la navigation arrière.

Heureusement, il existe une solution simple pour contourner ce problème. Il suffit d’appeler l’ancienne méthode onBackPressed() dans le callback getOnBackInvokedDispatcher().registerOnBackInvokedCallback pour les appareils API 33 et supérieurs. Voici le code source que j’ai ajouté :

// ...
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                    OnBackInvokedDispatcher.PRIORITY_DEFAULT,
                    this::onBackPressed
            );
        }
// ...

Conclusion

Migrer une application Android mature vers l’API 35 peut sembler intimidant face aux nouvelles exigences edge-to-edge et aux écrans arrondis. Pourtant, comme nous l’avons vu, il est souvent possible de résoudre les problèmes avec des solutions simples : l’ajout de padding et d’un background dynamique permet de garantir l’accessibilité et la compatibilité sans devoir reprendre toutes les Activity.

Chaque application ayant ses spécificités, il est probable que vous rencontriez d’autres défis ou trouviez des astuces complémentaires. N’hésitez pas à partager vos retours d’expérience, vos questions ou vos solutions dans les commentaires. Je suis curieux de découvrir comment vous avez abordé la migration de votre application vers l’API 35 ou 36.

Laisser un commentaire