Реалізація Pull-to-Refresh в мобільному додатку
Pull-to-refresh — один з найпізнаваніших жестів у мобільних додатках. Платформи дають готові компоненти: RefreshControl у React Native, SwipeRefreshLayout на Android, UIRefreshControl на iOS, RefreshIndicator у Flutter. Головні проблеми — не в реалізації самого жесту, а в управленні станом оновлення та коректній поведінці при помилках.
Часті помилки
Спіннер не сховується при помилці. onRefresh викликається, запрос падає, але refreshing у RefreshControl залишається true назавжди. Причина: setRefreshing(false) викликається тільки в then(), а не в finally(). Правильно:
const onRefresh = useCallback(async () => {
setRefreshing(true);
try {
await fetchData();
} catch (e) {
showError(e.message);
} finally {
setRefreshing(false); // завжди сховуємо спіннер
}
}, []);
Конфлікт з іншими жестами. Pull-to-refresh всередину ScrollView, який сам всередину Modal або BottomSheet — жест іноді перехоплюється батьківським контейнером. На Android SwipeRefreshLayout вирішує через canChildScrollUp(). У React Native при використанні @gorhom/bottom-sheet — enableOverDrag={false} у bottom sheet, щоб вертикальний свайп вниз не конфліктував з pull-to-refresh.
Двойное оновлення. Якщо користувач встиг потягнути двічі до отримання першої відповіді — летять два запроса. Рішення: флаг isRefreshing блокує повторний виклик.
Реалізація за платформами
На Flutter — RefreshIndicator обгортає ListView чи CustomScrollView. onRefresh має повертати Future — віджет сам сховає індикатор після завершення. Кастомізуйте колір через color та backgroundColor. Для CustomScrollView — SliverRefreshControl (Cupertino-стиль, нативний для iOS look).
На Android Compose — PullToRefreshBox з Material 3 (Compose 1.3+) чи SwipeRefresh з accompanist (застарів, але все ще зустрічається). isRefreshing — стан з ViewModel, onRefresh — suspend функція через viewModelScope.launch.
На iOS нативно — UIRefreshControl додається до scrollView.refreshControl. beginRefreshing() / endRefreshing() управляють станом. У SwiftUI — .refreshable {} модифікатор на List чи ScrollView.
UX-деталі
Після успішного оновлення показуємо краткое уведомлення («Обновлено только что»), якщо список змінився. Якщо нових даних немає — молча сховуємо спіннер, не пишемо «Немає нових даних» — це дратує.
Поріг активації pull-to-refresh: стандартний ~60–80pt достатньо. Не робимо поріг занадто малим — випадкові спрацювання при початку скролу.
Що входить в роботу
- Інтеграція
RefreshControl/RefreshIndicator/SwipeRefreshLayout - Коректне управління станом (always
finallyдля сховування) - Обробка помилок без зависання спіннера
- Захист від подвійного оновлення
- При необхідності: кастомний анімований refresh indicator
Часові рамки
4 години — 1 робочий день. Включаючи кастомний індикатор — до 2 днів. Вартість розраховується індивідуально.







