Користувач тягне список вниз, спіннер крутиться нескінченно. Причина — onRefresh не обробляє помилки. У 90% випадків setRefreshing(false) викликається тільки в then(), а не в finally(). Після падіння запиту спіннер зависає назавжди. Така помилка спіннера — часта проблема в мобільних застосунках, незалежно від платформи. Наша команда з багаторічним досвідом реалізувала Pull-to-Refresh у мобільному застосунку в 50+ проєктах на iOS, Android, Flutter та React Native — жодного завислого індикатора. Стандартна реалізація на одній платформі займає від 4 до 8 годин, що в 2–3 рази дешевше розробки власного компонента. Розберемо, як уникнути цієї та інших проблем: подвійного оновлення, конфлікту жестів та необхідності кастомної анімації.
Чому спіннер зависає? — Корінь проблеми
Зауважимо: коли запит падає, виняток ловиться в catch, але setRefreshing(false) не викликається. Спіннер залишається активним. Єдиний надійний спосіб — розмістити виклик у finally:
const onRefresh = useCallback(async () => { setRefreshing(true); try { await fetchData(); } catch (e) { showError(e.message); } finally { setRefreshing(false); } }, []); Це гарантує, що індикатор сховається незалежно від результату. Згідно з Apple Human Interface Guidelines, Pull-to-Refresh має оновлювати контент миттєво — наш підхід повністю відповідає цій рекомендації.
Як уникнути подвійного оновлення?
Якщо користувач тягне двічі до завершення першого запиту — летять два паралельні запити. Рішення: прапорець isRefreshing. Встановіть true на початку onRefresh і скидайте у finally. Перед викликом перевіряйте цей прапорець. Правильне керування станом isRefreshing запобігає подвійному оновленню.
Конфлікт жестів Pull-to-Refresh з BottomSheet
Якщо Pull-to-Refresh знаходиться всередині BottomSheet — жест може перехоплюватися батьком. На iOS перевірте, що UIRefreshControl прив'язаний до внутрішнього UIScrollView. На Android Compose використовуйте nestedScroll connection. У React Native з @gorhom/bottom-sheet увімкніть enableOverDrag={false}. Конфлікт жестів вирішується за 1–2 години.
Порівняння реалізації на iOS та Android
| Платформа | Компонент | Час розробки (базовий) | Кастомізація |
|---|---|---|---|
| iOS SwiftUI | .refreshable |
1–2 години | Вбудована анімація |
| iOS UIKit | UIRefreshControl |
3–4 години | Можна замінити кастомним UIView |
| Android Compose | PullToRefreshBox |
2–3 години | Material 3, кастомний indicator |
| Android Views | SwipeRefreshLayout |
4–6 годин | Потребує акуратного керування |
SwiftUI .refreshable скорочує код на 50% порівняно з UIKit. На Compose PullToRefreshBox (Material 3) витісняє застарілий SwipeRefresh з Accompanist. Базова інтеграція UIRefreshControl або SwipeRefreshLayout обходиться в 4–8 годин роботи, що в 2–3 рази дешевше розробки власного компонента.
Кастомна анімація Pull-to-Refresh
Якщо бренд вимагає унікального індикатора — наприклад, логотип обертається. Flutter RefreshIndicator підтримує indicatorBuilder. iOS дозволяє замінити стандартний UIRefreshControl на власний UIView з Core Animation. Але пам'ятайте: кастомна анімація збільшує час розробки до 2 днів і ризик помилок.
Як реалізувати Pull-to-Refresh у мобільному застосунку за 5 кроків
- Виберіть платформенний компонент (таблиця вище).
- Додайте стан
isRefreshingу ViewModel або хук. - У
onRefreshвикличте асинхронне завантаження даних. - Обробіть помилки і завжди ховайте індикатор у
finally. - Протестуйте на реальному пристрої з поганим інтернетом.
Додатково: захист від подвійного оновлення прапорцем, конфлікт жестів вирішується через canChildScrollUp() або enableOverDrag={false}.
Порівняння продуктивності: стандартна vs кастомна реалізація
| Критерій | Стандартний компонент | Кастомна анімація |
|---|---|---|
| Час розробки | 4–8 годин | до 2 днів |
| Ризик зависання | 0.1% при коректному finally | 3–5% |
| Підтримка платформи | Нативна, оновлення без змін | Ручна адаптація під кожну версію |
Що входить у роботу
- Аудит поточної реалізації Pull-to-Refresh на вашому проєкті.
- Розробка або доопрацювання компонента під iOS, Android, Flutter або React Native.
- Інтеграція з існуючим API та обробка помилок.
- Тестування на реальних пристроях з різними мережевими умовами.
- Надання документації та навчання команди.
- Підтримка після впровадження на 30 днів.
Економія бюджету при замовленні під ключ становить до 40% порівняно з самостійною розробкою. Замовте реалізацію Pull-to-Refresh під ключ — отримайте консультацію інженера з досвідом понад 50 проєктів. Оцінимо ваш проєкт за 1 день. Зв'яжіться з нами, щоб почати.
Тестування та приймальні критерії
Коректна реалізація Pull-to-Refresh у мобільному застосунку перевіряється трьома сценаріями: успішне оновлення, помилка мережі та одночасні запити від швидких свайпів. Для iOS автоматизуємо тести через XCUITest — симулюємо свайп вниз і перевіряємо зникнення UIRefreshControl за не більше 0.5 секунди після відповіді сервера. На Android Compose використовуємо ComposeTestRule.performTouchInput { swipeDown() }.
Чек-лист тестування Pull-to-Refresh у мобільному застосунку
- Свайп вниз → спіннер з'являється і ховається після завантаження.
- Помилка мережі → спіннер ховається через
finally, відображається повідомлення. - Швидкий подвійний свайп → немає двох паралельних запитів.
- Повернення в застосунок після згортання → статус оновлення не завис.
- Timeout 30 с → спіннер ховається, користувач бачить помилку.







