2022-10-27

[Flutter] Katana Listenables

Hello. My name is Masaru Hirose.

There may not be much demand for it, but I needed it for personal use, so I created a new Flutter package.

In a nutshell,

Freezed (like) multiple ChangeNotifiers that can be combined into one.

I’ve put together some instructions on how to use it, so if you’re interested, go ahead and give it a try!

katana_listenables

The base part of the package that allows Listenable values to be grouped together and treated as a ChangeNotifier.
https://pub.devhttps://pub.dev
title
Building system for katana listenables packages. Create a ChangeNotifier that automatically groups multiple Listenables together.
https://pub.devhttps://pub.dev
title

Introduction

You may want to manage multiple TextEditingControllers, ValueNotifiers, and other controllers that inherit these ChangeNotifiers when creating widgets.

It would then generally look something like the following.

  • Define multiple ChangeNotifier inherited classes in State<StatefulWidget> and do addListener for all of them
    • It is also necessary to create a separate method to execute setState()
  • Create a class inheriting from ChangeNotifier for use in the state management package and addListener for all

I have created the following package to simplify the above implementation.

  • Create a class inheriting from ChageNotofier with a simple description
  • Monitor its parameters by passing an object inheriting from ChageNotofier as a parameter.
    • When a change occurs in a monitored ChageNotifier, the change is propagated to its own object.

For example, the following statement is used

@listenables
class ControllerGroup with _$ControllerGroup, ChangeNotifier {
  factory ControllerGroup({
    required TextEditingController emailTextEditingController,
    required TextEditingController passwordTextEditingController,
    required FocusNode focusNode,
    ValueNotifier<bool> checkTerms,
  }) = _ControllerGroup;
}

When build_runner is run with this, a class inheriting from ChangeNotifier (Listenable) given as an argument is automatically generated.

If you load this in riverpod, for example, as follows…

final controllerProvider = ChangeNotifierProvider((_) {
  return ControllerGroup(
    emailTextEditingController: TextEdigingController(),
    passwordTextEditingController: TextEdigingController(),
    focusNode: FocusNode(),
    checkTerms: ValueNotifier(false),
 );
});

class TestPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref){
    final controller = ref.watch(controllerProvider);

    ~~~~
    controller.emailTextEditingController.text = "New Text"; // At this time, the controller is also notified of the change and the widget is updated again.
    ~~~~
  }
}

When the contents of emailTextEditingController defined in the controller are updated, the change notification is propagated to the controller as well.

Since the controller is also a ChangeNotifier, changes can be monitored by addListener, etc.

Installation

Import the following package for code generation using build_runner.

flutter pub add katana_listenables
flutter pub add --dev build_runner
flutter pub add --dev katana_listenables_builder

Implementation

Make a Class

Create a class as follows

Add part '(filename).listenable.dart';.

Annotate the defined class with @listenables and mixin _$(defined class name) and ChangeNotifier.

The constructor is created in the factory and defines classes that inherit from ChangeNotifier and Listenable that you want to use in the parameters.

(Required values are marked "required"; if "required" is not marked, leave it as it is.)

After the constructor, write = _ (the name of the defined class).

// controller.dart

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

part 'controller.listenable.dart';

@listenables
class ControllerGroup with _$ControllerGroup, ChangeNotifier {
  factory ControllerGroup({
    required TextEditingController emailTextEditingController,
    required TextEditingController passwordTextEditingController,
    required FocusNode focusNode,
    ValueNotifier<bool> checkTerms,
  }) = _ControllerGroup;
}

Code Generation

Automatic code generation is performed by entering the following command.

flutter pub run build_runner build --delete-conflicting-outputs

How to use

Since the created class inherits from ChangeNotifier, it can be used in the same way as a general ChangeNotifier.

  • For State<StatefulWidget>
class TestPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState => TestPageState();
}

class TestPageState extends State<TestPage> {
  final controller = ControllerGroup(
    emailTextEditingController: TextEdigingController(),
    passwordTextEditingController: TextEdigingController(),
    focusNode: FocusNode(),
    checkTerms: ValueNotifier(false),
  );

  @override
  void initState(){
    super.initState();
    controller.addListener(_handledOnUpdate);
  }
	
  void _handledOnUpdate(){
    setState((){});
  }

  @override
  void dispose(){
    super.dispose();
    controller.removeListener(_handledOnUpdate);
    controller.dispose();
  }

  @override
  Widget build(BuildContext context, WidgetRef ref){
    final controller = ref.watch(controllerProvider);

    ~~~~
    controller.emailTextEditingController.text = "New Text"; // At this time, the controller is also notified of the change and the widget is updated again.
    ~~~~
  }
}

  • For riverpod
final controllerProvider = ChangeNotifierProvider((_) {
  return ControllerGroup(
    emailTextEditingController: TextEdigingController(),
    passwordTextEditingController: TextEdigingController(),
    focusNode: FocusNode(),
    checkTerms: ValueNotifier(false),
  );
});

class TestPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref){
    final controller = ref.watch(controllerProvider);

    ~~~~
    controller.emailTextEditingController.text = "New Text"; // At this time, the controller is also notified of the change and the widget is updated again.
    ~~~~
  }
}

Additional Usage

Adding Methods

To add a method, use the following writing style.

(defined class name). _(); constructor must be added.

// controller.dart

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

part 'controller.listenable.dart';

@listenables
class ControllerGroup with _$ControllerGroup, ChangeNotifier {
  factory ControllerGroup({
    required TextEditingController emailTextEditingController,
    required TextEditingController passwordTextEditingController,
    required FocusNode focusNode,
    ValueNotifier<bool> checkTerms,
  }) = _ControllerGroup;
  ControllerGroup._(); // Additional Required

  bool checked {
    return checkTerms?.value ?? false;
  }
}

Conclusion

I made it for my own use, but if you think it fits your implementation philosophy, by all means, use it!

Also, I’re releasing the source here, so issues and PullRequests are welcome!

If you have any further work requests, please contact me directly through my Twitter or website!

Offers app development and apps using Flutter and Unity. Includes information on music and videos created by the company. Distribution of images and video materials. We also accept orders for work.
https://mathru.nethttps://mathru.net
title

GitHub Sponsors

Sponsors are always welcome. Thank you for your support!

Developed the katana/masamune framework, which has dramatically improved the overall efficiency of Flutter-based application development.
https://github.comhttps://github.com
title