Case Study
FinDash App Icon

FinDash

Adaptive Flutter Financial Dashboard UI Showcase across Desktop, Tablet & Mobile

FinDash Mobile Layout — Stacked vertical financial dashboard screen

Overview

FinDash is a Flutter-based responsive financial dashboard demo and adaptive UI showcase focused on solving a core multi-device design challenge: how to present dense financial information—expense summaries, card carousels, transaction histories, quick invoice forms, and income charts—across desktop monitors, tablets, and mobile phones without relying on naive uniform scaling.

Rather than stretching a single layout across screen sizes, FinDash orchestrates three dedicated layout compositions (DashboardDesktopLayout, DashboardTabletLayout, and DashboardMobileLayout) through a reusable AdaptiveLayout widget powered by LayoutBuilder and AnimatedSwitcher, complemented by SizeConfig viewport thresholds and clamped responsive typography (getResponsiveFontSize).

Beyond layout adaptation, FinDash implements functional dashboard workflows powered by flutter_bloc (DashboardCubit and ThemeCubit) over a separated data/domain/presentation structure using DashboardRepository and DashboardMockDatasource. Users can switch reporting periods (Weekly, Monthly, Yearly), search transactions by title, filter by Income or Expense, export the currently filtered dataset to CSV or multi-page A4 PDF reports, interact with fl_chart pie segments, and persist Light/Dark mode preferences via SharedPreferences—establishing a clean UI foundation with planned future evolution toward live backend integration, authentication, and localization.

RoleLead Developer
PlatformDesktop, Tablet & Mobile
Tech Stack
FlutterDartBLoC / Cubit (flutter_bloc)fl_chartexpandable_page_viewcsv · pdf · printing · share_plusSharedPreferencesShimmer & DevicePreviewflutter_svg
Interactive Feature Showcase

FinDash

Adaptive Flutter Financial Dashboard UI Showcase across Desktop, Tablet & Mobile

Desktop Layout (≥ 1300px) — Full two-column layout with persistent sidebar1280 × 671px

Adaptive Responsive Dashboard

Three dedicated viewport layouts (DashboardDesktopLayout ≥ 1300px, DashboardTabletLayout 800px–1299px, and DashboardMobileLayout < 800px) orchestrated at runtime via AdaptiveLayout, LayoutBuilder, and a 400ms AnimatedSwitcher — paired with SizeConfig.tablet (900px) navigation adaptation and clamped responsive typography.

Technical Architecture

A high-level overview of how the system is structured to ensure scalability and maintainability.

Adaptive Layout & Viewport Orchestration (core/widgets & presentation/layouts)

Runtime layout switching and viewport-aware navigation composition across Desktop, Tablet, and Mobile breakpoints.

AdaptiveLayout uses LayoutBuilder + AnimatedSwitcher (400ms, Curves.easeOut) with breakpoints at < 800px (Mobile), 800px–1299px (Tablet), and ≥ 1300px (Desktop).DashBoardView checks MediaQuery width against SizeConfig.tablet (900px) to conditionally attach the mobile AppBar (menu trigger + theme toggle) and 70%-width slide-out CustomDrawer.DashboardTabletLayout reuses DashboardMobileLayout inside a 1:3 Row alongside CustomDrawer, eliminating widget duplication between tablet and mobile vertical flows.

Separated Data, Domain & Mock Repository Layer (features/dashboard/data & domain)

Feature-oriented separation decoupling presentation Cubits from the underlying mock data source via an abstract repository contract.

Abstract DashboardRepository contract defining getTransactions(period), getExpenses(period), getUserInfo(), and getSummary(period).DashboardRepositoryImpl maps raw payloads from DashboardMockDatasource into immutable domain models (TransactionModel, AllExpensesItemModel, UserInfoModel, DashboardData).DashboardMockDatasource simulates asynchronous network latency (300ms–700ms Future.delayed) across distinct Weekly, Monthly, and Yearly mock datasets.

Cubit State Management & Persisted Theme Preferences (presentation/cubits)

Unidirectional state management handling asynchronous period loading, combinable search/type filtering, and persistent theme mode.

DashboardCubit manages DashboardInitial, DashboardLoading, DashboardSuccess(DashboardData), and DashboardError(message) states using Future.wait parallel loading.Combinable client-side filtering (_applyFilters) intersects case-insensitive title search (searchQuery) with transaction type filters (All, Income, Expense).ThemeCubit persists isDarkMode in SharedPreferences and drives MaterialApp themeMode between AppTheme.light and AppTheme.dark.

Reusable Core Widgets, Adaptive Charting & Export Service (core/utils & widgets)

Modular dashboard sections, viewport-aware fl_chart visualization, clamped typography, and CSV/PDF document generation.

IncomeSectionBody inspects viewport width (width >= 1300 && width < 1750) to switch between DetailedIncomeChart and the side-by-side IncomeChart + IncomeDetails legend.ExportService generates CSV files via package:csv + share_plus (Share.shareXFiles) and formatted A4 multi-page PDF reports via package:pdf + printing (Printing.layoutPdf).getResponsiveFontSize() scales Montserrat typography using viewport-specific scale factors (divisors 900, 1000, 1920) clamped to [0.8x, 1.2x].

Engineering Insights

Key technical decisions and challenges encountered during the development process.

Key Decisions

Dedicated Breakpoint Layout Trees Instead of Uniform Scaling

Context

A three-column desktop financial dashboard becomes unreadable on mobile phones if scaled down proportionally, while a mobile single-column stack wastes horizontal screen real estate on desktop monitors.

Approach

Implemented AdaptiveLayout using LayoutBuilder to mount distinct widget trees at < 800px (DashboardMobileLayout), < 1300px (DashboardTabletLayout), and ≥ 1300px (DashboardDesktopLayout), while DashboardTabletLayout composes CustomDrawer alongside DashboardMobileLayout to share the single-column content stack without code duplication.

Secondary Desktop Breakpoint (1300px – 1749px) for Income Chart Readability

Context

At the entry desktop breakpoint (1300px), the right column holds only 1/3 of the content area alongside the sidebar and left column, causing a side-by-side donut chart and text legend row to crowd text labels.

Approach

Added a secondary width check in IncomeSectionBody (width >= SizeConfig.desktop && width < 1750) that swaps the side-by-side Row (IncomeChart + IncomeDetails) for DetailedIncomeChart, rendering percentages and category titles directly on the interactive PieChart sections until wider monitor space (≥ 1750px) is available.

Clamped Multi-Tier Scale Factors for Responsive Typography

Context

Fixed pixel font sizes either overflow compact cards on small screens or appear undersized on wide desktop displays, whereas unconstrained linear scaling produces extreme text sizes.

Approach

Built getResponsiveFontSize() and getScaleFactor() in core/utils/responsive_font_size.dart using tier-specific reference widths (width / 900 below 900px, (width / 1000).clamp(0.9, 1.1) below 1300px, and width / 1920 on desktop) and clamped every computed font size between 80% and 120% of its base Montserrat style.

Repository Abstraction Over Mock Data to Support Interactive Workflows

Context

Even in a UI-focused dashboard showcase, hardcoding static lists directly inside widgets prevents realistic testing of loading skeletons, period transitions, search filtering, and report exports.

Approach

Introduced DashboardRepository and DashboardMockDatasource beneath DashboardCubit. Switching between Weekly, Monthly, and Yearly periods triggers DashboardShimmerLoader and reloads period-scoped mock datasets, while search, filter chips, and CSV/PDF exports operate reactively on DashboardData.filteredTransactions.

Technical Challenges

Coordinating Scrollable Dashboard Columns Without Unbounded Height Exceptions

The Challenge

Combining a multi-section two-column desktop layout and a stacked mobile layout inside CustomScrollView while using Expanded children for aligned bottom sections can trigger Flutter RenderBox unbounded height errors.

The Solution

Wrapped layout columns inside CustomScrollView with SliverFillRemaining(hasScrollBody: false), allowing sections like IncomeSection to expand naturally to fill remaining viewport height on large screens while scrolling safely when content exceeds vertical bounds.

Adaptive-Height Card Carousel Synchronization with Animated Indicators

The Challenge

Standard Flutter PageView requires a fixed height or aspect constraint, which complicates responsive card sizing across mobile, tablet, and desktop column widths.

The Solution

Used ExpandablePageView in MyCardsPageView paired with an AspectRatio(420 / 215) MyCard container and a PageController listener in MyCardsSection that drives the 300ms AnimatedContainer width transition in CustomDotIndicator.

Preserving Active Search and Filter Context Across CSV & PDF Exports

The Challenge

Exporting raw unfiltered transactions when a user has narrowed the list by search query, transaction type (Income/Expense), and time period produces inconsistent reports.

The Solution

Centralized _applyFilters() inside DashboardCubit so every search or chip selection updates state.data.filteredTransactions, and wired ExportButton to pass both filteredTransactions and selectedPeriod directly into ExportService.exportToCsv() and ExportService.exportToPdf().

Adaptive Responsive Layout System

One unified financial dashboard adapting across Desktop (≥ 1300px), Tablet (800px – 1299px), and Mobile (< 800px) breakpoints.

01 · Viewport Tier1280 × 671px

Desktop Layout (≥ 1300px) — Full two-column layout with persistent sidebar

DashboardDesktopLayout composes an outer Row with an Expanded (flex: 1) CustomDrawer sidebar, a 32px gap, and an Expanded (flex: 3) CustomScrollView + SliverFillRemaining workspace whose inner Row splits AllExpensesAndQuickInvoiceSection (Expanded flex: 2) and the right-hand MyCardsAndTransactionHistory + Expanded IncomeSection column (Expanded flex: 1) separated by a 24px gap.

02 · Viewport Tier1280 × 883px

Tablet Layout (800px – 1299px) — Sidebar + single-column scrollable content

DashboardTabletLayout composes a Row with an Expanded (flex: 1) CustomDrawer sidebar, a 32px gap, an Expanded (flex: 3) Padding(top: 25) wrapping DashboardMobileLayout (single-column CustomScrollView), and a trailing 32px horizontal spacer.

03 · Viewport Tier384 × 694px

Mobile Layout (< 800px) — Stacked vertical layout with hamburger drawer

DashboardMobileLayout stacks AllExpensesAndQuickInvoiceSection, MyCardsAndTransactionHistory, and IncomeSection vertically with 24px spacing inside CustomScrollView + SliverFillRemaining, while DashBoardView (< 900px via SizeConfig.tablet) attaches a top AppBar with theme toggle and a 70%-width slide-out CustomDrawer.

Ready to dive deeper?

Explore the source code or see how this architecture translates to other projects in my portfolio.

Up Next

Social Mate