💻 Développement

dev-android-kotlin-advisor

Développement Android natif avec Kotlin et Jetpack Compose — architecture MVVM/MVI, UI Compose, Hilt, Room, Coroutines, Flow, tests, Gradle KTS, Play Store.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-android-kotlin-advisor --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-android-kotlin-advisor -Launch"

🚀 Déjà installé ?

claude "/dev-android-kotlin-advisor"

Ou tapez /dev-android-kotlin-advisor dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

AndroidKotlinJetpack ComposeAndroid StudioGradleRoomHiltCoroutinesPlay Store

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-android-kotlin-advisor ~/.claude/skills/

Payload du plugin : skills/dev-android-kotlin-advisor · source éditable : dev-skills/android-kotlin-advisor

📖 Manuel

Android Kotlin Advisor

1. Choix d'architecture

ContextePattern recommandé
App simple, 1–3 écransMVVM + Repository (StateFlow)
App Compose multi-écransMVI (état immuable, sealed class UiState)
Large équipe / multi-featureClean Architecture + multi-module Gradle

Structure recommandée multi-module :

app/
feature/home/
feature/profile/
core/data/
core/domain/
core/ui/
build-logic/               ← convention plugins partagés

2. UI avec Jetpack Compose

Toujours préférer les composables stateless (state hoisting) :

// BON : state hissé, composable testable et réutilisable
@Composable
fun LoginForm(
    state: LoginUiState,
    onEmailChange: (String) -> Unit,
    onSubmit: () -> Unit,
) { ... }

// MAUVAIS : état caché dans le composable, non testable
@Composable
fun LoginForm() {
    var email by remember { mutableStateOf("") }
    ...
}

Checklist Compose :

3. State management

// ViewModel pattern recommandé
@HiltViewModel
class HomeViewModel @Inject constructor(
    private val repo: PostRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

    init { loadPosts() }

    private fun loadPosts() = viewModelScope.launch {
        repo.getPosts()
            .catch { _uiState.value = HomeUiState.Error(it.message) }
            .collect { _uiState.value = HomeUiState.Success(it) }
    }
}

sealed class HomeUiState {
    object Loading : HomeUiState()
    data class Success(val posts: List<Post>) : HomeUiState()
    data class Error(val msg: String?) : HomeUiState()
}

Collecte lifecycle-aware dans un composable :

val state by viewModel.uiState.collectAsStateWithLifecycle()

4. Injection de dépendances

Hilt (recommandé pour projets standard Google) :

// App
@HiltAndroidApp class App : Application()

// Fragment/Activity
@AndroidEntryPoint class HomeFragment : Fragment()

// Module
@Module @InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .build()
}

Koin (léger, sans génération de code) :

val appModule = module {
    singleOf(::UserRepository)
    viewModelOf(::HomeViewModel)
}
// App: startKoin { modules(appModule) }

Critère de choix : Hilt si AGP 8+, équipe > 3, besoin de validation compile-time. Koin si prototype, équipe Dagger-phobe, ou KMP envisagé.

5. Couche Data

Room (ORM local) :

@Entity data class Post(@PrimaryKey val id: Int, val title: String)

@Dao interface PostDao {
    @Query("SELECT * FROM post") fun getAll(): Flow<List<Post>>
    @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(post: Post)
}

@Database(entities = [Post::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
    abstract fun postDao(): PostDao
    // migration : addMigrations(MIGRATION_1_2)
}

Retrofit + kotlinx.serialization :

@Serializable data class PostDto(val id: Int, val title: String)

interface PostApi {
    @GET("posts") suspend fun getPosts(): List<PostDto>
}
// OkHttp : ajouter HttpLoggingInterceptor en DEBUG seulement

DataStore (remplace SharedPreferences) :

val Context.dataStore by preferencesDataStore("settings")
val DARK_MODE = booleanPreferencesKey("dark_mode")
// write: dataStore.edit { it[DARK_MODE] = true }
// read:  dataStore.data.map { it[DARK_MODE] ?: false }

6. Concurrence — Coroutines & Flow

// NE PAS utiliser GlobalScope
// NE PAS bloquer avec runBlocking dans le code de prod

// Dispatchers : IO pour réseau/disk, Default pour CPU, Main pour UI
viewModelScope.launch(Dispatchers.IO) {
    val result = api.getPosts()      // suspend fun
    withContext(Dispatchers.Main) { /* màj UI */ }
}

// Canal one-shot (navigation, snackbar)
private val _events = Channel<UiEvent>(Channel.BUFFERED)
val events = _events.receiveAsFlow()
// emit: _events.send(UiEvent.NavigateToDetail(id))

7. Tests

// Unit test ViewModel avec Turbine + MockK
@Test fun `loading posts emits Success state`() = runTest {
    val repo = mockk<PostRepository>()
    every { repo.getPosts() } returns flowOf(listOf(Post(1, "titre")))
    val vm = HomeViewModel(repo)
    vm.uiState.test {
        assertEquals(HomeUiState.Loading, awaitItem())
        assertTrue(awaitItem() is HomeUiState.Success)
        cancelAndIgnoreRemainingEvents()
    }
}

// Compose UI test
@get:Rule val composeRule = createComposeRule()

@Test fun `login button is disabled when email is empty`() {
    composeRule.setContent { LoginForm(state = LoginUiState(), ...) }
    composeRule.onNodeWithTag("btn_submit").assertIsNotEnabled()
}

Dépendances test :

testImplementation("io.mockk:mockk:1.13.x")
testImplementation("app.cash.turbine:turbine:1.x")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.x")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")

8. Gradle KTS & build

Convention plugin partagé (build-logic/) :

// build-logic/src/.../AndroidFeatureConventionPlugin.kt
class AndroidFeatureConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) = with(target) {
        pluginManager.apply("com.android.library")
        pluginManager.apply("org.jetbrains.kotlin.android")
        extensions.configure<LibraryExtension> {
            compileSdk = 35
            defaultConfig.minSdk = 24
        }
    }
}

Signature en CI (ne jamais committer le keystore) :

# Variables CI : KEY_ALIAS, KEY_PASSWORD, STORE_PASSWORD, KEYSTORE_BASE64
echo "$KEYSTORE_BASE64" | base64 -d > release.jks

Garde-fous / Anti-patterns

Anti-patternCorrectif
Context dans un Singleton/ViewModelUtiliser applicationContext ou injecter via Hilt @ApplicationContext
Recomposition excessiveWrapper lambda en remember { {} }, utiliser derivedStateOf pour les calculs coûteux
runBlocking en prodToujours launch ou async dans un scope approprié
Mutation d'état UI depuis le thread IOToujours withContext(Dispatchers.Main) ou StateFlow
SharedPreferences dans code ComposeMigrer vers DataStore
Hardcoder les URLs / clés APIBuildConfig + variables CI / secrets manager
Ignorer les migrations RoomToujours définir Migration(oldV, newV), ne jamais utiliser fallbackToDestructiveMigration() en prod
Fragment backstack manuel en ComposeDéléguer entièrement à navigation-compose

Versions de référence (2026)