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