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