Communitygithub.com

lerchenflo/claudeskills

Usa esta skill para migrar tu app de Jetpack Compose y añadir soporte edge-to-edge adaptativo, además de solucionar problemas comunes. Úsala para arreglar componentes de UI (como botones o listas) que quedan ocultos o se solapan con la barra de navegación o la barra de estado, corregir insets del IME y garantizar la legibilidad de las barras del sistema.

¿Qué es claudeskills?

Esta skill automatiza la migración de aplicaciones Android Jetpack Compose a un diseño edge-to-edge adaptativo, asegurando que la UI se renderice correctamente detrás de las barras del sistema (barra de estado, barra de navegación) y que los componentes críticos sigan siendo accesibles. Es útil para desarrolladores que quieran modernizar sus apps, eliminando el espacio en blanco tradicional y aprovechando al máximo la pantalla, especialmente en dispositivos con muescas o gestos. La skill soporta los ajustes necesarios en Activities, Scaffolds, componentes de Material 2 y 3, listas, FABs, campos de texto (IME) y diálogos a pantalla completa, proporcionando patrones de código correctos e incorrectos para evitar errores comunes como el doble padding. Funciona con proyectos que usan Android Jetpack Compose y apuntan a SDK 35 o superior, guiando al usuario paso a paso desde la planificación hasta la verificación final.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lerchenflo/claudeskills/tree/HEAD/android/edge-to-edge

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Prerrequisitos

  • El proyecto DEBE usar Android Jetpack Compose.
  • El proyecto DEBE apuntar a SDK 35 o superior. Si el SDK es inferior a 35, aumentar el SDK a 35.

Paso 1: planificar

  1. Localizar y analizar todas las clases Activity para detectar cuáles tienen soporte edge-to-edge existente. Para cada Activity que no lo tenga, planificar hacerla edge-to-edge.
  2. En cada Activity, localizar y analizar todos los componentes de listas y FAB para detectar cuáles tienen soporte edge-to-edge existente. Para cada componente sin soporte, planificar hacerlo edge-to-edge.
  3. En cada Activity, buscar TextField, OutlinedTextField o BasicTextField. Si se encuentran, DEBES verificar que el IME no oculte el campo de entrada siguiendo la sección de IME de esta skill.

Paso 2: añadir soporte edge-to-edge

  1. Agregar enableEdgeToEdge antes de setContent en onCreate en cada Activity que aún no llame a enableEdgeToEdge.
  2. Agregar android:windowSoftInputMode="adjustResize" en el AndroidManifest.xml para todas las Activities que usen teclado suave.

Paso 3: aplicar insets

  • La app DEBE aplicar los insets del sistema, o alinear el contenido a las reglas, para que la UI crítica siga siendo tachable. Elige solo un método para evitar el doble padding:

    1. PREFERIDO: Cuando esté disponible, usa Scaffolds y pasa PaddingValues al lambda de contenido.
    Scaffold { innerPadding ->
        // innerPadding tiene en cuenta las barras del sistema y cualquier componente del Scaffold
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .consumeWindowInsets(innerPadding),
            contentPadding = innerPadding
        ) { /* Contenido */ }
    }
    
    1. PREFERIDO: Cuando esté disponible, usa el manejo automático de insets o los modificadores de padding en componentes de Material.

      • Los componentes de Material 3 gestionan las áreas seguras para sus propios componentes, incluyendo:
        • TopAppBar
        • SmallTopAppBar
        • CenterAlignedTopAppBar
        • MediumTopAppBar
        • LargeTopAppBar
        • BottomAppBar
        • ModalDrawerSheet
        • DismissibleDrawerSheet
        • PermanentDrawerSheet
        • ModalBottomSheet
        • NavigationBar
        • NavigationRail
      • Para componentes de Material 2, usa el parámetro windowInsets para aplicar insets manualmente en BottomAppBar, TopAppBar y BottomNavigation. NO apliques padding al contenedor padre; en su lugar, pasa los insets directamente al componente de la barra. Aplicar padding al contenedor padre evita que el fondo de la barra se dibuje en el área de la barra del sistema. Por ejemplo, para TopAppBar, elige solo una de las siguientes opciones:
        1. PREFERIDO: TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)
        2. TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))
        3. TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
    2. Para componentes fuera de un Scaffold, usa modificadores de padding, como Modifier.safeDrawingPadding() o Modifier.windowInsetsPadding(WindowInsets.safeDrawing).

      Box(
          modifier = Modifier
              .fillMaxSize()
              .safeDrawingPadding()
      ) {
          Button(
              onClick = {},
              modifier = Modifier.align(Alignment.BottomCenter)
          ) {
              Text("Login")
          }
      }
      
    3. Para componentes profundamente anidados con padding excesivo, usa WindowInsetsRulers (ej. Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). Consulta la sección IME para un ejemplo de código.

    4. Cuando necesites que un elemento (ej. un encabezado personalizado o un scrim decorativo) tenga las mismas dimensiones que una barra del sistema, usa modificadores de tamaño de insets (ej. Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). Consulta la sección Listas para un ejemplo de código.

Scaffolds adaptativos

  • NavigationSuiteScaffold gestiona las áreas seguras para sus propios componentes, como NavigationRail o NavigationBar. Sin embargo, los scaffolds adaptativos (ej. NavigationSuiteScaffold, ListDetailPaneScaffold) no propagan PaddingValues a su contenido interno. DEBES aplicar insets a cada pantalla o componente individual (ej. contentPadding de lista o padding de FAB) como se describe en Paso 3. NO apliques safeDrawingPadding o modificadores similares al padre de NavigationSuiteScaffold. Esto recorta e impide una pantalla edge-to-edge.

IME

  • Para cada Activity con teclado suave, verifica que android:windowSoftInputMode="adjustResize" esté configurado en el AndroidManifest.xml. NO uses SOFT_INPUT_ADJUST_RESIZE porque está obsoleto. Luego, mantén el foco en el campo de entrada. Elige una opción:
      1. PREFERIDO: Agrega Modifier.fitInside(WindowInsetsRulers.Ime.current) al contenedor de contenido. Esto es preferible a imePadding() porque reduce el jank y el padding extra causado por olvidar consumir insets aguas arriba en la jerarquía.
      1. Agrega imePadding al contenedor de contenido. El modificador de padding DEBE colocarse antes de Modifier.verticalScroll(). NO uses Modifier.imePadding() si el padre ya tiene en cuenta el IME con contentWindowInsets (ej. contentWindowInsets = WindowInsets.safeDrawing). Hacerlo causará doble padding.

Patrones de código para IMEs con Scaffolds

CORRECTO

CORRECTO porque contentWindowInsets contiene insets del IME, que se pasan al lambda de contenido como innerPadding.

// CORRECTO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* Contenido */ }
}

CORRECTO porque fitInside ajusta el contenido a los insets del IME independientemente de contentWindowInsets.

// CORRECTO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .fitInside(WindowInsetsRulers.Ime.current)
            .verticalScroll(rememberScrollState())
    ) { /* Contenido */ }
}

CORRECTO porque el contentWindowInsets por defecto no contiene insets del IME, y imePadding() aplica los insets del IME:

// CORRECTO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* Contenido */ }
}

INCORRECTO

INCORRECTO porque habrá padding excesivo cuando se abra el IME. Los insets del IME se aplican dos veces, una con innerPadding, que contiene los insets del IME de los valores de contentWindowInsets pasados, y otra con imePadding:

// INCORRECTO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* Contenido */ }
}

INCORRECTO porque el IME cubrirá el contenido. El contentWindowInsets por defecto del Scaffold NO contiene insets del IME.

// INCORRECTO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* Contenido */ }
}

Patrones de código para IMEs sin Scaffolds

CORRECTO

Los siguientes ejemplos de código NO causarán padding excesivo.

// CORRECTO
Box(
    // Insets consumidos
    modifier = Modifier.safeDrawingPadding() // o imePadding(), safeContentPadding(), safeGesturesPadding()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Contenido */ }
}

// CORRECTO
Box(
    // Insets consumidos
    modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // o WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Contenido */ }
}

// CORRECTO
Box(
    // Insets no consumidos, pero irrelevante debido a fitInside
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // o WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .fitInside(WindowInsetsRulers.Ime.current)
    ) { /* Contenido */ }
}

INCORRECTO

El siguiente ejemplo de código CAUSARÁ padding excesivo porque los insets del IME se aplican dos veces:

// INCORRECTO
Box(
    // Insets no consumidos
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // o WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Contenido */ }
}

Contraste de la barra de navegación e íconos de las barras del sistema

  • Si la Activity usa enableEdgeToEdge de WindowCompat, DEBES establecer isAppearanceLightNavigationBars e isAppearanceLightStatusBars al inverso del tema del dispositivo para apps que soporten tema claro y oscuro, para que los íconos de las barras del sistema sean legibles. Se recomienda hacer esto en tu archivo de tema. NO hagas esto si las Activities usan enableEdgeToEdge de ComponentActivity, porque maneja los colores de los íconos automáticamente.

    // Usar solo si se llama a `enableEdgeToEdge` desde `WindowCompat`.
    // Aplicar a tu archivo de tema.
    @Composable
    fun MyTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        content: @Composable () -> Unit
    ) {
        val view = LocalView.current
        if (!view.isInEditMode) {
            SideEffect {
                val window = (view.context as? Activity)?.window ?: return@SideEffect
                val controller = WindowCompat.getInsetsController(window, view)
    
                // Íconos oscuros para modo claro (!darkTheme), íconos claros para modo oscuro
                controller.isAppearanceLightStatusBars = !darkTheme
                controller.isAppearanceLightNavigationBars = !darkTheme
            }
        }
    
        MaterialTheme(content = content)
    }
    
  • Si alguna pantalla usa un Scaffold o un NavigationSuiteScaffold con una barra inferior (ej. BottomAppBar, NavigationBar), establece window.isNavigationBarContrastEnforced = false en la Activity correspondiente para SDK 29+. Esto evita que el sistema agregue un fondo translúcido a la barra de navegación, asegurando que los colores de tu barra inferior se extiendan hasta la parte inferior de la pantalla.

Listas

  • Aplica el padding de insets (como el innerPadding del Scaffold) al parámetro contentPadding de los componentes desplazables (ej. LazyColumn, LazyRow). NO lo apliques como Modifier.padding() al contenedor padre de la lista, ya que esto recorta el contenido y evita que se desplace detrás de las barras del sistema.
  • Crea un composable translúcido que cubra la barra del sistema para que los íconos sigan siendo legibles.
class SystemBarProtectionSnippets : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // enableEdgeToEdge establece window.isNavigationBarContrastEnforced = true
        // que se usa para agregar un scrim translúcido en la navegación de tres botones
        enableEdgeToEdge()

        setContent {
            MyTheme {
                // Contenido principal
                MyContent()

                // Después de dibujar el contenido principal, dibujar protección de la barra de estado
                StatusBarProtection()
            }
        }
    }
}

@Composable
private fun StatusBarProtection(
    color: Color = MaterialTheme.colorScheme.surfaceContainer,
) {
    Spacer(
        modifier = Modifier
            .fillMaxWidth()
            .height(
                with(LocalDensity.current) {
                    (WindowInsets.statusBars.getTop(this) * 1.2f).toDp()
                }
            )
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        color.copy(alpha = 1f),
                        color.copy(alpha = 0.8f),
                        Color.Transparent
                    )
                )
            )
    )
}

Diálogos

Si ambas condiciones siguientes son verdaderas, entonces el diálogo es a pantalla completa y debe hacerse edge-to-edge:

  1. DialogProperties contiene usePlatformDefaultWidth = false.
  2. El diálogo llama a Modifier.fillMaxSize().

Para hacer que un diálogo a pantalla completa sea edge-to-edge, establece decorFitsSystemWindows = false en DialogProperties.

Dialog(
    onDismissRequest = { /* Manejar cierre */ },
    properties = DialogProperties(
        // 1. Permite que el diálogo abarque todo el ancho de la pantalla
        usePlatformDefaultWidth = false,
        // 2. Permite que el diálogo se dibuje detrás de las barras de estado y navegación
        decorFitsSystemWindows = false
    )
) { /* Contenido */ }

Lista de verificación

  • ¿Cada Activity llama a enableEdgeToEdge()?
  • ¿Está configurado adjustResize en el AndroidManifest.xml?
  • ¿Cada TextField, OutlinedTextField o BasicTextField tiene un padre con imePadding(), fitInside, Modifier.safeDrawingPadding(), Modifier.safeContentPadding(), Modifier.safeGesturesPadding(), o contentWindowInsets establecido a WindowInsets.safeDrawing o WindowInsets.ime?
  • ¿El primer y último elemento de la lista se dibujan alejados de las barras del sistema pasando insets a contentPadding?
  • ¿Los FABs se dibujan sobre las barras de navegación ya sea estando dentro de un Scaffold o aplicando Modifier.safeDrawingPadding()?
  • ¿El proyecto compila? Ejecuta ./gradlew build para asegurarte.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

lerchenflo/claudeskills

Upgrades, or migrates, an Android project to use Android Gradle Plugin (AGP) version 9. Do not use this skill for migrating Kotlin Multiplatform (KMP) projects.

lerchenflo/claudeskills

Provides instructions for installing and using the `android` CLI. The `android` command-line tool is a critical tool for Android development and helps you create new Android projects, run Android apps on devices, manage and interact with Android virtual devices (including screenshots and UI inspection), manage Android SDK components, look up official Android documentation, and discover and install official Android skills.

lerchenflo/claudeskills

Manages Android performance profiling and debugging. Triggers when the user asks to record or analyze Android performance data, such as system traces, heap dumps, method recordings, callstack samples, memory allocations, or investigate bottlenecks, jank, memory leaks, and app startup issues on Android, or when the user asks to write, debug, or execute ad-hoc SQL queries. Applies to both user and system apps or services.

lerchenflo/claudeskills

Analyzes Android apps to identify key user workflows for AppFunctions such as creating a note, playing media, or sending an automated or AI agent triggered message, voice commands, or system shortcuts, without needing to open the app UI. Generates Kotlin code to expose these workflows to the Android system, allowing agents to discover and execute them on-device. Also refines KDoc documentation to ensure AI agents correctly understand and use the provided functionality.

Skills relacionados