RefreshIndicator class
A widget that supports the Material "swipe to refresh" idiom.
Learn more about RefreshIndicator on the Flutter YouTube channel.
When the child's Scrollable descendant overscrolls, an animated circular progress indicator is faded into view. When the scroll ends, if the indicator has been dragged far enough for it to become completely opaque, the onRefresh callback is called. The callback is expected to update the scrollable's contents and then complete the Future it returns. The refresh indicator disappears after the callback's Future has completed.
The trigger mode is configured by RefreshIndicator.triggerMode.
This example shows how RefreshIndicator can be triggered in different ways.
To see it in action, copy and run this code snippet on DartPad.
import 'package:material_ui/material_ui.dart';
/// Flutter code sample for [RefreshIndicator].
void main() => runApp(const RefreshIndicatorExampleApp());
class RefreshIndicatorExampleApp extends StatelessWidget {
const RefreshIndicatorExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: RefreshIndicatorExample());
}
}
class RefreshIndicatorExample extends StatefulWidget {
const RefreshIndicatorExample({super.key});
@override
State<RefreshIndicatorExample> createState() =>
_RefreshIndicatorExampleState();
}
class _RefreshIndicatorExampleState extends State<RefreshIndicatorExample> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RefreshIndicator Sample')),
body: RefreshIndicator(
key: _refreshIndicatorKey,
color: Colors.white,
backgroundColor: Colors.blue,
strokeWidth: 4.0,
onRefresh: () async {
// Replace this delay with the code to be executed during refresh
// and return a Future when code finishes execution.
return Future<void>.delayed(const Duration(seconds: 3));
},
// Pull from top to show refresh indicator.
child: ListView.builder(
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Show refresh indicator programmatically on button tap.
_refreshIndicatorKey.currentState?.show();
},
icon: const Icon(Icons.refresh),
label: const Text('Show Indicator'),
),
);
}
}
This example shows how to trigger RefreshIndicator in a nested scroll view using the notificationPredicate property.
To see it in action, copy and run this code snippet on DartPad.
import 'package:flutter/gestures.dart';
import 'package:material_ui/material_ui.dart';
/// Flutter code sample for [RefreshIndicator].
void main() => runApp(const RefreshIndicatorExampleApp());
class RefreshIndicatorExampleApp extends StatelessWidget {
const RefreshIndicatorExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: PointerDeviceKind.values.toSet(),
),
home: const RefreshIndicatorExample(),
);
}
}
class RefreshIndicatorExample extends StatelessWidget {
const RefreshIndicatorExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RefreshIndicator Sample')),
body: RefreshIndicator(
color: Colors.white,
backgroundColor: Colors.blue,
onRefresh: () async {
// Replace this delay with the code to be executed during refresh
// and return asynchronous code
return Future<void>.delayed(const Duration(seconds: 3));
},
// This check is used to customize listening to scroll notifications
// from the widget's children.
//
// By default this is set to `notification.depth == 0`, which ensures
// the only the scroll notifications from the first scroll view are listened to.
//
// Here setting `notification.depth == 1` triggers the refresh indicator
// when overscrolling the nested scroll view.
notificationPredicate: (ScrollNotification notification) {
return notification.depth == 1;
},
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Container(
height: 100,
alignment: .center,
color: Colors.pink[100],
child: Column(
mainAxisAlignment: .center,
children: <Widget>[
Text(
'Pull down here',
style: Theme.of(context).textTheme.headlineMedium,
),
const Text("RefreshIndicator won't trigger"),
],
),
),
),
SliverToBoxAdapter(
child: Container(
color: Colors.green[100],
height: 300,
child: ListView.builder(
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return const ListTile(
title: Text('Pull down here'),
subtitle: Text('RefreshIndicator will trigger'),
);
},
),
),
),
SliverList.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return const ListTile(
title: Text('Pull down here'),
subtitle: Text("Refresh indicator won't trigger"),
);
},
),
],
),
),
);
}
}
This example shows how to use RefreshIndicator without the spinner.
To see it in action, copy and run this code snippet on DartPad.
import 'dart:ui';
import 'package:material_ui/material_ui.dart';
/// Flutter code sample for [RefreshIndicator.noSpinner].
void main() => runApp(const RefreshIndicatorExampleApp());
class RefreshIndicatorExampleApp extends StatelessWidget {
const RefreshIndicatorExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
PointerDeviceKind.trackpad,
PointerDeviceKind.stylus,
},
),
home: const RefreshIndicatorExample(),
);
}
}
class RefreshIndicatorExample extends StatefulWidget {
const RefreshIndicatorExample({super.key});
@override
State<RefreshIndicatorExample> createState() =>
_RefreshIndicatorExampleState();
}
class _RefreshIndicatorExampleState extends State<RefreshIndicatorExample> {
bool _isRefreshing = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RefreshIndicator.noSpinner Sample')),
body: Stack(
children: <Widget>[
RefreshIndicator.noSpinner(
// Callback function used by the app to listen to the
// status of the RefreshIndicator pull-down action.
onStatusChange: (RefreshIndicatorStatus? status) {
if (status == RefreshIndicatorStatus.done) {
setState(() {
_isRefreshing = false;
});
}
},
// Callback that gets called whenever the user pulls down to refresh.
onRefresh: () async {
// This can be also done in onStatusChange when the status is RefreshIndicatorStatus.refresh.
setState(() {
_isRefreshing = true;
});
// Replace this delay with the code to be executed during refresh
// and return asynchronous code.
return Future<void>.delayed(const Duration(seconds: 3));
},
child: CustomScrollView(
slivers: <Widget>[
SliverList.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return ListTile(
tileColor: Colors.green[100],
title: const Text('Pull down here'),
subtitle: const Text(
'A custom refresh indicator will be shown',
),
);
},
),
],
),
),
// Shows an overlay with a CircularProgressIndicator when refreshing.
if (_isRefreshing)
ColoredBox(
color: Colors.black45,
child: Align(
child: CircularProgressIndicator(
color: Colors.purple[500],
strokeWidth: 10,
semanticsLabel: 'Circular progress indicator',
),
),
),
],
),
);
}
}
Troubleshooting
Refresh indicator does not show up
The RefreshIndicator will appear if its scrollable descendant can be overscrolled, i.e. if the scrollable's content is bigger than its viewport. To ensure that the RefreshIndicator will always appear, even if the scrollable's content fits within its viewport, set the scrollable's Scrollable.physics property to AlwaysScrollableScrollPhysics:
ListView(
physics: const AlwaysScrollableScrollPhysics(),
// ...
)
A RefreshIndicator can only be used with a vertical scroll view.
See also:
- material.io/design/platform-guidance/android-swipe-to-refresh.html
- RefreshIndicatorState, can be used to programmatically show the refresh indicator.
- RefreshProgressIndicator, widget used by RefreshIndicator to show the inner circular progress spinner during refreshes.
CupertinoSliverRefreshControl, an iOS equivalent of the pull-to-refresh pattern. Must be used as a sliver inside a CustomScrollView instead of wrapping around a ScrollView because it's a part of the scrollable instead of being overlaid on top of it.
- Inheritance
-
- Object
- DiagnosticableTree
- Widget
- StatefulWidget
- RefreshIndicator
Constructors
- RefreshIndicator({Key? key, double displacement = 40.0, double edgeOffset = 0.0, required RefreshCallback onRefresh, Color? color, Color? backgroundColor, ScrollNotificationPredicate notificationPredicate = defaultScrollNotificationPredicate, String? semanticsLabel, String? semanticsValue, double strokeWidth = RefreshProgressIndicator.defaultStrokeWidth, RefreshIndicatorTriggerMode triggerMode = RefreshIndicatorTriggerMode.onEdge, double elevation = 2.0, required Widget child})
-
Creates a refresh indicator.
const
- RefreshIndicator.adaptive({Key? key, double displacement = 40.0, double edgeOffset = 0.0, required RefreshCallback onRefresh, Color? color, Color? backgroundColor, ScrollNotificationPredicate notificationPredicate = defaultScrollNotificationPredicate, String? semanticsLabel, String? semanticsValue, double strokeWidth = RefreshProgressIndicator.defaultStrokeWidth, RefreshIndicatorTriggerMode triggerMode = RefreshIndicatorTriggerMode.onEdge, double elevation = 2.0, required Widget child})
-
Creates an adaptive RefreshIndicator based on whether the target
platform is iOS or macOS, following Material design's
Cross-platform guidelines.
const
-
RefreshIndicator.noSpinner({Key? key, required RefreshCallback onRefresh, ValueChanged<
RefreshIndicatorStatus?> ? onStatusChange, ScrollNotificationPredicate notificationPredicate = defaultScrollNotificationPredicate, String? semanticsLabel, String? semanticsValue, RefreshIndicatorTriggerMode triggerMode = RefreshIndicatorTriggerMode.onEdge, double elevation = 2.0, required Widget child}) -
Creates a RefreshIndicator with no spinner and calls
onRefreshwhen successfully armed by a drag event.const
Properties
- backgroundColor → Color?
-
The progress indicator's background color. The current theme's
ThemeData.canvasColor by default.
final
- child → Widget
-
The widget below this widget in the tree.
final
- color → Color?
-
The progress indicator's foreground color. The current theme's
ColorScheme.primary by default.
final
- displacement → double
-
The distance from the child's top or bottom edgeOffset where
the refresh indicator will settle. During the drag that exposes the refresh
indicator, its actual displacement may significantly exceed this value.
final
- edgeOffset → double
-
The offset where RefreshProgressIndicator starts to appear on drag start.
final
- elevation → double
-
Defines the elevation of the underlying RefreshIndicator.
final
- hashCode → int
-
The hash code for this object.
no setterinherited
- key → Key?
-
Controls how one widget replaces another widget in the tree.
finalinherited
- notificationPredicate → ScrollNotificationPredicate
-
A check that specifies whether a ScrollNotification should be
handled by this widget.
final
- onRefresh → RefreshCallback
-
A function that's called when the user has dragged the refresh indicator
far enough to demonstrate that they want the app to refresh. The returned
Future must complete when the refresh operation is finished.
final
-
onStatusChange
→ ValueChanged<
RefreshIndicatorStatus?> ? -
Called to get the current status of the RefreshIndicator to update the UI as needed.
This is an optional parameter, used to fine tune app cases.
final
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- semanticsLabel → String?
-
The SemanticsProperties.label for this progress indicator.
final
- semanticsValue → String?
-
The SemanticsProperties.value for this progress indicator.
final
- strokeWidth → double
-
Defines strokeWidth for
RefreshIndicator.final - triggerMode → RefreshIndicatorTriggerMode
-
Defines how this RefreshIndicator can be triggered when users overscroll.
final
Methods
-
createElement(
) → StatefulElement -
Creates a StatefulElement to manage this widget's location in the tree.
inherited
-
createState(
) → RefreshIndicatorState -
Creates the mutable state for this widget at a given location in the tree.
override
-
debugDescribeChildren(
) → List< DiagnosticsNode> -
Returns a list of DiagnosticsNode objects describing this node's
children.
inherited
-
debugFillProperties(
DiagnosticPropertiesBuilder properties) → void -
Add additional properties associated with the node.
inherited
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toDiagnosticsNode(
{String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode -
Returns a debug representation of the object that is used by debugging
tools and by DiagnosticsNode.toStringDeep.
inherited
-
toString(
{DiagnosticLevel minLevel = DiagnosticLevel.info}) → String -
A string representation of this object.
inherited
-
toStringDeep(
{String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) → String -
Returns a string representation of this node and its descendants.
inherited
-
toStringShallow(
{String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a one-line detailed description of the object.
inherited
-
toStringShort(
) → String -
A short, textual description of this widget.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited