Membuat aplikasi keuangan dengan flutter

Membuat aplikasi keuangan dengan flutter

Flutter PHP

Membuat aplikasi keuangan dengan flutter

Dalam bab ini, kita akan mempelajari cara menulis aplikasi seluler lengkap, cost_calculator. Tujuan dari cost_calculator adalah untuk menyimpan informasi pengeluaran kami. Fitur lengkap dari aplikasi ini adalah sebagai berikut –

  • Daftar biaya.
  • Formulir untuk memasukkan biaya baru.
  • Pilihan untuk mengedit / menghapus biaya yang ada.
  • Total pengeluaran pada setiap contoh.

Kami akan memprogram aplikasi pengeluaran_kalculator menggunakan fitur-fitur canggih kerangka kerja Flutter yang disebutkan di bawah ini.

  • Penggunaan lanjutan dari ListView untuk menampilkan daftar pengeluaran.
  • Pemrograman formulir.
  • Pemrograman database SQLite untuk menyimpan pengeluaran kami.
  • scoped_model state management to simplify our programming.

Let us start programming the expense_calculator application.

  • Create a new Flutter application, expense_calculator in Android studio.
  • Open pubspec.yaml and add package dependencies.

dependencies:

flutter:

sdk: flutter

sqflite: ^1.1.0

path_provider: ^0.5.0+1

scoped_model: ^1.0.1

intl: any

  • Observe these points here −
    • sqflite is used for SQLite database programming.
    • path_provider is used to get system specific application path.
    • scoped_model is used for state management.
    • intl is used for date formatting.
  • Android studio will display the following alert that the pubspec.yaml is updated.

Aplikasi Lanjutan Penulisan Peringatan

  • Click Get dependencies option. Android studio will get the package from Internet and properly configure it for the application.
  • Remove the existing code in main.dart.
  • Add new file, Expense.dart to create Expense class. Expense class will have the below properties and methods.
    • property: id − Unique id to represent an expense entry in SQLite database.
    • property: amount − Amount spent.
    • property: date − Date when the amount is spent.
    • property: category − Category represents the area in which the amount is spent. e.g Food, Travel, etc.,
    • formattedDate − Used to format the date property
    • fromMap − Used to map the field from database table to the property in the expense object and to create a new expense object.

factory Expense.fromMap(Map<String, dynamic> data) {

return Expense(

data[‘id’],

data[‘amount’],

DateTime.parse(data[‘date’]),

data[‘category’]

);

}

    • toMap − Used to convert the expense object to Dart Map, which can be further used in database programming

Map<String, dynamic> toMap() => {

“id” : id,

“amount” : amount,

“date” : date.toString(),

“category” : category,

};

    • columns − Static variable used to represent the database field.
  • Enter and save the following code into the Expense.dart file.

import ‘package:intl/intl.dart’; class Expense {

final int id;

final double amount;

final DateTime date;

final String category;

String get formattedDate {

var formatter = new DateFormat(‘yyyy-MM-dd’);

return formatter.format(this.date);

}

static final columns = [‘id’, ‘amount’, ‘date’, ‘category’];

Expense(this.id, this.amount, this.date, this.category);

factory Expense.fromMap(Map<String, dynamic> data) {

return Expense(

data[‘id’],

data[‘amount’],

DateTime.parse(data[‘date’]), data[‘category’]

);

}

Map<String, dynamic> toMap() => {

“id” : id,

“amount” : amount,

“date” : date.toString(),

“category” : category,

};

}

  • The above code is simple and self explanatory.
  • Add new file, Database.dart to create SQLiteDbProvider class. The purpose of the SQLiteDbProvider class is as follows −
    • Get all expenses available in the database using getAllExpenses method. It will be used to list all the user’s expense information.

Future<List<Expense>> getAllExpenses() async {

final db = await database;

 

List<Map> results = await db.query(

“Expense”, columns: Expense.columns, orderBy: “date DESC”

);

List<Expense> expenses = new List();

results.forEach((result) {

Expense expense = Expense.fromMap(result);

expenses.add(expense);

});

return expenses;

}

    • Get a specific expense information based on expense identity available in the database using getExpenseById method. It will be used to show the particular expense information to the user.

