Stateful vs Stateless Widget
State adalah property dari sebuah Widget. Widget yang extends Stateless Widget tidak pernah berubah dan selalu konstan. Stateful Widget akan berubah ketika user melakukan interaksi dengannya. Interaksi user secara langsung tidak terdapat pada Stateless Widget. Untuk mengimplemetasikan StatefulWidget, berikut langkah-langkahnya (Source : Smartherd Flutter) :
Membuat Stateful Widget
Kita akan membuat sebuah aplikasi baru dari nol. Aplikasi yang akan kita buat adalah menampilkan kota favorit yang kita dapatkan dari menuliskan di dalam sebuah text field dan tulisan kota akan muncul ketika kita submit (onSubmitted), atau ketika kita mengetik nama kotanya (onChanged).
Implementasikan langkah-langkah di section sebelumnya ke dalam main.dart :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Stateful Widget Example', home: FavoriteCity(), )); } class FavoriteCity extends StatefulWidget { @override State<StatefulWidget> createState() { return _FavoriteCityState(); } } class _FavoriteCityState extends State<FavoriteCity> { String nameCity = ''; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Stateful App Example"), ), body: Container( margin: EdgeInsets.all(20.0), child: Column( children: <Widget>[ TextField( onSubmitted: (String userInput) { setState(() { nameCity = userInput; }); }, ), Padding( padding: EdgeInsets.all(30.0), child: Text( "Your Favorite city is $nameCity", style: TextStyle(fontSize: 20.0), ), ), ], ), ), ); } } |
Result :
Flutter Dropdown
Selanjutnya kita akan membuat sebuah tombol dropdown. Dropdown widgets dapat berupa kumpulan string atau tipe data lainnya. Widget ini juga memiliki property yaitu items, kemudian terdapat method toList() untuk mengubah item ke dalam list. Langsung saja kita lihat contoh pembuatannya:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Stateful Widget Example', home: FavoriteCity(), )); } class FavoriteCity extends StatefulWidget { @override State<StatefulWidget> createState() { return _FavoriteCityState(); } } class _FavoriteCityState extends State<FavoriteCity> { String nameCity = ''; var _currencies = ['Rupiah', 'Dollar', 'Pound', 'Others']; var _currentItemSelected = 'Rupiah'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Stateful App Example"), ), body: Container( margin: EdgeInsets.all(20.0), child: Column( children: <Widget>[ TextField( onChanged: (String userInput) { setState(() { nameCity = userInput; }); }, ), DropdownButton<String>( items: _currencies.map((String dropDownStringItem) { return DropdownMenuItem<String>( value: dropDownStringItem, child: Text(dropDownStringItem), ); }).toList(), onChanged: (String newValueSelected) { _onDropDownItemSelected(newValueSelected); }, value: _currentItemSelected, ), Padding( padding: EdgeInsets.all(30.0), child: Text( "Your Favorite city is $nameCity", style: TextStyle(fontSize: 20.0), ), ), ], ), ), ); } void _onDropDownItemSelected(String newValueSelected) { setState(() { this._currentItemSelected = newValueSelected; }); } } |
Interest Calculator
Selanjutnya kita akan melakukan pembuatan interest calculator, sebuah aplikasi untuk menghitung cicilan kredit. Aplikasi ini akan berisi Column widget, Image, TextField, RaisedButton, dll. Kita akan membuat aplikasi nya dari nol :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(title: 'Interest Calculator', home: SIForm())); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( child: Column( children: <Widget>[], ), ), ); } } |
Widget Fetch Image
Di aplikasi ini kita akan menampilkan image seperti pada tutorial sebelumnya. Tambahkan satu folder images dan masukkan gambar, disini saya menggunakan image money.png dari smartherd.
main.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(title: 'Interest Calculator', home: SIForm())); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: Column( children: <Widget>[ getImageAsset(), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image(image: assetImage, width: 125.0, height: 125.0,); return Container(child: image, margin: EdgeInsets.all(_minimumPadding * 10),) } } |
pubspec.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
name: aplikasi_flutter description: Sebuah Aplikasi Flutter version: 1.0.0+1 environment: sdk: ">=2.1.0 <3.0.0" dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec # The following section is specific to Flutter. flutter: assets: - images/money.png |
Membuat Text Field
TextField mempunyai beberapa property, misalnya keyboardType dan decoration seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(title: 'Interest Calculator', home: SIForm())); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: Column( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0) ) ), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0) ) ), ), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image(image: assetImage, width: 125.0, height: 125.0,); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10),) } } |
Element Column dengan Satu Row yang Berisi Dua Column
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(title: 'Interest Calculator', home: SIForm())); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: Column( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: 'Rupiah', onChanged: (String newValueSelected) {}, ), ), ], ), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } } |
Raised Button
Column juga bisa diganti menjadi ListView supaya bisa di scroll sampai bawah :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(title: 'Interest Calculator', home: SIForm())); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: ListView( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextField( keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: 'Rupiah', onChanged: (String newValueSelected) {}, ), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: RaisedButton( child: Text("Calculate"), onPressed: () {}), ), Expanded( child: RaisedButton(child: Text("Reset"), onPressed: () {}), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Text("To Do Text"), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } } |
Menambahkan Style dan Theme
Untuk mengatur theme app secara keseluruhan, Scaffold widget memiliki theme property. Property theme ini diisi dengan ThemeData widget yang berisi theme yang kita inginkan. Ini akan menyebabkan berubahnya tampilan untuk seluruh aplikasi. Tambahkan lagi color untuk bagian form dan button, sehingga hasilnya akan seperti ini :
main.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Interest Calculator', home: SIForm(), debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.indigo, accentColor: Colors.indigoAccent), )); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; @override Widget build(BuildContext context) { // TODO: implement build TextStyle textStyle = Theme.of(context).textTheme.title; return Scaffold( // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: ListView( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextField( keyboardType: TextInputType.number, style: textStyle, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: 'Rupiah', onChanged: (String newValueSelected) {}, ), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: RaisedButton( color: Theme.of(context).accentColor, textColor: Theme.of(context).primaryColorDark, child: Text( "Calculate", textScaleFactor: 1.5, ), onPressed: () {}), ), Expanded( child: RaisedButton( color: Theme.of(context).primaryColorDark, textColor: Theme.of(context).primaryColorLight, child: Text( "Reset", textScaleFactor: 1.5, ), onPressed: () {}), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Text( "To Do Text", style: textStyle, ), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } } |
Menambahkan Logic pada Aplikasi
Selanjutnya kita akan menambahkan logic supaya aplikasi bisa berjalan dan elemen dapat berinteraksi dengan pengguna aplikasi. Kita mulai dengan tombol dropdown. Saat ini, ketika kita memilih mata uang selain rupiah, dropdown tidak berubah. Untuk itu, kita akan membuat satu variabel yaitu _currentItemSelected untuk menunjukkan saat itu apa mata uang yang dipilih.
1 |
var _currentItemSelected = 'Rupiah'; |
Selanjutnya pada bagian dropdown, tambahkan value dan onchange event :
1 2 3 4 |
value: _currentItemSelected, onChanged: (String newValueSelected) { _onDropDownItemSelected(newValueSelected); }, |
Buat method baru _onDropDownItemSelected :
1 2 3 4 5 |
void _onDropDownItemSelected(newValueSelected){ setState(() { this._currentItemSelected = newValueSelected; }); } |
Jalankan aplikasi, maka sekarang value pada dropdown akan berubah sesuai mata uang yang kita pilih.
Next, kita akan mencoba untuk mengekstrak value dari TextField. Pada Flutter, terdapat satu notifier yang dinamakan TextEditingController. Buat method baru untuk menghitung nilai dan return String, kemudian atur supaya ketika tombol calculate ditekan, akan muncul hasil dari perhitungan nilai tersebut. Sehingga main.dart menjadi seperti ini :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Interest Calculator', home: SIForm(), debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.indigo, accentColor: Colors.indigoAccent), )); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; var _currentItemSelected = 'Rupiah'; TextEditingController principalController = TextEditingController(); TextEditingController roiController = TextEditingController(); TextEditingController termController = TextEditingController(); var displayResult = ''; @override Widget build(BuildContext context) { // TODO: implement build TextStyle textStyle = Theme.of(context).textTheme.title; return Scaffold( // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: ListView( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: principalController, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: roiController, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: termController, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: _currentItemSelected, onChanged: (String newValueSelected) { _onDropDownItemSelected(newValueSelected); }, ), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: RaisedButton( color: Theme.of(context).accentColor, textColor: Theme.of(context).primaryColorDark, child: Text( "Calculate", textScaleFactor: 1.5, ), onPressed: () { setState(() { this.displayResult = _calculateTotalReturns(); }); }), ), Expanded( child: RaisedButton( color: Theme.of(context).primaryColorDark, textColor: Theme.of(context).primaryColorLight, child: Text( "Reset", textScaleFactor: 1.5, ), onPressed: () {}), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Text( this.displayResult, style: textStyle, ), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } void _onDropDownItemSelected(newValueSelected) { setState(() { this._currentItemSelected = newValueSelected; }); } String _calculateTotalReturns() { double principal = double.parse(principalController.text); double roi = double.parse(roiController.text); double term = double.parse(termController.text); double totalAmountPayable = principal + (principal * roi * term) / 100; String result = 'After $term years, your investment will be worth $totalAmountPayable'; return result; } } |
Hasilnya :
Langkah berikutnya adalah memberikan functionality pada Reset button. Ketika reset button ditekan, maka form akan kembali ke data awal.
main.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Interest Calculator', home: SIForm(), debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.indigo, accentColor: Colors.indigoAccent), )); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; var _currentItemSelected = ''; @override void initState() { super.initState(); _currentItemSelected = _currencies[0]; } TextEditingController principalController = TextEditingController(); TextEditingController roiController = TextEditingController(); TextEditingController termController = TextEditingController(); var displayResult = ''; @override Widget build(BuildContext context) { // TODO: implement build TextStyle textStyle = Theme.of(context).textTheme.title; return Scaffold( // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text("Interest Calculator"), ), body: Container( margin: EdgeInsets.all(_minimumPadding * 2), child: ListView( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: principalController, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: roiController, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextField( keyboardType: TextInputType.number, style: textStyle, controller: termController, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: _currentItemSelected, onChanged: (String newValueSelected) { _onDropDownItemSelected(newValueSelected); }, ), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: RaisedButton( color: Theme.of(context).accentColor, textColor: Theme.of(context).primaryColorDark, child: Text( "Calculate", textScaleFactor: 1.5, ), onPressed: () { setState(() { this.displayResult = _calculateTotalReturns(); }); }), ), Expanded( child: RaisedButton( color: Theme.of(context).primaryColorDark, textColor: Theme.of(context).primaryColorLight, child: Text( "Reset", textScaleFactor: 1.5, ), onPressed: () { setState(() { _reset(); }); }), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Text( this.displayResult, style: textStyle, ), ), ], ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } void _onDropDownItemSelected(newValueSelected) { setState(() { this._currentItemSelected = newValueSelected; }); } String _calculateTotalReturns() { double principal = double.parse(principalController.text); double roi = double.parse(roiController.text); double term = double.parse(termController.text); double totalAmountPayable = principal + (principal * roi * term) / 100; String result = 'After $term years, your investment will be worth $totalAmountPayable $_currentItemSelected'; return result; } void _reset() { principalController.text = ''; roiController.text = ''; termController.text = ''; displayResult = ''; _currentItemSelected = _currencies[0]; } } |
Form Validation pada Flutter Menggunakan TextFormField
Sebelumnya, aplikasi yang kita buat meskipun sudah berfungsi namun masih belum bisa menampilkan pesan error ketika user salah memasukkan input. Kali ini kita akan membuat validasi dengan TextFormField dari aplikasi yang sudah kita buat.
Langkah pertama adalah mengubah Container widget menjadi Form widget dan memberikan Padding widget sebagai child dari Form widget.
Langkah berikutnya adalah memberikan GlobalKey untuk key properties pada Form widget.
Selanjutnya, ubah TextField widget menjadi TextFormField, kemudian tambahkan validator dan berikan fungsi untuk melakukan validasi.
Terakhir, berikan property ketika tombol disubmit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Interest Calculator', home: SIForm(), debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, primaryColor: Colors.indigo, accentColor: Colors.indigoAccent), )); } class SIForm extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _SIFormState(); } } class _SIFormState extends State<SIForm> { var _formKey = GlobalKey<FormState>(); var _currencies = ['Rupiah', 'Dollar', 'Pound']; final _minimumPadding = 5.0; var _currentItemSelected = ''; @override void initState() { super.initState(); _currentItemSelected = _currencies[0]; } TextEditingController principalController = TextEditingController(); TextEditingController roiController = TextEditingController(); TextEditingController termController = TextEditingController(); var displayResult = ''; @override Widget build(BuildContext context) { // TODO: implement build TextStyle textStyle = Theme.of(context).textTheme.title; return Scaffold( // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text("Interest Calculator"), ), body: Form( key: _formKey, child: Padding( padding: EdgeInsets.all(_minimumPadding * 2), child: ListView( children: <Widget>[ getImageAsset(), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextFormField( keyboardType: TextInputType.number, style: textStyle, controller: principalController, validator: (String value) { if (value.isEmpty) { return 'Please enter principal amount'; } }, decoration: InputDecoration( labelText: 'Principal', hintText: 'Entry Principal e.g 1000000', errorStyle: TextStyle( color: Colors.yellowAccent, fontSize: 15.0, ), labelStyle: textStyle, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: TextFormField( keyboardType: TextInputType.number, style: textStyle, controller: roiController, validator: (String value) { if (value.isEmpty) { return 'Please enter roi value'; } }, decoration: InputDecoration( labelText: 'Rate of Interest', hintText: 'In Percent', labelStyle: textStyle, errorStyle: TextStyle( color: Colors.yellowAccent, fontSize: 15.0, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: TextFormField( keyboardType: TextInputType.number, style: textStyle, controller: termController, validator: (String value) { if (value.isEmpty) { return 'Please enter term value'; } }, decoration: InputDecoration( labelText: 'Term', hintText: 'Time in Years', labelStyle: textStyle, errorStyle: TextStyle( color: Colors.yellowAccent, fontSize: 15.0, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5.0))), ), ), Container( width: _minimumPadding * 5, ), Expanded( child: DropdownButton<String>( items: _currencies.map((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), value: _currentItemSelected, onChanged: (String newValueSelected) { _onDropDownItemSelected(newValueSelected); }, ), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Row( children: <Widget>[ Expanded( child: RaisedButton( color: Theme.of(context).accentColor, textColor: Theme.of(context).primaryColorDark, child: Text( "Calculate", textScaleFactor: 1.5, ), onPressed: () { setState(() { if (_formKey.currentState.validate()) { this.displayResult = _calculateTotalReturns(); } }); }), ), Expanded( child: RaisedButton( color: Theme.of(context).primaryColorDark, textColor: Theme.of(context).primaryColorLight, child: Text( "Reset", textScaleFactor: 1.5, ), onPressed: () { setState(() { _reset(); }); }), ), ], ), ), Padding( padding: EdgeInsets.only( top: _minimumPadding, bottom: _minimumPadding), child: Text( this.displayResult, style: textStyle, ), ), ], ), ), ), ); } Widget getImageAsset() { AssetImage assetImage = AssetImage('images/money.png'); Image image = Image( image: assetImage, width: 125.0, height: 125.0, ); return Container( child: image, margin: EdgeInsets.all(_minimumPadding * 10), ); } void _onDropDownItemSelected(newValueSelected) { setState(() { this._currentItemSelected = newValueSelected; }); } String _calculateTotalReturns() { double principal = double.parse(principalController.text); double roi = double.parse(roiController.text); double term = double.parse(termController.text); double totalAmountPayable = principal + (principal * roi * term) / 100; String result = 'After $term years, your investment will be worth $totalAmountPayable $_currentItemSelected'; return result; } void _reset() { principalController.text = ''; roiController.text = ''; termController.text = ''; displayResult = ''; _currentItemSelected = _currencies[0]; } } |