Implementing Pull-to-Refresh in Mobile Application
Pull-to-refresh is one of most recognizable gestures in mobile apps. Platforms provide ready components: RefreshControl in React Native, SwipeRefreshLayout on Android, UIRefreshControl on iOS, RefreshIndicator in Flutter. Main problems aren't in gesture implementation itself but in managing refresh state and correct error behavior.
Common Mistakes
Spinner doesn't hide on error. onRefresh called, request fails, but refreshing in RefreshControl stays true forever. Reason: setRefreshing(false) called only in then(), not finally(). Correct:
const onRefresh = useCallback(async () => {
setRefreshing(true);
try {
await fetchData();
} catch (e) {
showError(e.message);
} finally {
setRefreshing(false); // always hide spinner
}
}, []);
Conflict with other gestures. Pull-to-refresh inside ScrollView that itself inside Modal or BottomSheet — gesture sometimes intercepted by parent container. On Android SwipeRefreshLayout solves via canChildScrollUp(). In React Native with @gorhom/bottom-sheet — enableOverDrag={false} in bottom sheet so vertical swipe down doesn't conflict with pull-to-refresh.
Double update. If user managed to pull twice before first response arrives — two requests sent. Solution: isRefreshing flag blocks repeat call.
Platform-Specific Implementation
On Flutter — RefreshIndicator wraps ListView or CustomScrollView. onRefresh must return Future — widget hides indicator after completion. Customize color via color and backgroundColor. For CustomScrollView — SliverRefreshControl (Cupertino-style, native iOS look).
On Android Compose — PullToRefreshBox from Material 3 (Compose 1.3+) or SwipeRefresh from accompanist (deprecated but still seen). isRefreshing state from ViewModel, onRefresh suspend function via viewModelScope.launch.
On iOS native — UIRefreshControl added to scrollView.refreshControl. beginRefreshing() / endRefreshing() manage state. In SwiftUI — .refreshable {} modifier on List or ScrollView.
UX Details
After successful update, show brief notification ("Updated just now") if list changed. If no new data — silently hide spinner, don't write "No new data" — annoying.
Pull-to-refresh activation threshold: standard ~60–80pt enough. Don't make threshold too small — accidental triggers on scroll start.
What's Included
- Integration of
RefreshControl/RefreshIndicator/SwipeRefreshLayout - Correct state management (always
finallyfor hiding) - Error handling without spinner hang
- Protection from double update
- If needed: custom animated refresh indicator
Timeline
4 hours — 1 business day. Including custom indicator — up to 2 days. Cost calculated individually.







