LLM уже неплохо справляются с Kotlin, но качество ответа сильно зависит от формулировки промта. Если попросить «напиши ViewModel с корутинами», можно получить код, который компилируется, но течёт по памяти или теряет состояние при повороте экрана. Другое дело — точный промт с указанием контекста, ограничений и ожидаемого результата.
В этой подборке — 12 промтов для Kotlin и Jetpack Compose. Одни заточены под асинхронную логику и работу с данными (корутины, Flow, ViewModel), другие — под вёрстку и состояние экрана (Compose). Критерий выбора простой: если задача про то, как получить и обработать данные, берите промты из первой группы; если про то, как показать и обновить UI, — из второй. Каждый промт сопровождается примером использования и фрагментом кода.
Как работать с этими промтами
LLM не знает ваш проект. Чем больше контекста вы дадите — версия Kotlin, используемые библиотеки (Hilt, Retrofit, Room), архитектурный паттерн, — тем меньше придётся переписывать. В каждом промте ниже есть плейсхолдеры в фигурных скобках: замените их на свои значения.
Ниже — проверенные формулировки. Для удобства они сгруппированы по назначению.
Промты для корутин и Flow
1. Безопасный запуск корутин в ViewModel
Задача: получить корутину, которая не переживёт ViewModel и не утечёт при отмене.
Промт:
Ты — Android-разработчик. Напиши ViewModel на Kotlin с использованием kotlinx.coroutines.
Требования:
- используй viewModelScope для всех корутин;
- при запуске suspend-функции {название} обрабатывай CancellationException и не глотай его;
- добавь обработку исключений через CoroutineExceptionHandler или try/catch с логированием;
- покажи, как отменить корутину при очистке ViewModel.
Версия Kotlin: 2.0. Библиотека: kotlinx-coroutines-android 1.8.0.
Пример использования: вы пишете ViewModel для загрузки профиля. LLM сгенерирует метод loadProfile(), который запускается в viewModelScope.launch, оборачивает вызов в try/catch, а в onCleared() (или через viewModelScope) автоматически отменяет корутину. Без этого промта модель часто предлагает GlobalScope.launch — прямой путь к утечке.
2. Холодный Flow с правильной буферизацией
Задача: получить Flow, который не блокирует эмиссию и корректно работает с медленным коллектором.
Промт:
Создай cold Flow на Kotlin, который эмитит значения из {источник, например, Room или сеть}.
Условия:
- используй flow { } или channelFlow { };
- добавь оператор buffer() с ёмкостью {N} и объясни, зачем он нужен;
- покажи, как коллектор может отписаться без утечки;
- приведи пример с collectLatest и объясни разницу с collect.
Пример: для потока обновлений курса валют из сети channelFlow с buffer(10) позволит не терять промежуточные значения, если UI обрабатывает их медленнее. LLM также покажет, что collectLatest отменяет предыдущую обработку — это критично для поиска с задержкой.
3. Обработка ошибок в корутинах без потери состояния
Задача: не дать исключению убить всю цепочку и сохранить предыдущие данные.
Промт:
Напиши suspend-функцию {название}, которая делает сетевой запрос и возвращает Result<{тип}>.
Требования:
- используй runCatching или try/catch, но не подавляй CancellationException;
- при ошибке сети верни Result.failure с кастомным исключением;
- покажи, как во ViewModel обновить UI-состояние ошибкой, не сбрасывая уже загруженные данные.
Пример: при загрузке списка товаров ошибка сети не должна очищать уже показанный список. LLM сгенерирует код, где состояние ошибки добавляется к существующему UiState, а не заменяет его.
4. Тестирование корутин с TestDispatcher
Задача: получить рабочий unit-тест для suspend-функции без реальных задержек.
Промт:
Напиши unit-тест на JUnit5 для suspend-функции {название}, используя kotlinx-coroutines-test.
Условия:
- замени главный диспетчер на StandardTestDispatcher;
- используй runTest и advanceUntilIdle;
- проверь, что при ошибке возвращается корректный Result;
- покажи, как избежать утечки тестового диспетчера между тестами.
Пример: тест для loadProfile() с эмуляцией задержки 100 мс выполнится мгновенно, а advanceUntilIdle() гарантирует, что все корутины завершились до проверки.
Промты для ViewModel и состояния
5. ViewModel с MutableStateFlow и неизменяемым состоянием
Задача: получить предсказуемый UI-стейт, который не меняется вручную из UI.
Промт:
Создай ViewModel для экрана {название}.
Требования:
- используй MutableStateFlow<{UiState}> с приватным сеттером и публичным StateFlow;
- UiState — data class с полями {перечислить};
- все обновления состояния — только через методы ViewModel;
- покажи, как собрать состояние из нескольких Flow через combine.
Пример: для экрана корзины UiState содержит список товаров, общую сумму и флаг загрузки. LLM сгенерирует private val _uiState = MutableStateFlow(CartUiState()) и val uiState = _uiState.asStateFlow(), исключив прямую мутацию из Composable.
6. Обработка однократных событий (навигация, тосты)
Задача: не показывать тост повторно при рекомпозиции.
Промт:
Реализуй во ViewModel механизм однократных событий для {навигация / показ тоста}.
Варианты: Channel, SharedFlow с replay=0, или StateFlow с ручным сбросом.
Сравни их по надёжности и выбери лучший для {сценарий}.
Покажи, как Composable подписывается на события через LaunchedEffect без дублирования.
Пример: LLM порекомендует Channel с receiveAsFlow() для навигации, так как SharedFlow без replay может потерять событие при подписке после рекомпозиции. В примере кода будет LaunchedEffect(Unit) { viewModel.events.collect { ... } }.
7. Инъекция зависимостей во ViewModel через Hilt
Задача: получить ViewModel с репозиторием, внедрённым через конструктор, без ручного создания фабрик.
Промт:
Покажи, как объявить ViewModel с @HiltViewModel и внедрить в неё репозиторий через конструктор.
Добавь аннотации @Inject и объясни, почему не нужен ViewModelProvider.Factory.
Приведи пример модуля Hilt для предоставления репозитория.
Пример: LLM сгенерирует @HiltViewModel class MyViewModel @Inject constructor(private val repo: MyRepository) : ViewModel(), а также модуль с @Provides fun provideRepo(...). Это избавит от boilerplate с фабриками.
Промты для Jetpack Compose
8. Stateless-компонент с hoisting состояния
Задача: разделить UI и логику, чтобы Composable легко тестировать.
Промт:
Напиши Composable {название} для отображения {что}.
Сделай его stateless: все состояние и колбэки передавай через параметры.
Покажи два варианта: stateless и stateful-обёртку, которая подписывается на ViewModel.
Объясни, почему hoisting состояния улучшает переиспользование и тестируемость.
Пример: для списка задач LLM создаст TaskList(tasks: List<Task>, onTaskClick: (Task) -> Unit) и обёртку TaskListScreen(viewModel), которая передаёт viewModel.uiState.collectAsState(). Такой компонент можно проверить в Preview без ViewModel.
9. Оптимизация рекомпозиции в списках
Задача: избежать лишних перерисовок при изменении одного элемента.
Промт:
У меня есть LazyColumn с элементами {тип}.
Предложи способы уменьшить рекомпозицию: key в items, derivedStateOf, remember с правильными ключами.
Покажи антипаттерн, когда лямбда создаётся заново при каждой рекомпозиции, и как это исправить.
Пример: LLM укажет, что items(tasks, key = { it.id }) помогает Compose переиспользовать элементы, а remember(tasks) { ... } избежать пересчёта производного состояния. Также покажет, что передача onClick = { viewModel.onTaskClick(it) } без remember создаёт новую лямбду при каждой рекомпозиции — лучше использовать method reference или remember.
10. Безопасная работа с контекстом и ресурсами
Задача: не утекать Activity через Composable.
Промт:
Как в Composable получить строковый ресурс или контекст без утечки?
Покажи правильный способ через stringResource и LocalContext, объясни, почему нельзя сохранять context в долгоживущих объектах.
Приведи пример с remember для получения цвета из ресурсов.
Пример: LLM покажет val context = LocalContext.current и предупредит, что сохранять его в ViewModel или синглтоне нельзя — это утечка Activity. Для цветов посоветует colorResource(id = R.color.my_color).
11. Навигация с сохранением состояния
Задача: сохранить состояние экрана при переходе вперёд и назад.
Промт:
Реализуй навигацию в Compose с помощью NavHost.
Покажи, как сохранить состояние скролла LazyColumn при возврате на экран.
Используй rememberSaveable и объясни, чем он отличается от remember.
Добавь пример с аргументами навигации и типами {тип аргумента}.
Пример: для списка товаров с переходом в детали LLM предложит обернуть LazyListState в rememberSaveable с кастомным Saver, чтобы позиция скролла восстанавливалась.
12. Тема и стили без дублирования
Задача: вынести цвета и типографику в единую тему.
Промт:
Создай MaterialTheme для приложения с кастомными цветами {перечислить} и типографикой.
Покажи, как использовать её в Composable через MaterialTheme.colorScheme и MaterialTheme.typography.
Добавь пример переключения светлой/тёмной темы через isSystemInDarkTheme.
Пример: LLM сгенерирует AppTheme с darkColorScheme и lightColorScheme, а также покажет, как в @Composable получить цвет через MaterialTheme.colorScheme.primary.
Сравнение групп промтов
| Критерий | Промты для корутин и Flow | Промты для Compose |
|---|---|---|
| Фокус | Асинхронность, потоки данных, ошибки | UI, состояние, рекомпозиция |
| Ключевые слова в промте | viewModelScope, Dispatchers, Flow, suspend, try/catch | remember, LaunchedEffect, state hoisting, recomposition |
| Частая ошибка без промта | GlobalScope, потеря CancellationException | Состояние внутри Composable, утечка контекста |
| Что проверять в ответе | Отмена корутин, обработка исключений | Стабильность лямбд, ключи в списках |
Итог
Промты — это не замена знаниям, а ускоритель. Чем точнее вы опишете контекст и ограничения, тем меньше времени потратите на правки. Для асинхронной логики всегда указывайте область видимости и обработку ошибок, для UI — состояние и жизненный цикл.
Попробуйте эти формулировки в своём проекте. Если хотите глубже разобраться в корутинах и Compose, обратите внимание на наши курсы по мобильной разработке — там разбираем не только синтаксис, но и подводные камни, которые LLM часто упускает.