Pada main.dart digunakan stateful widget, karena proses render ulang tampilan dihandle disini.

import 'package:flutter/material.dart';
import './models/trans.dart';
import './widgets/expense_list.dart';
import './widgets/form_expense.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expense Tracker',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<Trans> _trans = [
Trans(
id: 'a001',
title: 'Notebook Aspire A5',
amount: 8200000,
txdate: DateTime.now()),
Trans(
id: 'a002',
title: 'Samsung Galaxy A5',
amount: 3200000,
txdate: DateTime.now()),
];
void _addNewExpense(String newTitle, double newAmount) {
final newExpense = Trans(
id: DateTime.now().toString(),
title: newTitle,
amount: newAmount,
txdate: DateTime.now());
setState(() {
_trans.add(newExpense);
});
}
void _showFormExpense(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return FormExpense(_addNewExpense);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Expense Tracker'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () => _showFormExpense(context),
)
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Card(
color: Colors.blue,
child: Text('Chart'),
),
ExpenseList(_trans),
]),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _showFormExpense(context),
),
);
}
}
Pembahasan Code
Fungsi untuk menambahkan data yang diinput user kedalam list dan mentrigger setState() untuk melakukan render ulang tampilan.
void _addNewExpense(String newTitle, double newAmount) {
final newExpense = Trans(
id: DateTime.now().toString(),
title: newTitle,
amount: newAmount,
txdate: DateTime.now());
setState(() {
_trans.add(newExpense);
});
}
Kita gunakan widget IconButton yang akan tampil disisi kanan atas, dan FloatingActionButton akan tampil dibawah. Ketika button ini ditekan, form input expense akan tampil.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Expense Tracker'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () => _showFormExpense(context),
)
],
),
...
...
...
body: SingleChildScrollView(
child: Column(
...
...
...
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _showFormExpense(context),
),
Fungsi _showFormExpense adalah fungsi untuk menampilkan form input dari form_expense.dart secara modal.
void _showFormExpense(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return FormExpense(_addNewExpense);
},
);
}
Menggunakan ThemeData
Kita akan bahas sedikit mengenai ThemeData, yang berguna untuk mengatur konsistensi styling pada design. Kita tambahkan code berikut pada class MyApp.
- primaryColor, untuk mengatur warna appBar.
- accentColor, untuk mengatur warna button, pada aplikasi ini floatingbutton.
- textTheme, untuk semua warna Text widget.
Yang perlu diperhatikan adalah informasi pada ThemeData akan ditimpa oleh inline styling pada widget. (Sama seperti css styling pada html).

theme: ThemeData(
primaryColor: Colors.red,
accentColor: Colors.red[200],
textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
),
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expense Tracker',
theme: ThemeData(
primaryColor: Colors.red,
accentColor: Colors.red[200],
textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
),
home: MyHomePage(),
);
}
}
Perhatian, karena Flutter ada SDK yang masih berkembang, ada kemungkinan dikemudian hari code diatas tidak berjalan. Silakan lihat dokumentasi terbaru ThemeData.