Expense Apps

แอปบันทึกค่าใช้จ่าย เพิ่มรายการใหม่ แสดงรายการและเลื่อนดูรายการทั้งหมด

ปรับแต่งการเพิ่มรายการใหม่และการแสดงผล

เปลี่ยนชนิดคีย์บอร์ด เป็นตัวเลข เมื่อต้องรับข้อมูลที่เป็นตัวเลขเท่านั้น
ใน new_trans.dart ตรงที่รับค่าจำนวนเงิน amount เพิ่มพารามิเตอร์ของ TextField(keyboardType: TextInputType.numberWithOptions(decimal: true),)

ให้เรียกฟังก์ชันเพิ่มรายการใหม่ เมื่อกดคีย์ Done หรือ Enter บนคีย์บอร์ด
ใน new_trans.dart เพิ่มพารามิเตอร์ของ TextField(onSubmitted: (_)=>addNewTrans(),)

สร้างฟังก์ชัน addNewTrans()
เนื่องจากมีหลายที่ ต้องเรียกใช้ฟังก์ชันในการเพิ่มรายการ เช่นเมื่อกดปุ่ม Done หรือ Enter จาก TextField() หรือเมื่อกดปุ่ม Add Item ถ้าสร้างเป็นฟังก์ชัน จะได้ไม่ต้องเขียนโค้ดซ้ำและสะดวกในการแก้ไขมากกว่าการใช้ฟังก์ชันไม่มีชื่อ
ใน new_trans.dart สร้างฟังก์ชัน void addNewTrans(){}
ถ้าตรวจสอบพบว่ามีข้อมูลอินพุทที่ถูกต้อง ก็ให้สร้างรายการใหม่ ถ้าไม่ถูกต้องเช่นตัวเลขเป็นค่าลบ หรือไม่มีชื่อรายการ ก็ไม่ต้องเพิ่ม

สังเกตว่าฟังก์ชันพอยเตอร์ของ onSubmitted: (){} ต้องมีพารามิเตอร 1 ตัว แต่เราไม่ได้ใช้งาน ก็ให้ใส่พารามิเตอร์ไม่มีชื่อแทน คือ (_){} และเนื่องจากเราเรียกใช้ฟังก์ชัน addNewTrans() เพียงคำสั่งเดียวใน {} ก็เขียนฟังก์ชันแบบสั้นแทนได้ คือ (_) => addNewTrans(),
ส่วนฟังก์ชันพอยเตอร์ของ onPressed: (){} ไม่มีพารามิเตอร์ ก็ใช้อันเดิม (){} และเรียกใช้ addNewTrans() เช่นเดียวกัน

new_trans.dart
 
	import 'package:flutter/material.dart';

	class NewTransaction extends StatelessWidget {
	  final Function addTrans;
	  final title = TextEditingController();
	  final amount = TextEditingController();
	
	  NewTransaction(this.addTrans);

	  void addNewTrans() {
		if (title.text.isEmpty || double.parse(amount.text) <= 0) {
		  print('not added');
		  return;
		}
		addTrans(title.text, double.parse(amount.text));
	  }
	
	  @override
	  Widget build(BuildContext context) {
		return Card(
		  elevation: 5,
		  margin: EdgeInsets.all(10),
		  child: Column(
			crossAxisAlignment: CrossAxisAlignment.end,
			children: [
			  TextField(
				decoration: InputDecoration(labelText: 'Title'),
				controller: title,
				onSubmitted: (_) => addNewTrans(),
			  ),
			  TextField(
				decoration: InputDecoration(labelText: 'Amount'),
				controller: amount,
				keyboardType: TextInputType.numberWithOptions(decimal: true),
				onSubmitted: (_) => addNewTrans(),
			  ),
			  ElevatedButton(
				onPressed: () {
				  print(title.text);
				  print(amount.text);
				  //addTrans(title.text, double.parse(amount.text));
				  addNewTrans();
				},
				child: Text('Add Item'),
			  ),
			],
		  ),
		);
	  }
	}

ปรับจุดทศนิยม ให้เป็น 2 ตำแหน่ง
ใน list.dart ปรับพารามิเตอร์ของการแสดงผลตัวเลขเป็นแบบกำหนดจุดทศนิยม 2 ตำแหน่งด้วย amount.toStringAsFixed(2)

list.dart
 
	...
	child: Text(
		'${transactions[index].amount.toStringAsFixed(2)} บาท',
		style: TextStyle(
			fontWeight: FontWeight.bold,
			fontSize: 20,
			color: Colors.purple),
	  ),
  	...
	

keyboard


decimal


สร้างปุ่ม + บนแอปบาร์

เราจะเรียกใช้วิดเจ็ตเพิ่มรายการ NewTransaction() เมื่อต้องการเท่านั้น โดยไม่จำเป็นต้องแสดงวิดเจ็ตตลอดเวลา
ให้เราซ่อนวิดเจ็ต NewTransaction() และเรียกใช้งานเมื่อกดปุ่ม + บนแอปบาร์ และซ่อนอีกครั้งเมื่อเพิ่มรายการสำเร็จ

ไปที่ main.dart เพิ่ม actions พารามิเตอร์ที่เป็น IconButton widget ให้กับ appBar

main.dart
 
	...
	appBar: AppBar(
        title: Text('Expense App'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {},
          ),
        ],
      ),
	...  

add


สร้างปุ่ม + แบบลอยบนหน้าจอด้านล่างตรงกลาง

ปุ่มนี้คือ FloatingActionButtton ที่ทำหน้าที่เหมือน IconButton บน appBar
ไปที่ main.dart เพิ่มพารามิเตอร์ที่เป็น floatingActionButton และ floatingActionButtonLocation ให้กับ Scaffold()

main.dart
 
class MyHomePage extends StatelessWidget {
	@override
	Widget build(BuildContext context) {
	  return Scaffold(
		floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
		floatingActionButton: FloatingActionButton(
		  child: Icon(Icons.add),
		  onPressed: () {},
		),
		appBar: AppBar(
		  title: Text('Expense App'),
		  actions: [
			IconButton(
			  icon: Icon(Icons.add),
			  onPressed: () {},
			),
		  ],
		),
	...  

float


สร้าง Action Sheet NewTransaction()

เมื่อกดปุ่ม + หรือ FloatingActionButton ให้แสดงวิดเจ็ต NewTransaction()
ใน main.dart สร้างฟังก์ชัน startNewTrans() ให้แสดงวิดเจ็ต NewTransaction() มื่อกดปุ่ม + หรือ FloatingActionButton
ในฟังก์ชัน startNewTrans เรียกใช้ showModalBottomSheet() เพื่อสร้าง Action Sheet NewTransaction()

main.dart
 
	class MyHomePage extends StatelessWidget {
		void startNewTrans(BuildContext ct) {
		  showModalBottomSheet(
			  context: ct,
			  builder: (_) {
				return NewTransaction();
			  });
		}
		...

เรียกใช้ฟังก์ชัน startNewTrans() จาก onPressed: ของ IconButton + และ FloatingActionButton

main.dart
 
...	
@override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: ()=> startNewTrans(context),
      ),
      appBar: AppBar(
        title: Text('Expense App'),
        actions: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: ()=> startNewTrans(context),
          ),
        ],
      ),
	  ...

คราวนี้จะมีปัญหาตรงที่ เราไม่มีฟังก์ชันพอยเตอร์ ที่จะส่งไปยังวิดเจ็ต NewTransaction() เพราะอยู่คนละคลาสกัน ตอนนี้ยังหาวิธีทำไม่ได้ ต้องย้ายคลาส UserTransaction() ที่เป็น StatefulWidget ซึ่งสร้าง NewTransaction() และแสดงรายการ TransactionList() มาไว้ใน main.dart และทำให้ main.dart เป็น StatefulWidget แทน
ถ้าเรานำ UserTransaction() มาจัดการใน main.dart ที่เป็น StatefulWidget แล้ว เราก็ไม่ต้องการ UserTransaction() ที่เป็น StatefulWidget อีก
ใน main.dart กดขวาที่ StatelessWidget เลือก Refactor... เลือก Convert to StatefulWidget

main.dart
 
	...
	class MyHomePage extends StatefulWidget {
		@override
		_MyHomePageState createState() => _MyHomePageState();
	  }
	  
	  class _MyHomePageState extends State {
		void startNewTrans(BuildContext ct) {
		  showModalBottomSheet(
			  context: ct,
			  builder: (_) {
				return NewTransaction();
			  });
		}
		...  

ปรับแก้ main.dart ดังนี้

  1. คัดลอก List กับฟังก์ชัน _addNewTrans() จาก UserTransaction() มาไว้ด้านบนของ startNewTrans() ในคลาส _MyHomePageState
  2. import Transaction()
    import 'models/trans.dart';
  3. เรียกใช้ TransactionList() แทน UserTransaction()
    //UserTransaction()
    TransactionList(_userTrans),
  4. import TransactionList()
    import 'widgets/list.dart';
  5. ส่งฟังก์ชันพอยเตอร์ _addNewTrans ไปยัง NewTransaction(_addNewTrans);
main.dart
 
	import 'package:expense/widgets/list.dart';
	import 'package:expense/widgets/new_trans.dart';
	import 'package:flutter/material.dart';
	import 'models/trans.dart';
	
	void main() => runApp(MyApp());
	
	class MyApp extends StatelessWidget {
	  // This widget is the root of your application.
	  @override
	  Widget build(BuildContext context) {
		return MaterialApp(
		  title: 'Expense App',
		  home: MyHomePage(),
		);
	  }
	}
	
	class MyHomePage extends StatefulWidget {
	  @override
	  _MyHomePageState createState() => _MyHomePageState();
	}
	
	class _MyHomePageState extends State {
	  final List _userTrans = [
		Transaction(
		  id: 't1',
		  title: 'bag',
		  amount: 99.5,
		  dt: DateTime.now(),
		),
		Transaction(
		  id: 't2',
		  title: 'paper',
		  amount: 59.5,
		  dt: DateTime.now(),
		),
	  ];
	  void _addNewTrans(String txTitle, double txAmount) {
		final newTrans = Transaction(
		  id: DateTime.now().toString(),
		  title: txTitle,
		  amount: txAmount,
		  dt: DateTime.now(),
		);
		setState(() {
		  _userTrans.add(newTrans);
		});
	  }
	
	  void startNewTrans(BuildContext ct) {
		showModalBottomSheet(
			context: ct,
			builder: (_) {
			  return NewTransaction(_addNewTrans);
			});
	  }
	
	  @override
	  Widget build(BuildContext context) {
		return Scaffold(
		  floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
		  floatingActionButton: FloatingActionButton(
			child: Icon(Icons.add),
			onPressed: () => startNewTrans(context),
		  ),
		  appBar: AppBar(
			title: Text('Expense App'),
			actions: [
			  IconButton(
				icon: Icon(Icons.add),
				onPressed: () => startNewTrans(context),
			  ),
			],
		  ),
		  body: SingleChildScrollView(
			child: Column(
			  //mainAxisAlignment: MainAxisAlignment.start,
			  crossAxisAlignment: CrossAxisAlignment.stretch,
			  children: [
				Container(
				  width: double.infinity,
				  child: Card(
					color: Colors.blue,
					child: Text('Chart'),
					elevation: 5,
				  ),
				),
				//UserTransaction()
				TransactionList(_userTrans),
			  ],
			),
		  ), // This trailing comma makes auto-formatting nicer for build methods.
		);
	  }
	}

run app
กดปุ่ม + หรือ FloatingActionButton เพื่อแสดง Action Sheet NewTransaction
ใส่ข้อมูล กดปุ่ม Add Item
กดในที่ว่างๆภายนอก Action Sheet NewTransaction เพื่อปิด Action Sheet

sheet1


sheet2


เพิ่มรายการใหม่บน Action Sheet NewTransaction

ถึงแม้ว่าเราจะแสดง Action Sheet New Transaction ด้วยปุ่ม + หรือ FloatingActionButton แต่เมื่อดปุ่ม Add Item ก็ยังเพิ่มรายการไม่ได้ เพราะดูเหมือนว่าข้อมูลที่ใส่ผ่าน Action Sheet NewTransaction จะถูกรีเซตเมื่อกด TextField() อันใดอันหนึ่ง ทำให้ไม่ผ่านเงื่อนไขการเพิ่มรายการ ซึ่งต้องมีข้อมูลจากทั้งสอง TextField()
สาเหตุมาจาก NewTransaction เป็น StatelessWidget ทำให้เก็บค่าไม่ได้ ดังนั้นเราต้องทำให้ NewTransaction เป็น StatefulWidget เพื่อให้เก็บค่าได้
เปิดไฟล์ new_trans.dart กดขวาที่ StatelessWidget เลือก Refactor... เลือก Convert to StatefulWidget
สังเกตว่า ฟลัตเตอร์จะสร้าง widget ที่เชื่อมโยงระหว่างคลาส Widget กับคลาส State ทำให้เราเรียกใช้ตัวแปรจาก Widget ภายใน State ได้ แม้ว่าจะอยู่คนละคลาสกัน

new_trans.dart
 
...
class NewTransaction extends StatefulWidget {
	final Function addTrans;
  
	NewTransaction(this.addTrans);
  
	@override
	_NewTransactionState createState() => _NewTransactionState();
  }
  
  class _NewTransactionState extends State {
	final title = TextEditingController();
  
	final amount = TextEditingController();
  
	void addNewTrans() {
	  if (title.text.isEmpty || double.parse(amount.text) <= 0) {
		print('not added');
		return;
	  }
	  widget.addTrans(title.text, double.parse(amount.text));
	}
  
	...  

เมื่อกดปุ่ม Add Item และเพิ่มรายการได้เรียบร้อย ถ้าต้องการให้ปิด Action Sheet NewTransaction ไปด้วย ให้เพิ่มโค้ด Navigator.of(context).pop(); ต่อจาก widget.addTrans(title.text, double.parse(amount.text)); ดังนี้

new_trans.dart
 
	...
    widget.addTrans(title.text, double.parse(amount.text));
    Navigator.of(context).pop();	
	...

run app
กดปุ่ม + หรือ FloatingActionButton เพื่อแสดง ActionSheet NewTransaction
ใส่ข้อมูล กดปุ่ม Add Item หรือกดปุ่ม Done หรือ Enter บนคีย์บอร์ด

sheet3


sheet4

2020. mnet50.com