Future<Expense> getExpenseById(int id) async {

final db = await database;

var result = await db.query(“Expense”, where: “id = “, whereArgs: [id]);

 

return result.isNotEmpty ?

Expense.fromMap(result.first) : Null;

}

    • Get the total expenses of the user using getTotalExpense method. It will be used to show the current total expense to the user.

Future<double> getTotalExpense() async {

final db = await database;

List<Map> list = await db.rawQuery(

“Select SUM(amount) as amount from expense”

);

return list.isNotEmpty ? list[0][“amount”] : Null;

}

    • Add new expense information into the database using insert method. It will be used to add new expense entry into the application by the user.

Future<Expense> insert(Expense expense) async {

final db = await database;

var maxIdResult = await db.rawQuery(

“SELECT MAX(id)+1 as last_inserted_id FROM Expense”

);

var id = maxIdResult.first[“last_inserted_id”];

var result = await db.rawInsert(

“INSERT Into Expense (id, amount, date, category)”

” VALUES (?, ?, ?, ?)”, [

id, expense.amount, expense.date.toString(), expense.category

]

);

return Expense(id, expense.amount, expense.date, expense.category);

}

    • Update existing expense information using update method. It will be used to edit and update existing expense entry available in the system by the user.

update(Expense product) async {

final db = await database;

 

var result = await db.update(“Expense”, product.toMap(),

where: “id = ?”, whereArgs: [product.id]);

return result;

}

    • Delete existing expense information using delete method. It will be used remove the existing expense entry available in the system by the user.

delete(int id) async {

final db = await database;

db.delete(“Expense”, where: “id = ?”, whereArgs: [id]);

}

  • The complete code of the SQLiteDbProvider class is as follows −

import ‘dart:async’;

import ‘dart:io’;

import ‘package:path/path.dart’;

import ‘package:path_provider/path_provider.dart’;

import ‘package:sqflite/sqflite.dart’;

import ‘Expense.dart’;

class SQLiteDbProvider {

SQLiteDbProvider._();

static final SQLiteDbProvider db = SQLiteDbProvider._();

 

static Database _database; Future<Database> get database async {

if (_database != null)

return _database;

_database = await initDB();

return _database;

}

initDB() async {

Directory documentsDirectory = await getApplicationDocumentsDirectory();

String path = join(documentsDirectory.path, “ExpenseDB2.db”);

return await openDatabase(

path, version: 1, onOpen:(db){}, onCreate: (Database db, int version) async {

await db.execute(

“CREATE TABLE Expense (

“”id INTEGER PRIMARY KEY,” “amount REAL,” “date TEXT,” “category TEXT””

)

“);

await db.execute(

“INSERT INTO Expense (‘id’, ‘amount’, ‘date’, ‘category’)

values (?, ?, ?, ?)”,[1, 1000, ‘2019-04-01 10:00:00’, “Food”]

);

/*await db.execute(

“INSERT INTO Product (‘id’, ‘name’, ‘description’, ‘price’, ‘image’)

values (?, ?, ?, ?, ?)”, [

2, “Pixel”, “Pixel is the most feature phone ever”, 800, “pixel.png”

]

);

await db.execute(

“INSERT INTO Product (‘id’, ‘name’, ‘description’, ‘price’, ‘image’)

values (?, ?, ?, ?, ?)”, [

3, “Laptop”, “Laptop is most productive development tool”, 2000, “laptop.png”

]

);

await db.execute(

“INSERT INTO Product (‘id’, ‘name’, ‘description’, ‘price’, ‘image’)

values (?, ?, ?, ?, ?)”, [

4, “Tablet”, “Laptop is most productive development tool”, 1500, “tablet.png”

]

);

await db.execute(

“INSERT INTO Product (‘id’, ‘name’, ‘description’, ‘price’, ‘image’)

values (?, ?, ?, ?, ?)”, [

5, “Pendrive”, “iPhone is the stylist phone ever”, 100, “pendrive.png”

]

);

await db.execute(

“INSERT INTO Product (‘id’, ‘name’, ‘description’, ‘price’, ‘image’)

values (?, ?, ?, ?, ?)”, [

6, “Floppy Drive”, “iPhone is the stylist phone ever”, 20, “floppy.png”

]

); */

}

);

}

Future<List<Expense>> getAllExpenses() async {

final db = await database;

List<Map>

results = await db.query(

“Expense”, columns: Expense.columns, orderBy: “date DESC”

);

List<Expense> expenses = new List();

results.forEach((result) {

Expense expense = Expense.fromMap(result);

expenses.add(expense);

});

return expenses;

}

Future<Expense> getExpenseById(int id) async {

final db = await database;

var result = await db.query(“Expense”, where: “id = “, whereArgs: [id]);

return result.isNotEmpty ? Expense.fromMap(result.first) : Null;

}

Future<double> getTotalExpense() async {

final db = await database;

List<Map> list = await db.rawQuery(

“Select SUM(amount) as amount from expense”

);

return list.isNotEmpty ? list[0][“amount”] : Null;

}

Future<Expense> insert(Expense expense) async {

final db = await database;

var maxIdResult = await db.rawQuery(

“SELECT MAX(id)+1 as last_inserted_id FROM Expense”

);

var id = maxIdResult.first[“last_inserted_id”];

var result = await db.rawInsert(

“INSERT Into Expense (id, amount, date, category)”

” VALUES (?, ?, ?, ?)”, [

id, expense.amount, expense.date.toString(), expense.category

]

);

return Expense(id, expense.amount, expense.date, expense.category);

}

update(Expense product) async {

final db = await database;

var result = await db.update(

“Expense”, product.toMap(), where: “id = ?”, whereArgs: [product.id]

);

return result;

}

delete(int id) async {

final db = await database;

db.delete(“Expense”, where: “id = ?”, whereArgs: [id]);

}

}

  • Here,
    • database is the property to get the SQLiteDbProvider object.
    • initDB is a method used to select and open the SQLite database.
  • Create a new file, ExpenseListModel.dart to create ExpenseListModel. The purpose of the model is to hold the complete information of the user expenses in the memory and updating the user interface of the application whenever user’s expense changes in the memory. It is based on Model class from scoped_model package. It has the following properties and methods −
    • _items − private list of expenses.
    • items − getter for _items as UnmodifiableListView<Expense> to prevent unexpected or accidental changes to the list.
    • totalExpense − getter for Total expenses based on the items variable.

