一個應用內一般會有兩種數據,部件內部的使用的臨時性數據以及不少部件使用的全局性數據,部件內部使用的數據能夠經過 StatefulWidget 來管理,可是全局性的數據若是經過從上到下傳遞的方式會使代碼寫的十分繁瑣,這時就須要一個狀態管理工具來進行管理了,本文說明如何使用 Provider 來管理這種應用的全局性的數據git
官方的定義是: A mixture between dependency injection (DI) and state management, built with widgets for widgets. 翻譯過來大意是一種依賴注入和狀態管理的混合方案,使用部件建立,做用於部件 😅github
官方文檔數組
應用中一般會有一些不少部件都須要的數據,如用戶的登陸信息,用戶設置,地理位置等,若是隻是使用 StatefullWeight 的話就須要將狀態提高到一個父部件中而後向下進行傳遞,會很繁瑣,使用 provider 的話能夠將對一種狀態數據的操做放到一個文件內,而後使用到這個數據的部件只須要使用就能夠了,當數據有變化時,部件會自動的從新構建,使界面更新。app
使用一個 todo 應用來講明如何在 Flutter 應用中使用 Provider,最終的完成的應用是這樣的,能夠新增,編輯和刪除 todo。less
源碼地址ide
首先使用命令行建立一個項目工具
flutter create flutter_provider_todos
複製代碼
而後在項目的 pubspec.yml 添加 providerpost
dependencies:
provider: ^3.1.0
複製代碼
建立一個 store 文件夾以及 todos.dart 用來存放應用中須要用到的全局性數據,新建一個 widget 目錄,用來存放應用中的部件以及一個顯示 todo 的頁面 todos_page.dartui
首先建立 todos 這個全局性的數據,修改 store/todos.dart,建立一個 Todo 類表示一個代辦事項,而後實現 Todos 類, Todos 混合了 ChangeNotifier 類,爲了使用 notifyListeners 方法來通知 UI 更新,所以須要導入 foundation.dart,Todos 類使用一個 _items 數組存放 Todo 數據,以及其它對 Todo 進行操做的方法。this
import 'package:flutter/foundation.dart';
class Todo {
bool finish;
String thing;
Todo({
@required this.thing,
this.finish = false,
});
}
class Todos extends ChangeNotifier {
List<Todo> _items = [
Todo(thing: 'Play lol', finish: true),
Todo(thing: 'Learn flutter', finish: false),
Todo(thing: 'Read book', finish: false),
Todo(thing: 'Watch anime', finish: false),
];
get items {
return [..._items];
}
get finishTodos {
return _items.where((todo) => todo.finish);
}
void refresh() {
notifyListeners();
}
void addTodo(Todo todo) {
_items.insert(0, todo);
refresh();
}
void removeTodo(int index) {
_items.removeAt(index);
refresh();
}
void editTodo(int index, String newThing, bool isFinish) {
Todo todo = _items[index];
todo.thing = newThing;
todo.finish = isFinish;
refresh();
}
void toggleFinish(int index) {
final todo = _items[index];
todo.finish = !todo.finish;
refresh();
}
bool isTodoExist(String thing) {
bool isExist = false;
for (var i = 0; i < _items.length; i++) {
final todo = _items[i];
if (todo.thing == thing) {
isExist = true;
}
}
return isExist;
}
}
複製代碼
而後使用 provider 提供的 ChangeNotifierProvider 方法將數據註冊到整個應用,若是有多個數據就須要使用 MultiProvider 方法
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'todos_page.dart';
import 'store/todos.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todos',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.green,
),
home: ChangeNotifierProvider(
builder: (context) => Todos(),
child: TodosPage(),
),
);
}
}
複製代碼
接着就是實現顯示 todo 列表的頁面,這個頁面就是要用到 Todos 類裏面的數據的部件,要使用 provider 的數據首先要導入 provider 以及對應的數據類 Todos,而後用 Consumer 加類型 Todos 來使用這個數據
Consumer<Todos>(
builder: (ctx, todos, child) {
return YourWidget()
},
)
複製代碼
這個頁面使用了一個 ListView.builder() 來渲染 Todos,而後每一項使用一個 ListTile 展現。新增,編輯和刪除對應了 3 個不一樣的部件,分別是 AddTodoButton(),EditTodoButton(), RemoveTodoButton()
// todos_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'store/todos.dart';
import 'widget/add_todo_button.dart';
import 'widget/edit_todo_button.dart';
import 'widget/remove_todo_button.dart';
class TodosPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Provider Todos')),
body: Consumer<Todos>(
builder: (ctx, todos, child) {
List<Todo> items = todos.items;
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) => Column(
children: <Widget>[
ListTile(
title: Text(
items[index].thing,
style: TextStyle(
color: items[index].finish ? Colors.green : Colors.grey,
),
),
trailing: Container(
width: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
EditTodoButton(todoIndex: index),
RemoveTodoButton(todoIndex: index),
],
),
),
),
Divider(),
],
),
);
},
),
floatingActionButton: AddTodoButton(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
複製代碼
接下來就是要實現這 3 個按鈕了,在 widget 目錄建立對應的文件,每一個按鈕都會使用到 Todos 類裏面定義的方法,因此都須要導入 provider 和 Todos 類,點擊按鈕會彈出一個對話框詢問對應的操做,
添加 Todo 按鈕
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../store/todos.dart';
class AddTodoButton extends StatefulWidget {
@override
_AddTodoButtonState createState() => _AddTodoButtonState();
}
class _AddTodoButtonState extends State<AddTodoButton> {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
@override
void dispose() {
_formKey.currentState.dispose();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<Todos>(
builder: (_, todos, child) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('add todo');
return showDialog(
context: context,
builder: (BuildContext _) {
return SimpleDialog(
title: Text('添加 Todo'),
contentPadding: const EdgeInsets.all(24.0),
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
autovalidate: false,
controller: _controller,
keyboardType: TextInputType.text,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '輸入你想作的事',
),
validator: (val) {
if (val.isEmpty) {
return '想作的事不能爲空';
}
bool isExist = todos.isTodoExist(val);
if (isExist) {
return '這件事情已經存在了';
}
return null;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () {
Navigator.pop(context);
},
),
RaisedButton(
child: Text(
'肯定',
style: TextStyle(color: Colors.white),
),
color: Theme.of(context).primaryColor,
onPressed: () {
final isValid =
_formKey.currentState.validate();
if (!isValid) {
return;
}
final thing = _controller.value.text;
todos.addTodo(Todo(
thing: thing,
finish: false,
));
_controller.clear();
Navigator.pop(context);
},
)
],
),
],
),
),
],
);
},
);
},
);
},
);
}
}
複製代碼
編輯 Todo 按鈕
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../store/todos.dart';
class EditTodoButton extends StatefulWidget {
final todoIndex;
const EditTodoButton({Key key, this.todoIndex}) : super(key: key);
@override
_EditTodoButtonState createState() => _EditTodoButtonState();
}
class _EditTodoButtonState extends State<EditTodoButton> {
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_formKey?.currentState?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<Todos>(
builder: (context, todos, child) {
final todoIndex = widget.todoIndex;
final Todo todo = todos.items[todoIndex];
return IconButton(
color: Colors.blue,
icon: Icon(Icons.edit),
onPressed: () {
return showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: Text('編輯 Todo'),
contentPadding: const EdgeInsets.all(24.0),
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: false,
autovalidate: false,
initialValue: todo.thing,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '輸入你想作的事',
),
onChanged: (val) {
todo.thing = val;
},
validator: (val) {
if (val.isEmpty) {
return '想作的事不能爲空';
}
return null;
},
),
SizedBox(height: 20),
SwitchListTile(
title: const Text('是否完成'),
value: todo.finish,
onChanged: (bool value) {
todo.finish = value;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () => Navigator.pop(context),
),
RaisedButton(
child: Text(
'肯定',
style: TextStyle(color: Colors.white),
),
color: Theme.of(context).primaryColor,
onPressed: () {
final isValid =
_formKey.currentState.validate();
if (!isValid) {
return;
}
Navigator.pop(context);
todos.editTodo(
todoIndex,
todo.thing,
todo.finish,
);
},
)
],
),
],
),
),
],
);
},
);
},
);
},
);
}
}
複製代碼
刪除 Todo 按鈕
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../store/todos.dart';
class RemoveTodoButton extends StatelessWidget {
final int todoIndex;
const RemoveTodoButton({Key key, this.todoIndex}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer<Todos>(builder: (_, todos, child) {
final Todo todo = todos.items[todoIndex];
return IconButton(
color: Colors.red,
icon: Icon(Icons.delete),
onPressed: () {
print('delete todo');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('確認刪除 ${todo.thing}?'),
actions: <Widget>[
FlatButton(
child: Text(
'取消',
style: TextStyle(color: Colors.grey),
),
onPressed: () => Navigator.pop(context),
),
FlatButton(
child: Text('確認'),
onPressed: () {
todos.removeTodo(todoIndex);
Navigator.pop(context);
},
),
],
);
},
);
},
);
});
}
}
複製代碼
能夠看到要使用對應的方法須要的只是向對應的部件注入這個數據,而後使用就能夠了
使用了 provider 後,數據以及對一個 Todo 的操做都放在一個文件裏面了,不用在多個層級間傳遞數據,而且在數據變化時自動更新了 UI,因此是十分有必要的。