Build a Flutter Calculator App
A calculator app is an excellent Flutter project for learning widgets, layouts, state management, user interaction, event handling, and basic Dart programming. In this project, we will build a functional calculator that allows users to enter numbers, perform arithmetic operations, clear the display, and calculate results.
The calculator interface changes whenever the user presses a button, so a StatefulWidget is appropriate for managing the calculator's changing state. Flutter uses setState() to notify the framework that state has changed and that the widget should rebuild. :contentReference[oaicite:0]{index=0}
1. What We Will Build
We will create a calculator application with the following features:
- Number buttons from 0 to 9
- Addition
- Subtraction
- Multiplication
- Division
- Decimal numbers
- Clear button
- Delete button
- Equals button
- Calculation display
- Basic error handling such as division by zero
- Responsive button layout
2. Calculator App Flow
Start Application
↓
Display Calculator Screen
↓
User Presses Number
↓
Update Display
↓
User Selects Operator
↓
Store First Number
↓
User Enters Second Number
↓
Press "="
↓
Perform Calculation
↓
Display Result
3. Technologies Used
| Technology | Purpose |
|---|
| Flutter | Build the application UI |
| Dart | Programming language and calculation logic |
| MaterialApp | Application-level Material Design structure |
| Scaffold | Basic application screen structure |
| Column | Arrange widgets vertically |
| GridView | Create calculator button layout |
| ElevatedButton | Create interactive calculator buttons |
| Text | Display numbers and results |
| StatefulWidget | Manage changing calculator state |
| setState() | Update the UI after state changes |
4. Why Use StatefulWidget?
A calculator has data that changes while the application is running. For example, the display changes when the user presses a number, the selected operator changes, and the result changes after a calculation.
Flutter's StatefulWidget is designed for UI whose state can change during its lifetime. The mutable state is stored in the associated State object. :contentReference[oaicite:1]{index=1}
Example
class CalculatorPage extends StatefulWidget {
const CalculatorPage({super.key});
@override
State createState() => _CalculatorPageState();
}
class _CalculatorPageState extends State {
String display = '0';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(display),
),
);
}
}
5. Create a New Flutter Project
Open a terminal and create a new Flutter project.
flutter create calculator_app
Move into the project directory:
cd calculator_app
Run the application:
flutter run
6. Flutter Project Structure
calculator_app/
├── android/
├── ios/
├── lib/
│ └── main.dart
├── test/
├── web/
├── pubspec.yaml
└── README.md
For this beginner project, most of the calculator implementation can be placed inside:
lib/main.dart
7. Import Flutter Material Package
Start the application by importing Flutter's Material package.
import 'package:flutter/material.dart';
This provides commonly used Material widgets such as MaterialApp, Scaffold, Text, Column, and buttons.
8. Create the main() Function
The main() function is the entry point of a Dart application.
void main() {
runApp(const CalculatorApp());
}
The runApp() function places the root widget into the Flutter widget tree. :contentReference[oaicite:2]{index=2}
9. Create the Root CalculatorApp Widget
class CalculatorApp extends StatelessWidget {
const CalculatorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Calculator',
home: const CalculatorPage(),
);
}
}
Important Properties
| Property | Purpose |
|---|
| debugShowCheckedModeBanner | Removes the debug banner |
| title | Defines application title information |
| home | Defines the first screen |
10. Create the Calculator Screen
The calculator screen can be implemented as a StatefulWidget.
class CalculatorPage extends StatefulWidget {
const CalculatorPage({super.key});
@override
State createState() => _CalculatorPageState();
}
Now create the associated state class:
class _CalculatorPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Calculator'),
),
body: const Center(
child: Text('Calculator'),
),
);
}
}
11. Understand Calculator State
The calculator needs to remember several values while the user interacts with it.
Required State Variables
String display = '0';
double? firstNumber;
String? operator;
bool shouldResetDisplay = false;
| Variable | Purpose |
|---|
| display | Current value shown on screen |
| firstNumber | Stores the first number of an operation |
| operator | Stores +, -, ×, or ÷ |
| shouldResetDisplay | Determines whether the next number should replace the display |
12. Create the Calculator Display
The display is the area where the entered number and calculation result are shown.
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
alignment: Alignment.bottomRight,
child: Text(
display,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
)
Why Use double.infinity?
double.infinity allows the display container to use the available horizontal space.
13. Create Calculator Buttons
A calculator contains multiple buttons. A grid is useful for arranging these buttons.
GridView.count(
crossAxisCount: 4,
children: [
calculatorButton('7'),
calculatorButton('8'),
calculatorButton('9'),
calculatorButton('÷'),
],
)
With four columns, calculator buttons can be arranged in a familiar calculator layout.
14. Calculator Button Layout
┌─────┬─────┬─────┬─────┐
│ C │ ⌫ │ % │ ÷ │
├─────┼─────┼─────┼─────┤
│ 7 │ 8 │ 9 │ × │
├─────┼─────┼─────┼─────┤
│ 4 │ 5 │ 6 │ − │
├─────┼─────┼─────┼─────┤
│ 1 │ 2 │ 3 │ + │
├─────┼─────┼─────┼─────┤
│ 0 │ . │ = │ │
└─────┴─────┴─────┴─────┘
15. Create a Reusable Calculator Button
Instead of writing the same button code repeatedly, create a reusable method.
Widget calculatorButton(String text) {
return Padding(
padding: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: () {
handleButtonPress(text);
},
child: Text(
text,
style: const TextStyle(fontSize: 24),
),
),
);
}
This makes the code shorter and easier to maintain.
16. Handle Number Button Presses
When the user presses a number, the number should be added to the display.
void inputNumber(String number) {
setState(() {
if (display == '0' || shouldResetDisplay) {
display = number;
shouldResetDisplay = false;
} else {
display += number;
}
});
}
Calling setState() tells Flutter that the state has changed and the UI should be rebuilt. :contentReference[oaicite:3]{index=3}
17. Example Number Input
Suppose the display initially contains:
0
The user presses:
5
The display becomes:
5
If the user then presses:
2
The display becomes:
52
18. Handle Decimal Numbers
The calculator should prevent multiple decimal points in the same number.
void inputDecimal() {
setState(() {
if (shouldResetDisplay) {
display = '0.';
shouldResetDisplay = false;
return;
}
if (!display.contains('.')) {
display += '.';
}
});
}
Example
5 → 5. → 5.2
If the user presses the decimal button again, the calculator does not add another decimal point.
19. Handle Operators
When an operator is pressed, store the current number and selected operator.
void selectOperator(String selectedOperator) {
setState(() {
firstNumber = double.tryParse(display);
operator = selectedOperator;
shouldResetDisplay = true;
});
}
Example
Input: 25
Press: +
Store:
firstNumber = 25
operator = +
20. Perform the Calculation
When the equals button is pressed, the calculator uses the stored first number, the selected operator, and the current display value.
void calculateResult() {
if (firstNumber == null || operator == null) {
return;
}
final secondNumber = double.tryParse(display);
if (secondNumber == null) {
return;
}
double result;
switch (operator) {
case '+':
result = firstNumber! + secondNumber;
break;
case '-':
result = firstNumber! - secondNumber;
break;
case '×':
result = firstNumber! * secondNumber;
break;
case '÷':
if (secondNumber == 0) {
display = 'Error';
return;
}
result = firstNumber! / secondNumber;
break;
default:
return;
}
setState(() {
display = formatResult(result);
firstNumber = null;
operator = null;
shouldResetDisplay = true;
});
}
21. Format the Result
Dart's double values can sometimes display unnecessary decimal zeros. A formatting method can make the calculator display cleaner.
String formatResult(double value) {
if (value == value.toInt()) {
return value.toInt().toString();
}
return value.toString();
}
Example
| Calculation Result | Display |
|---|
| 5.0 | 5 |
| 10.0 | 10 |
| 5.5 | 5.5 |
| 12.25 | 12.25 |
22. Handle Clear Button
The clear button resets the calculator to its initial state.
void clearCalculator() {
setState(() {
display = '0';
firstNumber = null;
operator = null;
shouldResetDisplay = false;
});
}
Example
Before:
125 + 50
Press C
After:
0
23. Handle Delete Button
The delete button can remove the last character from the display.
void deleteLastCharacter() {
setState(() {
if (display.length <= 1 || display == 'Error') {
display = '0';
} else {
display = display.substring(0, display.length - 1);
}
});
}
Example
1234
Press delete
123
24. Handle All Button Types
A central method can determine which action should be performed for each button.
void handleButtonPress(String value) {
if (value == 'C') {
clearCalculator();
} else if (value == '⌫') {
deleteLastCharacter();
} else if (value == '.') {
inputDecimal();
} else if (value == '=') {
calculateResult();
} else if (['+', '-', '×', '÷'].contains(value)) {
selectOperator(value);
} else {
inputNumber(value);
}
}
25. Complete Calculator App Code
The following is a complete beginner-friendly calculator implementation using Flutter and Dart.
import 'package:flutter/material.dart';
void main() {
runApp(const CalculatorApp());
}
class CalculatorApp extends StatelessWidget {
const CalculatorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Calculator',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
),
useMaterial3: true,
),
home: const CalculatorPage(),
);
}
}
class CalculatorPage extends StatefulWidget {
const CalculatorPage({super.key});
@override
State createState() => _CalculatorPageState();
}
class _CalculatorPageState extends State {
String display = '0';
double? firstNumber;
String? operator;
bool shouldResetDisplay = false;
void inputNumber(String number) {
setState(() {
if (display == '0' || shouldResetDisplay) {
display = number;
shouldResetDisplay = false;
} else {
display += number;
}
});
}
void inputDecimal() {
setState(() {
if (shouldResetDisplay) {
display = '0.';
shouldResetDisplay = false;
return;
}
if (!display.contains('.')) {
display += '.';
}
});
}
void selectOperator(String selectedOperator) {
setState(() {
firstNumber = double.tryParse(display);
operator = selectedOperator;
shouldResetDisplay = true;
});
}
void calculateResult() {
if (firstNumber == null || operator == null) {
return;
}
final secondNumber = double.tryParse(display);
if (secondNumber == null) {
return;
}
double result;
switch (operator) {
case '+':
result = firstNumber! + secondNumber;
break;
case '-':
result = firstNumber! - secondNumber;
break;
case '×':
result = firstNumber! * secondNumber;
break;
case '÷':
if (secondNumber == 0) {
setState(() {
display = 'Error';
firstNumber = null;
operator = null;
shouldResetDisplay = true;
});
return;
}
result = firstNumber! / secondNumber;
break;
default:
return;
}
setState(() {
display = formatResult(result);
firstNumber = null;
operator = null;
shouldResetDisplay = true;
});
}
String formatResult(double value) {
if (value == value.toInt()) {
return value.toInt().toString();
}
return value.toString();
}
void clearCalculator() {
setState(() {
display = '0';
firstNumber = null;
operator = null;
shouldResetDisplay = false;
});
}
void deleteLastCharacter() {
setState(() {
if (display.length <= 1 || display == 'Error') {
display = '0';
} else {
display = display.substring(
0,
display.length - 1,
);
}
});
}
void handleButtonPress(String value) {
if (value == 'C') {
clearCalculator();
} else if (value == '⌫') {
deleteLastCharacter();
} else if (value == '.') {
inputDecimal();
} else if (value == '=') {
calculateResult();
} else if (['+', '-', '×', '÷'].contains(value)) {
selectOperator(value);
} else {
inputNumber(value);
}
}
Widget calculatorButton(String value) {
return Padding(
padding: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: () => handleButtonPress(value),
child: Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
);
}
@override
Widget build(BuildContext context) {
final buttons = [
'C', '⌫', '%', '÷',
'7', '8', '9', '×',
'4', '5', '6', '-',
'1', '2', '3', '+',
'0', '.', '=', '',
];
return Scaffold(
appBar: AppBar(
title: const Text('Calculator'),
centerTitle: true,
),
body: SafeArea(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
alignment: Alignment.bottomRight,
child: Text(
display,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
),
),
Expanded(
flex: 5,
child: GridView.count(
crossAxisCount: 4,
padding: const EdgeInsets.all(8),
children: buttons.map((button) {
if (button.isEmpty) {
return const SizedBox();
}
return calculatorButton(button);
}).toList(),
),
),
],
),
),
);
}
}
26. Understanding the Complete Code
MaterialApp
MaterialApp provides the main Material Design application structure.
Scaffold
Scaffold provides the basic screen structure containing the app bar and body.
SafeArea
SafeArea helps keep content away from areas such as system UI cutouts and status/navigation regions.
Column
The Column places the calculator display above the button area.
Expanded
Expanded divides the available vertical space between the display and button grid.
GridView.count
GridView.count creates the calculator's four-column button layout.
ElevatedButton
Each calculator button is an interactive button that triggers an action when pressed.
27. Calculator Calculation Examples
| Input | Operation | Result |
|---|
| 5 + 3 | Addition | 8 |
| 10 - 4 | Subtraction | 6 |
| 6 × 7 | Multiplication | 42 |
| 20 ÷ 5 | Division | 4 |
| 2.5 + 3.5 | Decimal addition | 6 |
| 10 ÷ 0 | Invalid division | Error |
28. Understanding the Calculation Process
Consider the following operation:
25 + 15 = 40
Step 1: Enter 25
display = "25"
Step 2: Press +
firstNumber = 25
operator = "+"
Step 3: Enter 15
display = "15"
Step 4: Press =
secondNumber = 15
result = 25 + 15
Step 5: Display Result
display = "40"
29. Why setState() is Important
When the calculator changes its display, operator, or other mutable state, the UI needs to reflect that change. Flutter's setState() tells the framework that the state has changed and schedules the widget to rebuild. :contentReference[oaicite:4]{index=4}
Without setState()
display = '25';
The internal variable changes, but Flutter is not explicitly notified that the widget needs to rebuild.
With setState()
setState(() {
display = '25';
});
The state changes and Flutter knows that the affected UI needs to be rebuilt.
30. Widget Tree of Calculator App
MaterialApp
└── CalculatorPage
└── Scaffold
├── AppBar
│ └── Text
└── SafeArea
└── Column
├── Expanded
│ └── Container
│ └── Text
└── Expanded
└── GridView
└── ElevatedButton
└── Text
31. Improving the Calculator UI
The basic calculator can be improved with colors, rounded buttons, different button styles, and better spacing.
Custom Button Style
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.all(20),
),
onPressed: () {},
child: const Text(
'7',
style: TextStyle(fontSize: 24),
),
)
32. Different Button Types
Calculator buttons can be separated into categories.
| Button Type | Examples |
|---|
| Number | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
| Operator | +, -, ×, ÷ |
| Control | C, ⌫ |
| Decimal | . |
| Result | = |
| Advanced | %, √, ± |
33. Adding Percentage Functionality
A percentage button can convert the current value into a percentage.
void calculatePercentage() {
setState(() {
final value = double.tryParse(display);
if (value != null) {
display = formatResult(value / 100);
}
});
}
The button handler can then recognize the % symbol.
34. Adding Positive and Negative Functionality
A calculator can also provide a button for changing the sign of a number.
void toggleSign() {
setState(() {
final value = double.tryParse(display);
if (value == null || value == 0) {
return;
}
display = formatResult(value * -1);
});
}
35. Handling Division by Zero
Division by zero should be handled explicitly rather than allowing an invalid calculation to produce an unwanted result.
case '÷':
if (secondNumber == 0) {
display = 'Error';
return;
}
result = firstNumber! / secondNumber;
break;
Example
10 ÷ 0
Result:
Error
36. Improving the Code Structure
For a small learning project, placing the calculator logic inside one state class is understandable. As the project grows, separating UI and calculation logic can make the application easier to maintain.
Possible Structure
lib/
├── main.dart
├── screens/
│ └── calculator_page.dart
├── widgets/
│ └── calculator_button.dart
└── services/
└── calculator_service.dart
37. Separate Calculator Logic
The calculation logic can be moved into a separate class.
class CalculatorService {
double calculate(
double first,
double second,
String operator,
) {
switch (operator) {
case '+':
return first + second;
case '-':
return first - second;
case '×':
return first * second;
case '÷':
if (second == 0) {
throw Exception('Cannot divide by zero');
}
return first / second;
default:
throw Exception('Invalid operator');
}
}
}
This approach separates calculation logic from the UI.
38. Reusable Calculator Button Widget
A dedicated widget can make the interface cleaner.
class CalculatorButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const CalculatorButton({
super.key,
required this.label,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(5),
child: ElevatedButton(
onPressed: onPressed,
child: Text(
label,
style: const TextStyle(fontSize: 24),
),
),
);
}
}
39. Responsive Calculator Layout
The calculator should work on different screen sizes. Avoid fixed dimensions that can cause overflow on small devices.
Useful Flutter Widgets
Expanded
Flexible
SafeArea
LayoutBuilder
MediaQuery
GridView
Example
Expanded(
child: GridView.count(
crossAxisCount: 4,
children: buttons,
),
)
40. Testing the Calculator
Test the calculator with normal, boundary, and invalid inputs.
Basic Test Cases
| Test | Expected Result |
|---|
| 2 + 3 | 5 |
| 10 - 7 | 3 |
| 4 × 5 | 20 |
| 20 ÷ 4 | 5 |
| 5.5 + 2.5 | 8 |
| 10 ÷ 0 | Error |
| Clear after calculation | 0 |
| Delete last digit | Last digit removed |
| Multiple decimal presses | Only one decimal point |
41. Debugging Calculator Problems
If the calculator does not work correctly, check the state variables and button handlers first.
Common Debugging Questions
- Is the button calling the correct method?
- Is the display being updated inside
setState()?
- Is the operator stored correctly?
- Is the first number stored correctly?
- Is the second number parsed correctly?
- Is division by zero handled?
- Is the decimal point handled correctly?
- Is the UI receiving the updated display value?
42. Common Mistakes
| Mistake | Problem | Solution |
|---|
| Not using StatefulWidget | Changing calculator state becomes difficult | Use StatefulWidget for local changing state |
| Not calling setState | UI may not reflect changed state | Update state inside setState |
| Multiple decimal points | Invalid number parsing | Check contains('.') first |
| Division by zero | Invalid calculation | Handle zero explicitly |
| Very large fixed button sizes | Screen overflow | Use flexible layouts |
| Repeated button code | Harder maintenance | Create reusable button widgets/methods |
| Mixing all logic everywhere | Difficult to understand code | Separate UI and calculation logic |
43. Best Practices
- Use
StatefulWidget for calculator state that changes during interaction.
- Use
setState() when updating local state that affects the UI.
- Keep calculation logic organized.
- Create reusable calculator buttons.
- Handle invalid input.
- Handle division by zero.
- Prevent duplicate decimal points.
- Use responsive layouts.
- Test calculations with different inputs.
- Separate business logic from UI as the application grows.
44. Mini Project Enhancements
After completing the basic calculator, students can extend the project with additional functionality.
- Scientific calculator mode
- Square root
- Power calculations
- Percentage calculations
- Positive/negative toggle
- Calculation history
- Dark mode
- Light mode
- Copy result button
- Delete individual characters
- Memory buttons
- Calculation history screen
- Responsive tablet layout
- Keyboard input
- Unit conversion
45. Calculator History Feature
A history feature can store previous calculations.
Example
Calculation History
25 + 15 = 40
10 × 5 = 50
100 ÷ 4 = 25
50 - 20 = 30
Possible State
List history = [];
After calculating a result, a formatted calculation can be added to the list.
46. Learning Concepts from This Project
| Concept | What You Learn |
|---|
| Widgets | How Flutter UI is constructed |
| StatefulWidget | How changing UI state is managed |
| setState() | How state changes trigger UI updates |
| Column | Vertical layout |
| GridView | Grid-based layout |
| ElevatedButton | User interaction |
| Functions | Reusable logic |
| switch | Operator-based calculation logic |
| double | Decimal calculations |
| String | Display and input values |
| Null safety | Handling values that may be absent |
| Responsive layout | Supporting different screen sizes |
47. Practical Project Flow
Create Flutter Project
↓
Create MaterialApp
↓
Create CalculatorPage
↓
Use StatefulWidget
↓
Create Calculator State
↓
Create Display
↓
Create Button Grid
↓
Handle Number Input
↓
Handle Operators
↓
Perform Calculation
↓
Handle Clear/Delete
↓
Handle Errors
↓
Test Application
↓
Improve UI
48. Interview Questions
Q1. Why is StatefulWidget used for a calculator?
A calculator's display, selected operator, and calculation values change while the user interacts with it, so these values can be managed using a StatefulWidget.
Q2. What does setState() do?
setState() notifies Flutter that the state has changed and that the widget should rebuild to reflect the updated state. :contentReference[oaicite:5]{index=5}
Q3. Why is GridView useful in a calculator?
A calculator contains buttons arranged in rows and columns, making a grid layout suitable for the interface.
Q4. Why should calculator buttons be reusable?
A reusable button component reduces duplicated code and makes the UI easier to maintain.
Q5. How can division by zero be handled?
Check whether the second number is zero before performing division and display an appropriate error state.
Q6. Why should decimal input be validated?
Multiple decimal points can create invalid numeric strings that cannot be parsed correctly as a number.
Q7. What is the purpose of firstNumber?
It stores the first operand while the user enters the second operand.
Q8. What is the purpose of the operator variable?
It stores the selected arithmetic operation such as addition, subtraction, multiplication, or division.
49. Summary
Building a calculator app is a practical way to understand the core concepts of Flutter development. The project demonstrates how Flutter widgets can be combined to create an interactive application and how changing application state can update the user interface.
The main concepts used in this project are:
- MaterialApp
- Scaffold
- StatefulWidget
- State
- setState()
- Column
- Expanded
- GridView
- ElevatedButton
- Text
- Dart functions
- Arithmetic operators
- Conditional statements
- Switch statements
- Null safety
- Error handling
- Responsive UI design
The most important learning point is that the calculator's changing data is maintained as state, and Flutter's setState() mechanism is used to rebuild the relevant UI when that state changes. :contentReference[oaicite:6]{index=6}
50. Learn Flutter with JustAcademy
JustAcademy Flutter Training Course
Register for Flutter Course Demo