【Flutter】Katana Listenables - mathru.net | Flutter/Unityによるアプリ開発/楽曲・映像制作/素材配布
2022-10-27

【Flutter】Katana Listenables

こんにちは。広瀬マサルです。

あまり需要はないかもしれませんが

私的に必要だったので新しいFlutterパッケージを作りました。

一言で簡単に言うと

複数のChangeNotifierを1つにまとめることができるfreezed(のようなもの)

です。

使い方をまとめたので興味ある方はぜひ使ってみてください!

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

はじめに

複数のTextEditingControllerやValueNotifierなど、Widgetを作成する際にこれらのChangeNotifierを継承したコントローラーなどの管理を行いたい場合があります。

その時大体は下記のようになるかと思います。

  • State<StatefulWidget>内で複数のChangeNotifierを継承したクラスを定義し、すべてに対してaddListenerを行う
    • setState()を実行するためのメソッドを別途作る必要もあり
  • 状態管理パッケージで利用するためのChangeNotifierを継承したクラスを作り、すべてに対してaddListenerを行う

上記の実装を簡略化したいと思い下記のようなパッケージを作りました。

  • 簡単な記述でChageNotofierを継承したクラスを作成
  • パラメーターにChageNotofierを継承したオブジェクトを渡すことでそのパラメーターを監視する
    • 監視しているChageNotifierに変更が発生した場合、自身のオブジェクトにも変更が伝搬される

例えば下記のような記述を行ないます。

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

これでbuild_runnerを走らせると引数に与えられたChangeNotifier(Listenable)を継承したクラスが自動生成されます。

これを例えば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"; // この時controllerにも変更が通知されてウィジェットの再更新が走る。
    ~~~~
  }
}

controllerの中で定義しているemailTextEditingControllerの中身が更新されるとその変更通知がcontrollerにも伝搬されます。

controllerもChangeNotifierなので、addListenerなどで変更を監視することができます。

インストール

build_runnerを用いたコードジェネレーションを行うため下記のパッケージをインポートします。

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

実装

クラス作成

下記のようにクラスを作成します。

part ‘(ファイル名).listenable.dart’;を追加します。

定義したクラスに@listenablesのアノテーションを付与し _$(定義したクラス名)ChangeNotifierをmixinします。

コンストラクタはfactoryで作成しパラメーターで使用したいChangeNotifierやListenableを継承したクラスを定義します。

(必須な値はrequiredを付与します。requiredを付与しない場合はそのまま記述してください)

コンストラクタの後に = _(定義したクラス名)を記述します。

// 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;
}

コードジェネレーション

下記のコマンドを入力することで自動でコード生成を行います。

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

利用方法

作成したクラスがChangeNotifierを継承したクラスになるので一般的なChangeNotifierと同じような使い方が可能です。

  • 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"; // この時controllerにも変更が通知されてウィジェットの再更新が走る。
    ~~~~
  }
}

  • 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"; // この時controllerにも変更が通知されてウィジェットの再更新が走る。
    ~~~~
	}
}

応用した使い方

メソッドの追加

メソッドを追加する場合は下記の書き方になります。

(定義したクラス名)._();のコンストラクタを追加する必要があります。

// 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._(); // 追加必須

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

おわりに

自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください!

また、こちらにソースを公開しているのでissueやPullRequestをお待ちしてます!

また仕事の依頼等ございましたら、私のTwitterWebサイトで直接ご連絡をお願いいたします!

FlutterやUnityを使ったアプリ開発やアプリの紹介。制作した楽曲・映像の紹介。画像や映像素材の配布。仕事の受注なども行っています。
https://mathru.nethttps://mathru.net
title

GitHub Sponsors

スポンサーを随時募集してます。ご支援お待ちしております!

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