NGHỆ THUẬT FLUTTER : CÁC CÁCH QUẢN LÝ STATE TRONG FLUTTER – Provider Basic
Provider trong Flutter là một trong những kiến thức cơ bản mà bất cứ lập trình viên Flutter nào cần phải được nắm vững.
Provider trong lập trình Flutter được coi như là một bản nâng cấp so với cách quản lý trạng thái : InheritedWidget mà tôi đã có bài viết trong chuyên đề nghệ thuật Flutter.
Provider được xây dựng cũng theo tư tưởng như InheritedWidget nhưng dễ sử dụng và rất nhiều tính năng ưu viêt như:
+ Phân bổ và xử lý tài nguyên đơn giản
+ lazy loading
+ Dễ dùng và thân thiện trong những project lớn và phức tạp
Ngoài ra, provider còn được tích hợp vào trong các cách quản lý trạng thái (State Management) khác như : flutter_bloc, Mobx
Nguyên lý
Provider được tạo ra dựa trên InheritedWidget, vì vậy nguyên lý của nó cũng tương tự,
Khi khai báo provider trên một Widget tổ tiên( cha, ông.. )-ancestor widget thì các Widget con cháu có thể sử dụng và cập nhật giá trị. Khi giá trị của model đăng kí provider thay đổi thì tất cả các Widget con cháu nào mà liên quan đến các thuộc tính của phần tử này sẽ được cập nhật lại.
Dưới đây tôi sẽ đưa ra một ví dụ: Tạo ra các widgets là các box và đặt đăng kí provider ở trước Widget cha. Các Widget con, có box sử dụng thuộc tính provider, có box thì không. Khi thay đổi giá trị các thuộc tính thì tất cả các Widget liên quan sẽ thay đổi.
Bắt đầu nào !
Tạo một Flutter project mới:
Cài đặt thư viện
Khai báo thư viện trên pubspec.yaml : provider
Ví dụ :
1 |
provider 4.3.2+2 |
Nhấn Pub get hoặc run trên Terminal : flutter packages get
Tạo một lớp Feature
Lớp này chứa các thuộc tính như : count, sử dụng ChangeNotifier để lắng nghe những sự thay đổi.
Class này chứa các phương thức và thuộc tính, đặt lắng nghe khi có sự thay đổi bằng việc sử dụng ChangeNotifier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class Feature with ChangeNotifier { int _count = 0; int get count => _count; /// Tăng số increaseCount() { _count ++; notifyListeners(); // khi thay đổi sẽ lắng nghe và thông báo đến các Widget liên quan } } |
2. Tạo các Widgets, khai báo provider và sử dụng các thuộc tính, phương thức của model trên.
* Khai báo Provider
Khai báo provider được thực hiện trước các Widget là Widget tổ tiên của các Widget con mà sử dụng các thuộc tính, phương thức của model mà đăng kí provider.
Thông thường đăng kí provider ở hàm main hay trong class đầu tiên của ứng dụng. Ví dụ :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void main(){ runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => Feature()), ], child:MaterialApp( home: Scaffold( body: Center( child: Container( child: Box1(), ), ), ), )); } } |
Từ đoạn code trên chúng ta biết được cách đăng kí Provider :
1 2 3 4 5 6 7 8 |
// dùng sau return và trước Widget .... return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => Feature()), ], child: // Widget nào đó |
* Tiếp theo là tạo các widget Box1, và Box21, Box22, Box23 là các Widget con của Box1 , các Widget : Box21, Box23 sẽ sử dụng thuộc tính của model : Feature thông qua provider còn Box22 thì không.
Do vậy khi thay đổi giá trị count ở Box1 thì Widget Box21, Box23 sẽ thay đổi
Các sử dụng thuộc tính:
* Dùng Watch khi lắng nghe để hiển thị
1 2 |
... context.watch<Feature>().count |
* Dùng read khi muốn dùng để thay đổi
1 2 3 4 5 6 |
// Tăng số context.read<Feature>().increaseCount(); // Thay đổi giá trị count ở Box1 context.read<Feature>().count = 5; |
Box1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider_base/feature.dart'; import 'box21.dart'; import 'box22.dart'; import 'box23.dart'; class Box1 extends StatefulWidget { @override _Box1State createState() => _Box1State(); } class _Box1State extends State<Box1> { @override Widget build(BuildContext context) { return Container( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Box21(), Box22(), Box23(), increaseNumber() ], ), ); } // Button of increasing Number increaseNumber() { return RaisedButton( child: Text("Tăng số", style: TextStyle(fontSize: 20),), onPressed: (){ context.read<Feature>().increaseCount(); }, ); } } |
Box21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import 'package:flutter/material.dart'; import 'package:provider_base/feature.dart'; import 'package:provider/provider.dart'; class Box21 extends StatefulWidget { @override _Box21State createState() => _Box21State(); } class _Box21State extends State<Box21> { @override Widget build(BuildContext context) { return Container( height: 100, width: 200, margin : EdgeInsets.only(top: 10), decoration: BoxDecoration( color: context.watch<Feature>().count%2==0 ? Colors.blue : Colors.red , ), child: Center( child: Text(context.watch<Feature>().count.toString(), style: TextStyle(color: Colors.white, fontSize: 20),), ) ); } } |
Box22
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import 'package:flutter/material.dart'; import 'package:provider_base/feature.dart'; import 'package:provider/provider.dart'; class Box22 extends StatefulWidget { @override _Box22State createState() => _Box22State(); } class _Box22State extends State<Box22> { @override Widget build(BuildContext context) { return Container( height: 100, width: 200, margin : EdgeInsets.only(top: 10), decoration: BoxDecoration( color: Colors.grey , ), child: Center( child: Text("Không dùng Provider", style: TextStyle(color: Colors.white, fontSize: 20),), ) ); } } |
Box23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import 'package:flutter/material.dart'; import 'package:provider_base/feature.dart'; import 'package:provider/provider.dart'; class Box23 extends StatefulWidget { @override _Box23State createState() => _Box23State(); } class _Box23State extends State<Box23> { @override Widget build(BuildContext context) { final provider = Provider.of<Feature>(context); return Container( height: 100, width: 200, margin : EdgeInsets.only(top: 10), decoration: BoxDecoration( color: context.watch<Feature>().count%2==0 ? Colors.blue : Colors.red , ), child: Center( child: Text(context.watch<Feature>().count.toString(), style: TextStyle(color: Colors.white, fontSize: 20),), ) ); } } |
Kết quả thì như hình bên trên.
Sourcecode của bài viết này trên Github.
Như vậy, các bạn đã biết các sử dụng provider một các cơ bản. Tóm lại bao gồm các bước sau:
+ Khai báo thư viện
+ Tạo model và đặt lắng nghe với ChangeNotifier
+ Khai báo provider ở các Widget cha
+ Sử dụng provider ở các Widget con.
Tiếp tục về Provider, tôi sẽ viết một bài viết nữa về advanced Provider, hi vọng được các bạn đón đọc
Chúc các bạn có được nhiều kiến thức từ blog của tôi !
anh cho em hỏi là khi nào dùng select(), khi nào dùng read() thì phù hợp ạ .
Dùng cả 2 cái đều được nhưng hiện thời anh thấy dùng read(), watch() là tiện nhất. Và có thể dùng hầu hết trường hợp
anh cho em hỏi về mấy hàm read(), với watch() , khi nào nên dùng read(), khi nào nên dùng watch() ạ?
Em có đang tìm hiểu và làm 1 ví dụ đơn giản về sate notifier và provider , em dùng select() để hiển thị state thay đổi của widget còn watch() cho việc update lại state cho widget.
Như bài viết thì em thấy hàm watch() là dùng để hiển thị state đã thay đổi, còn hàm read() dùng để thay đổi giá trị của state.
Anh có thể giải thích giúp em một chút được không ạ? em có đọc với doc của provider nhưng vẫn hơi khó hiểu cách dùng của 3 hàm này.