double get totalExpense {

double amount = 0.0;

for(var i = 0; i < _items.length; i++) {

amount = amount + _items[i].amount;

}

return amount;

}

    • load − Used to load the complete expenses from database and into the _items variable. It also calls notifyListeners to update the UI.

void load() {

Future<List<Expense>>

list = SQLiteDbProvider.db.getAllExpenses();

list.then( (dbItems) {

for(var i = 0; i < dbItems.length; i++) {

_items.add(dbItems[i]);

} notifyListeners();

});

}

    • byId − Used to get a particular expenses from _items variable.

Expense byId(int id) {

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == id) {

return _items[i];

}

}

return null;

}

    • add − Used to add a new expense item into the _items variable as well as into the database. It also calls notifyListeners to update the UI.

void add(Expense item) {

SQLiteDbProvider.db.insert(item).then((val) {

_items.add(val); notifyListeners();

});

}

    • Update − Used to Update expense item into the _items variable as well as into the database. It also calls notifyListeners to update the UI.

void update(Expense item) {

bool found = false;

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == item.id) {

_items[i] = item;

found = true;

SQLiteDbProvider.db.update(item); break;

}

}

if(found) notifyListeners();

}

    • delete − Used to remove an existing expense item in the _items variable as well as from the database. It also calls notifyListeners to update the UI.

void delete(Expense item) {

bool found = false;

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == item.id) {

found = true;

SQLiteDbProvider.db.delete(item.id);

_items.removeAt(i); break;

}

}

if(found) notifyListeners();

}

  • The complete code of the ExpenseListModel class is as follows −

import ‘dart:collection’;

import ‘package:scoped_model/scoped_model.dart’;

import ‘Expense.dart’;

import ‘Database.dart’;

class ExpenseListModel extends Model {

ExpenseListModel() {

this.load();

}

final List<Expense> _items = [];

UnmodifiableListView<Expense> get items =>

UnmodifiableListView(_items);

 

/*Future<double> get totalExpense {

return SQLiteDbProvider.db.getTotalExpense();

}*/

 

double get totalExpense {

double amount = 0.0;

for(var i = 0; i < _items.length; i++) {

amount = amount + _items[i].amount;

}

return amount;

}

void load() {

Future<List<Expense>> list = SQLiteDbProvider.db.getAllExpenses();

list.then( (dbItems) {

for(var i = 0; i < dbItems.length; i++) {

_items.add(dbItems[i]);

}

notifyListeners();

});

}

Expense byId(int id) {

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == id) {

return _items[i];

}

}

return null;

}

void add(Expense item) {

SQLiteDbProvider.db.insert(item).then((val) {

_items.add(val);

notifyListeners();

});

}

void update(Expense item) {

bool found = false;

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == item.id) {

_items[i] = item;

found = true;

SQLiteDbProvider.db.update(item);

break;

}

}

if(found) notifyListeners();

}

void delete(Expense item) {

bool found = false;

for(var i = 0; i < _items.length; i++) {

if(_items[i].id == item.id) {

found = true;

SQLiteDbProvider.db.delete(item.id);

_items.removeAt(i); break;

}

}

if(found) notifyListeners();

}

}

  • Open main.dart file. Import the classes as specified below −

import ‘package:flutter/material.dart’;

import ‘package:scoped_model/scoped_model.dart’;

import ‘ExpenseListModel.dart’;

import ‘Expense.dart’;

  • Add main function and call runApp by passing ScopedModel<ExpenseListModel> widget.

void main() {

final expenses = ExpenseListModel();

runApp(

ScopedModel<ExpenseListModel>(model: expenses, child: MyApp(),)

);

}

  • Here,
    • expenses object loads all the user expenses information from the database. Also, when the application is opened for the first time, it will create the required database with proper tables.
    • ScopedModel provides the expense information during the whole life cycle of the application and ensures the maintenance of state of the application at any instance. It enables us to use StatelessWidget instead of StatefulWidget.
  • Create a simple MyApp using MaterialApp widget.

class MyApp extends StatelessWidget {

// This widget is the root of your application.

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘Expense’,

theme: ThemeData(

primarySwatch: Colors.blue,

),

home: MyHomePage(title: ‘Expense calculator’),

);

}

}

  • Buat widget MyHomePage untuk menampilkan semua informasi pengeluaran pengguna bersama dengan total pengeluaran di bagian atas. Tombol mengambang di pojok kanan bawah akan digunakan untuk menambah pengeluaran baru.

class MyHomePage extends StatelessWidget {

MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text(this.title),

),

body: ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return ListView.separated(

itemCount: expenses.items == null ? 1

: expenses.items.length + 1,

itemBuilder: (context, index) {

if (index == 0) {

return ListTile(

title: Text(“Total expenses: ”

+ expenses.totalExpense.toString(),

style: TextStyle(fontSize: 24,

fontWeight: FontWeight.bold),)

);

} else {

index = index – 1;

return Dismissible(

key: Key(expenses.items[index].id.toString()),

onDismissed: (direction) {

expenses.delete(expenses.items[index]);

Scaffold.of(context).showSnackBar(

SnackBar(

content: Text(

“Item with id, ”

+ expenses.items[index].id.toString() +

” is dismissed”

)

)

);

},

child: ListTile( onTap: () {

Navigator.push(

context, MaterialPageRoute(

builder: (context) => FormPage(

id: expenses.items[index].id,

expenses: expenses,

)

)

);

},

leading: Icon(Icons.monetization_on),

trailing: Icon(Icons.keyboard_arrow_right),

title: Text(expenses.items[index].category + “: ” +

expenses.items[index].amount.toString() +

” \nspent on ” + expenses.items[index].formattedDate,

style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))

);

}

},

separatorBuilder: (context, index) {

return Divider();

},

);

},

),

floatingActionButton: ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return FloatingActionButton( onPressed: () {

Navigator.push(

context, MaterialPageRoute(

builder: (context) => ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return FormPage( id: 0, expenses: expenses, );

}

)

)

);

// expenses.add(new Expense(

// 2, 1000, DateTime.parse(‘2019-04-01 11:00:00’), ‘Food’)

);

// print(expenses.items.length);

},

tooltip: ‘Increment’, child: Icon(Icons.add), );

}

)

);

}

}

  • Sini,
    • ScopedModelDescendant digunakan untuk meneruskan model pengeluaran ke widget ListView dan FloatingActionButton.
    • Widget ListView.separated dan ListTile digunakan untuk membuat daftar informasi pengeluaran.
    • Widget yang dapat ditutup digunakan untuk menghapus entri pengeluaran menggunakan gerakan menggesek.
    • Navigator digunakan untuk membuka antarmuka edit dari entri pengeluaran. Ini dapat diaktifkan dengan mengetuk entri pengeluaran.
  • Buat widget FormPage. Tujuan dari widget FormPage adalah untuk menambah atau memperbarui entri pengeluaran. Ini menangani validasi entri biaya juga.

class FormPage extends StatefulWidget {

FormPage({Key key, this.id, this.expenses}) : super(key: key);

final int id;

final ExpenseListModel expenses;

 

@override _FormPageState createState() => _FormPageState(id: id, expenses: expenses);

}

class _FormPageState extends State<FormPage> {

_FormPageState({Key key, this.id, this.expenses});

 

final int id;

final ExpenseListModel expenses;

final scaffoldKey = GlobalKey<ScaffoldState>();

final formKey = GlobalKey<FormState>();

 

double _amount;

DateTime _date;

String _category;

 

void _submit() {

final form = formKey.currentState;

if (form.validate()) {

form.save();

if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category));

else expenses.update(Expense(this.id, _amount, _date, _category));

Navigator.pop(context);

}

}

@override

Widget build(BuildContext context) {

return Scaffold(

key: scaffoldKey, appBar: AppBar(

title: Text(‘Enter expense details’),

),

body: Padding(

padding: const EdgeInsets.all(16.0),

child: Form(

key: formKey, child: Column(

children: [

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.monetization_on),

labelText: ‘Amount’,

labelStyle: TextStyle(fontSize: 18)

),

validator: (val) {

Pattern pattern = r’^[1-9]\d*(\.\d+)?$’;

RegExp regex = new RegExp(pattern);

if (!regex.hasMatch(val))

return ‘Enter a valid number’; else return null;

},

initialValue: id == 0

? ” : expenses.byId(id).amount.toString(),

onSaved: (val) => _amount = double.parse(val),

),

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.calendar_today),

hintText: ‘Enter date’,

labelText: ‘Date’,

labelStyle: TextStyle(fontSize: 18),

),

validator: (val) {

Pattern pattern = r’^((?:19|20)\d\d)[- /.]

(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$’;

RegExp regex = new RegExp(pattern);

if (!regex.hasMatch(val))

return ‘Enter a valid date’;

else return null;

},

onSaved: (val) => _date = DateTime.parse(val),

initialValue: id == 0

? ” : expenses.byId(id).formattedDate,

keyboardType: TextInputType.datetime,

),

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.category),

labelText: ‘Category’,

labelStyle: TextStyle(fontSize: 18)

),

onSaved: (val) => _category = val,

initialValue: id == 0 ? ”

: expenses.byId(id).category.toString(),

),

RaisedButton(

onPressed: _submit,

child: new Text(‘Submit’),

),

],

),

),

),

);

}

}

  • Sini,
    • TextFormField digunakan untuk membuat entri formulir.
    • properti validator dari TextFormField digunakan untuk memvalidasi elemen formulir bersama dengan pola RegEx.
    • Fungsi _submit digunakan bersama dengan objek pengeluaran untuk menambah atau memperbarui pengeluaran ke dalam database.
  • Kode lengkap dari file main.dart adalah sebagai berikut –

import ‘package:flutter/material.dart’;

import ‘package:scoped_model/scoped_model.dart’;

import ‘ExpenseListModel.dart’;

import ‘Expense.dart’;

void main() {

final expenses = ExpenseListModel();

runApp(

ScopedModel<ExpenseListModel>(

model: expenses, child: MyApp(),

)

);

}

class MyApp extends StatelessWidget {

// This widget is the root of your application.

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘Expense’,

theme: ThemeData(

primarySwatch: Colors.blue,

),

home: MyHomePage(title: ‘Expense calculator’),

);

}

}

class MyHomePage extends StatelessWidget {

MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text(this.title),

),

