Pull-to-Refresh Implementation Without Spinner Freeze

User pulls down, spinner spins forever. The cause — `onRefresh` doesn't handle errors. In 90% of cases `setRefreshing(false)` is called only in `then()`, not in `finally()`. After a failed request, the spinner freezes indefinitely. This spinner freeze is a common issue in mobile apps regardless of p

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Pull-to-Refresh Implementation Without Spinner Freeze
Simple
from 4 hours to 2 days

Our competencies:

Frequently Asked Questions

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    897
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    783
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    1004
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    598

User pulls down, spinner spins forever. The cause — onRefresh doesn't handle errors. In 90% of cases setRefreshing(false) is called only in then(), not in finally(). After a failed request, the spinner freezes indefinitely. This spinner freeze is a common issue in mobile apps regardless of platform. Our team has implemented Pull-to-Refresh in 50+ projects on iOS, Android, Flutter, and React Native — not a single frozen indicator. A standard implementation on one platform takes 4–8 hours. Let's break down how to avoid this and other problems: double refresh, gesture conflict, and custom animation needs.

Root Cause: Why the Spinner Freezes?

When a request fails, the exception is caught in catch, but setRefreshing(false) is never called. The spinner stays active. The only reliable way is to place the call in finally:

const onRefresh = useCallback(async () => { setRefreshing(true); try { await fetchData(); } catch (e) { showError(e.message); } finally { setRefreshing(false); } }, []); 

This guarantees the indicator hides regardless of the outcome. According to Apple Human Interface Guidelines, Pull-to-Refresh should update content instantly — our approach fully aligns with this recommendation.

How to Avoid Double Refresh?

If the user pulls twice before the first request completes — two parallel requests fire. Solution: an isRefreshing flag. Set true at the start of onRefresh and reset in finally. Check this flag before calling the fetch. Proper state management of isRefreshing prevents double refresh.

Pull-to-Refresh Gesture Conflict with BottomSheet

If Pull-to-Refresh is inside a BottomSheet — the gesture may be intercepted by the parent. On iOS verify that UIRefreshControl is attached to the inner UIScrollView. On Android Compose use nestedScroll connection. In React Native with @gorhom/bottom-sheet enable enableOverDrag={false}. Gesture conflict resolution takes 1–2 hours.

Platform Comparison

Platform Component Development Time (Basic) Customization
iOS SwiftUI .refreshable 1–2 hours Built-in animation
iOS UIKit UIRefreshControl 3–4 hours Can replace with custom UIView
Android Compose PullToRefreshBox 2–3 hours Material 3, custom indicator
Android Views SwipeRefreshLayout 4–6 hours Requires careful management

SwiftUI .refreshable reduces code by 50% compared to UIKit. On Compose PullToRefreshBox (Material 3) is replacing the deprecated SwipeRefresh from Accompanist. Basic integration of UIRefreshControl or SwipeRefreshLayout takes 4–8 hours.

Custom Pull-to-Refresh Animation

If the brand requires a unique indicator — e.g., a rotating logo. Flutter's RefreshIndicator supports indicatorBuilder. iOS allows replacing the standard UIRefreshControl with a custom UIView using Core Animation. But note: custom animation increases development time to up to 2 days and introduces higher risk of bugs.

How to Implement Pull-to-Refresh in 5 Steps

  1. Choose the platform component (table above).
  2. Add isRefreshing state in ViewModel or hook.
  3. In onRefresh, call async data loading.
  4. Handle errors and always hide the indicator in finally.
  5. Test on a real device with poor network.

Additionally: prevent double refresh with a flag, resolve gesture conflicts via canChildScrollUp() or enableOverDrag={false}.

Performance Comparison: Standard vs Custom Implementation

Criteria Standard Component Custom Animation
Development Time 4–8 hours up to 2 days
Freeze Risk 0.1% with correct finally 3–5%
Platform Support Native, updates without changes Manual adaptation per version

What's Included in the Work

  • Audit of current Pull-to-Refresh implementation.
  • Development or refinement of component for iOS, Android, Flutter, or React Native.
  • Integration with existing API and error handling.
  • Testing on real devices under various network conditions.
  • Documentation and team training.
  • 30-day post-launch support.

Order a turnkey Pull-to-Refresh implementation — get a consultation from an engineer with 50+ projects experience. We'll evaluate your project in 1 day. Contact us to start.

Testing and Acceptance Criteria

A correct Pull-to-Refresh implementation is verified with three scenarios: successful refresh, network error, and simultaneous requests from fast swipes. For iOS we automate tests via XCUITest — simulate a swipe down and verify UIRefreshControl disappears within 0.5 seconds after server response. On Android Compose we use ComposeTestRule.performTouchInput { swipeDown() }. Writing test coverage for Pull-to-Refresh takes additional time depending on platform and logic complexity.

Pull-to-Refresh Testing Checklist
  1. Swipe down → spinner appears and hides after loading.
  2. Network error → spinner hides via finally, error message displayed.
  3. Fast double swipe → no two parallel requests.
  4. App re-entering after backgrounding → refresh state not frozen.
  5. 30-second timeout → spinner hides, user sees error.