【Flutter】FCM与Notifications集成流程

FCM的集成与Niotfications的使用

前言

说起App推送功能,国内的我们大家都用集成产品如极光个推或者使用厂商推送,而如果是是海外版的应用,所以只能用 FCM 了。

说起 FCM 大家都知道只要你用了 Firebase 那么你的各自依赖必然就会高,导致的就是 Flutter 的 android 项目的target依赖升级,kotlin版本升级,gradle版本升级,androidx升级等一系列的问题。

并且在 Android 项目中使用 FCM 依赖,在前台的时候还需要我们自己手动的弹出通知 Notification,所以我们还需要集成到 notification 插件。

而使用 noticifation 插件之后又会导致一些列的依赖冲突。

这里单独出一篇文章把踩过的坑记录一下。

一、依赖集成

首先我们集成 FCM 的插件

yaml 复制代码
  # FCM推送
  firebase_messaging: ^15.2.6

毫无意外的报错,接下来我们需要升级 gradle,kotlin,target 等依赖

打开app中的android文件夹,在根目录的 settings.gradle 升级对应的依赖。

java 复制代码
pluginManagement {
    def flutterSdkPath = {
        def properties = new Properties()
        file("local.properties").withInputStream { properties.load(it) }
        def flutterSdkPath = properties.getProperty("flutter.sdk")
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
        return flutterSdkPath
    }()

    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")

    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/central' }
        maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
        maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }
        maven { url 'https://jitpack.io' }
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
    id "com.android.application" version "8.7.3" apply false
    id "org.jetbrains.kotlin.android" version "1.9.0" apply false

    // 添加 Google Services 插件
    id "com.google.gms.google-services" version "4.4.0" apply false
}

include ":app"

给大家参考,一定别忘记了 Google Services 。

在 gradle.wrapper 中的配置,推荐设置 gradle 版本为8.0+

ini 复制代码
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

一旦设置 gradle 为 8.0+ 那么你的 android/app 下面的 build.gradle 中还需要修改一些配置

  1. 强制指定 namespace 属性
  2. 自动给子依赖添加 namespace 属性
  3. target 35 (因为andorid-lifecycle版本高了,可以运行但是无法打包,推荐升级到35)
  4. appcompat 依赖不能太低
  5. 别忘记添加 Google Services 应用

给出示例:

