Flutter: навигация по уведомлениям из фоновой задачи без проблем с контекстом

Мое приложение выполняет следующие действия: оно запускает фоновую задачу с помощью Flutter Workmanager, который проверяет некоторые значения, а затем выдает уведомление через локальное уведомление Flutter. В методе инициализации плагина FlutterLocalNotifications я могу указать встроенную функцию, которая должна переходить на страницу. Поскольку у меня нет контекста Builder, я должен использовать клавишу навигатора с OnGenerateRoute, чтобы перенаправить пользователя на сайт. Однако это не работает, и я не знаю почему. Я знаю, что этот код полезен, когда приложение перестало работать.

Пример кода

      final NotificationAppLaunchDetails? notificationAppLaunchDetails =
      await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
  String initialRoute = HomePage.routeName;
  if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
    selectedNotificationPayload = notificationAppLaunchDetails!.payload;
    initialRoute = SecondPage.routeName;
  }

Но что делать, если приложение еще живо? Код моего проекта указан ниже.

Главный Дарт

      void main() {

  WidgetsFlutterBinding.ensureInitialized();
  Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
  Workmanager().registerPeriodicTask("1", "test",frequency: Duration(minutes: 15));
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: "/",
      navigatorKey: NavigationService.navigatorKey,
      onGenerateRoute: RouteGenerator.generateRoute
    );
  }
}

RouteGenerator.dart

      class RouteGenerator {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    final args = settings.arguments;

    switch(settings.name) {
      case '/first':
        return MaterialPageRoute(builder: (_) => Page1(title: "First"));
      case '/second':
        return MaterialPageRoute(builder: (_) => Page2(title: "Second"));
      case '/third':
        return MaterialPageRoute(builder: (_) => Page3(title: "Third"));
      case '/fourth':
        return MaterialPageRoute(builder: (_) => Page4(title: "Fourth"));
    }
    return MaterialPageRoute(builder: (_) => Page0(title: "Root!"));
  }
}

class NavigationService {
  static final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();

  static Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState!.pushNamed(routeName);
  }
}

service.dart

      
class DevHttpOverrides extends HttpOverrides {
  @override
  HttpClient createHttpClient(SecurityContext? context) {
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
  }
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async{
        HttpOverrides.global = new DevHttpOverrides();
        var url = 'https://172.16.0.100/handler.php?page=settings';
        http.Response response = await http.get(Uri.parse(url));
        List<dynamic> list = jsonDecode(response.body);


        SharedPreferences prefs = await SharedPreferences.getInstance();
        var usage = "Beides";
        var checkValue = "temp_out";
        var borderValueString = "14.9";
        var checktype = "Grenzwert überschreiten";

        var borderValueDouble;
        var message = "";
        if(usage != "Nur Home Widgets" && checkValue != "" && borderValueString != "" && checktype != "")
        {
          var value = list[0][checkValue];

          if (double.tryParse(borderValueString) != null && double.tryParse(value) != null)
          {
            borderValueDouble = double.parse(borderValueString);
            value = double.parse(value);
          }

          if (checktype == "Grenzwert unterschreiten")
          {
            if (borderValueDouble is double)
            {
              if (value <= borderValueDouble)
              {
                message = "Grenzwert unterschritten";
              }
            }
          }
          else if (checktype == "Grenzwert überschreiten")
          {
            if (borderValueDouble is double)
            {
              if (value >= borderValueDouble)
              {
                message = "Grenzwert überschritten";
              }
            }
          }
          else if (checktype == "Entspricht Grenzwert")
          {
            if (borderValueDouble == value)
            {
              message = "Grenzwert erreicht";
            }
          }
        }

        if(message != "")
        {
          FlutterLocalNotificationsPlugin flip = new FlutterLocalNotificationsPlugin();
          var android = new AndroidInitializationSettings('@mipmap/ic_launcher');
          var ios = new IOSInitializationSettings();

          var settings = new InitializationSettings(android: android, iOS: ios);
          flip.initialize(settings, onSelectNotification: (String? payload) async {
            await NavigationService.navigatorKey.currentState!.push(MaterialPageRoute(builder: (context) => Page4(title: "Hello")));
      
  });
            


          var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
              '1',
              'weatherstation',
              'Notify when values change',
              importance: Importance.max,
              priority: Priority.high
          );

          var iOSPlatformChannelSpecifics = new IOSNotificationDetails();

          var platformChannelSpecifics = new NotificationDetails(
              android: androidPlatformChannelSpecifics,
              iOS: iOSPlatformChannelSpecifics);

          await flip.show(0, message,
              'App öffnen für weitere Details',
              platformChannelSpecifics, payload: 'Default_Sound'
          );
        }

