Flutter - Using Nested Models and Providers - GeeksforGeeks

24 Jan, 2021
The provider is a simple state management technique that is used for managing a piece of data around the app. It is basically a wrapper around inherited widgets but it is easy to understand and manage.
Before going ahead we have to understand some concepts in provider –
ChangeNotifier: It is basically a simple class that is used for notifying any changes that happen to a given model to the part of the app that is listening for them. We can describe them as a subscription service. i.e. any changes to data get updated to all the parts of the app that are subscribed to it.
ChangeNotifierProvider: It is a widget available in the provider package that provides an instance of the ChangeNotifier to its children. It is basically placed above all the package that needs the data of the model.
Consumer: We can use the data of the model via Consumer as the ChangeNotfierProvider is present as the parent of it. We have to specify the generics (<TaskData> here) so that the provider knows which data to fetch. It has one argument builder. Builder is a function that is called when there is a change in data. It has three arguments context which is the current context of the builder, then the instance of ChangeNotifier (here data), and the third is a child which is used for large widget for optimization purpose.
Syntax:
return Consumer<TaskData>(
  builder: (context, data, child) {
    return Text(data.task);
  },
);
Now that we have covered the basic concepts of Provider. Now we will understand each of these topics with the help of a sample app. We will be building a sample task management app.
First, we need to add the provider package to the dependencies section of pubspec.yaml.
dependencies:
  flutter:
    sdk: flutter
  provider: ^4.3.2+4 #ADD