java 复制代码
plugins {
    id "com.android.application"
    id "kotlin-android"
    id "kotlin-kapt"
    id "dev.flutter.flutter-gradle-plugin"
    id 'com.google.gms.google-services'
}

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new Exception("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

android {
    namespace 'com.xxx.xxx'
    compileSdk 35

    compileOptions {
        // 启用核心库脱糖(Core Library Desugaring)
        coreLibraryDesugaringEnabled true

        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    lintOptions {
        // 如打包出现Failed to transform libs.jar to match attributes
        checkReleaseBuilds false
    }

    defaultConfig {
        applicationId "com.xxx.xxx"
        minSdkVersion 21
        targetSdkVersion 35
        versionCode 100
        versionName "1.0.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        multiDexEnabled true

        ndk {
            //选择要添加的对应 cpu 类型的 .so 库。
            abiFilters 'armeabi-v7a', 'arm64-v8a'

        }

        manifestPlaceholders = [
                JPUSH_PKGNAME   : applicationId,
                JPUSH_APPKEY    : "1234567890",
                JPUSH_CHANNEL   : "developer-default",
                queryAllPackages: ""
        ]
    }

    //配置keystore签名
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
        debug {

        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            //默认系统混淆
            minifyEnabled true
            //是否可调试
            debuggable false
            //Zipalign优化
            zipAlignEnabled true
            //移除无用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            signingConfig signingConfigs.release
            //是否可调试
            debuggable true
        }
    }
}

flutter {
    source '../..'
}

// 覆盖所有子模块的命名空间规则
subprojects {
    afterEvaluate { project ->
        if (project.hasProperty("android") && project.android.namespace == null) {
            project.android.namespace = "com.hongyegroup.app24ifm.${project.name}"
        }
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0"

    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'

    implementation 'com.github.bumptech.glide:glide:4.11.0'
    kapt 'com.github.bumptech.glide:compiler:4.11.0'

    implementation 'androidx.appcompat:appcompat:1.6.1'  //加入AndroidX依赖

}

接下来你就能编译通过并且运行了。

然后我们在加入本地通知的依赖

yaml 复制代码
  flutter_local_notifications: ^19.2.1

pub get 之后不出意外的报错了,首先如果你有 device_info_plus 插件的话他们是有冲突的,需要升级 device_info_plus 到新版本。

yaml 复制代码
  device_info_plus: ^11.3.0

  flutter_local_notifications: ^19.2.1

并且在你的 android/app 下面的 build.gradle 中还需要修改一些配置

需要加入脱糖的配置

arduino 复制代码
android {
    namespace 'com.xxx.xxx'
    compileSdk 35

    compileOptions {
        // 启用核心库脱糖(Core Library Desugaring)
        coreLibraryDesugaringEnabled true

        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    ...
}

dependencies {
    ...
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}

在上面的完整版 build.gradle 中我已经给出了。

到此我们的依赖才算集成完成,那么怎么使用呢?我推荐一个工具类即可。

二、工具类封装

这里直接给出源码,获取FCM token 并且初始化LocalNotification与 FCM 的消息监听

fcm`_utils.dart

dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:router/componentRouter/component_service_manager.dart';
import 'package:shared/utils/log_utils.dart';

// 定义一个回调函数类型
typedef TokenCallback = void Function(String token);

class FcmUtils {
  //FCM SDK对象
  final _firebaseMessaging = FirebaseMessaging.instance;

  //Notifications插件对象
  final _localNotifications = FlutterLocalNotificationsPlugin();

  //Android 8+ 的消息通道
  final _androidChannel = const AndroidNotificationChannel(
      'Normal', //设置 Channel ID
      'Channel Normal Android', //设置 Channel 名称
      importance: Importance.defaultImportance); //设置该 Channel 的优先级

  // 初始化,获取设备token
  Future<void> initNotifications({
    TokenCallback? onToken, // Token回调函数
  }) async {
    //申请动态通知权限
    await _firebaseMessaging.requestPermission();
    //获取 FCM Token
    final fCMToken = await _firebaseMessaging.getToken();
    if (fCMToken != null) {
      Log.d("FCM -> Token:$fCMToken");
      onToken?.call(fCMToken); // 将初始 Token 通过回调传递
    }
    //刷新 FCM Token 的监听
    _firebaseMessaging.onTokenRefresh.listen((newToken) {
      Log.d("FCM onTokenRefresh -> Token:$newToken");
      onToken?.call(newToken); // 将刷新后的 Token 通过回调传递
    });

    //拿到 FCM Token 之后初始化一些监听
    _initPushNotifications();
    _initLocalNotifications();
  }

  /// Notifications 插件初始化,监听前台消息
  Future _initLocalNotifications() async {
    const iOS = DarwinInitializationSettings();
    // @drawable/ic_launcher是应用的图标,路径是:android/app/src/main/res/mipmap/ic_launcher.png
    const android = AndroidInitializationSettings('@mipmap/ic_launcher');
    // Notifications 插件只需要处理 Android 和 iOS 平台的配置
    const settings = InitializationSettings(android: android, iOS: iOS);
    // Notifications 插件初始化
    await _localNotifications.initialize(settings, onDidReceiveNotificationResponse: (NotificationResponse response) {
      // android 前台消息点击回调
      final message = RemoteMessage.fromMap(jsonDecode(response.payload!));
      Log.d(response);
      // 处理收到消息
      handleMessage(message);
    });
    //Android 8 以上会通过 Channel 创建对应的通知渠道
    final platform = _localNotifications.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
    await platform?.createNotificationChannel(_androidChannel);
  }

  /// FCM 初始化接收消息的各种回调
  Future _initPushNotifications() async {
    await _firebaseMessaging.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);

    // 打开app时,会执行该回调,获取消息(通常是程序终止时,点击消息打开app的回调)
    _firebaseMessaging.getInitialMessage().then(
      (RemoteMessage? message) {
        if (message == null) return; // 没有消息不执行后操作
        handleMessage(message);
      },
    );

    // 后台程序运行时,点击消息触发
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) => handleMessage(message));

    // 前台消息,android不会弹出通知框,所以需要 Notifications 插件自定义本地通知(iOS没有前台消息,iOS的前台消息和后台运行时一样的效果)
    FirebaseMessaging.onMessage.listen((message) {
      Log.d("前台收到的消息 -> message:${message.data}");
      final notification = message.notification;
      if (notification == null) return;
      if (Platform.isIOS) return;

      _localNotifications.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
              android: AndroidNotificationDetails(
            _androidChannel.id,
            _androidChannel.name,
            icon: '@mipmap/ic_launcher',
          )),
          payload: jsonEncode(message.toMap()));
    });

  }

  // 处理收到的消息,比如跳转页面或者其他处理
  void handleMessage(RemoteMessage message) {
    final dataJson = message.data;
    Log.d("点击消息的处理 handleMessage -> data:$dataJson");

  }

基本上每一行我都给出了注释,我们只需要调用 initNotifications 传入 callback 就可以获取到 fcmToken 和 FCM Refresh 之后的 fcmToken 。

如何使用呢?

首先把 Firebase 的谷歌服务配置导入到项目

如果是iOS则比较简单一些,只需要替换项目中的GoogleService-Info.plist文件,并启动连接Firebase即可。

从 Firebase 的控制台下载并替换项目中的文件:ios/Runner/GoogleService-Info.plist,并且在 ios/Runner/AppDelegate.swift:

配置好对应的证书即可收到推送。

Flutter 的代码如下:

创建我们的 FirebaseOptions 初始化配置类:

MyFirebaseOptions:

php 复制代码
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
    show defaultTargetPlatform, kIsWeb, TargetPlatform;

class MyFirebaseOptions {
  static FirebaseOptions get currentPlatform {
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        return android;
      case TargetPlatform.iOS:
        return ios;
      default:
        throw UnsupportedError(
          'MyFirebaseOptions are not supported for this platform.',
        );
    }
  }

  static const FirebaseOptions android = FirebaseOptions(
    apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c',
    appId: '1:406099696497:android:d7780551ab203907262ab6',
    messagingSenderId: '406099696497',
    projectId: 'flutterfire-e2e-tests',
    storageBucket: 'flutterfire-e2e-tests.firebasestorage.app'
  );

  static const FirebaseOptions ios = FirebaseOptions(
    apiKey: 'AIzaSyDooSUGSf63Ghq02_iIhtnmwMDs4HlWS6c',
    appId: '1:406099696497:ios:acd9c8e17b5e620e3574d0',
    messagingSenderId: '406099696497',
    projectId: 'flutterfire-e2e-tests',
    databaseURL:
        'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app',
    storageBucket: 'flutterfire-e2e-tests.appspot.com',
    androidClientId:
        '406099696497-tvtvuiqogct1gs1s6lh114jeps7hpjm5.apps.googleusercontent.com',
    iosClientId:
        '406099696497-taeapvle10rf355ljcvq5dt134mkghmp.apps.googleusercontent.com',
    iosBundleId: 'io.flutter.plugins.firebase.tests',
  );

}

然后我们在应用的入口 main.dart 中初始化

csharp 复制代码
void main() async {
   WidgetsFlutterBinding.ensureInitialized();

  //初始化FCM
  Firebase.initializeApp(name: 'Your App Name', options: MyFirebaseOptions.currentPlatform);
}

初始化完成之后怎么获取到FCM并监听FCM消息呢?

这个得看你的实际业务场景,你可以在初始化之后就开始监听,也可以在任意你想要的地方,比如我们的场景就是用户登录之后才开始监听获取到fcmToken并post给后台。

我就在首页的逻辑中添加的逻辑

scss 复制代码
  void _registerFCM() {
    //获取token,并初始化监听
    FcmUtils().initNotifications(onToken: (fcmToken) {
      Log.d("准备注册FCM Token :$fcmToken");
      _doRegisterFCM(fcmToken);
    });
  }

  /// 调用接口注册FCMToken
  void _doRegisterFCM(String fcmToken) async {
    final result = await _authRepository.registerFCMToken(fcmToken: fcmToken, cancelToken: cancelToken);

    //请求成功去首页
    if (result.isSuccess) {
      Log.d("注册 FMC Token 成功!");
    } else {
      ToastEngine.show(result.errorMsg ?? "UnKnow Error");
    }
  }

这样后台有推送的事件就可以通过 fcmToken 推送到指定的设备了:

后记

本文我们讲了 fmc 与 notification 的插件集成中安卓模块遇到的冲突问题与推荐的升级(iOS没有这些问题)。

其次我们给出了推荐的 FCM初始化工具类,方便获取到 fcmToken 与 消息的监听,并且对前台消息的 Notification 给出示例。

推荐大家多用几个机型和网络尝试,只要有一个设备能收到消息就说明没有问题,其他机型收不到推送可能是网络的问题,可以关闭梯子或者开启梯子尝试,个人亲历有的设备关闭梯子才能收到,有的设备只有开启梯子才能收到。怪异的很。把包发给国外的同事就没这些烦恼,就很顺。

那本文的代码比较简单,全部的代码也已经在文中展出了。

如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,理解不正确的地方,同学们都可以指出修正。

今天的分享就到这里啦,当然如果你有其他更好的方式,也希望大家能评论区交流一起学习进步。

如果感觉本文对你有一点的启发和帮助,还望你能点赞支持一下,你的支持对我真的很重要。

Ok,这一期就此完结。

相关推荐
LawrenceLan1 天前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹1 天前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者961 天前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者961 天前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨1 天前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者961 天前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难1 天前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios