You are on page 1of 27

Bài 11: ScopedModel trong Flutter - Học lập trình Flutter cơ bản

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.

Ví dụ ta tạo một model là Product


class Product extends Model {
final String name;
final String description;
final int price;
final String image;
int rating;

Product(this.name, this.description, this.price, this.image, this.rating);


factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating; notifyListeners();
}
}
ScopedModel

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ố:

Content :ScopedModelDescendant chuyển sang context của ứng dụng

Child : Một phần của UI và không thay đổi dựa trên Data Model

Model : Dưới đây là ví dụ tường minh 

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ằng cách thêm Scope_model vào pubspec.yaml ởphần dependencies

dependencies: scoped_model: ^1.0.1


Oke, bạn nên sử dụng version mới nhất nhé, cập nhật tại
đây : https://pub.dev/packages/scoped_model

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());

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 state demo home page'),
);
}
}
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: 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;

Product(this.name, this.description, this.price, this.image, this.rating);


factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],
json['price'],
json['image'],
json['rating'],
);
}
void updateRating(int myRating) {
rating = myRating;
notifyListeners();
}
}
Tốt rồi, giờ chúng ta sẽ sử dụng thuộc tính notifyListeners để lắng nghe sự thay đổi của UI khi mà người dùng
đánh giá

Chúng ta sẽ viết phương thức getProduct để tạo ra nội dung cho lớp Product

import product.dart in main.dart


import 'Product.dart';
static List<Product> getProducts() {
List<Product> items = <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é 

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(

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;

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(
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); }
)
],
)
)
)

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;

Product(this.name, this.description, this.price, this.image, this.rating);


factory Product.fromMap(Map<String, dynamic> json) {
return Product(
json['name'],
json['description'],

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';

void main() => runApp(MyApp());


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 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

MaterialPageRoute(builder: (context) => Widget())

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 :

Navigator.push( context, MaterialPageRoute(builder: (context) => Widget()), );

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 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 state demo home page'
),
);
}
}
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: Center(
child: Text('Hello World',)
),
);
}
}
Tiếp theo chúng ta sẽ tạo một lớp Product như sau :

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

import product.dart in main.dart


import 'Product.dart
static List<Product> getProducts() {
List<Product> items = <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.

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;
print(_rating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(

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.

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/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()),

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.

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 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é.

class ProductPage extends StatelessWidget {

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);

static List<Product> getProducts() {


List<Product> items = <Product>[];
items.add(
Product(
"Pixel",
"Pixel is the most featureful phone ever",
800,
"pixel.jpg"
)
);
items.add(
Product(
"Laptop",
"Laptop is most productive development tool",
2000,
"laptop.jpg"
)
);
items.add(
Product(
"Tablet",
"Tablet is the most useful device ever for meeting",
1500,
"tablet.jpg"
)
);
items.add(
Product(
"Pendrive",
"iPhone is the stylist phone ever",

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;

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(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,

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

You might also like