Правильное место для инициации уведомлений во Флаттере - PullRequest
3 голосов
/ 22 мая 2019

В настоящее время я борюсь с тем, чтобы уведомления запускались так, как я хочу, чтобы они появлялись во Флаттере. Мое текущее решение состоит в том, чтобы иметь динамическую LandingPage, которая, в зависимости от FirebaseAuth, перенаправляется либо на страницу входа, либо на главный экран.

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

Внутри LandingPage я вызову функцию в моем синглтоне для настройки уведомлений для меня. Как видите, я передаю контекст здесь. Это потому, что я хочу показать Snackbar из обратного вызова onMessage моих уведомлений.

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, который мне нужно будет пройти через все мои страницы. Этот подход также очень медленный, как я слышал.

Ответы [ 2 ]

1 голос
/ 24 июня 2019

Не существует такого понятия, как Context для всего приложения, но вы можете создать Scaffold для всего приложения Snackbars.

Инициализировать 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(...)
0 голосов
/ 23 июня 2019

Один из способов сделать это - использовать потоки (вам не нужно использовать 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()
)

Обратите внимание, в моей реализации, яна самом деле не используйте никаких данных из потока, поэтому вам придется добавить их в.

Заманчиво передавать контекст в ваше состояние, но, как вы обнаружили, это плохая идея по разным причинам (особенномодульное тестирование) и означает, что вам, вероятно, следует переосмыслить архитектуру или сделать что-то подобное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...