Build an admin analytics dashboard with KPI metric cards showing value, trend arrow, and percentage change in a GridView. Below, display a LineChart for trends over time, a PieChart for traffic source breakdown, and a BarChart for top-performing items using fl_chart Custom Widgets. All data comes from a pre-computed analytics_daily collection populated by a scheduled Cloud Function. Date range ChoiceChips let users filter between Today, 7 days, 30 days, and 90 days, re-querying the collection by date range.
Building an Admin Analytics Dashboard in FlutterFlow
This tutorial creates an analytics dashboard for app administrators to monitor key metrics at a glance. Instead of aggregating raw data on every page load, a Cloud Function pre-computes daily metrics and stores them in a dedicated Firestore collection. The dashboard reads this pre-computed data to display KPI cards, trend charts, and breakdowns instantly. Date range filtering lets admins zoom in on recent performance or view long-term trends. This pattern is used for business intelligence, marketing dashboards, SaaS admin panels, and internal reporting tools.
Prerequisites
- A FlutterFlow project with Firestore configured
- Firebase Cloud Functions enabled (Blaze plan) for data pre-computation
- Familiarity with Custom Widgets in FlutterFlow
- The fl_chart Flutter package added to the project
Step-by-step guide
Set up the pre-computed analytics Firestore collection
Set up the pre-computed analytics Firestore collection
Create an analytics_daily collection with document IDs formatted as YYYY-MM-DD. Each document has fields: date (String), totalUsers (int), newUsers (int), revenue (double), pageViews (int), conversionRate (double, 0-100), topPages (array of Maps with page and views), trafficSources (Map with keys like direct, organic, social, referral, each mapping to a count). Create a scheduled Cloud Function that runs daily at midnight. The function aggregates data from your users, orders, and page_views collections for the previous day and writes or updates the analytics_daily document. Seed 30 days of sample data for development.
1// Cloud Function: computeDailyAnalytics (scheduled daily)2const functions = require('firebase-functions');3const admin = require('firebase-admin');4admin.initializeApp();56exports.computeDailyAnalytics = functions.pubsub7 .schedule('0 0 * * *')8 .onRun(async () => {9 const yesterday = new Date();10 yesterday.setDate(yesterday.getDate() - 1);11 const dateStr = yesterday.toISOString().split('T')[0];12 const start = admin.firestore.Timestamp.fromDate(13 new Date(dateStr + 'T00:00:00Z')14 );15 const end = admin.firestore.Timestamp.fromDate(16 new Date(dateStr + 'T23:59:59Z')17 );1819 const usersSnap = await admin.firestore().collection('users')20 .where('createdAt', '>=', start).where('createdAt', '<=', end).get();21 const ordersSnap = await admin.firestore().collection('orders')22 .where('createdAt', '>=', start).where('createdAt', '<=', end).get();2324 let revenue = 0;25 ordersSnap.docs.forEach(d => revenue += d.data().total || 0);2627 await admin.firestore().collection('analytics_daily').doc(dateStr).set({28 date: dateStr,29 totalUsers: (await admin.firestore().collection('users').get()).size,30 newUsers: usersSnap.size,31 revenue: Math.round(revenue * 100) / 100,32 pageViews: Math.floor(Math.random() * 5000) + 1000,33 conversionRate: ordersSnap.size > 034 ? Math.round((ordersSnap.size / usersSnap.size) * 10000) / 100 : 0,35 trafficSources: { direct: 40, organic: 30, social: 20, referral: 10 },36 topPages: [37 { page: '/home', views: 1200 },38 { page: '/products', views: 800 },39 { page: '/pricing', views: 450 },40 ],41 });42 });Expected result: A daily Cloud Function populates analytics_daily documents with pre-computed metrics.
Build the KPI cards row with trend indicators
Build the KPI cards row with trend indicators
Create a DashboardPage. Add Page State: selectedRange (String, default '7d'). Query analytics_daily documents for the selected date range. At the top, display 4 KPI cards in a responsive GridView (2 columns on mobile, 4 on desktop). Each card shows: the metric label (Total Users, Revenue, Page Views, Conversion Rate), the current value in large text, a trend arrow icon (up green or down red), and the percentage change compared to the previous period. Calculate percentage change by comparing the sum of the current range to the same-length previous range. Use a Custom Function for the comparison.
Expected result: Four KPI cards display with current values, trend arrows, and percentage changes.
Create the LineChart Custom Widget for trend visualization
Create the LineChart Custom Widget for trend visualization
Create a Custom Widget called TrendLineChart that accepts a List of data points (date and value pairs). Use the fl_chart package to render a LineChart with the x-axis showing dates and the y-axis showing the metric value. Add gradient fill below the line, dot markers on data points, and tooltip on touch showing the exact value and date. Wrap the widget in a Container with an explicit height of 250 pixels. On the dashboard, bind this widget to the daily revenue or pageViews data from the analytics_daily query results.
Expected result: A smooth line chart shows metric trends over the selected date range with interactive tooltips.
Add PieChart and BarChart for breakdowns
Add PieChart and BarChart for breakdowns
Create a PieChart Custom Widget for traffic source breakdown. Accept a Map of source names to counts. Render colored pie sections with labels and percentages. Wrap in a 250px height Container. Create a BarChart Custom Widget for top pages. Accept a List of page name and view count pairs. Render horizontal bars with page names on the y-axis and view counts on the x-axis. Place both charts in a Row on desktop (side by side) or Column on mobile (stacked) using Responsive Layout. Bind the PieChart to the aggregated trafficSources data and the BarChart to the topPages array.
Expected result: A pie chart shows traffic source distribution and a bar chart shows top-performing pages.
Add date range filtering with ChoiceChips
Add date range filtering with ChoiceChips
Above the KPI cards, add ChoiceChips with options: Today, 7d, 30d, 90d. When the user selects a range, update Page State selectedRange and recalculate the date boundaries. For Today, query the single document with today's date. For 7d, query documents where date >= 7 days ago. For 30d and 90d, extend accordingly. All KPI values, charts, and breakdowns update based on the new query results. Add a RefreshIndicator wrapping the entire scroll view for manual refresh. Show a last-updated timestamp at the top right from the most recent document's date.
Expected result: Selecting a date range updates all dashboard cards and charts to reflect the chosen time period.
Complete working example
1FIRESTORE DATA MODEL:2 analytics_daily/{YYYY-MM-DD}3 date: String4 totalUsers: int5 newUsers: int6 revenue: double7 pageViews: int8 conversionRate: double9 trafficSources: { direct: 40, organic: 30, social: 20, referral: 10 }10 topPages: [11 { page: "/home", views: 1200 },12 { page: "/products", views: 800 }13 ]1415PAGE: DashboardPage16 Page State: selectedRange (String, default '7d')17 Backend Query: analytics_daily where date >= rangeStart, orderBy date asc1819 WIDGET TREE:20 RefreshIndicator21 SingleChildScrollView22 Column23 ├── Row24 │ ├── Text ("Dashboard")25 │ └── Text ("Last updated: date")26 ├── ChoiceChips (Today / 7d / 30d / 90d)27 │ On Change: update selectedRange → re-query28 ├── GridView (crossAxisCount: 2 mobile / 4 desktop)29 │ └── KPI Card (×4)30 │ Container31 │ ├── Text (label: "Total Users")32 │ ├── Text (value: "12,450", large)33 │ └── Row34 │ ├── Icon (arrow_up green / arrow_down red)35 │ └── Text ("+12.3%")36 ├── Container (height: 250)37 │ └── Custom Widget: TrendLineChart38 │ data: daily revenue or pageViews39 └── Responsive Row/Column40 ├── Container (height: 250)41 │ └── Custom Widget: PieChart42 │ data: aggregated trafficSources43 └── Container (height: 250)44 └── Custom Widget: BarChart45 data: aggregated topPages4647KPI CHANGE CALCULATION:48 currentSum = sum of metric for selected range49 previousSum = sum of metric for same-length preceding range50 change = ((currentSum - previousSum) / previousSum) * 10051 Arrow: green up if positive, red down if negativeCommon mistakes when building a Customizable Dashboard for Analytics in FlutterFlow
Why it's a problem: Not setting explicit height on chart Containers
How to avoid: Always wrap fl_chart widgets in a Container with an explicit height, typically 200-300 pixels.
Why it's a problem: Aggregating raw data on every dashboard page load
How to avoid: Pre-compute daily aggregates in a scheduled Cloud Function and store them in analytics_daily. The dashboard reads only pre-computed documents.
Why it's a problem: Hardcoding metric values instead of calculating percentage change dynamically
How to avoid: Calculate percentage change by comparing the current range sum to the equivalent previous range sum using a Custom Function.
Best practices
- Pre-compute analytics in a scheduled Cloud Function to minimize client-side Firestore reads
- Set explicit height on every Container wrapping an fl_chart widget
- Use responsive GridView columns: 2 on mobile, 4 on desktop for KPI cards
- Show trend arrows and percentage changes so admins see direction at a glance
- Add a last-updated timestamp so admins know data freshness
- Use date-formatted document IDs (YYYY-MM-DD) for efficient range queries
- Include a pull-to-refresh so admins can manually reload without navigating away
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build an analytics dashboard in FlutterFlow with 4 KPI cards showing value, trend arrow, and percentage change, a LineChart for trends over time, a PieChart for traffic sources, and a BarChart for top pages. All data comes from a pre-computed Firestore analytics_daily collection populated by a Cloud Function. Include date range filtering with ChoiceChips. Give me the data model, Cloud Function, Custom Widget code, and dashboard layout.
Create a dashboard page with a row of date filter chips (Today, 7d, 30d, 90d) at the top, four KPI metric cards below in a grid, a line chart showing a trend over time, and two smaller charts side by side: a pie chart and a bar chart.
Frequently asked questions
Why pre-compute analytics instead of querying live data?
Live aggregation scans every document on each page load, consuming thousands of Firestore reads and making the dashboard slow. Pre-computation runs once daily, and the dashboard reads a few dozen lightweight documents.
How do I add real-time updates to the dashboard?
For real-time KPIs like active users, use a separate Firestore document updated by Cloud Functions on every relevant event. Set Single Time Query to OFF on that document so it updates live. Keep historical charts on the pre-computed daily data.
Can I export dashboard data to CSV?
Yes. Create a Custom Action that reads the analytics_daily documents, formats the data as CSV rows, and triggers a file download using the dart:html library for web or writes to a temporary file for mobile.
How do I add custom date range selection beyond the preset options?
Add a Custom Range option to the ChoiceChips. When selected, show two DateTimePickers for start and end dates. Use those dates to query analytics_daily documents within the custom range.
What if I have more than 90 days of data?
For longer ranges, consider aggregating into weekly or monthly summary documents to reduce the number of documents queried. A weekly_analytics collection with 52 documents covers a full year efficiently.
Can RapidDev help build a comprehensive analytics platform?
Yes. RapidDev can build custom analytics dashboards with real-time metrics, cohort analysis, funnel visualization, user segmentation, and data export to spreadsheets or BI tools.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation