Flutter FCM 后台调用平台具体代码

Posted

技术标签:

【中文标题】Flutter FCM 后台调用平台具体代码【英文标题】:Flutter FCM Background Call Platform Specific Code 【发布时间】:2020-01-21 23:52:16 【问题描述】:

FCM 后台处理程序无法调用在MainActivity.java 中注册的特定于平台的自定义代码(在flutter tutorial 之后)。有什么办法可以使这项工作?我是 Flutter、java 和 android 开发的新手,我的搜索陷入了死胡同。

我可以强制打开设置了标志的应用程序,检查该标志然后调用该方法吗?

是否有一些我可以订阅并从后台发送到的事件/消息系统?

以下代码产生以下输出:

I/flutter ( 6448): BACKGROUND_HANDLER::
I/flutter ( 6448): TRIGGERING ALARM::BEFORE
I/flutter ( 6448): TRIGGERING ALARM::AFTER
E/flutter ( 6448): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: MissingPluginException(No implementation found for method setAlarm on channel com.hybridalert.flutter_hybrid/alarm)
E/flutter ( 6448): #0      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:314:7)
E/flutter ( 6448): <asynchronous suspension>
E/flutter ( 6448): #1      triggerAlarm (package:flutter_hybrid_alert/src/ui/home.dart:30:37)
E/flutter ( 6448): <asynchronous suspension>
E/flutter ( 6448): #2      backgroundHandle (package:flutter_hybrid_alert/src/ui/home.dart:67:5)
E/flutter ( 6448): #3      _fcmSetupBackgroundChannel.<anonymous closure> (package:firebase_messaging/firebase_messaging.dart:38:30)
E/flutter ( 6448): #4      _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #5      _fcmSetupBackgroundChannel.<anonymous closure> (package:firebase_messaging/firebase_messaging.dart:31:42)
E/flutter ( 6448): #6      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:397:55)
E/flutter ( 6448): #7      _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #8      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:394:39)
E/flutter ( 6448): #9      MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:365:54)
E/flutter ( 6448): #10     _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binary_messenger.dart:110:33)
E/flutter ( 6448): #11     _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #12     _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binary_messenger.dart:101:37)
E/flutter ( 6448): #13     _invoke3.<anonymous closure> (dart:ui/hooks.dart:293:15)
E/flutter ( 6448): #14     _rootRun (dart:async/zone.dart:1124:13)
E/flutter ( 6448): #15     _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 6448): #16     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter ( 6448): #17     _invoke3 (dart:ui/hooks.dart:292:10)
E/flutter ( 6448): #18     _dispatchPlatformMessage (dart:ui/hooks.dart:154:5)
E/flutter ( 6448): 

home.dart

import "package:cloud_firestore/cloud_firestore.dart";
import "package:firebase_messaging/firebase_messaging.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_hybrid_alert/src/api/firestore.dart";
import "package:flutter_hybrid_alert/src/api/handlers.dart";
import "package:flutter_hybrid_alert/src/api/storage.dart";
import "package:flutter_hybrid_alert/src/models/models.dart";
import "package:flutter_hybrid_alert/src/routes.dart";
import "package:flutter_hybrid_alert/src/store/actions/auth_actions.dart";
import "package:flutter_hybrid_alert/src/store/actions/blacklist_actions.dart";
import "package:flutter_hybrid_alert/src/store/actions/user_actions.dart";
import "package:flutter_hybrid_alert/src/store/app_state.dart";
import "package:flutter_hybrid_alert/src/store/reducers/auth_reducer.dart";
import "package:flutter_hybrid_alert/src/ui/login.dart";
import "package:flutter_hybrid_alert/src/widgets/indicators/app_loading.dart";
import "package:flutter_local_notifications/flutter_local_notifications.dart";
import "package:flutter_redux/flutter_redux.dart";
import "package:nested_navigators/nested_nav_item.dart";
import "package:nested_navigators/nested_navigators.dart";
import "package:redux/redux.dart";

enum NestedNavItemKey  blacklist, recent, history, settings 

const platform = const MethodChannel("com.hybridalert.flutter_hybrid/alarm");

Future<void> triggerAlarm() async 
  try 
    final bool res = await platform.invokeMethod("setAlarm");
    print(res ? "Alarm triggered successfully." : "Alarm trigger failed.");
   on PlatformException catch (e) 
    print("Error triggering alarm: \"$e.message\".");
  


/// showNotification - Init local notifications and show a new one.
void showNotification(String body) 
  final androidInitSettings = AndroidInitializationSettings("@mipmap/ic_launcher");
  final initSettings = InitializationSettings(androidInitSettings, null);
  final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  flutterLocalNotificationsPlugin.initialize(initSettings);

  const String notificationChannel = "HybridAlert";
  final androidPlatformChannelSpecifics = AndroidNotificationDetails(
    notificationChannel,
    notificationChannel,
    "Alerts for Hybrid platform.",
    importance: Importance.Max,
    priority: Priority.High,
    ticker: "ticker",
  );

  final platformChannelSpecifics = NotificationDetails(androidPlatformChannelSpecifics, null);
  flutterLocalNotificationsPlugin.show(0, notificationChannel, body, platformChannelSpecifics);


Future<dynamic> backgroundHandle(Map<String, dynamic> message) 
  print("BACKGROUND_HANDLER::");
  if (message.containsKey("data")) 
    // Handle data message
    final dynamic data = message["data"];
    showNotification("BG:: " + data["name"]);

    // THIS THROWS THE ERROR
    print("TRIGGERING ALARM::BEFORE");
    triggerAlarm();
    print("TRIGGERING ALARM::AFTER");
  

  return null;


/// handleNotifications - Force local notifications.
Future<dynamic> handleNotifications(Map<String, dynamic> message) async 
  print("NOTIFICATION_HANDLER::");
  if (message.containsKey("data")) 
    final dynamic data = message["data"];
    return showNotification("FG:: " + data["name"]);
  

  final dynamic body = message["notification"]["body"];
  return showNotification(body);


Future<void> initFCM() async 
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
  _firebaseMessaging.requestNotificationPermissions();

  return _firebaseMessaging.getToken().then((token) 
    print("TOKEN:: $token");
    return handleSetUserToken(token);
  ).then((_) 
    return _firebaseMessaging.configure(
      onMessage: handleNotifications,
      onBackgroundMessage: backgroundHandle,
    );
  );


class Home extends StatefulWidget 
  @override
  State<StatefulWidget> createState() 
    return _HomeState();
  


triggerMainThread() async 
  try 
    final bool res = await platform.invokeMethod("setAlarm");
    print(res ? "Alarm triggered successfully." : "Alarm trigger failed.");
   on PlatformException catch (e) 
    print("Error triggering alarm: \"$e.message\".");
  


class _HomeState extends State<Home> 
  AppUserSettings settings;
  String userId;
  Store store;

  @override
  void initState() 
    super.initState();

    // THIS WORKS FINE
    print("TRY_MAIN::BEFORE");
    triggerMainThread();
    print("TRY_MAIN::AFTER");

    initFCM();
    if (userId == null) 
      getStorageUser().then((String id) 
        store.dispatch(SetUserFBEmailAction(id));
        setState(() 
          userId = id;
        );
      );
    
  

  @override
  Widget build(BuildContext context) 
    store = StoreProvider.of<AppState>(context);
    final AppState state = store.state;
    final Stream<DocumentSnapshot> userStream =
        userId != null ? usersRef.document(userId).get().asStream() : null;

    return StreamBuilder(
      stream: userStream,
      builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) 
        if (!snapshot.hasData || (state.loading.settings && state.auth.hybridAuthenticated)) 
          return buildAppLoadingIndicator("Loading settings...");
        

        /*       Initialize User Store Data
         ======================================== */

        // Set hybrid email in store from firebase
        final AppUser appUser = AppUser.fromJson(snapshot.data.data);
        final List<String> stateBlacklist = state.blacklist;
        final List<String> blacklist = appUser.settings.blacklist;
        if (blacklist != null) 
          if (stateBlacklist != null) 
            // Compare the two
            final bool listsEqual = listEquals(blacklist, stateBlacklist);
            if (!listsEqual) 
              store.dispatch(SetBlacklistAction(blacklist));
            
           else 
            store.dispatch(SetBlacklistAction(blacklist));
          
        

        final String hybridEmail = appUser.email;
        final String stateHybridEmail = state.user.hybridEmail;
        final bool hybridEmailChanged = hybridEmail != stateHybridEmail;
        if (hybridEmailChanged) 
          store.dispatch(SetUserHybridEmailAction(hybridEmail));
        

        // Set firebase userId as fbEmail.
        if (state.user.fbEmail == null) 
          store.dispatch(SetUserFBEmailAction(userId));
        

        //  Build the Settings UI.
        return NestedNavigators(
          items: 
            NestedNavItemKey.recent: NestedNavigatorItem(
              initialRoute: Routes.recent,
              icon: Icons.home,
              text: "Recent",
            ),
            NestedNavItemKey.history: NestedNavigatorItem(
              initialRoute: Routes.history,
              icon: Icons.history,
              text: "History",
            ),
            NestedNavItemKey.blacklist: NestedNavigatorItem(
              initialRoute: Routes.blacklist,
              icon: Icons.list,
              text: "Blacklist",
            ),
            NestedNavItemKey.settings: NestedNavigatorItem(
              initialRoute: Routes.settings,
              icon: Icons.settings,
              text: "Settings",
            ),
          ,
          generateRoute: (routeSettings) 
            return Routes.generateRoute(routeSettings, store.state);
          ,
          buildBottomNavigationItem: (key, item, selected) => BottomNavigationBarItem(
            icon: Icon(
              item.icon,
//          color: Colors.blue,
            ),
            title: Text(
              item.text,
              style: TextStyle(
                fontSize: 18,
              ),
            ),
          ),
          bottomNavigationBarTheme: Theme.of(context).copyWith(
            splashColor: Colors.grey,
          ),
        );
      ,
    );
  


class AppHome extends StatefulWidget 
  @override
  State<StatefulWidget> createState() 
    return AppHomeState();
  


class AppHomeState extends State 
  String hybridToken;

  @override
  void initState() 
    super.initState();
    getStorageToken().then((token) 
      setState(() 
        hybridToken = token;
      );
    );
  

  @override
  Widget build(BuildContext context) 
    return StoreConnector<AppState, AppState>(
      converter: (store) => store.state,
      builder: (BuildContext context, AppState state) 
        final AuthState authState = state.auth;
        final bool firebaseAuth = authState.firebaseAuthenticated;
        final bool hybridAuth = authState.hybridAuthenticated;
        if (!hybridAuth) 
          if (hybridToken != null && hybridToken != "") 
            final AuthState newState = AuthState(true, true);
            store.dispatch(SetAuthAction(newState));
          
        

        final bool isLoggedIn = hybridAuth && firebaseAuth;
        if (isLoggedIn) 
          return Home();
        

        return LoginScreen();
      ,
    );
  

MainActivity.java

package com.hybridalert.flutter_hybrid_alert;

import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity 
    private static final String CHANNEL = "com.hybridalert.flutter_hybrid/alarm";

    // I'm aware this is the default code from the flutter tutorial. Just testing at this point.
    private int setAlarm() 
        int batteryLevel = -1;
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) 
            BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
         else 
            Intent intent = new ContextWrapper(getApplicationContext()).
                    registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
            batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
                    intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        

        return batteryLevel;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodCallHandler() 
                    @Override
                    public void onMethodCall(MethodCall call, Result result) 
                        System.out.println("CALL_METHOD::" + call.method);
                        // Note: this method is invoked on the main thread.
                        if (call.method.equals("setAlarm")) 
                            int batteryLevel = setAlarm();

                            if (batteryLevel != -1) 
                                result.success(true);
                             else 
                                result.error("UNAVAILABLE", "Battery level not available.", null);
                            
                         else 
                            result.notImplemented();
                        
                    
                );

    

【问题讨论】:

检查here。 同时检查GeoFencing使用服务注册插件。 @AbhayKoradiya 将"click_action": "FLUTTER_NOTIFICATION_CLICK" 添加到数据有效负载的第一个建议不会触发onMessageonResume 就像多个资源说它应该...我不知道是否或什么我做错了。您能否解释一下您建议的第二个链接(地理围栏)中发生了什么? 检查Flutter FCM ReadMe Guide。小心。它显示在第 4 点。 @AbhayKoradiya 我不确定你在说什么。在重新检查文档时,似乎确认这不是一个与我正在寻找的解决方案相匹配的解决方案。除非有我遗漏的东西,在这种情况下,请告诉我而不是神秘地告诉我。我需要应用程序在没有任何用户交互的情况下打开或移至前台。 【参考方案1】:

如果您已完成https://pub.dev/packages/firebase_messaging 中的所有步骤。然后将 backgroundhandler 函数放在 main() 之外。或者你可以创建一个静态类:

class BackgroundHandler 
  static Future<dynamic> backgroundHandler(Map<String, dynamic> message)  
    if (message.containsKey('data')) 
      final dynamic data = message['data'];

    

    if (message.containsKey('notification')) 
      final dynamic notification = message['notification'];
    
   

【讨论】:

以上是关于Flutter FCM 后台调用平台具体代码的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 在 IOS 上使用 FCM 的后台通知

当应用程序处于后台/终止并收到 FCM 通知时,如何在 Flutter 中显示本地通知?

如何在 Flutter 中使用 Agora 实现 Callkeep?

Flutter IOS 通知的 FCM 在应用程序处于后台或终止时不显示

如何使用 FCM 后台通知更新 UI?

接收方 FCM 通知点击事件