Skip to content

[firebase_messaging] 'registrar.activity() must not be null' exception occurs in 'onBackgroundMessage' #1849

Closed
@khudev-ksw

Description

@khudev-ksw

Bug Description

When the app goes to the fcm configure : _firebaseMessaging.configure(),
it throws exception

PlatformException (PlatformException(error, registrar.activity() must not be null, null))

I also tried handler method as STATIC, but it throws the same exception.

To Reproduce

Steps to reproduce the behavior:

  1. Define TOP-LEVEL or STATIC background handler method myBackgroundMessageHandler
  2. Config myBackgroundMessageHandler as onBackgroundMessage inside the _firebaseMessaging.configure()
  3. Run
  4. It throws exception when the process dive into the _firebaseMessaging.configure()
  5. When I remove onBackgroundMessage as comment, it runs normally.

My codes

homePage.dart

final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();

String _urlBase = url.urlBase;

Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
  if (message.containsKey('data')) {
    // Handle data message
    final dynamic data = message['data'];
    print(data);
  }
  if (message.containsKey('notification')) {
    // Handle notification message
    final dynamic notification = message['notification'];
    print(notification);
  }
  // Or do other work.
}

class Homepage extends StatefulWidget {
  Homepage({Key key, this.pageIndex}) : super(key: key);
  final pageIndex;
  @override
  _HomepageState createState() => _HomepageState();
}

class _HomepageState extends State<Homepage> {
  //URL launcher
  static const platform = const MethodChannel('example.com/channel');

  ScrollController _controller;
  @override
  void initState() {
    super.initState();

    _regChannel();
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        try {
          await getUserNotificationLists();

          String iconImg;

          for (var eachNotification in _userNotificationList) {
            if (eachNotification['post_id'] == message['data']['post_id']) {
              iconImg = eachNotification['icon_img'];
              break;
            }
          }

          showOverlayNotification((context) {
            return overlayNotificationCard(
                message['notification']['title'],
                message['notification']['body'],
                iconImg,
                message['data']['post_id'],
                context);
          }, duration: Duration(milliseconds: 5000));
          print("onMessage: $message");
        } catch (e) {
          print(e);
        }
      },
      onBackgroundMessage: Platform.isIOS ? null : myBackgroundMessageHandler,
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
      },
    );
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(sound: true, badge: true, alert: true));
    _firebaseMessaging.onIosSettingsRegistered
        .listen((IosNotificationSettings settings) {
      print("Settings registered: $settings");
    });

    getUserNotificationLists();

    _getEvent();
    _getPinnedEvent();
    _checkUser();
    _checkFcmToken();
    _widgetIndex = widget.pageIndex;
    _bIndex = widget.pageIndex;
    _controller = ScrollController();
  }
.
.
.

Application.kt

package com.example.event

import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
    override fun onCreate() {
        super.onCreate()
        FlutterFirebaseMessagingService.setPluginRegistrant(this)
    }

    override fun registerWith(registry: PluginRegistry?) {
        GeneratedPluginRegistrant.registerWith(registry)
    }
    
}

MainActivity.kt

package com.example.event

import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant

import io.flutter.plugin.common.MethodChannel

import android.os.Build
import android.app.NotificationManager
import android.app.NotificationChannel

class MainActivity: FlutterActivity() {

  private val CHANNEL = "example.com/channel"

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
    MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
      if (call.method == "getChannel") {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
          val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
          val cid = getString(R.string.default_notification_channel_id)
          val checkChannel = notificationManager.getNotificationChannel(cid)
          
          if(checkChannel == null) {
            // Create the NotificationChannel
            val name = "default"
            val importance = NotificationManager.IMPORTANCE_MAX
            val mChannel = NotificationChannel(cid, name, importance)
            mChannel.description = "default desc"
            
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            notificationManager.createNotificationChannel(mChannel)

            result.success("registerd!")
          }
          else{
            result.success("already registerd!")
          }
        }
      } else {
        result.notImplemented()
      }
    }
  }
}

android/build.gradle

buildscript {
    ext.kotlin_version = '1.3.61'
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath 'com.google.gms:google-services:4.3.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

android/app/build.gradle

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 GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.1'
}

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

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    compileSdkVersion 28

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

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.event"
        minSdkVersion 21
        targetSdkVersion 28
        multiDexEnabled true
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
       release {
           keyAlias keystoreProperties['keyAlias']
           keyPassword keystoreProperties['keyPassword']
           storeFile file(keystoreProperties['storeFile'])
           storePassword keystoreProperties['storePassword']
       }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release

            minifyEnabled true
            useProguard true

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
       }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.facebook.android:facebook-login:[5,6)'
    implementation "com.google.firebase:firebase-messaging:20.1.0"
    // implementation "com.google.firebase:firebase-analytics:17.2.1"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    
}

apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.event">

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <!--application -  android:name="io.flutter.app.FlutterApplication" -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <application
        android:name=".Application"
        android:label="EXAMPLE"
        android:icon="@mipmap/ic_launcher">
        <meta-data android:name="com.google.android.geo.API_KEY"
                android:value="AbcDeFgHiJKLMNopqrstuvwxyzdklE99dkkdfsdfs"/>
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <meta-data
                android:name="com.google.firebase.messaging.default_notification_channel_id"
                android:value="@string/default_notification_channel_id"/>
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <!-- facebook login -->
        <meta-data android:name="com.facebook.sdk.ApplicationId"
            android:value="@string/facebook_app_id"/>

        <activity android:name="com.facebook.FacebookActivity"
            android:configChanges=
                    "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
            android:label="@string/app_name" />

        <activity
            android:name="com.facebook.CustomTabActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/fb_login_protocol_scheme" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Exception Details

homePage.dart

스크린샷 2020-01-22 오전 4 40 56

firebase_messaging.dart

스크린샷 2020-01-22 오전 4 41 16

platform_channel.dart

스크린샷 2020-01-22 오전 4 41 27

message_codecs.dart

스크린샷 2020-01-22 오전 4 41 45

Development Env

flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.14.6 18G2022, locale ko-KR)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 11.1)
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.41.1)
[✓] Connected device (1 available)

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    StaleIssue with no recent activityimpact: crowdAffects many people, though not necessarily a specific customer with an assigned label. (P2)plugin: messagingtype: bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions