// Chiffrement transparent via delegateclassEncryptedPrefsDelegate(privateval prefs: SharedPreferences, privateval key: String) :
ReadWriteProperty<Any?, String?> {
overridefungetValue(thisRef: Any?, property: KProperty<*>) =
prefs.getString(key, null)?.decrypt()
overridefunsetValue(thisRef: Any?, property: KProperty<*>, value: String?) {
prefs.edit { putString(key, value?.encrypt()) }
}
}
classUserSession(prefs: SharedPreferences) {
var token: String? by EncryptedPrefsDelegate(prefs, "auth_token")
}
// Delegates Android courantsval viewModel: MyViewModel by viewModels()
val args: MyFragmentArgs by navArgs()
val binding by viewBinding(FragmentPaymentBinding::bind)
6. Inline Functions et Reified Types
// Parsing générique type-safe (évite Class<T> explicite)inlinefun<reified T> String.fromJson(): T =
Moshi.Builder().build().adapter(T::class.java).fromJson(this)!!
val response: PaymentResponse = jsonString.fromJson()
// Mesure de performance sans overhead lambdainlinefun<T>measureMs(label: String, block: () -> T): T {
val start = System.currentTimeMillis()
return block().also { Log.d("PERF", "$label: ${System.currentTimeMillis() - start}ms") }
}
// Logging conditionnel sans allocation si désactivéinlinefunlogDebug(tag: String, msg: () -> String) {
if (BuildConfig.DEBUG) Log.d(tag, msg())
}
7. Jetpack Compose — Performance
// derivedStateOf pour éviter les recompositions inutilesval isFormValid by remember {
derivedStateOf { amount > 0 && recipient.isNotBlank() }
}
// LaunchedEffect pour effets side-effect UI
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is Event.Navigate -> navController.navigate(event.route)
is Event.ShowError -> scaffoldState.snackbarHostState.showSnackbar(event.msg)
}
}
}
// Stable keys pour les listes
LazyColumn {
items(transactions, key = { it.id }) { tx ->
TransactionRow(tx)
}
}
8. Patterns Kotlin-natifs
// Strategy via function types (plus simple que classe abstraite)funprocessPayment(amount: Long, strategy: (Long) -> Result<Unit>) = strategy(amount)
val cardStrategy: (Long) -> Result<Unit> = { amount -> cardService.charge(amount) }
// Factory avec reifiedinlinefun<reified T : ViewModel>viewModelFactory(crossinline create: () -> T) =
object : ViewModelProvider.Factory {
overridefun<VM : ViewModel>create(cls: Class<VM>): VM = create() as VM
}
Garde-fous et anti-patterns
Anti-pattern
Problème
Correctif
GlobalScope.launch
Memory leak, pas de cancellation
viewModelScope ou scope custom
.collect {} sans repeatOnLifecycle
Collect en background, crash config change
repeatOnLifecycle(STARTED)
!! sur nullable
NullPointerException en prod
?: return, let {}, requireNotNull()
var + mutableListOf dans ViewModel
Race condition, état incohérent
val + StateFlow immuable
LiveData en nouvelle feature
API obsolète, moins composable
StateFlow + asLiveData() si legacy
runBlocking dans coroutine
Deadlock si Dispatchers.Main
withContext à la place
flow {} avec emit depuis thread non-coroutine
Exception IllegalStateException
callbackFlow {} pour callbacks async
copy() data class non utilisé
Mutation directe d'état partagé
Toujours créer un nouvel objet
Bonnes pratiques 2026
Kotlin 2.x : active le compilateur K2 (kotlin.experimental.tryK2=true dans gradle.properties). Meilleure inférence de types, smart casts élargis.
Context receivers (stable K2) : prefer context(CoroutineScope) pour passer le scope implicitement aux fonctions de repository.
data object : utilise data object (Kotlin 1.9+) pour les singletons dans sealed classes (equals, toString corrects).
Structured concurrency stricte : CoroutineScope doit toujours être lié à un cycle de vie. Teste avec UnconfinedTestDispatcher + runTest.
Compose Stability : annote les classes non-Kotlin (@Stable, @Immutable) pour éviter les recompositions. Utilise le Compose Compiler Report pour auditer.
Kotlinx Serialization : préférer à Moshi/Gson pour les nouveaux projets — natif Kotlin, supporte sealed, moins de réflexion.