Popular Searches
Popular Course Categories
Popular Courses

Build a Flutter Calculator App

Build a Flutter Calculator App

Flutter Practical Projects


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














TechnologyPurpose
FlutterBuild the application UI
DartProgramming language and calculation logic
MaterialAppApplication-level Material Design structure
ScaffoldBasic application screen structure
ColumnArrange widgets vertically
GridViewCreate calculator button layout
ElevatedButtonCreate interactive calculator buttons
TextDisplay numbers and results
StatefulWidgetManage 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







PropertyPurpose
debugShowCheckedModeBannerRemoves the debug banner
titleDefines application title information
homeDefines 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;







VariablePurpose
displayCurrent value shown on screen
firstNumberStores the first number of an operation
operatorStores +, -, ×, or ÷
shouldResetDisplayDetermines 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 ResultDisplay
5.05
10.010
5.55.5
12.2512.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










InputOperationResult
5 + 3Addition8
10 - 4Subtraction6
6 × 7Multiplication42
20 ÷ 5Division4
2.5 + 3.5Decimal addition6
10 ÷ 0Invalid divisionError



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 TypeExamples
Number0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Operator+, -, ×, ÷
ControlC, ⌫
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













TestExpected Result
2 + 35
10 - 73
4 × 520
20 ÷ 45
5.5 + 2.58
10 ÷ 0Error
Clear after calculation0
Delete last digitLast digit removed
Multiple decimal pressesOnly 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











MistakeProblemSolution
Not using StatefulWidgetChanging calculator state becomes difficultUse StatefulWidget for local changing state
Not calling setStateUI may not reflect changed stateUpdate state inside setState
Multiple decimal pointsInvalid number parsingCheck contains('.') first
Division by zeroInvalid calculationHandle zero explicitly
Very large fixed button sizesScreen overflowUse flexible layouts
Repeated button codeHarder maintenanceCreate reusable button widgets/methods
Mixing all logic everywhereDifficult to understand codeSeparate 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
















ConceptWhat You Learn
WidgetsHow Flutter UI is constructed
StatefulWidgetHow changing UI state is managed
setState()How state changes trigger UI updates
ColumnVertical layout
GridViewGrid-based layout
ElevatedButtonUser interaction
FunctionsReusable logic
switchOperator-based calculation logic
doubleDecimal calculations
StringDisplay and input values
Null safetyHandling values that may be absent
Responsive layoutSupporting 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


whatsapp