ARTICLE AD BOX
I have a Firebase Cloud Function written in JavaScript (Node.js) that should trigger when Firebase Analytics logs the app_remove event from Android devices.
When triggered, the function is supposed to call one of our backend APIs.
The function deploys successfully, and it works correctly when I test it using the Firebase console’s testing tools. However, the issue is that the function does not run in real-time in the production environment — it never triggers when an actual app_remove event occurs.
Here’s what I’ve already checked:
The function deploys without any errors
Testing through Firebase's built-in event simulator works
Logs show no record of the function firing in production
Android app is correctly logging other Analytics events
Is there something I might be missing about how Firebase handles the app_remove event, or do Cloud Functions require a different type of trigger for uninstall events?
Any guidance on how to get this function to execute reliably in a real Android uninstall scenario would be greatly appreciated.
Firebase cloud function :
import * as functions from "firebase-functions"; import axios from "axios"; import * as qs from "qs"; export const onAppRemove = functions.analytics .event("app_remove") .onLog(async (event) => { try { console.log("Start function.."); const eventMainType = "app_event"; const eventType = "app_remove"; const userId = event.user?.userId || "unknown"; const userProps = event.user?.properties || {}; const appInfo = event.appInfo || {}; const timestamp = event.timestampMicros; // Extract Firebase user properties const appVersion = userProps["app_version"]?.value || appInfo.appVersion || "unknown"; const isProduction = userProps["isProduction"]?.value || "unknown"; const deviceMake = userProps["deviceMake"]?.value || "unknown"; const deviceModel = userProps["deviceModel"]?.value || "unknown"; const osVersion = userProps["osVersion"]?.value || appInfo.appPlatform || "unknown"; // Map uninstall data in same style as Android events const params = { eventName: eventType, userId, appVersion, isProduction, deviceMake, deviceModel, osVersion, timestamp, }; // Format query string just like Android const queryParams = qs.stringify(params); const fullUrl = `https://example.com/postback/${eventMainType}?${queryParams}`; console.log("Sending uninstall postback:", fullUrl); const response = await axios.get(fullUrl); console.log("Postback sent successfully:", response.status); } catch (error) { console.error("Error sending uninstall postback:", error); } });From android side :
lateinit var firebaseAnalytics: FirebaseAnalytics try { firebaseAnalytics = Firebase.analytics // Configure Firebase Analytics for 16KB page size compatibility firebaseAnalytics.setAnalyticsCollectionEnabled(true) // Set session timeout to handle potential memory page size issues firebaseAnalytics.setSessionTimeoutDuration(1800000) // 30 minutes trackUserLogin() Log.d("MyApplication", "Firebase Analytics initialized successfully") } catch (e: Exception) { Log.e("MyApplication", "Failed to initialize Firebase Analytics: ${e.message}") Sentry.captureException(e) // Fallback initialization with minimal configuration try { firebaseAnalytics = FirebaseAnalytics.getInstance(this) trackUserLogin() Log.d("MyApplication", "Firebase Analytics fallback initialization successful") } catch (fallbackException: Exception) { Log.e("MyApplication", "Firebase Analytics fallback initialization failed: ${fallbackException.message}") Sentry.captureException(fallbackException) } } private fun trackUserLogin() { try { CoroutineScope(Dispatchers.IO).launch { with(firebaseAnalytics) { setUserId(getAdvertiserId()) setUserProperty("app_version", "V${BuildConfig.VERSION_NAME}") setUserProperty("isProduction", "${BuildConfig.IS_PRODUCTION}") setUserProperty("deviceMake", Build.MANUFACTURER) setUserProperty("deviceModel", Build.MODEL) setUserProperty("osVersion", Build.VERSION.RELEASE) } } Log.d("MyApplication", "Analytics: User ID set in Firebase Analytics") } catch (e: Exception) { Log.e("MyApplication", "Analytics: Failed to set User ID: ${e.message}") Sentry.captureException(e) } }