async_button_builder
a builder that adds loading, disabled, errored and completed states on top of buttons that perform asynchronous tasks. it can be used with most any button or even on top of a custom material button. it includes fluid animation between states as well using animatedsize in combination with animatedswitcher which gives possibility to define your own transitions.
getting started
include the package:
async_button_builder: <latest_version>
wrap the builder around a button, passing the onpressed and child element to builder instead of the button directly. these two are the only required fields.
asyncbuttonbuilder(
child: text('click me'),
onpressed: () async {
await future.delayed(duration(seconds: 1));
},
builder: (context, child, callback, _) {
return textbutton(
child: child,
onpressed: callback,
);
},
),
the fourth value in the builder allows you listen to the loading state. this can be used to conditionally style the button. this package depends freezed
in order to create a sealed union to better handle the possible states.
asyncbuttonbuilder(
child: text('click me'),
loadingwidget: text('loading...'),
onpressed: () async {
await future.delayed(duration(seconds: 1));
throw 'shucks';
},
builder: (context, child, callback, buttonstate) {
final buttoncolor = buttonstate.when(
idle: () => colors.yellow[200],
loading: () => colors.grey,
success: () => colors.orangeaccent,
error: () => colors.orange,
);
return outlinedbutton(
child: child,
onpressed: callback,
style: outlinedbutton.stylefrom(
primary: colors.black,
backgroundcolor: buttoncolor,
),
);
},
),
you can also drive the state of the button yourself using the buttonstate
field:
asyncbuttonbuilder(
buttonstate: buttonstate.completing(),
// ...
),
async_button_builder
even works for custom buttons. you can define your own widgets for loading, error, and completion as well as define the transitions between them. this example is a little verbose but shows some of what’s possible.
asyncbuttonbuilder(
child: padding(
// value keys are important as otherwise our custom transitions
// will have no way to differentiate between children.
key: valuekey('foo'),
padding: const edgeinsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: text(
'click me',
style: textstyle(color: colors.white),
),
),
loadingwidget: padding(
key: valuekey('bar'),
padding: const edgeinsets.all(8.0),
child: sizedbox(
height: 16.0,
width: 16.0,
child: circularprogressindicator(
valuecolor: alwaysstoppedanimation<color>(colors.white),
),
),
),
successwidget: padding(
key: valuekey('foobar'),
padding: const edgeinsets.all(4.0),
child: icon(
icons.check,
color: colors.purpleaccent,
),
),
onpressed: () async {
await future.delayed(duration(seconds: 2));
},
loadingswitchincurve: curves.bounceinout,
loadingtransitionbuilder: (child, animation) {
return slidetransition(
position: tween<offset>(
begin: offset(0, 1.0),
end: offset(0, 0),
).animate(animation),
child: child,
);
},
builder: (context, child, callback, state) {
return material(
color: state.maybewhen(
success: () => colors.purple[100],
orelse: () => colors.blue,
),
// this prevents the loading indicator showing below the
// button
clipbehavior: clip.hardedge,
shape: stadiumborder(),
child: inkwell(
child: child,
ontap: callback,
),
);
},
),
Comments are closed.