body: ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return ListView.separated(

itemCount: expenses.items == null ? 1

: expenses.items.length + 1, itemBuilder: (context, index) {

if (index == 0) {

return ListTile( title: Text(“Total expenses: ”

+ expenses.totalExpense.toString(),

style: TextStyle(fontSize: 24,fontWeight:

FontWeight.bold),) );

} else {

index = index – 1; return Dismissible(

key: Key(expenses.items[index].id.toString()),

onDismissed: (direction) {

expenses.delete(expenses.items[index]);

Scaffold.of(context).showSnackBar(

SnackBar(

content: Text(

“Item with id, ” +

expenses.items[index].id.toString()

+ ” is dismissed”

)

)

);

},

child: ListTile( onTap: () {

Navigator.push( context, MaterialPageRoute(

builder: (context) => FormPage(

id: expenses.items[index].id, expenses: expenses,

)

));

},

leading: Icon(Icons.monetization_on),

trailing: Icon(Icons.keyboard_arrow_right),

title: Text(expenses.items[index].category + “: ” +

expenses.items[index].amount.toString() + ” \nspent on ” +

expenses.items[index].formattedDate,

style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))

);

}

},

separatorBuilder: (context, index) {

return Divider();

},

);

},

),

floatingActionButton: ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return FloatingActionButton(

onPressed: () {

Navigator.push(

context, MaterialPageRoute(

builder: (context)

=> ScopedModelDescendant<ExpenseListModel>(

builder: (context, child, expenses) {

return FormPage( id: 0, expenses: expenses, );

}

)

)

);

// expenses.add(

new Expense(

// 2, 1000, DateTime.parse(‘2019-04-01 11:00:00’), ‘Food’

)

);

// print(expenses.items.length);

},

tooltip: ‘Increment’, child: Icon(Icons.add),

);

}

)

);

}

}

class FormPage extends StatefulWidget {

FormPage({Key key, this.id, this.expenses}) : super(key: key);

final int id;

final ExpenseListModel expenses;

 

@override

_FormPageState createState() => _FormPageState(id: id, expenses: expenses);

}

class _FormPageState extends State<FormPage> {

_FormPageState({Key key, this.id, this.expenses});

final int id;

final ExpenseListModel expenses;

final scaffoldKey = GlobalKey<ScaffoldState>();

final formKey = GlobalKey<FormState>();

double _amount; DateTime _date;

String _category;

void _submit() {

final form = formKey.currentState;

if (form.validate()) {

form.save();

if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category));

else expenses.update(Expense(this.id, _amount, _date, _category));

Navigator.pop(context);

}

}

@override

Widget build(BuildContext context) {

return Scaffold(

key: scaffoldKey, appBar: AppBar(

title: Text(‘Enter expense details’),

),

body: Padding(

padding: const EdgeInsets.all(16.0),

child: Form(

key: formKey, child: Column(

children: [

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.monetization_on),

labelText: ‘Amount’,

labelStyle: TextStyle(fontSize: 18)

),

validator: (val) {

Pattern pattern = r’^[1-9]\d*(\.\d+)?$’;

RegExp regex = new RegExp(pattern);

if (!regex.hasMatch(val)) return ‘Enter a valid number’;

else return null;

},

initialValue: id == 0 ? ”

: expenses.byId(id).amount.toString(),

onSaved: (val) => _amount = double.parse(val),

),

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.calendar_today),

hintText: ‘Enter date’,

labelText: ‘Date’,

labelStyle: TextStyle(fontSize: 18),

),

validator: (val) {

Pattern pattern = r’^((?:19|20)\d\d)[- /.]

(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$’;

RegExp regex = new RegExp(pattern);

if (!regex.hasMatch(val)) return ‘Enter a valid date’;

else return null;

},

onSaved: (val) => _date = DateTime.parse(val),

initialValue: id == 0 ? ” : expenses.byId(id).formattedDate,

keyboardType: TextInputType.datetime,

),

TextFormField(

style: TextStyle(fontSize: 22),

decoration: const InputDecoration(

icon: const Icon(Icons.category),

labelText: ‘Category’,

labelStyle: TextStyle(fontSize: 18)

),

onSaved: (val) => _category = val,

initialValue: id == 0 ? ” : expenses.byId(id).category.toString(),

),

RaisedButton(

onPressed: _submit,

child: new Text(‘Submit’),

),

],

),

),

),

);

}

}

  • Sekarang, jalankan aplikasinya.
  • Tambahkan pengeluaran baru menggunakan tombol mengambang.
  • Edit pengeluaran yang ada dengan mengetuk entri pengeluaran.
  • Hapus pengeluaran yang ada dengan menggesek entri pengeluaran ke salah satu arah.

Beberapa screen shot dari aplikasi tersebut adalah sebagai berikut –

Kalkulator Biaya

Masukkan Rincian Pengeluaran

Total Biaya

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *