Flutter: ลองเขียนแอปสไตล์ BLoC Pattern สำหรับ Noob

Created on Sep 06, 2019
Design Pattern รูปแบบ หรือสไตล์การเขียนโปรแกรม มีความสำคัญอย่างมากเมื่อเราอยากสร้างโปรเจคที่สามารถนำไปต่อยอดได้ ดูแลรักษาได้ และเพิ่มประสิทธิภาพได้
BLoC pattern หรือ Business Logic of Component คือ Design Pattern ที่มีแนวคิดของการแยก Logic ออกจากส่วนแสดงผล หรือพูดอีกนัยนึงคือ เราพยายามแยกให้โค๊ดอันนึงทำหน้าที่คำนวนตรรกะต่างๆ แล้วอีกอันก็ประกอบไปด้วยการแสดงผล ซึ่งให้อารมณ์คล้ายๆ JavaScript กับ Html แต่เรานำมาใช้บน Flutter นั่นเอง
สร้าง BLoC สำหรับทุกหน้า
ด้วยหลักการของ BLoC จากเดิมที่แอปหน้านึงจะมีเพียง 1 ไฟล์ เราก็ต้องเพิ่มไฟล์ของ BLoC เพิ่มเข้าไปด้วย เพราะเราต้องแยกส่วนของหลักการออกมา ดังนั้น จากจุดนี้ไปก็จะพบว่า BloC จะมีความยากระดับนึง แล้วอาจจะ เกินความจำเป็น สำหรับแอปที่ไม่ได้มีความซับซ้อนมาก แต่ก็คุ้มค่า ถ้าแอปเราเริ่มมีขนาดระดับนึง หรือเมื่อต้องส่ง state ไปมาระหว่างหน้า
เนื่องจาก BLoC เป็นเพียง Pattern
ดังนั้นเราสามารถเลือก State Management ที่จะใช้ได้ ซึ่งโดยทั่วไปก็จะนิยมใช้ Stream กับ flutter_bloc ในบทความนี้จะเลือกใช้
flutter_bloc
เพราะเริ่มมีความนิยมมากกว่า
ก่อนจะเริ่มเอา BLoC มาใช้ ลองมาดูโครงสร้างของมันกันก่อน
จากรูปข้างบน จะเห็น flow ของข้อมูล โดย bloc เองก็จะทำหน้าที่เป็นตัวกลางของข้อมูล จะไม่ได้ไปมีส่วนในการเรนเดอร์ UI ส่วนของ UI เองก็จะไม่ได้มี logic ข้างใน แต่จะรับข้อมูลมาจาก BLoC แทน หรือถ้าจะเรียกฟังก์ชั่นก็จะเรียกผ่าน BLoC เช่นกัน
⚠️⚠️⚠️ (อัพเดท 8/7/2020) ถ้าใครใช้ flutter_bloc เวอร์ชั่นตั้งแต่ 1.0.0 ขึ้นไป จะมีการเปลี่ยนชื่อ Api บางส่วนดังนี้
bloc.state.listen -> bloc.listen
bloc.currentState -> bloc.state
bloc.dispatch -> bloc.add
bloc.dispose -> bloc.close
สามารถอ่านเพิ่มเติมได้จาก https://link.medium.com/qnfMcEcW00
ดังนั้นตอนทำตาม อาจใช้เวอร์ชั่นที่ระบุไว้ไปก่อนได้ หลักการทำงานของ flutter_bloc ยังเหมือนเดิม

เริ่มเขียน Flutter ด้วย BLoC Pattern กัน

ก่อนที่เราจะเริ่มเขียน BLoC Pattern แบบจริงจัง ลองมาทำความคุ้นเคยกับคำสั่งต่างๆที่เราต้องใช้กันก่อน ฉะนั้น มาลองแก้ โปรแกรมแรกเริ่ม Increment app ให้เป็น BLoC Pattern กัน

เตรียมความพร้อม

flutter create increment_app
หลังจากนั้นก็ทำการแก้ไขไฟล์
pubspec.yml
โดยก็อปด้านล่าง หรือ เพิ่ม
flutter_bloc: ^0.18.3
equatable: ^0.2.0
เข้าไปในส่วน
dependencies:
name: increment_app
description: A new Flutter project.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^0.18.3 # เรียกเพื่อใช้ state management ของ flutter_bloc เพื่อใช้
equatable: ^0.2.0 # ใช้ประกอบกับ flutter_bloc
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
view raw pubspec.yaml hosted with ❤ by GitHub
แล้วอัพเดท dependencies โดยรัน
flutter packages get
เป็นอันจบขั้นตอนการเตรียมความพร้อม

เริ่มแก้ไขโค๊ดกัน

เราจะเริ่มต้นด้วยการสร้าง bloc เพื่อรองรับการใช้งานของส่วน UI กันก่อน โดยตอนนี้ Logic เรามีเพียงแสดงตัวเลข กับ เพิ่มค่าของเลขเมื่อกดปุ่มบวก ตามเดิมใน
main.dart
เราจะพบว่าค่าของเลขถูกเก็บใน
_MyHomePageState
และถูกอัพเดทโดยฟังก์ชั่น
_incrementCounter()
โดยเรียก
setState()
เพื่ออัพเดทการแสดงผลของตัวเลข
เราจะทำการย้าย logic ออกมา แล้วเปลี่ยนไปใช้ state management ของ
flutter_bloc
แทน โดยเริ่มต้นดังต่อไปนี้
สร้างโฟลเดอร์สำหรับ จัดการ logic ของเรา
increment_app/lib/**bloc**
แล้วสร้างไฟล์สำหรับ bloc หน้า main ของเราจำนวน 4 ไฟล์ดังนี้
  • increment_app/lib/bloc/**counter_event.dart**
    รับ event จากส่วน UI
  • increment_app/lib/bloc/**counter_state.dart**
    อัพเดทข้อมูลส่วน UI
  • increment_app/lib/bloc/**counter_bloc.dart**
    logic ที่รับข้อมูลจาก
    counter_event.dart
    ประมวลผล แล้วส่งต่อไปยัง
    counter_state.dart
  • increment_app/lib/bloc/**counter.dart**
    export ไฟล์ข้างต้นทั้ง 3 เพื่อความง่ายในการเรียกใช้งาน
ก่อนอื่น มารวมไฟล์เพื่อให้ export ได้ง่าย ที่
**counter.dart**
กันก่อน
export 'counter_bloc.dart';
export 'counter_event.dart';
export 'counter_state.dart';
view raw counter.dart hosted with ❤ by GitHub
เริ่มต้นที่
counter_event.dart
import package meta
สำหรับใช้เรียก
@immutable
และ
equatable
สำหรับใช้เปรียบเทียบ state ในอดีตกับปัจจุบัน เพื่อให้
flutter_bloc
สามารถเช็ค แล้วอัพเดทเฉพาะค่าที่จำเป็นได้
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
สร้าง abstract class CounterEvent โดยที่ขยายมาจาก
Equatable
ที่เรา import มา
@immutable

abstract class CounterEvent extends Equatable {

CounterEvent([List props = const []]) : super(props);

}
สร้าง class IncrementCounter ที่ทำหน้าที่ส่งต่อตัวเลขไปยัง bloc
class IncrementCounter extends CounterEvent {

final int counter;

IncrementCounter(this.counter) : super([counter]);

@override

String toString() => 'IncrementCounter {counter : $counter}';

}
รวมกันทั้งหมด จะได้โค๊ดดังนี้
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
@immutable
abstract class CounterEvent extends Equatable {
CounterEvent([List props = const []]) : super(props);
}
class IncrementCounter extends CounterEvent {
final int counter;
IncrementCounter(this.counter) : super([counter]);
@override
String toString() => 'IncrementCounter {counter : \$counter}';
}
มาต่อกันที่
counter_state.dart
Import meta กับ equatable เข้ามา
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
สร้าง abstract class CounterState
@immutable

abstract class CounterState extends Equatable{

CounterState([List props = const []]) : super(props);

}
สร้าง class UpdateCounterState สำหรับส่งค่า counter ให้ UI นำไปใช้งาน
class UpdateCounterState extends CounterState {

final int counter;


UpdateCounterState(this.counter): super([counter]);

@override

String toString() {

return 'UpdateCounterState { counter: $counter}';

}

}
รวมกันได้ดังนี้
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
@immutable
abstract class CounterState extends Equatable{
CounterState([List props = const []]) : super(props);
}
class UpdateCounterState extends CounterState {
final int counter;
UpdateCounterState(this.counter): super([counter]);
@override
String toString() {
return 'UpdateCounterState { counter: \$counter}';
}
}
หลังจากเราได้สร้าง event กับ state เรียบร้อยแล้ว เรามาต่อกันที่ bloc ต่อ ซึ่งทำหน้าที่รับค่าจาก event แล้วประมวลผลตามประเภทของ event แล้วส่งค่าต่อไปยัง state
ส่วน logic ของเรา
counter_bloc.dart
import async
กับ
bloc
เข้ามา และก็นำ event กับ state เข้ามาด้วย โดยเรียกผ่าน
counter.dart
import 'dart:async';

import 'package:bloc/bloc.dart';

import './counter.dart';
ต่อมา สร้าง class CounterBloc ที่เราจะขยายมาจาก Bloc
<CounterEvent, CounterState>
โดยต้องระบุ event และ state ที่นำมาใช้ ซึ่งคือ CounterEvent กับ CounterState
ในส่วนนี้แนะนำให้ก็อปโค๊ดด้านล่างมาก่อน แล้วเราจะมาอธิบายส่วนต่างๆ กัน
import 'dart:async';
import 'package:bloc/bloc.dart';
import './counter.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
@override
CounterState get initialState { //--> 1*
return UpdateCounterState(0);
}
@override
Stream<CounterState> mapEventToState( //--> 2*
CounterEvent event,
) async* {
if (event is IncrementCounter) {
yield* _mapIncrementCountertoState(event);
}
}
Stream<CounterState> _mapIncrementCountertoState(IncrementCounter event) async* { //--> 3*
final int counter = event.counter + 1;
yield UpdateCounterState(counter);
}
}
  • 1* — 
    initialState
    เป็นการบอกค่าตั้งต้น ที่จะใช้เมื่อเริ่มรันแอปของเรา ซึ่งในตัวอย่างนี้ เราให้เริ่มต้นที่ 0
  • 2* — 
    mapEventToState
    ใช้เพื่อโยง event ไปยัง state หรือจะเรียกว่าเป็นตัวระบุฟังก์ชั่นที่จะใช้คำนวนค่าจาก event อีกด้วย แล้วส่งข้อมูลไปยัง state ที่ต้องการ
  • 3* — _mapUpdateCountertoState ฟังก์ชั่นที่ถูกเรียกใช้ตาม event ที่ถูกส่งมา โดยในส่วนนี้จะประกอบไปด้วย logic อย่างเช่น
    final int counter = event.counter + 1;
    ที่อยู่ในโค๊ดตัวอย่าง
ในตัวอย่างนี้อาจจะทำให้สงสัยได้ว่า จะมี 2* ไปทำไม ถ้าเรามีแค่ event อันเดียว และ state อันเดียว ที่เราใส่ไปเช่นนี้ไว้ก่อนเพราะในความเป็นจริงแล้ว เราจะมี event และ state มากกว่า 1 อัน เช่นเราอาจจะเพิ่มปุ่มลดตัวเลขทีละ 1 เข้าไป เราก็ต้องเพิ่ม event อีกอัน
*เดี๋ยวจะลองเพิ่มส่วนนี้ในภายหลัง
ตอนนี้ เราได้ทำในส่วนของ bloc เรียบร้อยแล้ว ต่อไปนี้เราก็สามารถเรียกใช้มันได้แล้ว
มาที่
main.dart
กันต่อเลย
เดิมๆ โค๊ดเราก็จะเป็นดังนี้
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'\$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
view raw main.dart hosted with ❤ by GitHub
เพิ่ม
counter.dart
กับ
flutter_bloc.dart
เข้ามา
import 'package:bloc_test/bloc/counter.dart';

import 'package:flutter_bloc/flutter_bloc.dart';
แก้
main()
ให้
BlocProvider
มาครอบ
MyApp()
ไว้ เพื่อให้เราสามารถเรียก Bloc ของเราจากหน้าไหนของแอปก็ได้ โดยต้องระบุ Bloc ที่เราจะใช้ด้วย ซึ่งคือ
CounterBloc()
/* void main() => runApp(MyApp()); */
void main() => runApp(BlocProvider(
builder: (BuildContext context) => CounterBloc(),
child: MyApp(),
));
view raw main.dart hosted with ❤ by GitHub
ตอนนี้แอปเราพร้อมที่จะเรียกใช้ bloc แล้ว
เราก็จะมาแก้ในส่วน
class _MyHomePageState extends State<MyHomePage>
ที่ซึ่งเดิมยังประกอบไปด้วยค่านับเลข
_counter
และ logic
_incrementCounter()
เราจะแทนที่สิ่งเหล่านี้ด้วย bloc ที่เราสร้าง
@override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context); // เรียกใช้ CounterBloc
return BlocBuilder( // เมื่อสร้าง widget ที่ต้องการนำค่าจาก bloc มาใช้ ให้นำ BlocBuilder มาคลุม
bloc: counterBloc, // ระบุ counterBloc ที่เราสร้าง
builder: (BuildContext context, CounterState state) {
final counter = (state as UpdateCounterState).counter; // ค่านับเลขถูกเรียกจาก state โดยเราจะระบุ state ที่นำมาใช้งานด้วย เพื่อให้ complier เราสามารถเข้าใจว่ามีตัวแปรใดบ้างในคลาสของ state นี้
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
/* '\$_counter', */
'\$counter', // เราเรียก counter มาใช้งานได้เลย
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counterBloc.dispatch(IncrementCounter(counter)), // เมื่อกดปุ่ม เราก็จะเรียก event โดยผ่านคำสั่ง dispatch ตามด้วย event
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
);
}
view raw main.dart hosted with ❤ by GitHub
หลังจากแก้ได้ตามนี้แล้ว เราก็สามารถลบ หรือคอมเม้น ตัวแปร และฟังก์ชั่นเดิมได้
int _counter = 0;

void _incrementCounter() {

setState(() {

_counter++;

});

}
เราจะรวมโค๊ดได้ดังนี้
import 'package:flutter/material.dart';
import 'package:bloc_test/bloc/counter.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(BlocProvider(
builder: (BuildContext context) => CounterBloc(),
child: MyApp(),
));
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/* int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
} */
@override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context); // เรียกใช้ CounterBloc
return BlocBuilder( // เมื่อสร้าง widget ที่ต้องการนำค่าจาก bloc มาใช้ ให้นำ BlocBuilder มาคลุม
bloc: counterBloc, // ระบุ counterBloc ที่เราสร้าง
builder: (BuildContext context, CounterState state) {
final counter = (state as UpdateCounterState).counter; // ค่านับเลขถูกเรียกจาก state โดยเราจะระบุ state ที่นำมาใช้งานด้วย เพื่อให้ complier เราสามารถเข้าใจว่ามีตัวแปรใดบ้างในคลาสของ state นี้
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
/* '\$_counter', */
'\$counter', // เราเรียก counter มาใช้งานได้เลย
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counterBloc.dispatch(IncrementCounter(counter)), // เมื่อกดปุ่ม เราก็จะเรียก event โดยผ่านคำสั่ง dispatch ตามด้วย event
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
);
}
}
view raw main.dart hosted with ❤ by GitHub
ตอนนี้แอปของเราก็พร้อมที่จะรันได้แล้ว
flutter run
หลังจากลองรันแล้ว เราจะพบว่าแทบไม่เห็นความแตกต่างเลย แต่ในความเป็นจริงแล้ว การที่เราเลือกใช้
flutter_bloc
จะมีความพิเศษที่ตัว state management นี้จะช่วยเราในการจัดการว่า widget ไหนจะต้องสร้างใหม่บ้าง จะเห็นผลผลอย่างมาก เมื่อหน้าตาแอปของเราประกอบไปด้วยหลายๆ widget และด้วย BLoC Pattern จะทำให้โค๊ดเราเป็นระเบียบ สามารถนำไปต่อยอดได้ง่าย
ไหนๆก็รันโค๊ดผ่านแล้ว เราลองมาเพิ่มอีกซัก event นึงโดยให้ทำหน้าที่ลดตัวเลขลงทีละ 1 และเพิ่มปุ่มลดตัวเลขด้วย
กลับไปที่
counter_event.dart
อีกรอบ
เพิ่มคลาสใหม่
DecrementCounter
ทำหน้าที่ส่ง event ลดเลข
class DecrementCounter extends CounterEvent {

final int counter;

DecrementCounter(this.counter) : super([counter]);

@override

String toString() => 'DecrementCounter {counter : $counter}';

}
สุดท้ายโค๊ดจะออกมาหน้าตาตามนี้
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
@immutable
abstract class CounterEvent extends Equatable {
CounterEvent([List props = const []]) : super(props);
}
class IncrementCounter extends CounterEvent {
final int counter;
IncrementCounter(this.counter) : super([counter]);
@override
String toString() => 'IncrementCounter {counter : \$counter}';
}
class DecrementCounter extends CounterEvent {
final int counter;
DecrementCounter(this.counter) : super([counter]);
@override
String toString() => 'DecrementCounter {counter : \$counter}';
}
เสร้จแล้วไปที่
counter_bloc.dart
เพื่อเพิ่มฟังก์ชั่นไว้รองรับ
DecrementCounter
ซึ่งทำหน้าที่ลดตัวเลขลงทีละ 1
Stream<CounterState> _mapDecrementCountertoState(DecrementCounter event) async\* {

final int counter = event.counter - 1;

yield UpdateCounterState(counter);

}
แล้วแก้
mapEventToState
ให้เรียกฟังก์ชั่นนี้เมื่อ event ที่ส่งมาเป็น
DecrementCounter
@override

Stream<CounterState> mapEventToState(

CounterEvent event,

) async\* {

if (event is IncrementCounter) {

yield\* _mapIncrementCountertoState(event);

}else if(event is DecrementCounter) {

yield\* _mapDecrementCountertoState(event);

}

}
หน้าตาจะออกมาตามนี้
import 'dart:async';
import 'package:bloc/bloc.dart';
import './counter.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
@override
CounterState get initialState {
return UpdateCounterState(0);
}
@override
Stream<CounterState> mapEventToState(
CounterEvent event,
) async* {
if (event is IncrementCounter) {
yield* _mapIncrementCountertoState(event);
}else if(event is DecrementCounter) {
yield* _mapDecrementCountertoState(event);
}
}
Stream<CounterState> _mapIncrementCountertoState(IncrementCounter event) async* {
final int counter = event.counter + 1;
yield UpdateCounterState(counter);
}
Stream<CounterState> _mapDecrementCountertoState(DecrementCounter event) async* {
final int counter = event.counter - 1;
yield UpdateCounterState(counter);
}
}
logic เสร็จแล้วก็กลับไปที่
main.dart
เพื่อเพิ่มปุ่มลดกัน
เพื่อให้เราสามารถใส่ FABs ได้หลายอัน เราก็จะเอา Column/Row Widget ไปครอบ
floatingActionButton: FloatingActionButton(

onPressed: () =>

counterBloc.dispatch(IncrementCounter(counter)),

tooltip: 'Increment',

child: Icon(Icons.add),

),
--> แก้เป็นด้านล่าง
floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end,

