Welcome to Our Website

Flutter with Provider State management

flutter with provider

In this blog we’ll take a look at the provider approach in Flutter. The provider pattern is far easier to learn and has much less boilerplate code as compared to BLoC pattern.

In this post, i’ll take demo of todo app and use the provider pattern on it. In this demo we can add todo task and later device them in three section like i)All todo list, ii)Completed todo list iii) In-Completed todo list.

You can get source code from Github.

I have also post blog on State Management in Flutter with GetX previously.

Benefits of Provider

The provider pattern in Flutter will look for the latest value provided. The diagram below will help you better understand.

provider working flow

In this diagram the GREEN object A will be available to the rest of the elements below it, that is B, C, D, E, and F.

Getting Started

Create a new Flutter project and name it whatever you want.

Now add the provider dependency for the provider pattern in the pubspec.yaml file.

dependencies:
  flutter:
    sdk: flutter
  provider: latest_version

I have considered following architecture

Models

Below is the code to create Todo Model class

class Todo{
  String title;
  bool completed;

  Todo({ this.completed = false, @required this.title});

  void toggleCompleted() {
    completed = !completed;
  }

}

Provider

I have created todo_notifier.dart file in which i have added business logic for todo app

class TodoNotifier with ChangeNotifier{
  List<Todo> _todos = [
    Todo(title: 'Todo One'),
    Todo(title: 'Todo Two'),
    Todo(title: 'Todo Three'),
    Todo(title: 'Todo four'),
  ];

  UnmodifiableListView<Todo> get allTodos => UnmodifiableListView(_todos);

  UnmodifiableListView<Todo> get inCompleteTodos => UnmodifiableListView(_todos.where((Todo todo) {
    return todo.completed == false;
  }));

  UnmodifiableListView<Todo> get completeTodos => UnmodifiableListView(_todos.where((Todo todo) {
    return todo.completed == true;
  }));

  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners();
  }

  void deleteTodo(Todo todo) {
    _todos.remove(todo);
    notifyListeners();
  }

  void toggleTodo(Todo todo) {
    final todoIndex = _todos.indexOf(todo);

    _todos[todoIndex].toggleCompleted();
    notifyListeners();
  }

}

Pages

Now we will make dependency injection to UI screen with help of ChangeNotifierProvider widget. Below is the code in main.dart file for dependency injection of TodoNotifier in main

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      builder: (context) => TodoNotifier(),
      child: MaterialApp(
        title: 'Flutter Todo',
        theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.light),
        home: MyHomePage(
          title: 'Todo App',
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
          appBar: AppBar(
            actions: <Widget>[
              IconButton(
                onPressed: () {
                  Navigator.push(context,
                      MaterialPageRoute(builder: (context) => AddTodoPage()));
                },
                icon: Icon(Icons.add),
              ),
            ],
            bottom: TabBar(
              tabs: [
                Tab(
                  text: 'All',
                ),
                Tab(
                  text: 'Complete',
                ),
                Tab(
                  text: 'In Complete',
                ),
              ],
            ),
            title: Text(widget.title),
          ),
          body: TabBarView(
            children: [
              AllTodos(),
              CompleteTodos(),
              InCompleteTodos(),
            ],
          )
          // This trailing comma makes auto-formatting nicer for build methods.
          ),
    );
  }
}

the file add_todo_page.dart is created for adding new task in todo list. In below example we access notifier with Provider.of<TodoNotifier> passing argument current context and listen: false , listen false as a result we do not need to update current button for adding task.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todos/models/todo_model.dart';
import 'package:todos/providers/todos_notifier.dart';

class AddTodoPage extends StatefulWidget {
  @override
  _AddTodoPageState createState() => _AddTodoPageState();
}

class _AddTodoPageState extends State<AddTodoPage> {
  final todoTextController = TextEditingController();
  bool completeStatus = false;

  @override
  void dispose() {
    todoTextController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Add new Todo"),
      ),
      body: ListView(
        children: <Widget>[
          Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              children: <Widget>[
                TextField(
                  controller: todoTextController,
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'Todo title',
                  ),
                ),
                CheckboxListTile(
                  value: completeStatus,
                  onChanged: (bool checked) {
                    setState(() {
                      completeStatus = checked;
                    });
                  },
                  title: Text('Complete?'),
                ),
                ElevatedButton(
                  onPressed: onAdd,
                  child: Text("Add"),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }

  void onAdd() {
    final String todoTitle = todoTextController.text;
    final bool complete = completeStatus;

    if(todoTitle.isNotEmpty){
      final Todo todo = Todo(title: todoTitle, completed: complete);
      Provider.of<TodoNotifier>(context,listen: false).addTodo(todo);
    }
    Navigator.pop(context);
  }
}

Now we display all task in list for that i have created all_todos_page.dart in which all task has been listed weather it is compete or In-complete.

class AllTodos extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final allTodos = Provider.of<TodoNotifier>(context).allTodos;
    return Container(
      child: TodoListView(todos: allTodos,),
    );
  }
}

Accordingly the completed task list in complite_todos_page.dart

class CompleteTodos extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final List<Todo> completeTodos = Provider.of<TodoNotifier>(context).completeTodos;

    return Container(
        child: TodoListView(
          todos: completeTodos,
        )
    );
  }
}

Moreover the In-completed task list in incompelet_todos_page.dart

class InCompleteTodos extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final List<Todo> inCompleteTodos = Provider.of<TodoNotifier>(context).inCompleteTodos;

    return Container(
      child: TodoListView(
        todos: inCompleteTodos,
      )
    );
  }
}

Now the common TodoListView used in all three list in todo_list_view.dart

class TodoListView extends StatelessWidget {
  final List<Todo> todos;

  const TodoListView({Key key, this.todos}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
        itemBuilder: (BuildContext context, int index) => ListTile(
          title: Text(todos[index].title),
          leading: Checkbox(
              value: todos[index].completed,
              onChanged: (bool checked) {
                Provider.of<TodoNotifier>(context,listen: false).toggleTodo(todos[index]);
              }),
          trailing: IconButton(
              icon: Icon(
                Icons.delete,
                color: Colors.red,
              ),
              onPressed: () {
                Provider.of<TodoNotifier>(context,listen: false).deleteTodo(todos[index]);
              }),
        ),
        itemCount: todos.length,
      ),
    );
  }
}

In above code you can notice that we access notifier with Provider.of<TodoNotifier> to toggle and delete item from the list and in this way we can manage state in app with help of Provider.

Afterwards when you run app you can see once added a task can be able to access in all widgets and can also be manipulated from any widget.


Hope you like the concept of provider in flutter. As compared to other other state management approach this is quire easy to implement.

Leave a Reply

Your email address will not be published. Required fields are marked *