.qwen/skills/pull-to-refresh/SKILL.md
Реализуй pull-to-refresh в Jetpack Compose с использованием Material 3 PullToRefreshBox. Используй этот навык при добавлении возможности обновления данных на экранах со списками или карточками.
npx skillsauth add easydev991/Jetpack-WorkoutApp pull-to-refreshInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Обязательные компоненты:
_isRefreshing) - отдельное StateFlow для управления состоянием загрузкиrefreshProfile()) - вызывает загрузку данных с сервераВажные правила:
isRefreshing StateFlow вместо основного uiStateuiState (используй параметр updateUiState = false)_isRefreshing в finally блокеОбязательные компоненты:
Важные правила:
PullToRefreshBoxrememberScrollState для verticalScrollisRefreshing из ViewModelСоздай отдельное StateFlow для состояния обновления:
// Состояние обновления данных (pull-to-refresh)
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
Добавь метод, который обновляет данные с сервера:
/**
* Обновляет данные с сервера (для pull-to-refresh).
*/
fun refreshData() {
val data = currentData.value ?: run {
logger.w(TAG, "Пропускаем обновление: данные отсутствуют")
return
}
viewModelScope.launch {
try {
_isRefreshing.update { true }
logger.i(TAG, "Начало обновления данных: ${data.id}")
// Загрузка данных с сервера
loadDataFromServer(data.id, updateUiState = false)
} catch (e: Exception) {
val errorMessage = "Ошибка обновления данных: ${e.message}"
logger.e(TAG, errorMessage)
} finally {
_isRefreshing.update { false }
}
}
}
Используй PullToRefreshBox для обертки контента:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyScreen(
modifier: Modifier = Modifier,
viewModel: MyViewModel,
) {
val scope = rememberCoroutineScope()
// Получаем состояние обновления (isRefreshing)
val isRefreshing by viewModel.isRefreshing.collectAsState()
// Состояние для pull-to-refresh
val pullRefreshState = rememberPullToRefreshState()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { viewModel.refreshData() },
state = pullRefreshState,
modifier = modifier.fillMaxSize(),
indicator = {
PullToRefreshDefaults.Indicator(
state = pullRefreshState,
isRefreshing = isRefreshing,
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = dimensionResource(R.dimen.spacing_regular))
)
}
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(dimensionResource(R.dimen.spacing_regular)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing_small))
) {
// Твой контент здесь
}
}
}
Для использования pull-to-refresh необходимы следующие импорты:
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
Проблема: Обновление данных меняет основной UI State, что вызывает перерисовку всего экрана.
Решение: Используй отдельное isRefreshing StateFlow для управления состоянием загрузки.
Проблема: Если при обновлении происходит ошибка, состояние isRefreshing остается true навсегда.
Решение: Всегда сбрасывай _isRefreshing в finally блоке.
Проблема: Pull-to-refresh не работает, потому что контент не поддерживает прокрутку.
Решение: Оберни контент в Column с verticalScroll(rememberScrollState()).
Проблема: Индикатор перекрывается TopAppBar или другими элементами.
Решение: Используй top = dimensionResource(R.dimen.spacing_regular) для отступа индикатора.
Тестируй метод обновления данных:
@Test
fun refreshData_whenCalled_thenUpdatesIsRefreshing() = runTest {
// Given
mainDispatcherRule.advanceUntilIdle()
// When
viewModel.refreshData()
advanceUntilIdle()
// Then
assertEquals(false, viewModel.isRefreshing.value)
}
Более подробные примеры кода см. в файле references/EXAMPLES.md.
testing
Пиши тесты в андроид-проекте правильно. Используй этот навык при написании любых типов тестов (unit, integration, UI), тестировании бизнес-логики, сетевых функций и компонентов UI.
tools
Правильно работай с локализацией в Android-проекте. Используй этот навык при добавлении новых строковых ресурсов, работе с plurals, форматировании дат и текстов.
tools
Блокировка UI контента экрана во время загрузки данных или выполнения асинхронных операций с отображением индикатора загрузки. Используй этот навык при разработке экранов с сетевыми запросами, операциями CRUD и других асинхронных действий.
tools
Корректная обработка безопасных зон в Jetpack Compose приложениях для Android. Используй при работе с TopAppBar, Scaffold и безопасными зонами экранов в Jetpack-WorkoutApp.