Analytics Dashboard Development for Mobile Apps
An analytics dashboard is not a set of pretty charts. It is a decision-making tool, and its value is determined by how quickly the user gets an answer to a specific question: "Where are users dropping off in the funnel?", "Which segment brings 80% of revenue?", "When did retention decline after the last update?". Deviation from this focus turns the dashboard into decoration.
Architectural Decisions
Data Sources and Aggregation
A dashboard in a mobile app rarely works with raw real-time data. Typical architecture:
- OLAP storage for historical data: ClickHouse, BigQuery, Redshift — queries on millions of rows in seconds
- Aggregate cache: Redis with TTL for frequently-requested metrics (DAU, MAU, revenue today)
- Streaming for near-realtime: Kafka → ClickHouse Materialized Views
If the dashboard shows only aggregated metrics (without drill-down to a specific user) — ClickHouse with pre-computed aggregates gives 50–200ms latency per query on a billion rows.
Client Architecture
On Flutter — BLoC with separate Cubits per dashboard widget, data loading in parallel via Future.wait:
class DashboardBloc extends Bloc<DashboardEvent, DashboardState> {
final AnalyticsRepository _repository;
Future<void> _onLoadDashboard(LoadDashboard event, Emitter emit) async {
emit(DashboardLoading());
try {
final results = await Future.wait([
_repository.fetchDAU(event.dateRange),
_repository.fetchRevenue(event.dateRange),
_repository.fetchRetentionCohorts(event.dateRange),
_repository.fetchTopScreens(event.dateRange),
]);
emit(DashboardLoaded(
dau: results[0] as List<DailyActiveUsers>,
revenue: results[1] as RevenueMetrics,
retention: results[2] as RetentionCohorts,
topScreens: results[3] as List<ScreenMetrics>,
));
} catch (e) {
emit(DashboardError(e.toString()));
}
}
}
Parallel loading is critical: if 4 charts load sequentially at 300ms each — users wait 1.2 seconds. Parallel — 300ms.
Visualization: Libraries and Choices
| Library | Platform | Strengths | Limitations |
|---|---|---|---|
fl_chart |
Flutter | Customization, line/bar/pie/scatter | No candlestick, no zoom |
syncfusion_flutter_charts |
Flutter | Rich set of types, zoom/pan | Commercial license |
| Charts (Google) | Android | Native Material look | Weak customization |
| DGCharts | iOS | Swift-native, animations | Swift/ObjC only |
| Victory Native | RN | Declarative API | Performance on >5k points |
For analytical dashboards needing zoom/pan and handling large point counts — syncfusion_flutter_charts or WebView with Echarts/Highcharts. WebView approach gives maximum flexibility but adds JS ↔ Dart communication overhead.
Filters and Interactivity
Date filters are the most common request. DateTimeRange picker with presets (Today / 7 days / 30 days / Quarter / Year) plus custom range. Important: debounce on filter change — don't reload data on every tap on the range:
// Debounce in StreamController or via rxdart
filterStream
.debounceTime(const Duration(milliseconds: 300))
.distinct()
.listen((filter) => bloc.add(UpdateFilter(filter)));
Drill-down — transition from aggregate to details (tap bar in chart → list of users in that segment). Implemented via routing with filter context passing.
Data Export
Analytics dashboard users expect export capability. On mobile — export to PDF and CSV.
PDF generation: on iOS PDFKit + UIGraphicsPDFRenderer, on Android PdfDocument. On Flutter — printing package with pdf package. Export chart screenshot via RepaintBoundary + toImage():
Future<Uint8List?> captureChart(GlobalKey chartKey) async {
final boundary = chartKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
final image = await boundary?.toImage(pixelRatio: 2.0);
final byteData = await image?.toByteData(format: ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
CSV via csv package, open via share_plus for email or Telegram sharing.
Performance with Large Data Volumes
The main dashboard issue is degradation with large time ranges. DAU chart for a year — 365 points, normal. Chart of hourly events for a year — 8760 points, already heavy for rendering. Solution: downsampling on server — return no more than N points for current scale. On zoom in — load more detailed data.
fl_chart at >500 points in LineChart lags on mid-range Android. Switch to Canvas drawing directly via CustomPainter or use lossyDownsample algorithm (Largest-Triangle-Three-Buckets) before passing data to library.
Work Phases
Requirements audit and metric agreement → API design for aggregates → OLAP setup if needed → UI component development → integration → performance optimization → publication.
Timeframe
MVP with 5–8 metrics, line charts and date filters: 3–5 weeks. Full dashboard with drill-down, cohort analysis, export and real-time metrics: 2–3 months. Cost calculated individually after requirements analysis.







