Site icon AppTractor

10 ошибок, которые Android-разработчики до сих пор допускают при работе с Jetpack Compose

Как человек, который за последние два года проанализировал сотни кодовых баз Jetpack Compose, могу сказать, что одни и те же ошибки повторяются снова и снова, что и побудило меня написать этот пост. Даже опытные Android-разработчики, привыкшие к традиционным View-системам, сталкиваются с ними при переходе на Compose.

Самое худшее? Прежде всего — многие из этих проблем вы не замечаете, пока ваше приложение не попадет в продакшн с реальными пользователями и на множестве различных устройств.

1. Вызов функций внутри Composable

Это самая большая проблема, снижающая производительность, которую я вижу в приложениях Compose:

@Composable
fun UserProfile(userId: String) {
    val userData = fetchUserData(userId) // DON'T DO THIS
    
    Text(text = userData.name)
}

Обратите внимание, что вызов функции запускает каждую перекомпозицию. Всегда используйте LaunchedEffect или запоминайте это с помощью соответствующих ключей.

@Composable
fun UserProfile(userId: String) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        userData = fetchUserData(userId)
    }
    
    userData?.let { user ->
        Text(text = user.name)
    }
}

2. Игнорирование CompositionLocals

Разработчики в итоге создают кошмар с передачей свойств через посредников, вместо того чтобы использовать CompositionLocal для данных темы, пользовательских настроек или внедрения зависимостей.

// Instead of passing theme through 5 levels of composables
val LocalAppTheme = compositionLocalOf { AppTheme() }

@Composable
fun MyApp() {
    CompositionLocalProvider(LocalAppTheme provides currentTheme) {
        MainScreen()
    }
}

3. Чрезмерное использование поднятия состояния (State Hoisting)

Не все состояния должны находиться на верхнем уровне. Если что-то является локальным состоянием пользовательского интерфейса, например, развернутое/свернутое состояние, состояние фокуса или анимация, храните это в компоненте, в котором оно используется.

@Composable
fun ExpandableCard() {
    // This state doesn't need to be hoisted
    var isExpanded by remember { mutableStateOf(false) }
    
    Card(
        modifier = Modifier.clickable { isExpanded = !isExpanded }
    ) {
        // Card content
    }
}

4. Неправильное понимание ключей remember()

Запоминание без ключей приводит к устаревшим данным и сбивающему с толку поведению:

@Composable
fun ProductList(products: List<Product>) {
    // Wrong: remembers first products list forever
    val processedProducts = remember { processProducts(products) }
    
    // Right: recomputes when products change
    val processedProducts = remember(products) { processProducts(products) }
}

5. Создание объектов в теле композабл

Это приводит к пустой работе, поскольку каждая перекомпозиция создает новые объекты, как в этом примере:

@Composable
fun AnimatedIcon() {
    // Creates new AnimationSpec every recomposition
    val animationSpec = tween<Float>(durationMillis = 300)
    
    // Better: remember expensive objects
    val animationSpec = remember { tween<Float>(durationMillis = 300) }
}

6. Игнорирование порядка модификаторов

Порядок модификаторов очень важен, поскольку он может повлиять на компоновку и скорость работы. Сначала фон, затем отступы, затем кликабельные элементы, затем ограничения размера:

// Wrong order - padding affects clickable area
Modifier
    .padding(16.dp)
    .clickable { }
    .background(Color.Blue)

// Correct order
Modifier
    .clickable { }
    .background(Color.Blue)
    .padding(16.dp)

7. Не использование derivedStateOf

Извлекайте производное состояние с помощью derivedStateOf, когда состояние зависит от другого состояния, чтобы избежать ненужной перекомпозиции:

@Composable
fun FilteredList(items: List<Item>, query: String) {
    // Recomputes on every recomposition
    val filteredItems = items.filter { it.name.contains(query) }
    
    // Better: only recomputes when dependencies change
    val filteredItems by remember {
        derivedStateOf { items.filter { it.name.contains(query) } }
    }
}

8. Блокировка потока композиции

Композиция выполняется в основном потоке. Тяжелые вычисления при композиции приводят к зависанию пользовательского интерфейса:

@Composable
fun ExpensiveCalculation(data: List<Int>) {
    // Blocks composition
    val result = data.map { heavyComputation(it) }
    
    // Better: move to background
    var result by remember { mutableStateOf<List<Int>>(emptyList()) }
    
    LaunchedEffect(data) {
        result = withContext(Dispatchers.Default) {
            data.map { heavyComputation(it) }
        }
    }
}

9. Неправильное управление побочными эффектами

Неправильное использование API для управления побочными эффектами = утечки памяти и сбои.

@Composable
fun LocationTracker() {
    DisposableEffect(Unit) {
        val locationListener = createLocationListener()
        locationManager.requestLocationUpdates(locationListener)
        
        onDispose {
            locationManager.removeUpdates(locationListener)
        }
    }
}

10. Отсутствие тестирования рекомпозиций

Тестирование Compose так же, как и обычных представлений, приводит к тому, что ошибки, связанные с рекомпозициями, остаются незамеченными. Используйте специальные инструменты тестирования:

@Test
fun testRecompositionBehavior() {
    var recompositionCount = 0
    
    composeTestRule.setContent {
        RecompositionCounter { recompositionCount++ }
        MyComposable()
    }
    
    // Assert recomposition count expectations
}

Путь к Compose мастерству

Команда разработчиков Android в Google заявила, что приложения, следующие этим принципам, испытывают на 40% меньше проблем с производительностью и получают гораздо более высокие оценки пользовательского опыта.

Ключевой вывод? Jetpack Compose — это не столько новый набор инструментов для создания пользовательского интерфейса, сколько новая парадигма, и с ней приходит необходимость отказаться от некоторых старых привычек разработки Android-приложений.

Используйте это как отправную точку для проверки вашего текущего кода Compose на наличие подобных проблем. Уделите время исправлению каждой ошибки по отдельности и используйте инструменты отладки Compose в Android Studio, чтобы убедиться, что вы решаете проблемы.

Разработчики, которые освоят подобные концепции на раннем этапе, будут создавать плавные и производительные Android-приложения, которые нравятся пользователям и которые конкуренты не смогут превзойти.

Источник

Exit mobile version