【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,这一期就此完结。

相关推荐
爱意随风起风止意难平10 小时前
003 flutter初始文件讲解(2)
学习·flutter
每次的天空11 小时前
Android第十一次面试flutter篇
android·flutter·面试
宋智孝的小迷弟16 小时前
Android 异步数据流:Kotlin Flow 为何成为新一代“利器”?LiveData 又有何局限?
android·面试·app
getapi16 小时前
为什么 uni-app 开发的 App 没有明显出现屏幕适配问题Flutter 开发的 App 出现了屏幕适配问题
flutter·uni-app
getapi16 小时前
使用 Flutter 开发 App 时,想要根据 Figma 设计稿开发出响应式 UI 界面
flutter·ui·figma
只可远观16 小时前
Flutter GridView网格组件
flutter
jianleepb18 小时前
2025Flutter(安卓)面试题详解
flutter
活哈哈哈哈19 小时前
安卓AppWidget桌面小组件在国产移动设备的一些适配问题
app
iOS阿玮21 小时前
不想被苹果卡审最好错开这两个提审时间
uni-app·app·apple
90后的晨仔21 小时前
Flutter 中常见的几种页面跳转方式
前端·flutter