Track user navigation patterns in FlutterFlow by logging screen_view events with session context to a Firestore `navigationEvents` collection — not just Firebase Analytics. Store the previous screen on each event to build a transition matrix. Use a Cloud Function to aggregate transition counts and identify drop-off points. Firebase Analytics alone gives 14-month retention with limited path analysis.
Why Firebase Analytics Alone Is Not Enough for Path Analysis
Firebase Analytics tracks screen views automatically in FlutterFlow apps, but its path analysis capabilities are limited. The Explorer report shows top paths but not full Sankey-style flow diagrams. Data is retained for only 14 months. You cannot run custom SQL against it without BigQuery linking (which requires a paid plan). Most importantly, you cannot join navigation events with your Firestore user data to answer questions like 'what path do paying users take that free users do not?' The solution is to log navigation events to your own Firestore collection alongside Firebase Analytics. This gives you complete ownership of the data, unlimited retention, and the ability to cross-join with any other Firestore data using Cloud Functions.
Prerequisites
- FlutterFlow project with Firebase Firestore and Firebase Authentication enabled
- Firebase Analytics connected to your FlutterFlow project
- Basic understanding of FlutterFlow page On Load actions
- An admin dashboard page or external analytics tool for viewing results
Step-by-step guide
Create the `navigationEvents` Firestore collection schema
Create the `navigationEvents` Firestore collection schema
In FlutterFlow, create a Firestore collection named `navigationEvents`. Add these fields: `userId` (String — Firebase Auth UID, or 'anonymous'), `sessionId` (String — a UUID generated at app launch and stored in App State), `screenName` (String — the current page name), `previousScreen` (String, nullable — the screen navigated from), `timestamp` (Timestamp), `platform` (String — 'ios', 'android', or 'web'), `appVersion` (String). The `sessionId` is the key field that ties events together into a session. Generate it once on app startup using a Custom Function that calls Dart's `Uuid` package and store it in an App State variable. This lets you reconstruct the exact path a user took in a single session.
Expected result: The `navigationEvents` collection appears in the FlutterFlow Firestore schema with all fields.
Generate a session ID on app launch and store it in App State
Generate a session ID on app launch and store it in App State
Go to FlutterFlow > App State and add a variable named `sessionId` (String, persisted: false — resets on each app launch). Add a Custom Function named `generateUuid` that returns a new UUID string: in Dart this is `const Uuid().v4()` from the `uuid` package (add `uuid: ^4.0.0` to your pubspec.yaml). On your app's initial page (usually the splash or entry page), add an On Page Load action: call the `generateUuid` Custom Function and set the `sessionId` App State variable to the result. Also set a `previousScreen` App State variable (String, default empty string) that you will update on every navigation.
1// Custom Function: generateUuid2import 'package:uuid/uuid.dart';34String generateUuid() {5 return const Uuid().v4();6}Expected result: A unique session ID is generated each time the app launches and stored in App State.
Log a navigation event on every page's On Load action
Log a navigation event on every page's On Load action
On each page in FlutterFlow, go to the page settings and add an On Page Load action chain. The chain should: (1) Run a Firestore Create Document action on `navigationEvents` with all fields populated — `userId` from `currentUser.uid` (or 'anonymous' if not signed in), `sessionId` from App State, `screenName` as a hardcoded string matching the page name, `previousScreen` from the `previousScreen` App State variable, `timestamp` as the current time, `platform` from the `Platform` Custom Function. (2) After logging, update the `previousScreen` App State variable to the current page name. This two-step pattern — log then update — ensures each event correctly captures where the user came from.
Expected result: Navigating through the app creates documents in `navigationEvents` with the correct `previousScreen` values forming a chain.
Build a transition matrix Cloud Function
Build a transition matrix Cloud Function
Create a Firebase Cloud Function named `buildTransitionMatrix`. It queries `navigationEvents` for the last 30 days (configurable), groups events by `previousScreen -> screenName` pairs, and counts occurrences of each pair. The result is a matrix where each row represents a source screen, each column a destination screen, and the value is the number of times users navigated from source to destination. The function saves the result to a Firestore document `analyticsCache/transitionMatrix` with a `computedAt` timestamp. Schedule it to run daily via Cloud Scheduler. In FlutterFlow, read the `analyticsCache/transitionMatrix` document to display the most common navigation paths on an admin dashboard.
1const { onCall } = require('firebase-functions/v2/https');2const { getFirestore, Timestamp } = require('firebase-admin/firestore');34exports.buildTransitionMatrix = onCall(async (request) => {5 const db = getFirestore();6 const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);78 const snapshot = await db9 .collection('navigationEvents')10 .where('timestamp', '>=', Timestamp.fromDate(thirtyDaysAgo))11 .where('previousScreen', '!=', '')12 .get();1314 const matrix = {};15 const screenCounts = {};1617 snapshot.forEach((doc) => {18 const { previousScreen, screenName } = doc.data();19 const key = `${previousScreen}___${screenName}`;20 matrix[key] = (matrix[key] || 0) + 1;21 screenCounts[screenName] = (screenCounts[screenName] || 0) + 1;22 });2324 // Convert to sorted array of transitions25 const transitions = Object.entries(matrix)26 .map(([key, count]) => {27 const [from, to] = key.split('___');28 return { from, to, count };29 })30 .sort((a, b) => b.count - a.count)31 .slice(0, 50); // Top 50 transitions3233 await db.collection('analyticsCache').doc('transitionMatrix').set({34 transitions,35 screenCounts,36 computedAt: new Date(),37 periodDays: 30,38 });3940 return { transitionCount: transitions.length };41});Expected result: The `analyticsCache/transitionMatrix` document contains a sorted list of navigation transitions with counts.
Identify drop-off screens by comparing entry vs exit counts
Identify drop-off screens by comparing entry vs exit counts
A drop-off screen is one that users navigate to but rarely navigate away from — they abandon the app. Extend the Cloud Function to compute, for each screen: `entryCount` (how many times it appeared as `screenName`) and `exitCount` (how many times it appeared as `previousScreen`). The drop-off rate is `1 - (exitCount / entryCount)`. A rate above 60% on a non-terminal screen (like a checkout page that should lead to success) indicates a problem. Store these per-screen metrics in the `analyticsCache/dropoffAnalysis` document. In FlutterFlow, display screens sorted by drop-off rate descending on your admin dashboard so the worst drop-off points are at the top.
Expected result: The admin dashboard shows a ranked list of screens by drop-off rate. Screens with unexpectedly high drop-offs are easy to identify.
Build a flow visualization component for the admin dashboard
Build a flow visualization component for the admin dashboard
In FlutterFlow, create a Component named `NavigationFlowChart`. It reads the `analyticsCache/transitionMatrix` document and displays the top 10 transitions as a vertical list of cards. Each card shows: an arrow from `from` screen name to `to` screen name, the count as a number, and a horizontal progress bar showing the count as a proportion of the maximum count. Sort by count descending. Add a Text widget at the top showing the data freshness (`computedAt` formatted as 'Updated X minutes ago'). Add a Refresh button that calls the `buildTransitionMatrix` Cloud Function. This is simpler to build in FlutterFlow than a true Sankey diagram but provides the same insight into the most common navigation paths.
Expected result: The admin dashboard shows a ranked list of the top 10 navigation transitions with counts and relative proportions.
Complete working example
1const { onCall } = require('firebase-functions/v2/https');2const { initializeApp } = require('firebase-admin/app');3const { getFirestore, Timestamp } = require('firebase-admin/firestore');45initializeApp();67exports.buildNavigationAnalytics = onCall(async (request) => {8 const uid = request.auth?.uid;9 if (!uid) throw new Error('Unauthenticated');10 // Optional: check if user is admin1112 const periodDays = request.data?.periodDays || 30;13 const db = getFirestore();14 const cutoff = new Date(Date.now() - periodDays * 24 * 60 * 60 * 1000);1516 const snapshot = await db17 .collection('navigationEvents')18 .where('timestamp', '>=', Timestamp.fromDate(cutoff))19 .get();2021 const transitionCounts = {};22 const screenEntry = {};23 const screenExit = {};24 const sessionPaths = {};2526 snapshot.forEach((doc) => {27 const { previousScreen, screenName, sessionId } = doc.data();2829 // Count screen entries30 screenEntry[screenName] = (screenEntry[screenName] || 0) + 1;3132 // Count transitions33 if (previousScreen) {34 screenExit[previousScreen] = (screenExit[previousScreen] || 0) + 1;35 const key = `${previousScreen}|||${screenName}`;36 transitionCounts[key] = (transitionCounts[key] || 0) + 1;37 }3839 // Track session paths40 if (!sessionPaths[sessionId]) sessionPaths[sessionId] = [];41 sessionPaths[sessionId].push(screenName);42 });4344 // Build top transitions45 const transitions = Object.entries(transitionCounts)46 .map(([key, count]) => {47 const [from, to] = key.split('|||');48 return { from, to, count };49 })50 .sort((a, b) => b.count - a.count)51 .slice(0, 50);5253 // Build drop-off analysis54 const dropoffAnalysis = Object.keys(screenEntry).map((screen) => {55 const entries = screenEntry[screen] || 0;56 const exits = screenExit[screen] || 0;57 const dropoffRate = entries > 0 ? Math.round((1 - exits / entries) * 100) : 0;58 return { screen, entries, exits, dropoffRate };59 }).sort((a, b) => b.dropoffRate - a.dropoffRate);6061 // Find most common full session paths (top 5)62 const pathCounts = {};63 Object.values(sessionPaths).forEach((path) => {64 const key = path.slice(0, 5).join(' > ');65 pathCounts[key] = (pathCounts[key] || 0) + 1;66 });67 const topPaths = Object.entries(pathCounts)68 .sort((a, b) => b[1] - a[1])69 .slice(0, 10)70 .map(([path, count]) => ({ path, count }));7172 const result = {73 transitions,74 dropoffAnalysis,75 topPaths,76 totalEvents: snapshot.size,77 totalSessions: Object.keys(sessionPaths).length,78 computedAt: new Date(),79 periodDays,80 };8182 await db.collection('analyticsCache').doc('navigationAnalytics').set(result);83 return { success: true, totalEvents: snapshot.size };84});Common mistakes
Why it's a problem: Logging screen views only in Firebase Analytics and relying on it for path analysis
How to avoid: Log navigation events to your own Firestore collection in parallel with Firebase Analytics. This gives you full SQL-equivalent access via Cloud Functions, unlimited retention, and the ability to join with any other app data.
Why it's a problem: Forgetting to update the `previousScreen` App State variable after logging the event
How to avoid: In the On Page Load action chain, log first, then update `previousScreen`. Use a reusable Custom Action to enforce this order consistently across all pages.
Why it's a problem: Running the transition matrix aggregation query client-side in FlutterFlow
How to avoid: Always aggregate navigation data in a Cloud Function. Cache the result in a single `analyticsCache` Firestore document and read that document in FlutterFlow — it costs one read regardless of how many events exist.
Best practices
- Log navigation events to Firestore alongside Firebase Analytics — own your data with unlimited retention and full query access.
- Use a session ID stored in App State to tie navigation events into complete user journeys across a single app session.
- Cache aggregated analytics results in a dedicated Firestore `analyticsCache` collection — never aggregate raw events client-side.
- Include a `previousScreen` field in every navigation event to enable transition matrix analysis without post-processing joins.
- Add optional `properties` JSON to navigation events so page-specific business context can enrich path analysis.
- Schedule the aggregation Cloud Function to run daily and store a `computedAt` timestamp so the dashboard shows data freshness.
- Exclude internal admin pages and error pages from drop-off analysis by filtering them out in the Cloud Function.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Firestore collection `navigationEvents` with fields: userId, sessionId, screenName, previousScreen, timestamp. I want to write a Cloud Function that computes: (1) a transition matrix showing count of each from/to screen pair, (2) a drop-off rate per screen (entries minus exits divided by entries), and (3) the top 10 most common complete session paths. Return all three as a single JSON object and save it to Firestore. Write the complete Cloud Function.
In FlutterFlow, I want to add a navigation event logger to every page in my app. The logger should read a sessionId from App State, write a document to Firestore with screenName and previousScreen, then update the previousScreen App State variable. I have 20 pages. What is the most efficient way to add this to all pages — should I use a Custom Action, a reusable Component, or something else? How do I add it without manually editing each page's On Load action?
Frequently asked questions
Will logging a Firestore event on every page navigation slow down my app?
No. Firestore writes are asynchronous — the write happens in the background without blocking the UI. The On Page Load action starts the Firestore write and immediately continues rendering the page. You will not notice any performance impact for typical usage volumes.
How much will Firestore cost to store all these navigation events?
Firestore charges $0.18 per 100,000 writes. An active user navigating through 20 screens per session costs about $0.000036 per session. At 1,000 daily active users, that is $36 per million sessions — roughly $1-2/month for most early-stage apps. Set up a lifecycle policy to delete events older than 90 days to keep storage costs low.
How do I track anonymous users who are not signed in?
Use Firebase Anonymous Authentication. In FlutterFlow, enable Anonymous sign-in and sign users in anonymously on app launch if they are not already authenticated. Anonymous users get a stable UID for the duration of the installation. Store their UID in navigation events. When they sign up later, Firebase links the anonymous account to the new account, preserving the UID.
Can I see individual user paths, not just aggregate statistics?
Yes. Query the `navigationEvents` collection filtered by a specific `userId` and ordered by `timestamp`, then group by `sessionId`. This gives you the exact sequence of screens for every session that user has ever had. This is useful for debugging a specific user's reported issue or understanding a high-value customer's behavior.
How do I set up automatic deletion of old navigation events?
Create a Cloud Scheduler job that runs weekly and calls a Cloud Function that deletes `navigationEvents` documents where `timestamp` is older than 90 days. Use a batched delete to avoid timeout issues on large collections. Alternatively, use Firebase's built-in TTL feature (Firestore TTL) by setting an `expireAt` field on each document — Firestore automatically deletes documents after that time at no additional cost.
Does this work alongside Firebase Analytics or does it replace it?
It works alongside Firebase Analytics. Keep Firebase Analytics enabled — it provides free crash reporting, audience segmentation, and integration with Google Ads. The Firestore-based navigation logging supplements it with custom path analysis and data ownership. You get the best of both: Firebase Analytics for standard marketing and growth metrics, and your Firestore events for deep product analytics.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation