Flutter:来自后台任务的通知导航,没有上下文问题

Posted

技术标签:

【中文标题】Flutter:来自后台任务的通知导航,没有上下文问题【英文标题】:Flutter: Notification Navigation from Background Task without context issue 【发布时间】:2021-09-30 17:43:17 【问题描述】:

我的应用程序执行以下操作:它使用 Flutter Workmanager 运行后台任务,该任务检查一些值,然后通过 Flutter Local Notification 引发通知。在 FlutterLocalNotifications Plugin 的初始化方法中,我可以指定一个内联函数,它应该导航到一个页面。由于我没有 Builder 上下文,我必须使用带有 OnGenerateRoute 的导航键将用户转发到站点。但是,这不起作用,我不知道为什么。我知道当应用程序被杀死时,这段代码很有用。

示例代码

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

但是当应用程序仍然存在时该怎么办?下面列出了我的项目代码。

Main.Dart

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);
  );

【问题讨论】:

【参考方案1】:

我通过对两个不同的事件做出反应来解决它。

如果应用程序启动,我会检查应用程序是否由通知启动。这可以在 main.dart 中的 createState 或 initState 中完成。这段代码对此很有用。

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

如果应用程序在后台并且通知再次启动应用程序,您必须使用 Widgets Binding Observer 并对 App Resume 事件做出反应。 Medium 上有一篇文章,其中包含此案例的示例代码。看看here。

在响应 App Resume Event 和使用 Flutter Local 通知插件时有一个缺点。即使应用程序再次进入后台状态并由用户手动恢复,上述代码在通知触发后始终传递 true。这意味着更改页面的代码将始终被调用,即使 如果您没有点击通知。因此,我使用布尔变量来触发 App State Resume 代码一次。显然,如果您通过通知进入应用程序,应用程序会恢复并收到第二次通知,则不会执行更改页面的代码。这是一种解决方法,但就我而言,这已经足够了。

【讨论】:

【参考方案2】:

你找到解决办法了吗?我还在开发一个 Android 应用程序,它更新 firestore 数据库中的用户数据(每隔 15 分钟)并向用户发送通知(这两个任务都使用 Flutter workmanager_plugin 在后台发生)。当用户点击通知时,他应该被导航到显示来自数据库的最新数据的路线。

前 2 个后台任务已成功执行,但单击通知时没有发生任何事情。我也在使用 GlobalKey 键来获取 MaterialApp 小部件的上下文,以便可以推送路由 Route。

FlutterLocalNotificationsPlugin.initialize() 方法的 onSelectNotification 属性似乎不适用于 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);
  

点击通知时。以下消息应打印在控制台上:“Inside on select Route Navigator.Route=Details”,他应该在 UserDetails 页面上导航,但似乎没有发生任何事情。

【讨论】:

以上是关于Flutter:来自后台任务的通知导航,没有上下文问题的主要内容,如果未能解决你的问题,请参考以下文章

处理来自后台任务的 toast 通知动作触发器 - UWP

如何从后台打开我的应用程序并导航到接收通知消息的页面?

Flutter 中的 AndroidManifest 中缺少默认通知通道元数据

当应用程序被杀死时,无法在推送通知单击时导航到特定屏幕(Flutter android)

在 Flutter 上单击背景通知时如何导航?

Dart / Flutter:在没有上下文的页面之间导航(使用 Navigator)(所以没有 Navigator.push?)