preface
There are usually two types of data within an application: temporary data used within a widget and global data used by many widgets. Data used within a widget can be managed through StatefulWidget, but global data can be cumbersome to write if passed from top to bottom. This requires a state management tool to manage, this article explains how to use the Provider to manage the global data of this application
What is the Provider
The official definition is: A mixture between dependency injection (DI) and state management, built with widgets for widgets. A mixture of dependency injection and state management, created using parts, for parts 😅
The official documentation
Why use providers
Applications often have data that many widgets need, such as user login information, user Settings, location, etc. If you just use StatefullWeight, you have to push the state to a parent widget and pass it down, which can be tedious. With providers, you can put operations on a state data in a file, and then the component that uses the data just needs to use it. When the data changes, the component automatically rebuilds to update the interface.
An example 🌰
Use a Todo application to illustrate how to use providers in the Flutter application. The final application looks like this: ToDo can be added, edited and deleted.
The source address
Create an
Start by creating a project using the command line
flutter create flutter_provider_todos
Copy the code
Then add the provider to the project’s pubspec.yml
dependencies:
provider: ^ 3.1.0
Copy the code
Dart creates a store folder and todos.dart to store global data needed in your application, a widget directory to store widgets in your application and a page todos_page.dart to display toDO
Dart, create a Todo class to represent an action. Then implement the Todos class. Todos is a mixture of the ChangeNotifier class. To use notifyListeners to notify UI updates, you need to import foundation.dart, the Todos class uses a _items array to store Todo data, and other methods of manipulating Todo.
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) {
finaltodo = _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; }}returnisExist; }}Copy the code
Then use the ChangeNotifierProvider method provided by the provider to register the data with the entire application. If there are multiple data, use the MultiProvider method
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(), ), ); }}Copy the code
List page
The next step is to implement the page that displays the toDO list. This page is the part that uses the data in the Todos class. To use the data of the Provider, the first step is to import the provider and the corresponding data class Todos. This data is then used with the Consumer plus type Todos
Consumer<Todos>(
builder: (ctx, todos, child) {
return YourWidget()
},
)
Copy the code
The page uses a ListView.Builder () to render Todos and then displays each item using a ListTile. 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, ); }}Copy the code
Realize the function
Create the corresponding files in the Widget directory. Each button uses the methods defined in the Todos class, so import the Provider and Todos classes. Click the button and a dialog box will pop up asking for the corresponding actions.
Add the Todo button
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('add 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: 'Type in what you want to do',
),
validator: (val) {
if (val.isEmpty) {
return 'What you want to do can't be idle.';
}
bool isExist = todos.isTodoExist(val);
if (isExist) {
return 'This thing already exists.';
}
return null;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('cancel'),
onPressed: () {
Navigator.pop(context);
},
),
RaisedButton(
child: Text(
'sure',
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); },) [,], [, [, [,], [, [,]; }); }); }); }}Copy the code
Edit the Todo button
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
voiddispose() { _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('edit 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: 'Type in what you want to do',
),
onChanged: (val) {
todo.thing = val;
},
validator: (val) {
if (val.isEmpty) {
return 'What you want to do can't be idle.';
}
return null;
},
),
SizedBox(height: 20),
SwitchListTile(
title: const Text('Done or not'),
value: todo.finish,
onChanged: (bool value) {
todo.finish = value;
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
child: Text('cancel'),
onPressed: () => Navigator.pop(context),
),
RaisedButton(
child: Text(
'sure',
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, ); },) [,], [, [, [,], [, [,]; }); }); }); }}Copy the code
Remove the Todo button
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('Confirm deletion${todo.thing}? '),
actions: <Widget>[
FlatButton(
child: Text(
'cancel',
style: TextStyle(color: Colors.grey),
),
onPressed: () => Navigator.pop(context),
),
FlatButton(
child: Text('confirm'), onPressed: () { todos.removeTodo(todoIndex); Navigator.pop(context); },),,); }); }); }); }}Copy the code
You can see that all you need to do to use the corresponding method is to inject the data into the corresponding part and then use it
conclusion
With providers, data and operations on a Todo are stored in a single file, data is not passed across multiple levels, and the UI is automatically updated when data changes, so it is necessary.
The original address
Managing the Flutter application State with Provider