invertase / react-native-firebase

🔥 A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.64k stars 2.21k forks source link

🔥 Can't get setBackgroundMessageHandler to work in RNFB v6 on Android #2796

Closed dorklord23 closed 4 years ago

dorklord23 commented 4 years ago

Issue

In react-native-firebase v6, I can't get setBackgroundMessageHandler to work in my app. Notifications are received just fine but the handler is not executed.

I have done it like in the guide to no avail.

import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import AsyncStorage from '@react-native-community/async-storage';
import App from './App';
import { name as appName } from './app.json';

messaging().setBackgroundMessageHandler(async ({ data: { title, message } }) => {
    console.log('in background');
    // Save the notification locally
    const notificationList = JSON.parse(await AsyncStorage.getItem('@SM_NOTIFICATIONS')) || [];
    notificationList.push({ title, message, isRead: false });
    await AsyncStorage.setItem('@SM_NOTIFICATIONS', JSON.stringify(notificationList));
});

AppRegistry.registerComponent(appName, () => App);

Nothing happened beside the incoming notifications. I expected the code to save the incoming notifications in AsyncStorage.


Project Files

iOS

Click To Expand

#### `ios/Podfile`: - [x] I'm not using Pods - [ ] I'm using Pods and my Podfile looks like: ```ruby # N/A ``` #### `AppDelegate.m`: ```objc // N/A ```


Android

Click To Expand

#### Have you converted to AndroidX? - [ ] my application is an AndroidX application? - [x] I am using `android/gradle.settings` `jetifier=true` for Android compatibility? - [x] I am using the NPM package `jetifier` for react-native compatibility? #### `android/build.gradle`: ```groovy // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { buildToolsVersion = "28.0.3" minSdkVersion = 21 compileSdkVersion = 28 targetSdkVersion = 28 supportLibVersion = "28.0.0" } repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.google.gms:google-services:4.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenLocal() google() jcenter() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" } } } // task wrapper(type: Wrapper) { wrapper { gradleVersion = '4.7' distributionUrl = distributionUrl.replace("bin", "all") } ``` #### `android/app/build.gradle`: ```groovy apply plugin: "com.android.application" import com.android.build.OutputFile /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation * entryFile: "index.android.js", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */ project.ext.react = [ entryFile: "index.js" ] apply from: '../../node_modules/react-native-unimodules/gradle.groovy' apply from: "../../node_modules/react-native/react.gradle" /** * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = false android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { applicationId "com.smonline" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 2 versionName "0.1" } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk true // If true, also generate a universal APK include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } signingConfigs { release { storeFile file(SM_RELEASE_STORE_FILE) storePassword SM_RELEASE_STORE_PASSWORD keyAlias SM_RELEASE_KEY_ALIAS keyPassword SM_RELEASE_KEY_PASSWORD } } buildTypes { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" signingConfig signingConfigs.release } } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.0' // implementation 'com.google.firebase:firebase-core:16.0.7' implementation 'com.google.firebase:firebase-messaging:20.0.0' // implementation "com.google.firebase:firebase-iid:17.0.4" implementation project(path: ":@react-native-firebase_app") implementation project(path: ":@react-native-firebase_messaging") implementation project(':react-native-webview') implementation project(':@react-native-community_async-storage') implementation project(':@react-native-community_netinfo') implementation project(':react-native-screens') implementation project(':react-native-reanimated') implementation project(':react-native-gesture-handler') implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" implementation "com.facebook.react:react-native:+" // From node_modules addUnimodulesDependencies() } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } apply plugin: 'com.google.gms.google-services' ``` #### `android/settings.gradle`: ```groovy apply from: '../node_modules/react-native-unimodules/gradle.groovy' include ':react-native-webview' project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') include ':@react-native-community_async-storage' project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android') include ':@react-native-community_netinfo' project(':@react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android') include ':react-native-screens' project(':react-native-screens').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screens/android') include ':react-native-reanimated' project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-reanimated/android') include ':react-native-gesture-handler' project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') includeUnimodulesProjects() rootProject.name = 'smonline' include ':@react-native-firebase_app' project(':@react-native-firebase_app').projectDir = new File(rootProject.projectDir, './../node_modules/@react-native-firebase/app/android') include ':@react-native-firebase_messaging' project(':@react-native-firebase_messaging').projectDir = new File(rootProject.projectDir, './../node_modules/@react-native-firebase/messaging/android') include ':app' ``` #### `MainApplication.java`: ```java package com.smonline; import android.app.Application; import io.invertase.firebase.app.ReactNativeFirebaseAppPackage; import io.invertase.firebase.messaging.ReactNativeFirebaseMessagingPackage; import com.facebook.react.ReactApplication; import com.reactnativecommunity.webview.RNCWebViewPackage; import com.reactnativecommunity.asyncstorage.AsyncStoragePackage; import com.reactnativecommunity.netinfo.NetInfoPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; import com.smonline.generated.BasePackageList; import com.swmansion.reanimated.ReanimatedPackage; import com.swmansion.rnscreens.RNScreensPackage; import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; import org.unimodules.adapters.react.ReactAdapterPackage; import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ReactModuleRegistryProvider; import org.unimodules.core.interfaces.Package; import org.unimodules.core.interfaces.SingletonModule; import expo.modules.constants.ConstantsPackage; import expo.modules.permissions.PermissionsPackage; import expo.modules.filesystem.FileSystemPackage; import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider( new BasePackageList().getPackageList(), Arrays.asList()); private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() { return Arrays.asList( new MainReactPackage(), new RNCWebViewPackage(), new AsyncStoragePackage(), new NetInfoPackage(), new ReactNativeFirebaseAppPackage(), new ReactNativeFirebaseMessagingPackage(), new ReanimatedPackage(), new RNGestureHandlerPackage(), new RNScreensPackage(), new ModuleRegistryAdapter(mModuleRegistryProvider) ); } @Override protected String getJSMainModuleName() { return "index"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); } } ``` #### `AndroidManifest.xml`: ```xml ```


Environment

Click To Expand

**`react-native info` output:** ``` React Native Environment Info: System: OS: Linux 4.15 Ubuntu 18.04.3 LTS (Bionic Beaver) CPU: (4) x64 AMD A8-4500M APU with Radeon(tm) HD Graphics Memory: 882.89 MB / 7.28 GB Shell: 4.4.20 - /bin/bash Binaries: Node: 10.16.0 - ~/opt/node/bin/node Yarn: 1.5.1 - ~/.yarn/bin/yarn npm: 6.12.0 - ~/opt/node/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman npmPackages: react: 16.8.3 => 16.8.3 react-native: 0.59.10 => 0.59.10 npmGlobalPackages: react-native-cli: 2.0.1 ``` - **Platform that you're experiencing the issue on**: - [ ] iOS - [x] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `6.0.2` - **`Firebase` module(s) you're using that has the issue:** - `Messaging` - **Are you using `TypeScript`?** - `N`

mikehardy commented 4 years ago

Have you tried a test app made from the react-native-firebase template on react-native stable current (I think 0.61.3 as of just today, but maybe still 0.61.2). I don't think that would make a difference - you appear to have integrated correctly for pre-auto-linking react-native 0.59 but you never know

@Salakar @Ehesp I worked with @dorklord23 on Discord on this and I'm stumped personally.

mikehardy commented 4 years ago

@dorklord23 if at all possible you should use the firebase-admin module from the command line and send a push notification that you control. Without exposing your API key or a device token (of course), can you show the exact JSON you are attempting to push to the device via firebase? And can you examine unfiltered logcat with the device plugged in to see the relevant details there (in case there are any)?

dorklord23 commented 4 years ago

@mikehardy I usually use Firebase Console to test notifications, though. Interestingly, I have used adb logcat -s RNFirebaseMsgService:V to peek at the log and it showed nothing when a notification arrived while the app was in background even though I have added a few logs in ReactNativeFirebaseMessagingService.java like so:

Click To Expand

```java @Override public void onMessageReceived(RemoteMessage remoteMessage) { Log.d(TAG, "onMessageReceived"); ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance(); // ---------------------- // NOTIFICATION Message // --------------------\/ // with no data if (remoteMessage.getNotification() != null && remoteMessage.getData().size() == 0) { Log.d(TAG, "TODO"); // TODO broadcast intent when notifications module ready return; } // ---------------------- // DATA Message // --------------------\/ // |-> --------------------- // App in Foreground // ------------------------ if (SharedUtils.isAppInForeground(getApplicationContext())) { Log.d(TAG, "onForeground"); emitter.sendEvent(ReactNativeFirebaseMessagingSerializer.remoteMessageToEvent(remoteMessage)); return; } // |-> --------------------- // App in Background/Quit // ------------------------ try { Log.d(TAG, "in background"); Intent intent = new Intent(getApplicationContext(), ReactNativeFirebaseMessagingHeadlessService.class); intent.putExtra("message", remoteMessage); ComponentName name = getApplicationContext().startService(intent); if (name != null) { Log.d(TAG, "waking the dragon"); HeadlessJsTaskService.acquireWakeLockNow(getApplicationContext()); } } catch (IllegalStateException ex) { Log.e(TAG, "Background messages only work if the message priority is set to 'high'", ex); } } ```

The log was like so:

10-30 00:29:16.141 26487 27890 D RNFirebaseMsgService: onMessageReceived
10-30 00:29:16.145 26487 27890 D RNFirebaseMsgService: onForeground

There should've been in background and waking the dragon in the log but alas there was none.

mikehardy commented 4 years ago

You will not be able to send silent pushes using the console. It just doesn't work for this purpose.

For full control you have to use the command line to test I think. I use something like this:

#!/bin/bash

DEV_SERVER_KEY="put your big old server key from firebase console here"

SERVER_KEY=$DEV_SERVER_KEY
#SERVER_KEY=$TEST_SERVER_KEY

curl -X POST \
--header "Authorization: key=$SERVER_KEY"  \
--Header "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d
 "{\"to\":\"/topics/AppInfo\",\"collapse_key\": \"AppInfo\",\"content_available\
": true, \"priority\": \"high\", \"data\":{\"IconCheck\":\"Testing Icon\"}}"
mikehardy commented 4 years ago

(note that specific payload may or may not work for you - it's a mish-mash of different things)

mikehardy commented 4 years ago

Oh, and run the logcat unfiltered. The play services things log under their own tags and processes etc

Salakar commented 4 years ago

The fact that Log.d(TAG, "onForeground"); is being logged indicates your app is in the foreground / currently visible? setBackgroundMessageHandler is only for messages received while your app is closed / not active in the foreground, whilst onMessage should be used for when your app is in the foreground.

The isAppInForeground utility is identical to the one used in v5 so there should be no difference down to this util.

dorklord23 commented 4 years ago

The fact that Log.d(TAG, "onForeground"); is being logged indicates your app is in the foreground / currently visible? setBackgroundMessageHandler is only for messages received while your app is closed / not active in the foreground, whilst onMessage should be used for when your app is in the foreground.

The isAppInForeground utility is identical to the one used in v5 so there should be no difference down to this util.

My bad. I was sending two notifications. The first one was when the app was in foreground (it was logged). The second one (the one that wasn't logged) was when the app is in background. It was quite late at night when I opened this issue.

dorklord23 commented 4 years ago

@dorklord23 if at all possible you should use the firebase-admin module from the command line and send a push notification that you control. Without exposing your API key or a device token (of course), can you show the exact JSON you are attempting to push to the device via firebase? And can you examine unfiltered logcat with the device plugged in to see the relevant details there (in case there are any)?

This is my JSON:

POST https://fcm.googleapis.com/v1/projects/my-project/messages:send

{
    "validate_only": false,
    "message": {
        "name": "XXX",
        "data": {
            "title": "judul data",
            "message": "pesan data"
        },
        "notification": {
            "title": "judul notifikasi",
            "body": "isi notifikasi"
        },
        "topic": "C3166"
    }
}

The unfiltered log is overwhelming. I'll let you know if I find anything interesting.

dorklord23 commented 4 years ago

@Salakar @mikehardy I have sent a few notifications while the app was in background. All of them were received but nothing is logged. There should have been a minimum of onMessageReceived in the log but nothing whatsoever even though I put it in the first line of onMessageReceived().

Salakar commented 4 years ago

@Salakar @mikehardy I have sent a few notifications while the app was in background. All of them were received but nothing is logged. There should have been a minimum of onMessageReceived in the log but nothing whatsoever even though I put it in the first line of onMessageReceived().

onMessageReceived is for foreground messages, e.g. while your app is active, not backgrounded

dorklord23 commented 4 years ago

@Salakar @mikehardy I have sent a few notifications while the app was in background. All of them were received but nothing is logged. There should have been a minimum of onMessageReceived in the log but nothing whatsoever even though I put it in the first line of onMessageReceived().

onMessageReceived is for foreground messages, e.g. while your app is active, not backgrounded

Then why is this piece of code inside of onMessageReceived? I presumed it is to start the headless task when the app is on background.

try {
    Intent intent = new Intent(getApplicationContext(), ReactNativeFirebaseMessagingHeadlessService.class);
    intent.putExtra("message", remoteMessage);
    ComponentName name = getApplicationContext().startService(intent);
    if (name != null) {
        HeadlessJsTaskService.acquireWakeLockNow(getApplicationContext());
    }
} catch (IllegalStateException ex) {
    Log.e(TAG, "Background messages only work if the message priority is set to 'high'", ex);
}
dorklord23 commented 4 years ago

Have you tried a test app made from the react-native-firebase template on react-native stable current (I think 0.61.3 as of just today, but maybe still 0.61.2). I don't think that would make a difference - you appear to have integrated correctly for pre-auto-linking react-native 0.59 but you never know

@Salakar @Ehesp I worked with @dorklord23 on Discord on this and I'm stumped personally.

I have made another app with the template and it's exactly the same case :disappointed:

mikehardy commented 4 years ago

Your example JSON is not a silent push. It should be data only, any notification stuff in there will confound the test I think

In order to simplify how this stuff works I have a learned behavior now of never sending notification information remotely. I always send data only ("silent push"), and use the handler to pop a locally crafted notification if that's what I want.

dorklord23 commented 4 years ago

Your example JSON is not a silent push. It should be data only, any notification stuff in there will confound the test I think

In order to simplify how this stuff works I have a learned behavior now of never sending notification information remotely. I always send data only ("silent push"), and use the handler to pop a locally crafted notification if that's what I want.

You're right!! :grinning: Adding notification is just making things difficult. I only send data and the handler is called correctly. Thank you @mikehardy ;)

mikehardy commented 4 years ago

I'm glad you got it working! This cements two things in my mind:

Good luck :-)

mikehardy commented 4 years ago

also that's the reason for testing with a script - it is literally impossible (at least as of this writing) to message data-only from the console. No idea why since it's so important, but there it is

cheolgyu commented 4 years ago

"react-native": "0.61.3", I tried it on android, onMessage, setBackgroundMessageHandler Good for you.  I will use it well, thank you

seb-montana commented 4 years ago

Hello, First, thanks for this topic which helps me on my first ReactNative application. With react-native-firebase v6, I get Message when app is Backgrounded & Foregrounded. But I don't know how to know in my app when user come from notification tap/click. I would like to redirect user on suitable view.

mikehardy commented 4 years ago

@seb-montana - RNFBv6 does not handle notification-anything. If you are sending mixed payloads from firebase cloud messaging (a cloud message with notification content and data content) then in the background the native firebase sdks will actually pop a notification for you, but react-native-firebase is not listening for the clicks or anything from the local notification APIs. You'll want to integrate react-native-push-notifications or similar and use their onNotification handler/listeners for that

seb-montana commented 4 years ago

Thanks you @mikehardy it's clear.. I've tried react-native-push-notifications yesterday and it's ok.