If you've installed a couple of apps in the Google Play Store or iOS App store then you might have noticed this data refresh design pattern. Whereby you pull or swipe down at the top of the screen and the refresh or reload is auto-initiated.

It's extrememly intuitive and this can be achieved easily in Flutter apps as well. This tutorial will explore how to achieve it in Flutter.

(a). Use Liquid-Pull-To-Refresh

This is a custom refresh indicator for flutter.

Here's a demo screenshot:

Step 1: Install it

Install it by declaring it as a dependency in your app's pubspec.yaml:

dependencies:
  liquid_pull_to_refresh: ^3.0.0

Then sync from your IDE or execute flutter packages get to intiate the sync operation.

Step 2: Import

Before you can use, after installation, you need to import it into your code:

import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart';

Step 3: Wrap ListView/GridView

Then you simply need to wrap your ListView or gridview with LiquidPullToRefresh.After that you provide the value of onRefresh parameter which is a refresh callback.

Note - LiquidPullToRefresh can only be used with a vertical scroll view.

Here;s how you do that:

LiquidPullToRefresh(
        key: _refreshIndicatorKey,  // key if you want to add
        onRefresh: _handleRefresh,  // refresh callback
        child: ListView(),      // scroll view
      );

Full Example

Let's look at a full example.

  1. Start by installing this package as has been discussed previously.
  2. Replace your main.dart with the following code:

main.dart

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Liquid Pull To Refresh'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);

  final String? title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  final GlobalKey<LiquidPullToRefreshState> _refreshIndicatorKey =
      GlobalKey<LiquidPullToRefreshState>();

  static int refreshNum = 10; // number that changes when refreshed
  Stream<int> counterStream =
      Stream<int>.periodic(Duration(seconds: 3), (x) => refreshNum);

  ScrollController? _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = new ScrollController();
  }

  static final List<String> _items = <String>[
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N'
  ];

  Future<void> _handleRefresh() {
    final Completer<void> completer = Completer<void>();
    Timer(const Duration(seconds: 3), () {
      completer.complete();
    });
    setState(() {
      refreshNum = new Random().nextInt(100);
    });
    return completer.future.then<void>((_) {
      _scaffoldKey.currentState?.showSnackBar(SnackBar(
          content: const Text('Refresh complete'),
          action: SnackBarAction(
              label: 'RETRY',
              onPressed: () {
                _refreshIndicatorKey.currentState!.show();
              })));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Stack(
          children: <Widget>[
            Align(alignment: Alignment(-1.0, 0.0), child: Icon(Icons.reorder)),
            Align(alignment: Alignment(-0.3, 0.0), child: Text(widget.title!)),
          ],
        ),
      ),
      body: LiquidPullToRefresh(
        key: _refreshIndicatorKey,
        onRefresh: _handleRefresh,
        showChildOpacityTransition: false,
        child: StreamBuilder<int>(
            stream: counterStream,
            builder: (context, snapshot) {
              return ListView.builder(
                padding: kMaterialListPadding,
                itemCount: _items.length,
                controller: _scrollController,
                itemBuilder: (BuildContext context, int index) {
                  final String item = _items[index];
                  return ListTile(
                    isThreeLine: true,
                    leading: CircleAvatar(child: Text(item)),
                    title: Text('This item represents $item.'),
                    subtitle: Text(
                        'Even more additional list item information appears on line three. ${snapshot.data}'),
                  );
                },
              );
            }),
      ),
    );
  }
}

Lastly run.

Reference

Find the reference links below:

Number Link
1. Download example
2. Read more
3. Follow package author