Building Tip Calculator App in Flutter

Sardor Islomov
Flutter Community
Published in
8 min readApr 19, 2021

Cross-platform development is becoming more popular these days. It decreases development time and cost. React Native, Flutter, Xamarin are popular cross-platform frameworks and libraries in the market

Flutter is an open source cross-platform framework from Google. Flutter is based on Dart language and it is similar to Java and Swift languages, that is why most of the native developers can easily switch to Flutter.

You’ll build a small tip calculator app in Flutter. In the process you’ll learn:

  • Flutter widgets
  • Role of build() and setState() methods
  • Stateless and Stateful widgets
  • Hot reload

You need to have a basic Dart and Flutter knowledge for this tutorial.

Final app of the tip calculator should look like this. Simple inputs for calculating tip and text widget for displaying total bill

Getting Started

You can download the project by going to my github page.

This tutorial will use Android Studio as a main IDEA. You can use any IDEA which supports Flutter SDK and Dart language.

You can open starter project in Android Studio by clicking Open an Existing Project

Inside Android Studio, go to the terminal and write a command flutter pub get. This will get all packages you need for the project

Project structure should look like this.

You mainly work with home_page.dart file.

To build and run the project, go to Run ▸ Run ‘main.dart’

Build and run the project.

Project shows AppBar and a text “Home Page”. In this article, you’ll add 4 widgets which will calculate tips for a given bill and display it on the screen.

Understanding Widgets

In Flutter, everything is a widget. Flutter divides widgets into three categories:

  • Stateful widget and State, mutable widget, that Flutter can create multiple times in the widget tree.
  • Stateless widget, immutable widget, that Flutter can create once in the widget tree.
  • Inherited widget, a widget which depends on environment state and can be read by its descendant widgets

Widget tree is how developers create their user interface. Image below is an example of a widget tree.

Stateless widget

Stateless widget does not recreate or redraw itself when the app is active. If you want to change the state of the widget then you have to create a new instance of the widget with new parameters.

Stateful Widget

Stateful widget has multiple States and you can change the current state using setState() method of State object.

One of the methods of Stateless widget and State are build(BuildContext context). This method is responsible for drawing widgets on the screen and returns a widget object to the widget tree. build() method is one of the lifecycle methods of Stateful widget.

BuildContext is the class which helps you to identify a widget in the widget tree. Each widget has its own BuildContext, it has a set of methods which you can use it in Statelesswidget.build and State.build methods.

You can read more about Flutter widget and their lifecycle in the official page of Flutter.

Adding TextField and Text Widgets

Remove Text(“Home Page”) widget and add a TextField and Text widgets vertically in the _HomePageState.

@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
TextField(),
TextField(),
Text("0"),
Text("0"),
],
),
);
}

You can use the TextField widget to collect user inputs via keyboard. You can use the Text widget to display a text on the screen. To position each widget vertically, you can use the Column widget.

Build and run, the app will display two empty inputs and two “0” texts vertically.

In order to increase development time, you can use hot reload.

Flutter Builds

Flutter supports Hot reload functionality. Hot reload loads newly changed code right into Virtual Machine or VM while saving current state.

To test it, you can change the AppBar title from “TipMe” to “TipYou” in the main.dart file.

appBar: AppBar(
title: Text('TipYou'),
),

You can click Run ▸ Flutter Hot Reload to start Hot reload

Within a couple of seconds you can see your changes in the app.

Additional Properties for TextField and Text Widgets

Now, you need to initialize some properties of widgets as global variables, to access them everywhere in the _HomePageState class.

//1TextEditingController _billController;
TextEditingController _tipController;
String _tipAmountText;
String _totalAmountText;
@override
void initState() {
super.initState();
//2
_billController = new TextEditingController(text: "0");
_tipController = new TextEditingController(text: "15");
_tipAmountText = "Tip: \$0.00";
_totalAmountText = "Total: \$0.00";
}
  1. TextField has a property called controller, it controls the text being edited. _tipAmountText and _totalAmountText are variables for Text widgets

2. You’ll initialize controllers in the initState() of State object. TextEditingController’s first parameter is a text and it will be default value for TextField

Note: “$” sign is a special character which is responsible for displaying variables in string in Dart. If you want to use $ sign in your string, then you need to banalize them with the “\” character.

Next, you need to assign global variables to a TextField and Text widgets. Add other properties to widgets such as: hint, keyboardType, textAlignment, callbacks.

// 1
void _calculateTip(){

}
@override
Widget build(BuildContext context) {
return new Container(
// 2
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 3
TextField(
decoration: InputDecoration(labelText: 'Bill \$'),
controller: _billController,
keyboardType: TextInputType.number,
onChanged: (text) {
_calculateTip();
},
),
TextField(
decoration: InputDecoration(labelText: 'Tip %'),
controller: _tipController,
keyboardType: TextInputType.number,
onChanged: (text) {
_calculateTip();
},
),
// 4
Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Text(
_tipAmountText,
textAlign: TextAlign.end,
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Text(
_totalAmountText,
textAlign: TextAlign.end,
),
)
],
),
);
}

1. _calculateTip() is the method that calculates tip and total bill.

2. You can’t use numerical values for padding and margins. You have to wrap them into EdgeInsets objects.

Most of the time, developers use two methods of EdgeInsets:

EdgeInsets.all(5) — same spacing from all sides.

EdgeInsets.fromLTRB(1,2,3,4) — custom spacing for all sides.

3. Properties of TextField widget.

decoration, InputDecoration is responsible for the appearance of the input field. labelText works as a hint for a TextField

controller, property that controls text being edited.

keyboardType, determines what type of keyboard to open

onChanged, Flutter will trigger a closure when a user enters text into a TextField.

4. Properties of Text widget.

text, first property in a Text widget, it displays actual text on the screen.

textAlign, property that aligns text in the right, left or center.

You can learn more about other properties of a TextField and Text widgets in the official docs of Flutter.

Build and run. Now, you’ll see that the TextField widgets show hints and take as an input only numerical values.

Adding Interactivity to Widgets

You’ll implement _calculateTip() method. As you remember, _calculateTip() is called every time when a user enters a new input into the TextField.

void _calculateTip() {
// 1
String billTxt = _billController.text;
String tipTxt = _tipController.text;
// 2
billTxt = billTxt.replaceAll(new RegExp(r"[^0-9]"), "");
tipTxt = tipTxt.replaceAll(new RegExp(r"[^0-9]"), "");

// 3
int bill = int.parse(billTxt.isNotEmpty
? billTxt : "0");
int tip = int.parse(tipTxt.isNotEmpty
? tipTxt : "0");
// 4
String finalTipAmount = "0";
String totalBill = "0";

// 5
if (tip <= 100 && tip>=0 && bill>=0) {
double tipAmount = (bill * (tip/100));
finalTipAmount = tipAmount.toStringAsFixed(2);
totalBill = (bill + tipAmount).toStringAsFixed(2);
}
// 6
setState(() {
_tipAmountText = "Tip: \$$finalTipAmount";
_totalAmountText = "Total: \$$totalBill";
});

}

Here is what is going on in the calculate method:

  1. Retrieve current values of TextFields widget.
  2. Remove unnecessary symbols from text with method replaceAll(new RegExp(r”[0-9]”), “”).
  3. To work well with division and addition, parse string into integer,
  4. tipAmount and totalBill are final texts for UI
  5. if the condition is true then calculate tip and total bill. Method toStringAsFixed(2) returns a string representation of double with 2 decimal digits after point.

6. setState() takes as an argument closure which will assign final results with global variables. After calling setState() method, Flutter redraws UI and calls build() method again.

Finally, you need to clear the TextEditingController from the memory. You can clear all that resources in the lifecycle method of _HomePageState class which is dispose(). Flutter will trigger the dispose() method of a State object when a widget is permanently removed from the widget tree.

@override
void dispose() {
if(_billController != null && _tipController != null){
_billController.dispose();
_tipController.dispose();
}
super.dispose();
}

dispose() method of TextEditingController will clear all the resources related to the controller.

Build and run. You can see a working app which calculates tip percentage and total bill.

Congratulations! You just created a fully functional TipMe app.

Where to Go from Here?

You can download the completed project files by going to my github page button at the top or bottom of the tutorial.

You can expand your knowledge about Flutter further by learning widgets in the official page of Flutter.

I hope you enjoyed this tutorial, and if you have any questions or comments, please comment below.

--

--