Professional Documents
Culture Documents
Trong bài trước chúng ta đã tìm hiểu cách quản lý trạng thái của widget bằng SatefullWidget, trong bài hôm
nay chúng ta tìm hiểu cách quản lý trạng thái trong ứng dụng bằng ScopedModel
Flutter cung cấp một phương pháp đơn giản để quản lý trạng thái của ứng dụng sử
dụng scoped_model package. Flutter package đơn giản là một thư viện với những phương thức được sử
dụng nhiều lần. Chúng ta sẽ tìm hiểu kỹ về package trong Flutter ở các bài học tới.
scoped_model cung cấp 3 class chính cho phép quản lý trạng thái của ứng dụng một cách mạnh mẽ:
Model
Model đóng gói trạng thái của một ứng dụng. Chúng ta có thể sử dụng nhiều Model (bằng việc kế thừa Model
class) để quản lý trạng thái của ứng dụng. Model có một phương thức duy nhất là notifyListeners, nó được
gọi bất cứ khi nào trạng thái của Model thay đổi. notifyListeners sẽ thực hiện các công việc cần thiết để cập
nhật giao diện.
ScopedModel là một widget, chúng ta hiểu đơn giản nó là một tiện ích để chúng ta có thể dễ dàng chuyển
Data Model từ widget cha xuống các widget con, cháu của nó. Ngoài ra nó còn có nhiệm vụ rebuild lại các
widget con giữ các model mà trong trường hợp model này được cập nhật. Nếu cần nhiều hơn một Data
Model thì chúng ta có thể sử dụng lồng ScopeModel. Dưới đây là hai dạng ScopedModel :
1
Single model :
ScopedModel<Product>(
model: item, child: AnyWidget()
)
Multiple model
ScopedModel<Product>(
model: item1,
child: ScopedModel<Product>(
model: item2, child: AnyWidget(),
),
)
ScopeModel.of là một phương thức dùng để lấy Data Model dưới ScopeModel. Và nó có thể được sử dụng
khi Data Model thay đổi kể cá khi giao diện (UI) không thay đổi. Dưới đây là ví dụ khi ta thay đổi UI( đánh giá )
của một sản phẩm
ScopedModel.of<Product>(context).updateRating(2);
ScopedModelDescendant
ScopedModelDescendant là một widget, nó lấy Data Model từ lớp cha và build lại UI bất kí khi nào Data
Model thay đổi.
ScopedModelDescendant có 2 thuộc tính là builder và child. Child là phần UI không bị thay đổi và sẽ được
chuyển cho hàm builder . Hàm buider sẽ nhận 3 đối số:
Child : Một phần của UI và không thay đổi dựa trên Data Model
return ScopedModelDescendant<ProductModel>(
builder: (context, child, cart) => { ... Actual UI ... },
child: PartOfTheUI(),
);
Bây giờ chúng ta sẽ sử dụng các ví dụ từ các bài học trước và sử dụng ScopeModel thay vì StatefulWidget :
Tạo một ứng dụng Flutter mới với tên project tùy ý bạn, ở đây tôi sẽ sử dụng tên
là product_scoped_model_app
Sau đó thay thế các dòng code mặc định trong hàm main.dart bằng product_state_app code nhé
Coppy các hình ảnh từ assets mà đã sử dụng trong các bài trước, tôi sẽ để link lại đây nhé
( https://vncoder.vn/bai-hoc/layout-trong-flutter-225 ) sau đó vào pubspec.yaml file tìm đến mục
assets và dán vào
flutter:
2
assets:
- assets/floppy.jpg
- assets/iphone.jpg
- assets/laptop.jpg
- assets/pendrive.jpg
- assets/pixel.jpg
- assets/tablet.jpg
Bây giờ chúng ta phải sử dụng gói thư viện của bên thứ ba vì nó không có trong Framework của flutter
Bây giờ bạn hãy thay thế đoạn code mặc định(main.dart) bằng đoạn code của chúng tôi nhé
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: Center(
child: Text( 'Hello World', )
),
3
);
}
}
Bạn nhớ import Scope_model vào main.dart nhé ^^
import 'package:scoped_model/scoped_model.dart';
Giờ chúng ta sẽ tạo lớp Product quen thuộc, Product.dart là lớp chứa thông tin của sản phẩm Product
import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
Chúng ta sẽ viết phương thức getProduct để tạo ra nội dung cho lớp Product
items.add(
Product(
"Pixel",
"Pixel is the most feature-full phone ever", 800,
"pixel.jpg", 0
)
4
);
items.add(
Product(
"Laptop", "Laptop is most productive development tool", 2000,
"laptop.jpg", 0
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting", 1500,
"tablet.jpg", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100, "pendrive.jpg", 0
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium", 20,
"floppy.jpg", 0
)
);
return items;
}
OK, tiếp tục chúng ta sẽ tạo một widget mới có tên là RatingBox và sử dụng Scope_model để support nhé
5
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 1
? Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
), color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
) : Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
6
),
),
],
);
}
}
ở đây chúng ta sử dụng StatelessWidget thay vì StatefulWidget.Cũng như vậy, chúng ta đã sử dụng phương
thức Product model’s updateRating để set giá trị phần đánh giá
tiếp theo chúng ta sẻ điều chỉnh widget ProductBox để làm việc với lớp Product, ScopedModel
và ScopedModelDescendant
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
7
)
]
),
)
);
}
}
Các bạn có thể thấy chúng ta đã đóng gói widget RatingBox bên trong ScopedModel
và ScopedModelDecendant
Bây giờ chúng ta sẽ thay đổi widget MyHomePage để sử dụng widget ProductBox như sau
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
)
);
}
}
Ở đây chúng ta đã sử dụng ListView.builder để xây dựng động danh sách Product
Vậy là xong, dưới đây là toàn bộ code được sử dụng :
Product.dart
import 'package:scoped_model/scoped_model.dart';
class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;
8
json['price'],
json['image'],
json['rating'],
);n
} void cn "Laptop is most productive development tool", 2000, "laptop.jpg", 0));
items.add(
Product(
"Tablet"cnvn,
"Tablet is the most useful device ever for meeting", 1500,
"tablet.jpg", 0
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium", 100,
"pendrive.jpg", 0
)
);
items.add(
Product(
"Floppy Drive",
"Floppy drive is useful rescue storage medium", 20,
"floppy.jpg", 0
)
)
; return items;
}
main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'Product.dart';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product state demo home page'),
9
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ProductBox(item: items[index]);
},
)
);
}
}
class RatingBox extends StatelessWidget {
RatingBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
double _size = 20;
print(item.rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 1? Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(1),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
10
child: IconButton(
icon: (item.rating >= 2
? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(2),
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
item.rating >= 3 ?
Icon( Icons.star, size: _size, )
: Icon( Icons.star_border, size: _size, )
),
color: Colors.red[500],
onPressed: () => this.item.updateRating(3),
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/" + this.item.image),
Expanded(
11
child: Container(
padding: EdgeInsets.all(5),
child: ScopedModel<Product>(
model: this.item, child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
this.item.name, style: TextStyle(
fontWeight: FontWeight.bold
)
),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
ScopedModelDescendant<Product>(
builder: (context, child, item) {
return RatingBox(item: item);
}
)
],
)
)
)
)
]
),
)
);
}
}
12
Bài 12: Navigator và Routing - Học lập trình Flutter cơ bản
Như chúng ta đã biết, trong Flutter những gì chúng ta nhìn thấy được gọi là các widget. Navigator cũng là một
widget có chức năng quản lý các trang của ứng dụng theo định dạng giống như ngăn xếp. Trong bất kì ứng
dụng nào, việc điều hướng từ một Full-screen ( page/screen ) để làm một công việc xác định nào đó ( chuyển
sang một Full-screen khác) sử dụng Navigator widget thì được gọi là Routing. Flutter cung cấp cho chúng ta
lớp Routing cơ bản - MaterialPageRoute với hai phương thức - Navigator.push và Navigator.pop . Hôm nay
chúng ta hãy cùng nhau tìm hiểu kĩ về chức năng không thể thiểu trong lập trình Flutter này nhé !
MaterialPageRoute
Đây là một widget được sử dụng để render giao diện người dùng nhằm thay thế toàn bộ màn hình với một
hiệu ứng chuyển đặc biệt nào đó
Tại đây, hàm buider sẽ chấp nhận chức năng để xây dựng nội dung bằng cách thay thế context hiện tại của
ứng dụng
Navigation.push
Từ một màn hình bất kì, ta muốn chuyển sang một màn hình khác sử dụng MaterialPageRoute widget như
sau :
Navigation.pop
Được sử dụng để quay về trang trước, các sử dụng đơn giản như sau :
Navigator.pop(context);
Để hiểu sâu hơn về Navigator chúng ta sẽ bắt tay vào xây dựng một ứng dụng đơn giản;. Let get started !!!
Tạo một ứng dụng Flutte mới với tên bất kì bạn muốn.Hãy copy thư mục assets từ product_nav_app sang
product_state_app và thêm assets vào file pubspec.yaml.( Hoặc các bạn có thể qua bài học này để lấy hình
ảnh nhé https://vncoder.vn/bai-hoc/layout-trong-flutter-225)
assets:
- assets/floppydisk.jpg
- assets/iphone.jpg
- assets/laptop.jpg
- assets/pendrive.jpg
- assets/pixel.jpg
- assets/tablet.jpg
13
Thay thế hàm main.dart mặc định bằng hàm main dưới đây
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
14
}
Tiếp đến chúng ta sẽ tạo phương thức getProduct từ lớp Product
items.add(
Product(
"Pixel",
"Pixel is the most feature-full phone ever", 800,
"pixel.png"
)
);
items.add(
Product(
"Laptop",
"Laptop is most productive development tool",
2000, "
laptop.png"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.png"
)
);
items.add(
Product(
"Pendrive",
"Pendrive is useful storage medium",
100,
"pendrive.png"
)
);
items.add(
Product(
"Floppy Drive",
15
"Floppy drive is useful rescue storage medium",
20,
"floppy.png"
)
);
return items;
}
';
Bây giờ chúng ta sẽ tạo một widget mới có tên là RatingBox dùng để đánh giá sản phẩm.
16
icon: (
_rating >= 1?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(
Icons.star,
17
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
Chúng ta sẽ tạo một ProductBox widget là một item trong list Product dùng để hiển thị thông tin và đánh giá
sản phẩm.
18
RatingBox(),
],
)
)
)
]
),
)
);
}
}
Tiếp tục, ta sẽ viết hàm có tên là MyHomePage widget để hiển thị toàn bộ danh sách Product và ta sẽ xử
dụng ListView.
@override
Widget build(BuildContext context) {
return Scaffold( appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
));
}
}
OKE, bây giờ là phần quan trọng trong bài hôm nay. Bây giờ chúng ta sẽ sử dụng MaterialPageRoute để
chuyển sang screen chi tiết sản phẩm nhé.
19
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/appimages/" + this.item.image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
this.item.name, style: TextStyle(
fontWeight: FontWeight.bold
)
),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)
)
)
]
),
),
),
);
}
}
20
Và chúng ta đã hoàn thành, dưới đây là toàn bộ code trong ứng dụng.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Product {
final String name;
final String description;
final int price;
final String image;
Product(this.name, this.description, this.price, this.image);
21
100,
"pendrive.jpg"
)
);
items.add(
Product(
"Floppy Drive",
"iPhone is the stylist phone ever",
20,
"floppydrive.jpg"
)
);
items.add(
Product(
"iPhone",
"iPhone is the stylist phone ever",
1000,
"iphone.jpg"
)
);
return items;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product Navigation demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
final items = Product.getProducts();
@override
22
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Navigation")),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ProductBox(item: items[index]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductPage(item: items[index]),
),
);
},
);
},
)
);
}
}
class ProductPage extends StatelessWidget {
ProductPage({Key key, this.item}) : super(key: key);
final Product item;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.item.name),
),
body: Center(
child: Container(
padding: EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.asset("assets/" + this.item.image,height: 100.0,width: MediaQuery.of(context).size.width,),
Expanded(
child: Container(
23
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.item.name, style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)
)
)
]
),
),
),
);
}
}
class RatingBox extends StatefulWidget {
@override
_RatingBoxState createState() => _RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
int _rating = 0;
void _setRatingAsOne() {
setState(() {
_rating = 1;
});
}
void _setRatingAsTwo() {
setState(() {
_rating = 2;
});
}
void _setRatingAsThree() {
setState(() {
_rating = 3;
});
}
Widget build(BuildContext context) {
double _size = 20;
24
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 1 ? Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsOne,
iconSize: _size,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 2 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsTwo,
iconSize: _size,
),
25
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (
_rating >= 3 ?
Icon(
Icons.star,
size: _size,
)
: Icon(
Icons.star_border,
size: _size,
)
),
color: Colors.red[500],
onPressed: _setRatingAsThree,
iconSize: _size,
),
),
],
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.item}) : super(key: key);
final Product item;
26
children: <Widget>[
Text(this.item.name, style: TextStyle(fontWeight: FontWeight.bold)),
Text(this.item.description),
Text("Price: " + this.item.price.toString()),
RatingBox(),
],
)
)
)
]
),
)
);
}
}
Sau khi hoàn thành, chúng ta sẽ run ứng dụng lên và click vào bất kì Product item nào, nó sẽ hiển thị các
trang chi tiết về sản phẩm liên quan và chúng ta có thể trở về màn hình HomePage bằng cách nhấn vào nút
quay lại. Dưới đây là hình ảnh để các bạn có thể dễ dàng quan sát hơn
Như vậy hôm nay chúng ta đã tìm hiểu cơ bản các khái niệm về Navigator , Routing và biết cách triển khai nó.
Cảm ơn các bạn đã theo dõi và chúc các bạn học tốt
27