同步 SnackBar 的动作函数

因为想在 flutter 中“在调用者端获得用户是否点击 snackbar 的动作按钮”这个功能,深入研究了一下 dart 的同步方式。

本以为 Dart 中和回调函数进行同步需要用到的会是semphoremutex等第三方包,而实际上只需要 dart:async 库中的 Completer 类。

同步

import 'dart:async';
import 'package:flutter/material.dart';

Future<bool> showSnackbarAndReturnResult(BuildContext context, String message) async {
final completer = Completer<bool>();
final scaffoldMessenger = ScaffoldMessenger.of(context);

scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(message),
action: SnackBarAction(
label: 'OK',
onPressed: () {
completer.complete(true); // 表示操作已完成
scaffoldMessenger.hideCurrentSnackBar();
},
),
),
);

return await completer.future;
}

void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Snackbar Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
final result = await showSnackbarAndReturnResult(
context,
'This is a Snackbar!',
);
if (result) {
print('Snackbar action completed!');
}
},
child: Text('Show Snackbar'),
),
),
),
));
}

completer.future 只在 completecompleteError 被调用后才会完成,从而达到了同步的目的。

snackbar

同步就是这么简单,但是上述代码并不能使用在产品中,因为很不完善。比如用户不点 snackbar 的按钮,哪怕 snackbar 隐藏,completer和函数 还是没有返回。为了完善上述功能,不能简单的使用 return await completer.future;,而需要加上延时和 snackbar 关闭判断。

为了获得 snackbar 是否隐藏,需要使用 showSnackBar 的返回值,一个ScaffoldFeatureController类型的 controller。延时比较简单,使用 Future.delayed即可。代码如下:

final completer = Completer<bool>();
...
final controller = scaffoldMessenger.showSnackBar(...);
...
await Future.any([
completer.future,
Future.delayed(Duration(seconds: seconds)),
controller.closed,
]);

if (completer.isCompleted) {
return true;
} else {
completer.complete(false);
return false;
}

修改 showSnackbarAndReturnResult 函数的参数,传入 seconds 即可。