children: <Widget>[

FloatingActionButton(

onPressed: () =>

counterBloc.dispatch(IncrementCounter(counter)),

tooltip: 'Increment',

child: Icon(Icons.add),

),

Padding(padding: EdgeInsets.only(left: 20),), // เพิ่มช่องว่าระหว่างปุ่ม

FloatingActionButton(

onPressed: () =>

counterBloc.dispatch(DecrementCounter(counter)),  // ทำหน้าที่ส่ง DecrementCounter(counter) แทน

tooltip: 'Increment',

child: Icon(Icons.remove),

),

],

),
สรุปโค็ดได้ดังนี้
import 'package:flutter/material.dart';
import 'package:increment_app/bloc/counter.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(BlocProvider(
builder: (BuildContext context) => CounterBloc(),
child: MyApp(),
));
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
final counterBloc = BlocProvider.of<CounterBloc>(context);
return BlocBuilder(
bloc: counterBloc,
builder: (BuildContext context, CounterState state) {
final counter = (state as UpdateCounterState).counter;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'\$counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () =>
counterBloc.dispatch(IncrementCounter(counter)),
tooltip: 'Increment',
child: Icon(Icons.add),
),
Padding(padding: EdgeInsets.only(left: 20),), // เพิ่มช่องว่าระหว่างปุ่ม
FloatingActionButton(
onPressed: () =>
counterBloc.dispatch(DecrementCounter(counter)), // ทำหน้าที่ส่ง DecrementCounter(counter) แทน
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
],
),
);
});
}
}
view raw main.dart hosted with ❤ by GitHub
เมื่อลองรันอีกรอบ แอปก็จะออกมาหน้าตาดังนี้
คงต้องจบ BLoC Pattern สำหรับ Noob ไว้เท่านี้ก่อน แล้วเราจะมาต่อกันที่ การนำ Bloc มาใช้กับแอปที่มีหลายหน้า เพื่อให้ตอบโจทย์โลกความเป็นจริงด้วย
ถ้าเขียนเสร็จแล้ว จะเอาลิงค์มาลงไว้ตรงนี้
หวังว่า medium อันนี้พอจะช่วยให้หลายๆ คนเข้าใจ BLoC Pattern มากขึ้น ถ้ามีความเห็นอะไร ก็คอมเม้นลงมาข้างล่างได้เลย

แนะนำเรื่องถัดไป