-1.6 C
New York
Thursday, February 2, 2023

Flutter: ValueListenableBuilder Example

This article walks you through a complete example of using ValueListenableBuilder, ValueNotifier, and ValueListenable in a multi-page (or multi-screen) Flutter application.

A Quick Note

The ValueListenableBuilder widget uses a builder callback to rebuild whenever a ValueListenable object triggers its notifications, providing the builder with the value of the object:Advertisements

ValueListenableBuilder({
  Key? key, 
  required ValueListenable<T> valueListenable, 
  required ValueWidgetBuilder<T> builder, 
  Widget? child
})

A ValueListenable is an interface that exposes a value and can be implemented by a ValueNotifier. The value property of the ValueNotifier is the current value stored in the notifier.

These words may be confusing. For more clarity, check the complete example below.

App Preview

The app we are going to build is a task app. It contains 2 pages (screens): HomePage and ArchivePage:

  • HomePage displays uncompleted tasks. This one also has a floating action button that can be used to add a new task. Next to each task there will be a checkbox used to mark the task as completed. You can press the “View Completed Button” to navigate to the ArchivePage.
  • ArchivePage displays completed tasks. Next to each task there will be an icon button used to bring that task to the “uncompleted” state.

AdvertisementsA demo is worth than a thousand words:

The Code

Create a new Flutter project and add 2 new files: home_screen.dart and archive_screen.dart. Here’s the directory structure:

.
??? archive_page.dart
??? home_page.dart
??? main.dart

1. Remove all of the default code in main.dart and add the following:

// main.dart
import 'package:flutter/material.dart';

import './home_page.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        // Remove the debug banner
        debugShowCheckedModeBanner: false,
        title: 'Kindacode.com',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: HomePage());
  }
}
Advertisements

2. The final code for HomePage:

// home_page.dart
import 'package:flutter/material.dart';

import './archive_page.dart';

// This screen only displays un-completed tasks
class HomePage extends StatelessWidget {
  // Using "static" so that we can easily access it from other screens
  static final ValueNotifier<List<Map<String, dynamic>>> tasksNotifier =
      ValueNotifier([]);

  // This function will be triggered when the floating button is pressed
  // Add new task
  void _addNewTask() {
    final List<Map<String, dynamic>> tasks = [...tasksNotifier.value];
    tasks.add({
      "id": DateTime.now().toString(),
      "title": "Task ${DateTime.now()}",
      "isDone": false
    });
    tasksNotifier.value = tasks;
  }

  // This function will be triggered when the checkbox next to a task is tapped
  // Finish a task
  // Change a task from "uncompleted" to "completed"
  void _finishTask(String updatedTaskId) {
    final List<Map<String, dynamic>> tasks = [...tasksNotifier.value];

    final int index = tasks.indexWhere((task) => task['id'] == updatedTaskId);
    tasks[index]['isDone'] = true;

    tasksNotifier.value = tasks;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Kindacode.com'),
      ),
      body: ValueListenableBuilder<List<Map<String, dynamic>>>(
        valueListenable: HomePage.tasksNotifier,
        builder: (_, tasks, __) {
          final uncompletedTasks =
              tasks.where((task) => task['isDone'] == false).toList();

          return Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).push(MaterialPageRoute(
                        builder: (BuildContext context) => ArchivePage(),
                      ));
                    },
                    child: Text('View Completed Tasks')),
                SizedBox(
                  height: 20,
                ),
                Text(
                  'You have ${uncompletedTasks.length} uncompleted tasks',
                  style: TextStyle(fontSize: 18),
                ),
                SizedBox(
                  height: 10,
                ),
                Expanded(
                  child: ListView.builder(
                    itemCount: uncompletedTasks.length,
                    itemBuilder: (_, index) => Card(
                        margin: EdgeInsets.symmetric(vertical: 15),
                        elevation: 5,
                        color: Colors.amberAccent,
                        child: ListTile(
                          title: Text(uncompletedTasks[index]['title']),
                          trailing: IconButton(
                            icon: Icon(Icons.check_box_outline_blank),
                            onPressed: () =>
                                _finishTask(uncompletedTasks[index]['id']),
                          ),
                        )),
                  ),
                ),
              ],
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: _addNewTask,
      ),
    );
  }
}

3. The final code for ArchivePage:

// archive_page.dart
import 'package:flutter/material.dart';

import './home_page.dart';

// This screen only display completed tasks
class ArchivePage extends StatelessWidget {
  // Change a task from "completed" to "uncompleted"
  void _uncheckTask(String updatedTaskId) {
    final List<Map<String, dynamic>> tasks = [...HomePage.tasksNotifier.value];

    final int index = tasks.indexWhere((task) => task['id'] == updatedTaskId);
    tasks[index]['isDone'] = false;

    HomePage.tasksNotifier.value = tasks;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Archive Screen'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: ValueListenableBuilder<List<Map<String, dynamic>>>(
          valueListenable: HomePage.tasksNotifier,
          builder: (_, tasks, __) {
            final completedTasks =
                tasks.where((task) => task['isDone'] == true).toList();
            return ListView.builder(
              itemCount: completedTasks.length,
              itemBuilder: (_, index) => Card(
                  margin: EdgeInsets.symmetric(vertical: 15),
                  elevation: 5,
                  color: Colors.pinkAccent,
                  child: ListTile(
                    title: Text(completedTasks[index]['title']),
                    trailing: IconButton(
                      icon: Icon(Icons.check_box),
                      onPressed: () =>
                          _uncheckTask(completedTasks[index]['id']),
                    ),
                  )),
            );
          },
        ),
      ),
    );
  }
}

Now run your project and play around with it to see what will happen.

Conclusion

We’ve gone over an end-to-end example of implementing the ValueListenableBuilder widget in Flutter. In the future, if you have more complex use cases and want to have more available solutions to choose from, you can browse the following articles:

You can also check out our Flutter category page, or Dart category page for the latest tutorials and examples.

Advertisements

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles