Développement Kotlin/Android

VérifiéSûr

Développez des applications Android avec Kotlin en suivant les meilleures pratiques. Couvre Jetpack Compose, coroutines, gestion d'état et configuration Gradle.

Spar Skills Guide Bot
DeveloppementIntermédiaire
11/03/2026
Claude Code
#kotlin#android#jetpack-compose#gradle-kotlin-dsl#null-safety

Recommandé pour

Synthèse automatique

Fournit des conseils de développement Kotlin et Android pour les projets Gradle/Compose, couvrant la configuration de build, la null-safety, les classes scellées, les classes de données/valeur et la gestion des erreurs.

Points forts

  • Inclut une configuration complète en Gradle Kotlin DSL et un catalogue de versions pour une application Compose.
  • Présente des idiomes Kotlin courants : appels sûrs, opérateur Elvis, Result scellé et classes de valeur.
  • Les exemples sont courts et directement réutilisables dans un projet Android.

Limites

  • Le contenu fourni s'arrête avant de détailler les coroutines et la gestion d'état annoncées dans la description.
  • Il est lié à Android et à des versions d'outils précises, donc peut vieillir avec AGP, Kotlin et Compose.
  • Il n'approfondit pas les tests, l'architecture ni les stratégies de migration.
Quand l'utiliser

À utiliser pour initialiser ou écrire du code Android en Kotlin avec Gradle Kotlin DSL et Jetpack Compose.

Quand l'éviter

À éviter pour du Kotlin hors Android, comme des services backend, ou lorsqu'il faut des conseils de migration à jour selon les versions.

Analyse de sécurité

Sûr
Score qualité82/100

The skill is purely instructional Kotlin/Android documentation. The declared Bash usage is limited to standard Gradle commands (build, test, lint, clean) executed in the developer's own project; nothing exfiltrates data, decodes payloads, disables safety mechanisms, or touches destructive paths. The only inherent risk is that `./gradlew` runs project-local build scripts, which is the normal and expected Android development workflow.

Points d'attention
  • •Declares Bash, Write, and Edit, but all listed shell usage is standard Gradle build/test/lint invocations; no destructive, obfuscated, or network-exfiltration instructions present.
  • •Example code includes a not-null assertion (`user!!.name`), flagged in-text as 'use sparingly' — a code-quality nit, not a skill-level risk.
Comment cette analyse est faite

Exemples

Scaffold Compose app
Create a new Android app in Kotlin with Gradle Kotlin DSL, a version catalog, Jetpack Compose, minSdk 26, and targetSdk 35.
Model result states
Refactor this Kotlin function to return a sealed Result type and handle Success, Error, and Loading exhaustively in a when expression.
Configure Gradle version catalog
Set up settings.gradle.kts, build.gradle.kts, and libs.versions.toml for a Compose Android app using Kotlin 2.0.0 and AGP 8.5.0.

name: kotlin description: Write Kotlin code for Android following best practices. Use when developing with Jetpack Compose, Android SDK, or Kotlin projects. Covers coroutines, state management, and tooling. allowed-tools: Read, Write, Edit, Bash, Glob, Grep

Kotlin / Android Development

Project Setup

Gradle Kotlin DSL

// settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "MyApp"
include(":app")

build.gradle.kts (app)

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.example.myapp"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 26
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
    }

    buildFeatures {
        compose = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = "17"
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.material3)

    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
}

Version Catalog (libs.versions.toml)

[versions]
kotlin = "2.0.0"
compose-bom = "2024.06.00"
lifecycle = "2.8.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.13.1" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }

[plugins]
android-application = { id = "com.android.application", version = "8.5.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

Type Patterns

Null Safety

// Safe call operator
val length = name?.length

// Elvis operator
val displayName = user?.name ?: "Guest"

// Safe cast
val number = value as? Int

// Not-null assertion (use sparingly)
val name = user!!.name

// let for null checks
user?.let { safeUser ->
    println(safeUser.name)
}

Sealed Classes

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

// Exhaustive when
fun handleResult(result: Result<User>) = when (result) {
    is Result.Success -> showUser(result.data)
    is Result.Error -> showError(result.exception)
    Result.Loading -> showLoading()
}

Data Classes

data class User(
    val id: String,
    val email: String,
    val name: String,
    val createdAt: Instant = Instant.now()
)

// Copy with modifications
val updatedUser = user.copy(name = "New Name")

// Destructuring
val (id, email, name) = user

Value Classes

@JvmInline
value class UserId(val value: String)

@JvmInline
value class Email(val value: String) {
    init {
        require(value.contains("@")) { "Invalid email" }
    }
}

Error Handling

Result Type

fun parseNumber(input: String): Result<Int> {
    return try {
        Result.success(input.toInt())
    } catch (e: NumberFormatException) {
        Result.failure(e)
    }
}

// Usage
parseNumber("123")
    .onSuccess { number -> println("Parsed: $number") }
    .onFailure { error -> println("Error: ${error.message}") }

// Transform
val doubled = parseNumber("42")
    .map { it * 2 }
    .getOrDefault(0)

runCatching

val result = runCatching {
    riskyOperation()
}.getOrElse { error ->
    logError(error)
    defaultValue
}

// Chain operations
runCatching { fetchUser(id) }
    .mapCatching { user -> processUser(user) }
    .onSuccess { result -> display(result) }
    .onFailure { error -> showError(error) }

Coroutines

Basic Coroutines

// Suspend function
suspend fun fetchUser(id: String): User {
    return withContext(Dispatchers.IO) {
        api.getUser(id)
    }
}

// Launch coroutine
viewModelScope.launch {
    try {
        val user = fetchUser("123")
        _uiState.value = UiState.Success(user)
    } catch (e: Exception) {
        _uiState.value = UiState.Error(e.message)
    }
}

Flow

// Create flow
fun observeUsers(): Flow<List<User>> = flow {
    while (true) {
        emit(repository.getUsers())
        delay(5000)
    }
}.flowOn(Dispatchers.IO)

// Collect flow
viewModelScope.launch {
    observeUsers()
        .catch { e -> emit(emptyList()) }
        .collect { users ->
            _users.value = users
        }
}

StateFlow

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun loadUser(id: String) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser(id)
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

Jetpack Compose

Basic Composable

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello, $name!",
        modifier = modifier.padding(16.dp),
        style = MaterialTheme.typography.headlineMedium
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MyAppTheme {
        Greeting("Android")
    }
}