    return Future.value(true);
  });
}

2 ответа

Я решил это, отреагировав на два разных события.

Если приложение запускается, я проверяю, было ли приложение запущено с помощью уведомления. Это можно сделать в createState или initState в main.dart. Этот код полезен для этого.

      final NotificationAppLaunchDetails? notificationAppLaunchDetails =
  await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
  selectedNotificationPayload = notificationAppLaunchDetails!.payload;
  initialRoute = SecondPage.routeName;
}

Если приложение работает в фоновом режиме и уведомление запускает приложение снова, вы должны использовать Widgets Binding Observer и реагировать на событие возобновления приложения. На Medium есть статья, в которой есть пример кода для этого случая. Взгляните здесь.

Есть один недостаток при реагировании на событие возобновления приложения и использовании плагина локальных уведомлений Flutter. Вышеупомянутый код всегда доставляет true после запуска уведомления, даже если приложение снова вошло в фоновое состояние и было возобновлено вручную пользователем. Это означает, что код для изменения страницы будет вызываться всегда, даже если вы не щелкали уведомление. Поэтому я использую логическую переменную для однократного запуска кода возобновления состояния приложения. Очевидно, что если вы войдете в приложение через уведомление, приложение будет возобновлено, и вы получите второе уведомление, код для изменения страницы не будет выполнен. Это обходной путь, но для моего случая он достаточно хорош.

Вы нашли какое-нибудь решение? Я также работаю над приложением для Android, которое обновляет пользовательские данные в базе данных firestore (с интервалом 15 минут) и отправляет пользователю уведомление (обе задачи выполняются в фоновом режиме с использованием flutter workmanager_plugin). Когда пользователь нажимает на уведомление, он должен перейти к маршруту, который показывает последние данные из базы данных.

Первые 2 фоновые задачи выполняются успешно, но при нажатии на уведомление ничего не происходит. Я также использую ключ GlobalKey для получения контекста виджета MaterialApp, чтобы можно было протолкнуть Route Route.

Похоже, что свойство onSelectNotification для метода FlutterLocalNotificationsPlugin.initialize() не работает для плагина workmanager. Я также добавил в него оператор печати, но в консоли ничего не отображается. Я подумал, что, возможно, у моего Globalkey есть какая-то ошибка, но когда я попробовал его для навигации по страницам в не фоновой задаче, это происходило успешно, аналогично onSelectNotification отлично работал для нерабочей задачи.

      void callbackDispatcher() {
  Workmanager().executeTask((task, data) async {
    if(task=='showNotification'){
       FlutterLocalNotificationsPlugin notifPlugin =
            FlutterLocalNotificationsPlugin();
        NotificationDetails notificationDetails = NotificationDetails(
          android: AndroidNotificationDetails(
            'main_channel',
            'Main Channel',
            'Main Notification Channel',
            importance: Importance.max,
            priority: Priority.high,
          ),
        );
        await notifPlugin.initialize(
           InitializationSettings(
              android: AndroidInitializationSettings('ic_launcher')),
           onSelectNotification: (String? payload) async {
             print('Inside on select Route Navigator. Route= $payload');
             switch (payload!) {
               case 'Home':
                  // navigatorKey is GlobalKey
                 navigatorKey.currentState!
                  .push(MaterialPageRoute(builder: (context) => Home()));
                break;
               case 'Auth':
                 navigatorKey.currentState!
                  .push(MaterialPageRoute(builder: (context) => Auth()));
               break;
               case 'Details':
                 navigatorKey.currentState!
                  .push(MaterialPageRoute(builder: (context) => UserDetails()));
               break;
          }
        });
      await notifPlugin.show(id, title, body, notificationDetails, payload:payload);
      
    }
    return Future.value(true);
  }
}

При нажатии на уведомление. На консоли должно быть напечатано следующее сообщение: «Внутри выберите Route Navigator. Route= Details», и он должен быть перемещен на странице UserDetails, но, похоже, ничего не происходит.

Другие вопросы по тегам