在 Flutter 中初始化通知的正确位置



我目前正在努力让通知按照我希望它们在 Flutter 上的方式运行。 我目前的解决方案是有一个动态的登陆页面,根据FirebaseAuth重定向到登录或主屏幕。

MaterialApp(
theme: ThemeData(
...
home: Scaffold(body: Builder(builder: (context) => LandingPage()))
),

在 LandingPage 中,我将在我的单例中调用一个函数来为我设置通知。如您所见,我在这里传递上下文。这是因为我想从通知的onMessage回调中显示Snackbar。

class LandingPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
FirebaseUser user = Provider.of<FirebaseUser>(context);
if (user != null) {
Singleton().setupMessaging(user.uid, context); //This is the line
return MainPage(userId: user.uid);
} else {
while(Navigator.canPop(context)){
Navigator.pop(context);
}
return LoginPage();
}
}
}

通过这一点,我正在努力实现,以便在用户登录后使消息传递系统运行。我仅在当前令牌未定义时才设置回调。

我现在遇到的问题是,所述上下文不是应用程序范围的,这意味着,一旦我导航到具有自己的上下文的新小部件,就无法再显示小吃栏。现在我不确定这是否是初始化消息传递的正确位置,因为没有应用程序范围的上下文。

setupMessaging(String uid) async{
if((await SharedPreferences.getInstance()).getBool('bNotifications') ?? true){
print("bNotifications disabled");
return;
}
_firebaseMessaging.getToken().then((token) {
if (_lastToken != token) {
if (_lastToken == null) {
if (Platform.isIOS) iOSPermission();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('onMessage $message');
Scaffold.of(context).showSnackBar(...); //Here I need the context
},
onResume: (Map<String, dynamic> message) async {
print('onResume $message');
},
onLaunch: (Map<String, dynamic> message) async {
print('onLaunch $message');
},
);
}
_lastToken = token;
}
});
}

我还考虑过在onMessage回调中显示本地通知,但是本地通知和firebase消息在iOS上不能协同工作。

我听说的最后一个选项是使用GlobalKey,我需要通过我所有的页面。据我所知,这种方法也非常缓慢。

没有应用程序范围的Context这样的东西,但您可以为您的Snackbars创建一个应用程序范围的Scaffold

初始化可静态访问的GlobalKey。必须在构建任何路由之前对其进行初始化,例如。在应用程序的main()函数中,或在返回MaterialApp的小部件的状态中:

static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();

将一个构建器添加到您的MaterialApp,该构建器用Scaffold包装每个页面:

builder: (BuildContext context, Widget child) {
return Scaffold(
key: scaffoldKey,
body: child,
);
}

现在,您不再需要特定于页面的Context,因为您可以使用顶级Scaffold来显示Snackbars

MyAppState.scaffoldKey.currentState.showSnackBar(...)

一种方法是使用流(您不必使用 BLoC 来爱流!

例如,在应用状态下,可以有类似以下内容的成员:

StreamController<NotificationData> _notificationsController;
Stream<NotificationData> get notificiations => _notificationsController.stream;
void sendNotification(NotificationData n) {
_notificationsController.add(n);
}

这真的很整洁,因为您可以从应用程序的其他部分、业务逻辑或任何合适的地方调用 sendNotification。
现在,您可以在希望通知显示的任何位置创建一个新小部件。这是我在我的应用程序中使用的(虽然它适用于对话框):

class DialogListener extends StatefulWidget {
final Stream stream;
final WidgetBuilder dialogBuilder;
final Widget child;
DialogListener({Key key, this.stream, this.dialogBuilder, this.child}) : super(key: key);
@override
_DialogListenerState createState() => new _DialogListenerState();
}
class _DialogListenerState extends State<DialogListener> {
StreamSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = widget.stream?.listen((_) {
if (mounted) {
showDialog(context: context, builder: widget.dialogBuilder);
}
});
}
@override
void dispose() { 
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}

然后我可以在DialogListener中包装我的页面或任何内容

DialogListener(
stream: Provider.of<MyState>(context).notifications,
dialogBuilder: (context) => MyNotificationDialog(),
child: RestOfMyPage()
)

请注意,在我的实现中,我实际上并没有使用流中的任何数据,因此您必须添加它。

将上下文传递给您的状态很诱人,但是由于各种原因(特别是单元测试),您会发现这是一个坏主意,这意味着您可能应该重新考虑体系结构或做类似的事情。

最新更新