【问题标题】:Correct place to init notifications in Flutter在 Flutter 中初始化通知的正确位置
【发布时间】:2019-10-08 20:11:09
【问题描述】:

我目前正在努力让通知按照我希望的方式在 Flutter 上运行。 我目前的解决方案是有一个动态的 LandingPage,它 - 取决于 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();
    }
  }
}

通过我试图实现的目标,在用户登录后让消息传递系统运行。我只在当前令牌未定义时设置回调。

我现在遇到的问题是,所说的上下文不是应用程序范围的,这意味着,一旦我导航到一个新的 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-callback 中显示本地通知,但本地通知和 firebase-messaging 在 iOS 上不能一起使用。

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

【问题讨论】:

    标签: flutter push-notification google-cloud-messaging


    【解决方案1】:

    没有应用程序范围的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(...)
    

    【讨论】:

      【解决方案2】:

      一种方法是使用流(您不必使用 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()
      )
      

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

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

      【讨论】:

      • 好吧,我可以在任何地方使用 LandingPage 中的上下文吗?因为这是我目前的方法,它似乎不起作用。至少不是通过将上下文作为变量传递。
      • @Thomas 不,你不能。上下文是小部件在树中的位置,当您更改页面时,它将不再存在。就像我说的,你不应该将上下文传递到你的业务逻辑中。
      猜你喜欢
      • 1970-01-01
      • 2023-03-27
      • 2023-03-03
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 2015-10-30
      • 1970-01-01
      • 2020-10-16
      相关资源
      最近更新 更多