Created
December 16, 2023 14:45
-
-
Save sma/febf9b60b04a0e7a14a2b2d4e0262c58 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
/// See [asyncTask]. | |
typedef Task<T> = Future<T?> Function(bool Function() cancelled); | |
/// Runs the future returned by [task] and shows a modal waiting dialog after | |
/// an initial [delay] (default 100 ms) with an optional [label] until the future | |
/// completes. The [task] is passed a function that returns whether the task | |
/// has been cancelled by the user. It should periodically check this and return | |
/// early if it is true. | |
Future<T?> asyncTask<T>({ | |
required BuildContext context, | |
required Task<T> task, | |
Duration? delay, | |
Widget? title, | |
Widget? cancelLabel, | |
}) async { | |
T? result; | |
var state = 0; | |
var isCancelled = false; | |
return Future.any([ | |
// run the task and set isDone when it completes so we can avoid showing | |
// the waiting dialog if it completes quickly enough; if it was already | |
// shown, we close it | |
task(() => isCancelled).then((value) => result = value).whenComplete(() { | |
if (state == 1 && context.mounted) { | |
Navigator.of(context).pop(); | |
} | |
state = 2; | |
}), | |
// show the waiting dialog after the delay; if the user cancels, we set | |
// isCancelled so the task can return early; also set hasDialog so we | |
// can close it if the task eventually completes | |
Future<void>.delayed(delay ?? const Duration(milliseconds: 100), () async { | |
if (state != 0 || !context.mounted) return; | |
state = 1; | |
return showDialog<void>( | |
context: context, | |
barrierDismissible: false, | |
builder: (_) => AlertDialog( | |
title: title, | |
content: const IntrinsicHeight(child: Center(child: CircularProgressIndicator())), | |
actions: [ | |
TextButton( | |
onPressed: () => isCancelled = true, | |
child: cancelLabel ?? const Text('Cancel'), | |
), | |
], | |
), | |
); | |
}), | |
]).then((_) => result); | |
} | |
void main() { | |
runApp(const App()); | |
} | |
class App extends StatelessWidget { | |
const App({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
home: Page(), | |
); | |
} | |
} | |
class Page extends StatelessWidget { | |
const Page({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: ElevatedButton( | |
onPressed: () async { | |
final result = await asyncTask( | |
context: context, | |
task: (isCancelled) async { | |
for (var i = 0; i < 40; i++) { | |
await Future<void>.delayed(const Duration(milliseconds: 90)); | |
if (isCancelled()) return null; | |
} | |
return 42; | |
}, | |
title: const Text('Waiting…'), | |
); | |
print('result: $result'); | |
}, | |
child: const Text('Run Task'), | |
), | |
)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment