Refreshindicators are a pull-down refresh control used by Flutter, but there are some common errors when data needs to be requested asynchronously. Especially, most tutorials on the Internet refresh data through setState, which will lead to the abnormal display of RefreshIndicator’s refresh animation.

Talk is cheap, show me the code.

Current article Flutter sample runs version 1.22.4

Scenario 1 –FutureBuilder:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage(), ); }}class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> _data;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: FutureBuilder<List<String>>(
        future: _fetchData(),
        builder: (context, snapshot) {
          if(snapshot.connectionState ! = ConnectionState.done) {return Container();
          } else {
            _data = snapshot.data;
            return RefreshIndicator(
              onRefresh: () {
                setState(() {});
                return Future.value();
              },
              child: ListView.separated(
                itemBuilder: (context, index) => Container(
                  height: 60,
                  child: Container(
                    padding: const EdgeInsets.all(8),
                    color: Colors.grey,
                    child: Text(_data[index]),
                  ),
                ),
                separatorBuilder: (context, index) => SizedBox(height: 8), itemCount: _data.length, ), ); }},),); } Future<List<String>> _fetchData() async {
    await Future.delayed(Duration(seconds: 1));
    return List.generate(Random().nextInt(20), (index) => "Test $index"); }}Copy the code

Scenario 2 — Direct Request:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage(), ); }}class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<String> _data;

  @override
  void initState() {
    super.initState();
    _fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: RefreshIndicator(
        onRefresh: () {
          _fetchData();
          return Future.value();
        },
        child: ListView.separated(
          itemBuilder: (context, index) => Container(
            height: 60,
            child: Container(
              padding: const EdgeInsets.all(8),
              color: Colors.grey,
              child: Text(_data[index]),
            ),
          ),
          separatorBuilder: (context, index) => SizedBox(height: 8), itemCount: _data? .length ??0,),),); }void _fetchData() async {
    await Future.delayed(Duration(seconds: 1));
    setState(() {
      _data = List.generate(Random().nextInt(20), (index) => "Test $index"); }); }}Copy the code

Solution —StreamBuilder:

In either case, the interface is rebuilt before the RefreshIndicator animation is completed (Option 1), or the data is refreshed after the RefreshIndicator animation is completed (Option 2). The reason is that setState and onRefresh are mutually exclusive.

With StreamBuilder, you can achieve the perfect effect:

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

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage(), ); }}class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final StreamController<List<String>> _streamController = StreamController();

  @override
  void initState() {
    super.initState();
    _init();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: RefreshIndicator(
        onRefresh: () async {
          List<String> data = await _fetchData();
          _streamController.sink.add(data);
          return Future.value();
        },
        child: StreamBuilder(
          stream: _streamController.stream,
          builder: (context, snapshot) {
            List<String> _data = snapshot.data;
            return ListView.separated(
              itemBuilder: (context, index) => Container(
                height: 60,
                child: Container(
                  padding: const EdgeInsets.all(8),
                  color: Colors.grey,
                  child: Text(_data[index]),
                ),
              ),
              separatorBuilder: (context, index) => SizedBox(height: 8), itemCount: _data? .length ??0,); },),),); }void _init() async {
    List<String> data = await _fetchData();
    _streamController.sink.add(data);
  }

  Future<List<String>> _fetchData() async {
    await Future.delayed(Duration(seconds: 1));
    return List.generate(Random().nextInt(20), (index) => "Test $index");
  }

  @override
  void dispose() {
    super.dispose(); _streamController.close(); }}Copy the code