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
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!
GitHub Sponsors
Sponsors are always welcome. Thank you for your support!