Flutter Showcase: Basic Calculator App (Part 2)

Simple calculator app with Flutter

Yudi Setiawan
Nusanet Developers
17 min readJan 4, 2019

--

Basic Calculator App

Series

Di artikel bagian kedua ini kita akan bahas logic dari aplikasi kalkulator yang akan kita buat. Jadi, pada aplikasi kalkulator kita ini ada beberapa case yang perlu kita tangani dan berikut adalah case-nya.

Case 1

Pada case ini pengguna melakukan perhitungan yang sederhana saja dimana, si pengguna pertama kali input bilangan pertama lalu, input operator dan selanjutnya input bilangan kedua dan tap button sama dengan. Pertama-tama kita perlu buat variable integer sebagai penyimpan bilangan pertama dan kedua seperti berikut.

int valueA;
int valueB;

Lalu, kita tambahkan juga variable String sebagai penyimpan nilai operatornya.

String operator;

Dan juga kita tambahkan widget StringBuffer sebagai nilai yang akan tampil di widget AutoSizeText

var sbValue = new StringBuffer();

Lalu, kita tempatkan nilai sbValue ke AutoSizeText seperti berikut.

AutoSizeText(
sbValue.toString(),
style: Theme.of(context).textTheme.display2,
maxLines: 1,
)

Lalu, kita juga perlu inisialisasi widget sbValue dan variable operator di method initState()

@override
void initState() {
super.initState();
sbValue.write("0");
operator = "";
}

Sedikit info bahwa method initState() akan terpanggil ketika UI-nya terbuat. Selanjutnya, kita buat method baru bernama appendValue(String str) seperti berikut.

void appendValue(String str) => setState(() {
// TODO: do something in here
});

Jadi method appendValue berfungsi untuk menambahkan nilai kedalam widget sbValue jika si pengguna tap button bilangan dan akan menyimpan nilai operator jika si pengguna tap button operator pada aplikasi kita. Berikut isi dari method tersebut untuk case 1 ini.

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
}

if (!isDoCalculate) {
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
}
}
});

Selanjutnya, coba kita panggil method appendValue didalam listener onPressed pada button berikut.

  1. /
  2. x
  3. -
  4. +
  5. =
  6. Dan button dari angka 0 s.d. 9 .

Contohnya seperti berikut pemanggilannya jika pada button perkalian.

Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"x",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("x");
},
),
)

Untuk sementara berikut kode lengkapnya.

class CalculatorAppState extends State<MainApp> {
final double _padding = 16.0;
final double _buttonFontSize = 24.0;

final Color _primarySwatchColor = Colors.orange;
final Color _titleAppBarColor = Colors.white;
final Color _buttonColorWhite = Colors.white;
final Color _buttonHighlightColor = Colors.grey[800];
final Color _buttonColorGrey = Colors.grey[500];
final Color _textColorWhite = Colors.white;

int valueA;
int valueB;
var sbValue = new StringBuffer();
String operator;

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: _primarySwatchColor),
home: Scaffold(
appBar: AppBar(
title: Text(
"Basic Calculator",
style: TextStyle(color: _titleAppBarColor),
),
),
body: Column(
children: <Widget>[
Expanded(
key: Key("expanded_bagian_atas"),
flex: 1,
child: Container(
key: Key("expanded_container_bagian_atas"),
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.all(_padding),
child: Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
AutoSizeText(
sbValue.toString(),
style: Theme.of(context).textTheme.display2,
maxLines: 1,
),
],
),
),
),
Expanded(
key: Key("expanded_bagian_bawah"),
flex: 1,
child: Column(
key: Key("expanded_column_bagian_bawah"),
children: <Widget>[
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 2,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"C",
style: TextStyle(
color: _primarySwatchColor,
fontSize: _buttonFontSize),
),
onPressed: () {
// TODO: do something in here
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Icon(
Icons.backspace,
color: _buttonColorGrey,
),
onPressed: () {
// TODO: do something in here
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"/",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("/");
},
),
)
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"7",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("7");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"8",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("8");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"9",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("9");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"x",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("x");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"4",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("4");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"5",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("5");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"6",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("6");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"-",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("-");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"1",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("1");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"2",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("2");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"3",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("3");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"+",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("+");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 3,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"0",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("0");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _primarySwatchColor,
highlightColor: _buttonHighlightColor,
child: Text(
"=",
style: TextStyle(
color: _textColorWhite,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("=");
},
),
),
],
),
),
],
),
),
],
),
),
);
}

@override
void initState() {
super.initState();
sbValue.write("0");
operator = "";
}

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
}

if (!isDoCalculate) {
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
}
}
});
}

Jika sudah kita tambahkan maka, selanjutnya kita test kodenya apakah sudah berjalan dengan benar pada case 1.

Oops… Case 1 masih ada salah nih.

Sepertinya masih ada yang salah nih. Jadi, salahnya adalah kita lupa tangani jika ternyata nilai awalnya 0 dan kemudian input bilangan lagi maka, seharusnya nilai 0 tadi kita clear dan masukkan bilangan yang baru. Berikut perubahannya pada method appendValue .

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
}
}
});

Sekarang coba kita test lagi. Saya rasa pasti sudah bisa kan. Ok, untuk case 1 sudah selesai kita buat. Sekarang lanjut lagi ke case 2.

Case 2

Di case 2, kita melihat bahwa jika bilangan pertama, operator, dan bilangan kedua sudah terpenuhi lalu si pengguna tap button operator lagi maka, itu akan melakukan perhitungan dan didepannya sudah ditambahkan dengan operator yang terbaru. Berikut kita tambahkan perubahannya pada method appendValue.

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
}
}
});

Sekarang coba jalankan lagi dan coba test case 2. Saya rasa sudah bisa kan.

Case 3

Di case 3 kita melihat bahwa ada button backspace yang berfungsi untuk menghapus nilai pada karakter terakhir. Sekarang coba kita buat method baru bernama deleteValue dan isi dengan kode berikut.

void deleteValue() => setState(() {
String strValue = sbValue.toString();
if (strValue.length > 0) {
String lastCharacter = strValue.substring(strValue.length - 1);
if (lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+") {
operator = "";
}
strValue = strValue.substring(0, strValue.length - 1);
sbValue.clear();
sbValue.write(strValue.length == 0 ? "0" : strValue);
}
});

Sekarang panggil method tersebut pada listener onPressed button backspace seperti berikut.

Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Icon(
Icons.backspace,
color: _buttonColorGrey,
),
onPressed: () {
deleteValue();
},
),
)

Sekarang coba jalankan lagi dan coba lakukan test case 3.

Case 4

Di case 4, kita lihat bahwa tanpa perlu hapus karakter terakhir pada operator kita bisa mengganti-ganti operatornya. Berikut perubahannya pada method appendValue

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
} else {
String strValue = sbValue.toString();
if (str == "/" || str == "x" || str == "-" || str == "+") {
sbValue.clear();
sbValue.write(strValue.substring(0, strValue.length - 1) + "" + str);
operator = str;
}
}
}
});

Sekarang coba jalankan lagi dan lakukan test case 4.

Case 5

Di case 5 bisa kita lihat bahwa belum selesai input bilangan kedua dan langsung tap button sama dengan maka, hasilnya sama dengan bilangan pertama. Berikut perubahannya pada method appendValue

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
} else {
String strValue = sbValue.toString();
String lastCharacter = strValue.substring(strValue.length - 1);
if (str == "/" || str == "x" || str == "-" || str == "+") {
sbValue.clear();
sbValue
.write(strValue.substring(0, strValue.length - 1) + "" + str);
operator = str;
} else if (str == "=" &&
(lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+")) {
sbValue.clear();
sbValue.write(strValue.substring(0, strValue.length - 1));
}
}
}
});

Sekarang coba jalankan lagi dan lakukan test case 5.

Case 6

Di case 6, kita melihat si pengguna melakukan clear nilainya. Silakan kita buat method baru bernama clearValue seperti berikut.

void clearValue() => setState(() {
operator = "";
sbValue.clear();
sbValue.write("0");
});

Dan panggil method tersebut di listener onPressed button Clear seperti berikut.

Expanded(
flex: 2,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"C",
style: TextStyle(
color: _primarySwatchColor,
fontSize: _buttonFontSize),
),
onPressed: () {
clearValue();
},
),
)

Sekarang coba jalankan lagi dan lakukan test case 6.

Berikut kode lengkapnya.

class CalculatorAppState extends State<MainApp> {
final double _padding = 16.0;
final double _buttonFontSize = 24.0;

final Color _primarySwatchColor = Colors.orange;
final Color _titleAppBarColor = Colors.white;
final Color _buttonColorWhite = Colors.white;
final Color _buttonHighlightColor = Colors.grey[800];
final Color _buttonColorGrey = Colors.grey[500];
final Color _textColorWhite = Colors.white;

int valueA;
int valueB;
var sbValue = new StringBuffer();
String operator;

@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: _primarySwatchColor),
home: Scaffold(
appBar: AppBar(
title: Text(
"Basic Calculator",
style: TextStyle(color: _titleAppBarColor),
),
),
body: Column(
children: <Widget>[
Expanded(
key: Key("expanded_bagian_atas"),
flex: 1,
child: Container(
key: Key("expanded_container_bagian_atas"),
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.all(_padding),
child: Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
AutoSizeText(
sbValue.toString(),
style: Theme.of(context).textTheme.display2,
maxLines: 1,
),
],
),
),
),
Expanded(
key: Key("expanded_bagian_bawah"),
flex: 1,
child: Column(
key: Key("expanded_column_bagian_bawah"),
children: <Widget>[
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 2,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"C",
style: TextStyle(
color: _primarySwatchColor,
fontSize: _buttonFontSize),
),
onPressed: () {
clearValue();
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Icon(
Icons.backspace,
color: _buttonColorGrey,
),
onPressed: () {
deleteValue();
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"/",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("/");
},
),
)
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"7",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("7");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"8",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("8");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"9",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("9");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"x",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("x");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"4",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("4");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"5",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("5");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"6",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("6");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"-",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("-");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"1",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("1");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"2",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("2");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"3",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("3");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"+",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("+");
},
),
),
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 3,
child: RaisedButton(
color: _buttonColorWhite,
highlightColor: _buttonHighlightColor,
child: Text(
"0",
style: TextStyle(
color: _buttonColorGrey,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("0");
},
),
),
Expanded(
flex: 1,
child: RaisedButton(
color: _primarySwatchColor,
highlightColor: _buttonHighlightColor,
child: Text(
"=",
style: TextStyle(
color: _textColorWhite,
fontSize: _buttonFontSize,
),
),
onPressed: () {
appendValue("=");
},
),
),
],
),
),
],
),
),
],
),
),
);
}

@override
void initState() {
super.initState();
sbValue.write("0");
operator = "";
}

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
} else {
String strValue = sbValue.toString();
String lastCharacter = strValue.substring(strValue.length - 1);
if (str == "/" || str == "x" || str == "-" || str == "+") {
sbValue.clear();
sbValue
.write(strValue.substring(0, strValue.length - 1) + "" + str);
operator = str;
} else if (str == "=" &&
(lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+")) {
sbValue.clear();
sbValue.write(strValue.substring(0, strValue.length - 1));
}
}
}
});

void deleteValue() => setState(() {
String strValue = sbValue.toString();
if (strValue.length > 0) {
String lastCharacter = strValue.substring(strValue.length - 1);
if (lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+") {
operator = "";
}
strValue = strValue.substring(0, strValue.length - 1);
sbValue.clear();
sbValue.write(strValue.length == 0 ? "0" : strValue);
}
});

void clearValue() => setState(() {
operator = "";
sbValue.clear();
sbValue.write("0");
});
}