We will create a model directory inside the lib directory and add task.dart and task_data.dart file inside it.
Dart
import 'package:flutter/foundation.dart';  class Task {  String task;  bool completed;  Task({@required this.task, this.completed = false});  void toggle() {    completed = !completed;  }}
The task.dart file has a class Task with two fields. 
String task which stores the name of the task to be displayed on the app when added.bool completed which will toggle according to the task as it is completed or not.
String task which stores the name of the task to be displayed on the app when added.
bool completed which will toggle according to the task as it is completed or not.
There is also a toggle function that switches the tasks from true to false and vice-versa.
Dart
import 'dart:collection';import 'package:flutter/foundation.dart';import 'package:task_management/model/task.dart';  class TaskData with ChangeNotifier {  List<Task> _tasks = [];  UnmodifiableListView<Task> get tasks => UnmodifiableListView(_tasks);    void addTask(Task task) {    _tasks.add(task);    notifyListeners();  }    void toggleTask(Task task) {    task.toggle();    notifyListeners();  }    void removeTask(Task task) {    _tasks.remove(task);    notifyListeners();  }}
The task_data.dart file has the TaskData class which is used to manage the data. We define this class with ChangeNotifier to make the data available to the parts of the app that are listening for it. The components of the class are
List of Task (_tasks) which is a private member so that the data cant be changed from outside the app.
There is an unmodifiable version of that list (tasks) so that the data is available for use inside the app.
The addTask method is used to add a new task to the app.
The toggleTask is used to change the task from completed to non-completed and vice-versa.
There is also a removeTask to remove a particular task from the list of tasks.
There is a notifyListeners() call at the end of each method so that listeners of the data get notified when data changes.
We are going to add the ChangeNotifierProvider in the main.dart above all the widgets. As our app is quite simple with just two screens we have to place it in the main.dart so that it is available to its descendants. The main.dart is as follows-
Dart
import 'package:flutter/material.dart';import 'package:provider/provider.dart';  import 'model/task_data.dart';import 'screens/home.dart';  void main() {  runApp(MyApp());}  class MyApp extends StatelessWidget {      // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider(      create: (context) => TaskData(),      child: MaterialApp(        debugShowCheckedModeBanner: false,        title: 'Provider Demo',        theme: ThemeData(          primarySwatch: Colors.green,        ),        home: Home(),      ),    );  }}
The MaterialApp widget is the child of ChangeNotifierProvider which takes a create field to get the instance of TaskData. We create two screens after this which are defined in the screens directory which is defined in the lib directory and two files i.e. home.dart and add_task.dart in it.
The Home screen is used to display the content of the List of tasks
Add Task Screen which is used to add a new task to lists of tasks.
Dart
import 'package:flutter/material.dart';import 'package:task_management/task_list.dart';import 'add_task.dart';  class Home extends StatelessWidget {      // create the appbar  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text('GeeksforGeeks'),      ),      body: Container(        padding: EdgeInsets.all(20),        child: TaskList(),      ),      floatingActionButton: FloatingActionButton(        child: Icon(          Icons.add,        ),        onPressed: () {          Navigator.push(              context, MaterialPageRoute(builder: (context) => AddTask()));        },      ),    );  }}
The Home screen contains an App Bar and then Container which has child TaskList widgets that show the list of tasks, and a FloatingActionButton to add new tasks to the app.
Dart
import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:task_management/model/task.dart';import 'package:task_management/model/task_data.dart';  class AddTask extends StatelessWidget {  @override  Widget build(BuildContext context) {    String text = '';    return Scaffold(      appBar: AppBar(        title: Text('Add Task'),      ),      body: Container(        padding: EdgeInsets.all(18),        child: Column(          children: [            TextField(              onChanged: (c) => text = c,              decoration: InputDecoration(                contentPadding: EdgeInsets.symmetric(horizontal: 10),                hintText: 'Enter Task',                border: const OutlineInputBorder(                  borderRadius: BorderRadius.all(Radius.circular(8.0)),                ),              ),            ),                          // add button            RaisedButton(              shape: RoundedRectangleBorder(                borderRadius: BorderRadius.all(                  Radius.circular(8),                ),              ),              child: Text(                'Submit',                style: TextStyle(                    fontWeight: FontWeight.bold,                    fontSize: 18,                    color: Colors.black),              ),                              // assign action              onPressed: () {                Provider.of<TaskData>(context, listen: false)                    .addTask(Task(task: text));                Navigator.pop(context);              },            )          ],        ),      ),    );  }}
The AddTask screen lets us add a new task with the default switch to be off (i.e. The task is not done). Here we use Provider.of, with the listen parameter set too false to add the data in the list. We have not used Consumer here as we don’t need the data.
Now we are going to make the widget that is going to render the tasks on the screen. So we define the following widget TaskList in lib called task_list.dart as:
Dart
import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:task_management/model/task_data.dart';  class TaskList extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Consumer<TaskData>(builder: (context, data, child) {      return ListView.builder(        scrollDirection: Axis.vertical,        shrinkWrap: true,        itemCount: data.size,        itemBuilder: (context, index) {          final task = data.tasks[index];                      // gesture detection          return GestureDetector(            onLongPress: () => data.removeTask(task),            child: Container(              margin: EdgeInsets.only(bottom: 10),              padding: EdgeInsets.fromLTRB(12, 5, 8, 5),              width: double.infinity,              decoration: BoxDecoration(                  color: Colors.black12,                  borderRadius: BorderRadius.circular(8)),              child: Row(                mainAxisAlignment: MainAxisAlignment.spaceBetween,                children: [                                      // text field                  Text(                    task.task,                    style: TextStyle(                        decoration:                            task.completed ? TextDecoration.lineThrough : null,                        fontSize: 16,                        fontWeight: FontWeight.bold),                  ),                                      // switch case                  Switch(                    value: task.completed,                    onChanged: (c) => data.toggleTask(task),                  ),                ],              ),            ),          );        },      );    });  }}
Output:
Here we have used the Consumer to get the data of TaskList we also provide the functionalities for removing and toggling the task as long press to remove and toggle to mark as done.
android
Flutter
Picked
Dart
Flutter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Flutter - Custom Bottom Navigation Bar
ListView Class in Flutter
Android Studio Setup for Flutter Development
Flutter - Flexible Widget
What is widgets in Flutter?
Flutter - Custom Bottom Navigation Bar
Flutter Tutorial
Flutter - Flexible Widget
Flutter - Stack Widget
Flutter - BorderRadius Widget