iOS-styled refresh control Widget and Sliver for Flutter.


🚀 Motivation

While developing one of my client's mobile application I felt a need for a UI component for the purpose that whenever a user wants to refresh the page, he/she should be able to trigger an all-over start for the page's data instead of going back and renavigating. By using RefreshIndicator which is included in the material package of the Flutter framework the mission was achieved. But, it was, seemingly, exotic on iOS devices. I felt a need, the need for an iOS-styled refresh control, which turned out to be included in the cupertino package of Flutter as CupertinoRefreshSliver.

So, why package then? 🤔

You see, CupertinoRefreshSliver is a sliver which means in order to be able to use it you have to use CustomScrollView to layout your widgets and Slivers for everything, although, if not for everything but at least with refactoring and wrapping widgets inside SliverToBoxAdapter, yet, always, Slivers are there. But who wants to refactor every view just to have iOS-styled refresh control? I didn't, so I've created a workaround which I call: the CupertinoRefresh widget.

-- Note N1: If you're already using CustomScrollView for layout you can use CupertinoRefreshSliver directly as the first element of your Slivers list. See the example below.

-- Note N2: Before you use CupertinoRefresh Widget read Performance concerns section.

🕹ī¸ Usage

There are two types of refresh control as mentioned above:

  1. CupertinoRefresh: a widget that can wrap any other widget (both scrollable and non-scrollable).
  2. CupertinoRefreshSliver: a Sliver that can be used in combination with Slivers (w/ CustomScrollView).

1. CupertinoRefresh

  onRefresh: () {
    return _fetchData();

  // The child of CupertinoRefresh widget must not be necessarily a
  // scrollable widget as it can be any widget

  child:  Column(
    children: [
      // While using with SingleChildScrollView is seamless,
      // ListView has its limitations as commented below:
      // itemCount must be specified/can not be infinite
      itemCount:  flutterFavorites.length,
      // shrinkWrap must be set as "true", otherwise you'll get RenderViewport issue
      shrinkWrap:  true,
      // For better control set NeverScrollableScrollPhysics to physics
      physics:  const  NeverScrollableScrollPhysics(),

      itemBuilder: (_,  int  index) { ... },

1. CupertinoRefreshSliver

  slivers: [
    // Place CupertinoRefreshSliver as the first element for the slivers parameter.
      onRefresh: _fetch,

📈 Performance concerns

While CupertinoRefreshSliver has no performance drawbacks, CupertinoRefresh can have and it's related to how scrollable widgets behave in the Flutter framework. Although you may not end up using this package consider reading the part below so that you can be aware of performance-related issues while using any kind of scrollable widget in Flutter.

The long and the short of the matter is that scrollable widgets like ListView, GridView, PageView, and CustomScrollView try to fill all the available space given by the parent element, even when the list items would require less space Read detailed.

Because the CupertinoRefresh is itself an intrinsically scrollable widget if you add another scrollable widget to it as a child, you'll get this error: Vertical viewport was given unbounded height. 😖

The solution for such cases is to set shrinkWrap property of the scrollable widget as true and physics as NeverScrollableScrollPhysics or setting a bounded height on the parent widget.

Now, setting shrinkWrap as true is the root of the performance drawback. According to the Flutter documentation, using shrinkWrap in lists is expensive performance-wise and should be avoided. Why? Because, when used as such, the scrollable widget builds all children at once (Flutter issue #26072).

In conclusion I would say, whether you use this package or not, always consider using Slivers for better performance and use ListView when there are lesser elements and rendering them are not expensive.

Read more:
-- dart-lang issue: #3496
-- dart code metric: avoid-shrink-wrap-in-lists
-- "Decoding Flutter" series: ShrinkWrap vs Slivers

🤓 Contributors

🙏 Credits

Thanks to Aljan Shikiyev for being inspiration for this Widget and Sliver.

🐞 Bugs/Requests

If you encounter any problems please open an issue. If you feel the library is missing a feature, please raise a ticket on GitHub and we'll look into it. Pull requests are welcome.

📃 License

MIT License