Worst Case

Setelah kita buat program diatas ternyata masih ada menghasilkan bug seperti berikut.

Bug 1
Bug 2
Bug 3

Sekarang mari kita selesaikan satu persatu bug tersebut.

Bug 1

Silakan kita tambahkan pengecekan bahwa jika nilai yang diinput merupakan 0 dan nilai pada widget AutoSizeText juga 0 maka, jangan masukkan nilai tersebut. Silakan tambahkan pengecekannya seperti berikut pada method appendValue .

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "0" && sbValue.toString() == "0") {
return;
} else if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}
...
)};

Sekarang coba lakukan test case bug 1.

Bug 2

Sepertinya kita ada kelupaan sesuatu yaitu kita harus set nilai variable operator menjadi empty string ketika menambahkan case 6. Berikut perubahannya pada method appendValue .

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
if (str == "0" && sbValue.toString() == "0") {
return;
} else if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
} else {
String strValue = sbValue.toString();
String lastCharacter = strValue.substring(strValue.length - 1);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = "";
sbValue.clear();
sbValue
.write(strValue.substring(0, strValue.length - 1) + "" + str);
operator = str;
} else if (str == "=" &&
(lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+")) {
operator = "";
sbValue.clear();
sbValue.write(strValue.substring(0, strValue.length - 1));
}
}
}
});

Sekarang coba lakukan test case bug 2.

Bug 3

Untuk bug 3 konsepnya hampir sama pada bug 1 kita tinggal tambahkan saja pengkondisian jika setelah karakter operator itu tidak boleh ada inputan bilangan 0. Berikut perubahannya pada method appendValue .

void appendValue(String str) => setState(() {
bool isDoCalculate = false;
String strValue = sbValue.toString();
String lastCharacter = strValue.substring(strValue.length - 1);
if (str == "0" &&
(lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+")) {
return;
} else if (str == "0" && sbValue.toString() == "0") {
return;
} else if (str == "=") {
isDoCalculate = true;
} else if (str == "/" || str == "x" || str == "-" || str == "+") {
if (operator.isEmpty) {
operator = str;
} else {
isDoCalculate = true;
}
}

if (!isDoCalculate) {
if (sbValue.toString() == "0" && str != "0") {
sbValue.clear();
}
sbValue.write(str);
} else {
List<String> values = sbValue.toString().split(operator);
if (values.length == 2 &&
values[0].isNotEmpty &&
values[1].isNotEmpty) {
valueA = int.parse(values[0]);
valueB = int.parse(values[1]);
sbValue.clear();
int total = 0;
switch (operator) {
case "/":
total = valueA ~/ valueB;
break;
case "x":
total = valueA * valueB;
break;
case "-":
total = valueA - valueB;
break;
case "+":
total = valueA + valueB;
}
sbValue.write(total);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = str;
sbValue.write(str);
} else {
operator = "";
}
} else {
String strValue = sbValue.toString();
String lastCharacter = strValue.substring(strValue.length - 1);
if (str == "/" || str == "x" || str == "-" || str == "+") {
operator = "";
sbValue.clear();
sbValue
.write(strValue.substring(0, strValue.length - 1) + "" + str);
operator = str;
} else if (str == "=" &&
(lastCharacter == "/" ||
lastCharacter == "x" ||
lastCharacter == "-" ||
lastCharacter == "+")) {
operator = "";
sbValue.clear();
sbValue.write(strValue.substring(0, strValue.length - 1));
}
}
}
});

Sekarang coba test case bug 3.

Kesimpulan

Jadi, pada artikel dua ini kita fokus ke logic dari aplikasi kalkulator yang kita buat. Dan buat semaksimal mungkin agar tidak ada case terlewati yang mengakibatkan menjadi bug. Logic berikut merupakan hasil pemikiran saya jadi, jika ada kode yang lebih baik lagi maka, silakan dikomentari ya. Dan berikut kode lengkapnya.

--

--