Expense Tracker Application Flutter
Last Updated :
26 Mar, 2025
In this tutorial, we will create a Flutter app that allows users to track their daily expenses and income, visualize spending patterns through charts, and store transaction data locally using Hive. This application will showcase how to handle user input, visualize data, and ensure local data persistence, providing users with a simple yet effective tool for managing their finances.
Application Features:
- Add and categorize transactions as either income or expenses.
- View and manage a list of all past transactions.
- Visualize spending trends with dynamic charts.
- Store and retrieve transaction data locally using Hive.
Below is the demo video for the app that we will be developing.
Setting Up the Project
Step 1 : Create a new Flutter Application
Create a new Flutter application using the command Prompt. To create a new app, write the below command and run it.
flutter create app_name
To know more about it refer this article: Creating a Simple Application in Flutter
Step 2 : Adding the Dependency
For this project, you’ll need these dependencies:
- hive_flutter: To store and manage transaction data locally.
- intl: For formatting dates within the application.
- fl_chart: To create dynamic charts for visualizing expenses over time.
hive_generator and build_runner are the dev_dependencies to generate .g.dart files.
To add the dependency to the pubspec.yaml file, add hive_flutter, fl_chart and intl as a dependency in the dependencies and hive_generator and build_runner as dev_dependencies part of the pubspec.yaml file, as shown below:
Dart
dependencies:
flutter:
sdk: flutter
hive_flutter: ^1.1.0
fl_chart: ^0.70.2
intl: ^0.20.2
dev_dependencies:
flutter_test:
sdk: flutter
hive_generator:
build_runner:
Now run the below command in the terminal.
flutter pub get
Or
Run the below command in the terminal.
flutter pub add hive_flutter fl_chart intl
Folder Structure
Create the following folder structure for better organization of your files:
This structure keeps database logic, models, and UI separated, making the code modular and easy to maintain.
Build the Application
The Application is divided into the following packages:
- Models: Contains Objects that encapsulate the data and behavior of the application domain.
- UI elements: Contains Code that defines the UI of the Application.
Step 1: Models
Let's start by creating a transaction model for our transaction data in trancs.dart.
trancs.dart:
Dart
// Importing Hive package for local storage
import 'package:hive/hive.dart';
// Part statement is used for code
// generation for the Transaction class.
part 'trancs.g.dart';
// Defining a Hive Type with a unique
// typeId of 0 to store Transaction objects.
@HiveType(typeId: 0)
class Transaction extends HiveObject {
// Field to store the category of the
// transaction (e.g., Food, Transportation).
@HiveField(0)
final String category;
// Field to store the transaction amount.
@HiveField(1)
final double amount;
// Field to store whether the transaction
// is an income (true) or an expense (false).
@HiveField(2)
final bool isIncome;
// Field to store the date of the transaction.
@HiveField(3)
final DateTime date;
// Constructor for the Transaction class
// with required fields.
Transaction({
required this.category,
required this.amount,
required this.isIncome,
required this.date,
});
}
Now you need to generate trancs.g.dart file , for that run the command in your terminal :
dart run build_runner build
Step 2 : UI Elements
Our Application contains three main UI component screens as mentioned below:
- Transaction Screen
- Add Transactions Screen
- Reports Screen
Now let us Observe all of them.
- Transaction Screen (Display Transitions)
The tranc_screen.dart is where users will see the list of past transactions he/she made :
tranc_screen.dart:
tranc_screen.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/models/trancs.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
class TransactionScreen extends StatelessWidget {
const TransactionScreen({super.key});
@override
Widget build(BuildContext context) {
// Retrieve the Hive box where
// transactions are stored
final transactionBox = Hive.box('transactions');
return Scaffold(
body: ValueListenableBuilder(
// Listens to changes in the transactions
// box and rebuilds UI accordingly
valueListenable: transactionBox.listenable(),
builder: (context, Box box, widget) {
// Display message when there are no transactions
if (box.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
// Icon indicating no transactions
Icons.money_off,
size: 80,
),
SizedBox(height: 16),
Text(
'No transactions yet.',
// Optionally style this text using theme later
),
],
),
);
} else {
// If transactions exist, display them in a ListView
return ListView.builder(
padding: const EdgeInsets.all(16),
// Number of transactions
itemCount: box.length,
itemBuilder: (context, index) {
// Retrieve the transaction at the current index
final transaction = box.getAt(index) as Transaction;
// Check if it's an income
final isIncome = transaction.isIncome;
// Format the date
final formattedDate = DateFormat.yMMMd()
.format(transaction.date);
// Display each transaction inside a card
return Card(
margin: const EdgeInsets.symmetric(vertical: 8),
elevation: 3,
child: ListTile(
leading: Icon(
isIncome
? Icons.arrow_upward
: Icons.arrow_downward, // Icon depending on transaction type
color: isIncome
? Colors.green
: Colors.red, // Color for income/expense
),
title: Text(
// Transaction category name
transaction.category,
// style: theme.textTheme.subtitle1, // Optional text styling
),
// Formatted transaction date
subtitle: Text(formattedDate),
trailing: Text(
isIncome
? '+ \$${transaction.amount.toStringAsFixed(2)}' // Display income with plus sign
: '- \$${transaction.amount.toStringAsFixed(2)}', // Display expense with minus sign
style: TextStyle(
fontWeight: FontWeight.bold,
color: isIncome
? Colors.green
: Colors.red, // Color based on transaction type
fontSize: 16,
),
),
),
);
},
);
}
},
),
);
}
}
Screenshot of the Transaction screen :
- Add Transaction Screen
The add_tranc.dart is where users can add transactions by entering values like amount, category, Income/Expense, and date :
add_tranc.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/models/trancs.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
class AddTransactionScreen extends StatefulWidget {
const AddTransactionScreen({super.key});
@override
_AddTransactionScreenState createState() => _AddTransactionScreenState();
}
class _AddTransactionScreenState extends State<AddTransactionScreen> {
// Form key for validation
final _formKey = GlobalKey<FormState>();
// Default category
String _category = 'Food';
TextEditingController _amountController = TextEditingController();
bool _isIncome = false;
// Default date as current date
DateTime _selectedDate = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
// Form with validation
key: _formKey,
child: ListView(
children: [
// Input for transaction amount
TextFormField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount',
border: OutlineInputBorder(),
icon: Icon(Icons.attach_money),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
// Validate amount
return 'Please enter an amount';
}
return null;
},
),
const SizedBox(height: 16),
// Dropdown for selecting category
DropdownButtonFormField<String>(
value: _category,
decoration: const InputDecoration(
labelText: 'Category',
border: OutlineInputBorder(),
icon: Icon(Icons.category),
),
onChanged: (String? newValue) {
setState(() {
_category = newValue!;
});
},
items: <String>['Food', 'Transportation', 'Entertainment']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
const SizedBox(height: 16),
// Switch for Income/Expense selection
SwitchListTile(
title: const Text('Income'),
value: _isIncome,
onChanged: (bool value) {
setState(() {
_isIncome = value;
});
},
secondary: Icon(
_isIncome ? Icons.arrow_upward : Icons.arrow_downward,
color: _isIncome ? Colors.green : Colors.red,
),
),
const SizedBox(height: 16),
// Date picker for transaction date
ListTile(
title: Text(
'Date: ${DateFormat('yyyy-MM-dd').format(_selectedDate)}',
),
trailing: const Icon(Icons.calendar_today),
// Opens date picker
onTap: () => _selectDate(context),
),
const SizedBox(height: 16),
// Button to add the transaction
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
onPressed: () {
if (_formKey.currentState!.validate()) {
// Save form data
_formKey.currentState!.save();
// Create and save transaction
final transaction = Transaction(
category: _category,
amount: _amountController.text.isNotEmpty
? double.parse(_amountController.text)
: 0.0,
isIncome: _isIncome,
date: _selectedDate,
);
Hive.box('transactions').add(transaction);
// Reset form after submission
setState(() {
_category = 'Food';
_amountController.clear();
_isIncome = false;
_selectedDate = DateTime.now();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Transaction added'),
),
);
}
},
icon: const Icon(
Icons.add,
color: Colors.white,
),
label: const Text('Add Transaction'),
),
],
),
),
),
);
}
// Date picker function
Future<void> _selectDate(BuildContext context) async {
final picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (picked != null && picked != _selectedDate) {
setState(() {
_selectedDate = picked; // Update selected date
});
}
}
}
Screenshot of the Add Transaction Screen :
- Report Screen (Graph)
This report.dart screen displays a graph of Expenses,
report.dart
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
import 'package:hive/hive.dart';
import '../models/trancs.dart';
class ReportsScreen extends StatelessWidget {
const ReportsScreen({super.key});
@override
Widget build(BuildContext context) {
final transactionBox = Hive.box('transactions');
final List<Transaction> transactions =
transactionBox.values.toList().cast<Transaction>();
// Group expenses by date
Map<String, double> dailyExpenses = {};
transactions.where((txn) => !txn.isIncome).forEach((txn) {
String dateKey = DateFormat('yyyy-MM-dd').format(txn.date);
dailyExpenses.update(dateKey, (value) => value + txn.amount,
ifAbsent: () => txn.amount);
});
// Create spots for graph based on daily expenses
List<FlSpot> expenseSpots = [];
List<String> dateLabels = [];
int index = 0;
dailyExpenses.forEach((date, amount) {
expenseSpots.add(FlSpot(index.toDouble(), amount));
dateLabels.add(date);
index++;
});
// Calculate max expense value for Y-axis limit
double maxExpense = dailyExpenses.isNotEmpty
? dailyExpenses.values.reduce((a, b) => a > b ? a : b)
: 100;
double interval =
(maxExpense / 5).ceilToDouble(); // Dynamic interval for better scaling
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: LineChart(
LineChartData(
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
int index = value.toInt();
if (index >= 0 && index < dateLabels.length) {
return Text(
DateFormat('MM/dd').format(
DateFormat('yyyy-MM-dd').parse(dateLabels[index])),
style: const TextStyle(fontSize: 10),
textAlign: TextAlign.center,
);
}
return const Text('');
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: interval, // Set dynamic interval for Y-axis
reservedSize: 40,
getTitlesWidget: (value, meta) {
return Text('\$${value.toInt()}',
style: const TextStyle(fontSize: 10));
},
),
),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: interval,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.5),
strokeWidth: 1,
dashArray: [5, 5],
);
},
),
lineBarsData: [
LineChartBarData(
spots: expenseSpots, // Set expense data points
isCurved: true,
color: Colors.redAccent,
barWidth: 4,
dotData:
const FlDotData(show: true), // Show dots on each data point
belowBarData: BarAreaData(
show: true,
color: Colors.redAccent
.withOpacity(0.2), // Light red area under the curve
),
),
],
borderData: FlBorderData(
show: true,
border: Border.all(color: Colors.black.withOpacity(0.3)),
),
minY: 0,
maxY: maxExpense +
(interval * 2), // Add extra space above the highest expense
),
),
),
);
}
}
Screenshot of the Reports Screen :
- Expense Home Screen
This expence_home_screen.dart screen is called from the main.dart file, and shows a Bottomtab :
expence_home_screen.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/screens/add_tranc.dart';
import 'package:geeks_for_geeks/screens/report.dart';
import 'package:geeks_for_geeks/screens/transc_screen.dart';
class ExpenseHomeScreen extends StatefulWidget {
const ExpenseHomeScreen({super.key});
@override
_ExpenseHomeScreenState createState() => _ExpenseHomeScreenState();
}
class _ExpenseHomeScreenState extends State<ExpenseHomeScreen> {
int _selectedIndex = 0;
final List<Widget> _screens = [
const TransactionScreen(),
const AddTransactionScreen(),
const ReportsScreen(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('GFG Expense Tracker'),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
body: _screens[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.green,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.6),
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Transactions',
),
BottomNavigationBarItem(
icon: Icon(Icons.add),
label: 'Add Transaction',
),
BottomNavigationBarItem(
icon: Icon(Icons.bar_chart),
label: 'Reports',
),
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
);
}
}
Main Application File Code
After successfully creating all the files mentioned above we can finally utilize them to create a fully functional Application.
Finally, let's Call Our ExpenseHomeScreen from the main.dart file
main.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/models/trancs.dart';
import 'package:geeks_for_geeks/screens/expence_home_screen.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive
await Hive.initFlutter();
// Register the TransactionAdapter
Hive.registerAdapter(TransactionAdapter());
// Open the Hive box
await Hive.openBox('transactions');
runApp(const ExpenseTrackerApp());
}
class ExpenseTrackerApp extends StatelessWidget {
const ExpenseTrackerApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'GFG Expense Tracker',
home: ExpenseHomeScreen(),
);
}
}
Click Here to access complete Application Code
Output:
Similar Reads
Creating a Finance Tracker Application in Flutter
Developing a new tracker for finance is a good practice to improve your skills and build a helpful application. Through Flutter, an open-source UI toolkit developed by Google, it is possible to design for iOS/ Android, web, and desktop. Here you will find out the recipe of how to create a finance tr
6 min read
EBook reader Application in Flutter
EBook reader Application brings the library to your fingertips. This application will be the travel partner of every book lover who loves to read books of their choice. The app is developed using Flutter and provider state management. It uses the Google Books API to fetch the data of books. The app
8 min read
Recipe Finder Application in Flutter
This app will display a list of recipes in a card format, each containing the recipe title, a rating, the cooking time, and a thumbnail image to give users a visual preview. The app is dynamic and flexible, allowing you to easily update the recipe list or modify the UI as needed. By the end of this
5 min read
Multi Page Applications in Flutter
Apps are widely used by humans in this techie world. The number of apps in the app store is increasing day by day. Due to this competition, app developers have started to add a number of features to their apps. To reduce this complexity, the content of the app is mostly divided into various pages so
5 min read
Can AI replace Flutter developers ?
Flutter, a brand-new Google UI toolkit, is gradually changing the state of cross-platform development by allowing to build applications with the use of natively compiled code for mobile, web, and line aller disciplines by using a single codebase. Flutter has been continuously experiencing active com
8 min read
Photo Editing Application in Flutter
With the introduction of the powerful image_editor_plus package, editing images directly within your Flutter app has become significantly more accessible. In this article, we'll dive deep into building a user-friendly image editor app. Users will have the ability to select an image from their camera
7 min read
Flutter - Architecture Application
Flutter architecture application mainly consists of: WidgetsGesturesConcept of StateLayersWidgetsWidgets are the primary component of any flutter application. It acts as a UI for the user to interact with the application. Any flutter application is itself a widget that is made up of a combination of
3 min read
Creating a Simple Application in Flutter
Flutter is an open-source cross-platform mobile application development SDK created by Google. It is highly user-friendly and builds high-quality mobile applications. The intention behind this article is to guide readers through the process of building an application through Flutter by creating a si
5 min read
Flutter - Programmatically Exit From the Application
In this article, we are going to see how to programmatically close a Flutter application. SystemNavigator.pop(): Works and is the RECOMMENDED way of exiting the app. exit(0): Also works but it's NOT RECOMMENDED as it terminates the Dart VM process immediately and the user may think that the app just
3 min read
Building eCommerce applications with Flutter
Flutter uses the Dart programming language and allows developers to build high-performance, visually attractive mobile applications for both iOS and Android platforms from a single codebase. Flutter is a powerful cross-platform development framework that enables building native-looking apps for both
8 min read