DEV Community

Cover image for Build a Responsive Flutter Expense Tracker Dashboard with Dynamic Charts
Phinter Atieno for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Build a Responsive Flutter Expense Tracker Dashboard with Dynamic Charts

TL;DR: Struggling to build a comprehensive Flutter dashboard? This guide details implementing a powerful Flutter Expense Tracker Dashboard using custom layouts and dynamic Syncfusion® Flutter Charts to visualize all your financial insights, from income to savings, seamlessly across devices. It covers how to consolidate financial insights like income, expenses, and savings growth into a responsive, interactive layout. Key features include dynamic charts, recent transaction tracking, and an adaptable design for mobile and desktop views.

Welcome to our guide on building a responsive Flutter Expense Tracker Dashboard! So far, we have covered the Setup and Import Pages of the Expense Tracker Sample in the Onboarding Pages. In this post, we’ll explore how to create an interactive dashboard that consolidates financial insights like income, expenses, and savings. Whether you’re developing for mobile or desktop, this step-by-step guide will help you implement dynamic charts and recent transaction tracking.

Now, let’s dive into the dashboard page, where we’ll walk through its implementation in the Flutter Expense Tracker Sample and explore its key features.

Consolidated details on the dashboard page

The dashboard page in the expense tracker sample provides a detailed summary of financial activity, offering a clear view of transactions and savings. It is structured into multiple sections, each highlighting key financial details:

  • Overall financial insights: Displaying key financial metrics like account balance, income, expenses, and savings.
  • Financial overview: An interactive chart visualizes income and expense trends based on the categories.
  • Recent transactions: Displays the most recently made transactions.
  • Account overview: An interactive chart visualizes income and expenses over different timeframes.
  • Savings growth: An interactive chart that visualizes savings growth over time.

Dashboard demo

Desktop view

Dashboard page on desktop with multiple financial insights


Dashboard page on desktop with multiple financial insights

Mobile view

Expense tracker dashboard on mobile with stacked financial metrics


Expense tracker dashboard on mobile with stacked financial metrics

Designing the dashboard layout

To build a complex, custom layout and a well-structured dashboard, we usedSlottedMultiChildRenderObjectWidget from the Flutter framework. This widget allows precise control over positioning while maintaining flexibility for mobile and desktop views. This approach ensures that each widget is placed in the correct slot, making the layout responsive and adaptable to different screen sizes.

Let’s break down how we have implemented the dashboard page step by step below.

Step 1: Defining slots with an enum

Before assigning each widget to its position, we defined all possible slots. This approach improves code maintainability and readability by ensuring each widget is mapped to a specific position.

/// Defines slots for different dashboard widgets.
enum DashboardWidgetsSlot {
   currentBalance,
   income,
   expense,
   savings,
   financialOverviewWidget,
   accountBalanceChart,
   recentTransactionWidget,
   savingGrowth,
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the slotted widget class

We created a widget by extending SlottedMultiChildRenderObjectWidget, the foundation for slot-based positioning.

class ETDashboardLayout extends SlottedMultiChildRenderObjectWidget<
      DashboardWidgetsSlot,
      RenderObject>{
   const DashboardWidget({
      required this.buildContext,
      // Other required properties..
      super.key,
   });
   final BuildContext buildContext;
   // Other required properties.
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Assigning widgets to slots

The childForSlot method maps each dashboard component to its respective slot. This method ensures that each widget is positioned correctly and efficiently.

@override
Widget? childForSlot(DashboardWidgetsSlot slot) {
  switch (slot) {
    case DashboardWidgetsSlot.currentBalance:
      return Padding(
         padding:
             isMobile(buildContext) ? mobileCardPadding : windowsCardPadding,
             child: OverallDetails(
                 insightTitle: 'Balance',
                 insightValue: toCurrency(
                 totalIncome - totalExpense,
                 userDetails.userProfile,
             ),
             // Other properties…  
     ),);
     case DashboardWidgetsSlot.income:
           // return income widget…  
     case DashboardWidgetsSlot.expense:
           // return expense widget…  
     case DashboardWidgetsSlot.savings:
           // return savings widget…  
     case DashboardWidgetsSlot.financialOverviewWidget:
           // return financial overview widget…  
     case DashboardWidgetsSlot.recentTransactionWidget:
           // return recent transaction widget…        
     case DashboardWidgetsSlot.accountBalanceChart:
           // return recent account balance widget…        
     case DashboardWidgetsSlot.savingGrowth:
           // return savings Growth widget…        
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Defining available slots

We override the slots getter from the SlottedMultiChildRenderObjectWidget to specify the available slots, ensuring the widget knows which slots to expect.

@override
Iterable<DashboardWidgetsSlot> get slots => DashboardWidgetsSlot.values;
Enter fullscreen mode Exit fullscreen mode

Step 5: Creating and updating RenderObject

The createRenderObject and updateRenderObject methods handle rendering logic. These methods ensure that widgets are properly initialized and updated when required.

@override
SlottedContainerRenderObjectMixin<DashboardWidgetsSlot, RenderObject>
      createRenderObject(BuildContext context) {
    return ETRenderDashboardWidgetsLayout(context);
}

@override
ETRenderDashboardWidgetsLayout updateRenderObject(BuildContext context,
 covariant SlottedContainerRenderObjectMixin<DashboardWidgetsSlot,RenderObject>
  renderObject,){
     return ETRenderDashboardWidgetsLayout(context);
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom RenderObject for dashboard widgets

To define the custom layout behavior, we created a class named ETRenderDashboardWidgetsLayout, which extends the Flutter RenderBox widget. This class determines how widgets are arranged within the dashboard.

/// A custom render box that lays out dashboard widgets in a specific arrangement.
class ETRenderDashboardWidgetsLayout extends RenderBox
  with SlottedContainerRenderObjectMixin<DashboardWidgetsSlot, RenderBox> {
   /// Creates a new dashboard widget layout with the given build context.
   ETRenderDashboardWidgetsLayout(this._buildContext);

   /// The build context used for responsive layout calculations.
   final BuildContext _buildContext;

   /// Sets up the parent data for child render objects.
   @override
   void setupParentData(RenderObject child) {
      if(child.parentData is! DashboardWidgetParentData) {
         child.parentData = DashboardWidgetParentData();
      }
      super.setupParentData(child); }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Handling layout with PerformLayout

The performLayout method in the RenderBox widget calculates the position of each child, dynamically adjusting the dashboard’s structure based on the available screen size.

/// Performs layout calculations for all child widgets.
@override
void performLayout() {
   final Size availableSize = constraints.biggest;
   final double commonInsightTileMinimumHeight =
   isMobile(buildContext)? 76.0: 108.0;
   const double commonInsightWidthFactor = 0.25;
   const double commonInsightMobileWidthFactor = 0.5;
   final bool isMobileOrTablet = isMobile(buildContext) || isTablet(buildContext);

 // Calculate sizes for insight boxes and dashboard widgets.
final Size insightBoxSize = Size(
    isMobileOrTablet
    ? (availableSize.width * commonInsightMobileWidthFactor)
    : (availableSize.width * commonInsightWidthFactor),
    commonInsightTileMinimumHeight,
);

final Size dashboardWidgetBoxSize = Size(
    availableSize.width,
    MediaQuery.of(buildContext).size.height -
    (insightBoxSize.height + 24.0 + AppBar().preferredSize.height),
);

final RenderBox? currentBalanceRenderBox = childForSlot(
    DashboardWidgetsSlot.currentBalance,
);

if(currentBalanceRenderBox != null)
{
    currentBalanceRenderBox.layout( BoxConstraints.tight(Size(width, height)), parentUsesSize: true, ); 

    // Set the position of the child. 
    final parentData = currentBalanceRenderBox.parentData! as DashboardWidgetParentData;     
    parentData.offset = Offset(x, y); 
}
    // repeat for other children…  

// Set the final size of the render object.  
   size = Size(constraints.maxWidth, height);
}
Enter fullscreen mode Exit fullscreen mode

Dashboard layout across different devices

Desktop view

  • Overall details: Displayed horizontally, each section occupying 25% of the screen width.
  • Financial overview and recent transactions: Placed side by side, both the financial overview covers 65% and the recent transactions cover 35% of the total width.
  • Account balance and savings growth: Placed side by side, sharing equal widths.

Mobile and tablet view

  • Overall details: Arranged in two rows, with two widgets per row.
  • Other sections: Stacked vertically for better readability.

Dashboard slots

Section 1: Overall financial insights

The overall financial insights section provides a quick financial summary, displaying account balance, income, expenses, and savings at the top of the dashboard. Each metric uses a custom ETCommonBox for a consistent and clean layout.

Calculation logic

  • Balance = total income – total expense
  • Income = sum of all income sources
  • Expense = sum of all expenses
  • Savings = sum of all saved amounts

Desktop view

Overall financial metrics are displayed in a horizontal layout on desktop


Overall financial metrics are displayed in a horizontal layout on desktop

Mobile view

Overall financial metrics in two rows on mobile view


Overall financial metrics in two rows on mobile view

Section 2: Financial overview

This section provides an intuitive way to analyze income and expenses using a segmented button, a duration dropdown, and a Syncfusion® Flutter Doughnut Chart.

Segmented button

Flutter’s built-in SegmentedButton widget allows users to toggle between income and expense. Based on the selection, the chart updates dynamically to display the respective categories.

Duration dropdown

The duration dropdown provides options to filter data based on:

  • This month
  • This year
  • Last 6 months
  • Last year

Selecting a duration updates the data source, ensuring synchronized data visualization for income or expense categories within the chosen period.

Syncfusion® Flutter Doughnut Chart

A Doughnut Chart visually represents financial data and updates dynamically based on:

  • The selected financial type (income/expense).
  • The selected duration.

The chart displays

  • Each category’s total expense or income.
  • Another section for categories contributing less than 8%.
DoughnutSeries<FinancialDetails, String> _buildDoughnutSeries(
 BuildContext context,
 List<FinancialDetails> currentDetails,
 String financialViewType,
 bool isExpense,
){
   return DoughnutSeries<FinancialDetails, String>(
      xValueMapper: (FinancialDetails details, int index) => details.category,
      yValueMapper: (FinancialDetails details, int index) => details.amount,
      dataLabelSettings: const DataLabelSettings(
      isVisible: true,
      labelIntersectAction: LabelIntersectAction.hide,
   ),
   radius: '100%',
   name: 'Expense',
   dataSource: currentDetails,
   animationDuration: 500,);
}
Enter fullscreen mode Exit fullscreen mode

Data processing and grouping

To ensure accuracy, data is processed through the following steps:

  • Filter transactions based on the selected duration.
  • Group transactions by category and calculate total amounts.
  • Consolidate minor categories into other categories if they contribute less than 8%.
  • Updating the chart’s data source accordingly.
void _dataGrouping(
 List<ExpenseDetails> expenseDetailsReference,
 List<IncomeDetails> incomeDetailsReference,
){
 final List<ExpenseDetails> expenseData = _filterByTimeFrame(
  expenseDetailsReference,
  filteredTimeFrame,
 );
 final List<IncomeDetails> incomeData = _filterByTimeFrame(
  incomeDetailsReference,
  filteredTimeFrame,
 );

 Map<String, double> expenseMap = {};
 Map<String, double> incomeMap = {};

 for (final ExpenseDetails detail in expenseData) {
  expenseMap.update(
   detail.category,
   (value) => value + detail.amount,
   ifAbsent: () => detail.amount,
  );
 }
 for (final IncomeDetails detail in incomeData) {
  incomeMap.update(
   detail.category,
   (value) => value + detail.amount,
   ifAbsent: () => detail.amount,
  );
 }

 if (expenseMap.isNotEmpty) {
  expenseMap = _othersValueMapping(expenseMap);
 }
 if (incomeMap.isNotEmpty) {
  incomeMap = _othersValueMapping(incomeMap);
 }

 incomeDetailsReference.clear();
 expenseDetailsReference.clear();

 final List<ExpenseDetails> expenseDetails = expenseMap.entries.map((entry) {
  return ExpenseDetails(
   category: entry.key,
   amount: entry.value,
   date: DateTime.now(),
   budgetAmount: 0.0,
  );
 }).toList();

 final List<IncomeDetails> incomeDetails = incomeMap.entries.map((entry) {
  return IncomeDetails(
   category: entry.key,
   amount: entry.value,
   date: DateTime.now(),
  );
 }).toList();

 expenseDetailsReference.addAll(expenseDetails);
 incomeDetailsReference.addAll(incomeDetails);
}
Enter fullscreen mode Exit fullscreen mode

Custom legend

A custom legend, designed using legendItemBuilder, ensures clear identification of financial data across desktop and mobile layouts. It displays:

  • Category icon (colored to match the chart segment).
  • Category name.
  • Total amount.

Desktop view

A Doughnut chart showing income and expenses on a desktop


A Doughnut chart showing income and expenses on a desktop

Mobile view

Doughnut chart and segmented controls in mobile layout


Doughnut chart and segmented controls in mobile layout

Section 3: Recent transactions

The recent transactions widget provides users with an overview of their latest transactions. This section displays the most recently made transactions, including the category, subcategory, transaction amount, and transaction date. Each transaction entry is dynamically updated as new transactions are added.

To enhance user experience, a View More option is included, allowing users to navigate to the full transactions page to see all their past transactions.

buildViewMoreButton(context, () {
 pageNavigatorNotifier.value = NavigationPagesSlot.transaction;
}),
Enter fullscreen mode Exit fullscreen mode

Desktop view

Recent transactions on the desktop


Recent transactions on the desktop

Mobile view

Recent transaction list optimized for mobile view


Recent transaction list optimized for mobile view

Section 4: Account overview

The account overview section provides an insightful and visually appealing representation of income and expenses over different timeframes. This enables users to track their financial trends with ease using Syncfusion® Flutter Cartesian Charts with Spline Area Series.

Visualizing income and expenses

To enhance financial tracking, this section includes:

  • Account balance display: A textual representation of the current balance.
  • Duration selection: A dropdown menu to switch between different timeframes (this year, last year, this month, last six months), dynamically updating the chart.
  • Spline Area Charts: Two overlapping area charts representing income and expenses, offering a clear financial overview.
  • Customized chart axes: The X and Y axes are customized using Syncfusion® axis types, ensuring clear readability and a professional appearance.

Implementation overview

Using the SfCartesianChart widget, a Spline Area Series is plotted to depict income and expense trends:

  • X-axis: Represents the time period (months or years) based on the selected duration.
  • Y-axis: Represents the monetary values of income and expenses.
  • Spline Area Series: Provides a smooth, visually appealing representation of financial fluctuations.

Key features:

  • Dynamic data update: The chart refreshes when a user selects a different duration from the dropdown.
  • Gradient fill: The income and expense areas use soft gradient fills to distinguish them visually.
  • Axis customization: Enhances clarity by formatting labels, gridlines, and scaling options using Syncfusion® axis types.
  • This section ensures users gain insights into financial patterns with a clear, interactive, and engaging visualization.

Expense chart

SplineAreaSeries<ExpenseDetails, DateTime> _buildExpenseSplineAreaSeries(
 BuildContext context,
 List<ExpenseDetails> expenseDetails,){
   return SplineAreaSeries<ExpenseDetails, DateTime>(
      xValueMapper: (ExpenseDetails data, _) => data.date,
      yValueMapper: (ExpenseDetails data, _) => data.amount,
      splineType: SplineType.monotonic,
      dataSource: expenseDetails,
      ...);
}
Enter fullscreen mode Exit fullscreen mode

Income chart

Similarly, an income chart can be created by applying the same approach while updating the data source to reflect income details.

Desktop view demo

Spline area chart comparing income and expenses on a desktop


Spline area chart comparing income and expenses on a desktop

Mobile view demo

Spline area chart showing financial trends on mobile


Spline area chart showing financial trends on mobile

Section 5: Savings growth

The Savings growth section visually represents how savings accumulate over time, allowing users to track their financial progress. It follows the same Spline Area Series visualization approach used in the account balance section.

Key features:

  • Duration selection: Users can filter savings growth over different periods (this year, last year, last six months, this month).
  • Spline Area Series: Displays how savings fluctuate over time with smooth transitions.
  • Gradient fill & axis customization: Ensures a clear and polished presentation of data.
  • View more navigation: A button directs users to the savings page for a detailed breakdown.

Implementation overview

This section utilizes SfCartesianChart with a Spline Area Series, similar to the account balance section:

  • X-axis: Represents the selected time duration.
  • Y-axis: Displays the accumulated savings amount.
  • Dynamic updates: The chart refreshes automatically after a different duration is selected.

By following this approach, we can seamlessly track savings trends.

/// Creates a spline area chart series for savings data. 
SplineAreaSeries<Saving, DateTime> _buildSavingsSplineAreaSeries( 
 BuildContext context, 
 List<Saving> savings, 
){ 
savings.sort((Saving a, Saving b) => a.savingDate.compareTo(b.savingDate)); 

return SplineAreaSeries<Saving, DateTime>( 
 xValueMapper: (Saving data, int index) => data.savingDate, 
 yValueMapper: (Saving data, int index) => data.savedAmount, 
 splineType: SplineType.monotonic, 
 dataSource: savings, 
 markerSettings: const MarkerSettings( 
  isVisible: true, 
  borderColor: Color.fromRGBO(134, 24, 252, 1), 
 )); 
} 
Enter fullscreen mode Exit fullscreen mode

Desktop Demo

Savings growth chart over time on desktop


Savings growth chart over time on desktop

Mobile Demo

Savings chart showing accumulated savings on mobile


Savings chart showing accumulated savings on mobile

FAQs

Q1: What is the Dashboard Page’s purpose?

It consolidates financial data, displaying balance, income, expenses, savings, and interactive charts for trends and transactions.

Q2: How does the Dashboard adapt to devices?

Using SlottedMultiChildRenderObjectWidget, it shows insights horizontally on desktop and stacks them in rows on mobile for readability.

Q3: What are the main Dashboard sections?

  • Financial Insights: Balance, income, expenses, savings.
  • Financial Overview: Doughnut Chart for category trends. Recent Transactions: Latest transactions with “View More.”
  • Savings Growth: Spline Area Chart for savings trends.

Q4: How are charts implemented?

Syncfusion widgets create dynamic Doughnut and Spline Area Charts, updating with timeframe selections and grouping minor categories (<8%) into “Others.”

Conclusion

In conclusion, building a responsive Flutter Expense Tracker Dashboard is a powerful way to offer a comprehensive, interactive, and user-friendly experience for tracking financial activities. By leveraging Syncfusion® Flutter widgets, you can create a smooth, interactive, visually appealing interface. This guide empowers you to analyze financial data effortlessly, make informed decisions, and track your financial health with confidence. Ready to build your dashboard? Start today with Syncfusion Flutter widgets!

The new version is available for current customers to download from the license and downloads page. If you are not a Syncfusion® customer, you can try our 30-day free trial for our newest features.

You can also contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

Related Blogs

This article was originally published at Syncfusion.com.

Warp.dev image

The best coding agent. Backed by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

Feature flag article image

Create a feature flag in your IDE in 5 minutes with LaunchDarkly’s MCP server ⏰

How to create, evaluate, and modify flags from within your IDE or AI client using natural language with LaunchDarkly's new MCP server. Follow along with this tutorial for step by step instructions.

Read full post

👋 Kindness is contagious

Dive into this thoughtful piece, beloved in the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A sincere "thank you" can brighten someone's day—leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay