在 Flutter 中初始化通知的正确位置
Correct place to init notifications in Flutter
我目前正在努力按照我希望的方式在 Flutter 上接收通知 运行。
我目前的解决方案是有一个动态的登陆页面,它 - 取决于 FirebaseAuth - 重定向到登录或主屏幕。
MaterialApp(
theme: ThemeData(
...
home: Scaffold(body: Builder(builder: (context) => LandingPage()))
),
在 LandingPage 中,我将在我的 Singleton 中调用一个函数来为我设置通知。如您所见,我在这里传递了上下文。这是因为我想从通知的 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();
}
}
}
我正在努力实现这一点,以便在用户登录后获得消息系统 运行ning。我只在当前令牌未定义时设置回调。
我现在遇到的问题是,所说的上下文不是应用程序范围的,这意味着,一旦我导航到一个新的 Widget,它有自己的上下文,Snackbar 就不能再显示了。现在我不确定这是否是初始化消息传递的正确位置,因为没有应用程序范围的上下文。
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,我需要通过它来遍历我的所有页面。我听说这种方法也很慢。
一种方法是使用流(您不必使用 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()
)
请注意,在我的实现中,我实际上并未使用流中的任何数据,因此您必须将其添加进去。
将上下文传递给您的状态很诱人,但由于各种原因(特别是单元测试),您发现这是一个坏主意,这意味着您可能应该重新考虑架构或做类似的事情。
没有 application-wide Context
这样的东西,但是您可以为 Snackbars
.[=24= 创建一个 application-wide Scaffold
]
初始化一个可以静态访问的GlobalKey
。必须在构建任何路由之前对其进行初始化,例如。在您的应用程序的 main()
功能中,或在 returns MaterialApp
:
的小部件状态中
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
向您的 MaterialApp
添加一个构建器,它用 Scaffold
:
包装每个页面
builder: (BuildContext context, Widget child) {
return Scaffold(
key: scaffoldKey,
body: child,
);
}
现在你不再需要 page-specific Context
因为你可以使用 top-level Scaffold
来显示 Snackbars
:
MyAppState.scaffoldKey.currentState.showSnackBar(...)
我目前正在努力按照我希望的方式在 Flutter 上接收通知 运行。 我目前的解决方案是有一个动态的登陆页面,它 - 取决于 FirebaseAuth - 重定向到登录或主屏幕。
MaterialApp(
theme: ThemeData(
...
home: Scaffold(body: Builder(builder: (context) => LandingPage()))
),
在 LandingPage 中,我将在我的 Singleton 中调用一个函数来为我设置通知。如您所见,我在这里传递了上下文。这是因为我想从通知的 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();
}
}
}
我正在努力实现这一点,以便在用户登录后获得消息系统 运行ning。我只在当前令牌未定义时设置回调。
我现在遇到的问题是,所说的上下文不是应用程序范围的,这意味着,一旦我导航到一个新的 Widget,它有自己的上下文,Snackbar 就不能再显示了。现在我不确定这是否是初始化消息传递的正确位置,因为没有应用程序范围的上下文。
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,我需要通过它来遍历我的所有页面。我听说这种方法也很慢。
一种方法是使用流(您不必使用 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()
)
请注意,在我的实现中,我实际上并未使用流中的任何数据,因此您必须将其添加进去。
将上下文传递给您的状态很诱人,但由于各种原因(特别是单元测试),您发现这是一个坏主意,这意味着您可能应该重新考虑架构或做类似的事情。
没有 application-wide Context
这样的东西,但是您可以为 Snackbars
.[=24= 创建一个 application-wide Scaffold
]
初始化一个可以静态访问的GlobalKey
。必须在构建任何路由之前对其进行初始化,例如。在您的应用程序的 main()
功能中,或在 returns MaterialApp
:
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
向您的 MaterialApp
添加一个构建器,它用 Scaffold
:
builder: (BuildContext context, Widget child) {
return Scaffold(
key: scaffoldKey,
body: child,
);
}
现在你不再需要 page-specific Context
因为你可以使用 top-level Scaffold
来显示 Snackbars
:
MyAppState.scaffoldKey.currentState.showSnackBar(...)