State Management

@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }

    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Count: $count",
            style = MaterialTheme.typography.headlineMedium
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

ViewModel Integration

@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> UserContent(state.user)
        is UiState.Error -> ErrorMessage(state.message)
    }
}

@Composable
fun UserContent(user: User) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(user.name, style = MaterialTheme.typography.titleLarge)
        Text(user.email, style = MaterialTheme.typography.bodyMedium)
    }
}

Side Effects

@Composable
fun UserDetailScreen(userId: String, viewModel: UserViewModel = hiltViewModel()) {
    // Run once when userId changes
    LaunchedEffect(userId) {
        viewModel.loadUser(userId)
    }

    // Run on every recomposition
    SideEffect {
        analytics.trackScreen("UserDetail")
    }

    // Cleanup when leaving composition
    DisposableEffect(Unit) {
        val listener = viewModel.addListener()
        onDispose {
            listener.remove()
        }
    }
}

Testing

Unit Tests (JUnit 5)

class UserViewModelTest {
    @Test
    fun `loadUser updates state to success`() = runTest {
        val repository = mockk<UserRepository>()
        coEvery { repository.getUser("123") } returns User("123", "test@example.com")

        val viewModel = UserViewModel(repository)
        viewModel.loadUser("123")

        assertEquals(
            UiState.Success(User("123", "test@example.com")),
            viewModel.uiState.value
        )
    }

    @Test
    fun `loadUser updates state to error on failure`() = runTest {
        val repository = mockk<UserRepository>()
        coEvery { repository.getUser(any()) } throws IOException("Network error")

        val viewModel = UserViewModel(repository)
        viewModel.loadUser("123")

        assertTrue(viewModel.uiState.value is UiState.Error)
    }
}

Compose UI Tests

class UserScreenTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun displaysUserName() {
        val user = User("1", "test@example.com", "John Doe")

        composeTestRule.setContent {
            MyAppTheme {
                UserContent(user = user)
            }
        }

        composeTestRule.onNodeWithText("John Doe").assertIsDisplayed()
        composeTestRule.onNodeWithText("test@example.com").assertIsDisplayed()
    }

    @Test
    fun buttonClickIncrementsCounter() {
        composeTestRule.setContent {
            MyAppTheme {
                Counter()
            }
        }

        composeTestRule.onNodeWithText("Count: 0").assertIsDisplayed()
        composeTestRule.onNodeWithText("Increment").performClick()
        composeTestRule.onNodeWithText("Count: 1").assertIsDisplayed()
    }
}

Tooling

# Build
./gradlew build
./gradlew assembleDebug
./gradlew assembleRelease

# Run tests
./gradlew test                          # Unit tests
./gradlew connectedAndroidTest          # Instrumented tests

# Linting
./gradlew detekt                        # Code smells
./gradlew ktlintCheck                   # Style check
./gradlew ktlintFormat                  # Auto-fix style

# Code analysis
./gradlew lint                          # Android Lint

# Clean
./gradlew clean

detekt.yml

build:
  maxIssues: 0

complexity:
  LongMethod:
    threshold: 30
  ComplexCondition:
    threshold: 4

style:
  MaxLineLength:
    maxLineLength: 120
  WildcardImport:
    active: true

naming:
  FunctionNaming:
    functionPattern: '[a-z][a-zA-Z0-9]*'

.editorconfig (ktlint)

[*.{kt,kts}]
indent_size = 4
max_line_length = 120
ktlint_code_style = android_studio

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/kotlin-android-development-mmlj2nyh' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/kotlin-android-development-mmlj2nyh/download' --output '.skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md.part' && mv '.skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md.part' '.skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Outils déclarés par l’auteur (à vérifier dans votre agent) : Read, Write, Edit, Bash, Glob, Grep

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.

Claude Code
test -f '.skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md' && test -s '.skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/kotlin' && cp -R '.skill-downloads/kotlin-android-development-mmlj2nyh' '.claude/skills/kotlin'

OpenSkills

OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.

Installation officielle d’OpenSkills
openskills install './.skill-downloads/kotlin-android-development-mmlj2nyh'

openskills read 'kotlin-android-development-mmlj2nyh'

La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.

Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.

openskills sync

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/kotlin-android-development-mmlj2nyh/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires