🌏

【Flutter】関数型リアクティブプログラミングパッケージ「asp」について

2023/06/26に公開

はじめに

https://twitter.com/luke_pighetti/status/1671576736721453056?s=20
こちらのtweetをきっかけに「asp」というリアクティブプログラミングパッケージが存在していることを知りました。
パッケージとして展開されたのが1ヶ月前と、かなり新しいパッケージとなりますが
今後のバージョンアップに期待の意味も込めて、触ってみたいと思います。

概要

導入

https://pub.dev/packages/asp
こちらの公式からプロジェクトに取り込みましょう

準備

  1. InheritedWidgetを継承しているRxRootrunAppの親として定義しましょう
  runApp(const RxRoot(child: MyApp()));

関数

  1. select関数を使用して監視対象のAtomオブジェクトを指定します。
final counter = Atom<int>(0);


class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    final value = context.select(() => counter.value);
    ...
    ...
  }
}

※また、複数のAtomをselectすることも可能です。

final counter = Atom<int>(0);
final counter2 = Atom<int>(0);


class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    final value = context.select(() => [counter, counter2]);
    ...
    ...
  }
}
  1. select関数以外に、明示的に監視対象の値を加工して提供したい場合に、getterを使用して監視することが可能
final counter = Atom<int>(0);
String get counterText => 'カウント数 - ${counter.value}';

Widget

RxBuilder

  • スコープを絞った状態管理
  RxBuilder(
    builder: (_) => Text('${counter.value}'),
  )

RxCallback

  • callback関数と同様、対象のAtomオブジェクトに変化が生じた際に、rxObserverが変化を受け取り、effect関数にコールバックとしてイベントを送信する
  RxCallback(
    effects: [
      rxObserver(
        () => counter.value,
	 effect: (value) => print(value),
      ),
    ],
    child: const Text(''),
  )

コレクション

RxList

  • 値の追加や削除などのリアクティブオブジェクトとして拡張したList
final rxList = RxList<String>([]);

class RxListWidget extends StatelessWidget {
  const RxListWidget({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        ...List.generate(
          rxList.length,
          (index) => Text(rxList[index]),
        ),
        ElevatedButton(
            onPressed: () => rxList.add('${counter.value}'),
            child: const Text('Tap')),
      ],
    );
  }
}

非同期

RxFuture

  • status / error / resultの結果を監視できる非同期リアクティブラッパー
final rxFuture = getCounterFuture(100).asAtom();
Future<int> getCounterFuture(int value) async {
  await Future.delayed(const Duration(milliseconds: 500));
  return value;
}
...
...

rxObserver(() {
  if (rxFuture.status == FutureStatus.pending) {
    return;
  }
  number = rxFuture.data ?? 0;
  print('$number');
});

実装

Value Notifier

notifier.dart
final counterNotifier = Atom<CounterNotifier>(CounterNotifier());

class CounterNotifier extends ValueNotifier<int> {
  CounterNotifier() : super(0);

  void increment(int count) {
    value = count;
  }

  void decrement(int count) {
    value = count;
  }
}

UI

main.dart
void main() {
  runApp(const RxRoot(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    // notifierを監視
    final notifier = context.select(() => counterNotifier.value);

    return MaterialApp(
      home: Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('${notifier.value}'),
            const SizedBox(height: 8),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
		    // 値を+1に更新
                    onPressed: () => notifier.increment(notifier.value + 1),
                    child: const Text('increment'),
		),
                const SizedBox(width: 8),
                ElevatedButton(
		    // 値を-1に更新
                    onPressed: () => notifier.decrement(notifier.value - 1),
                    child: const Text('decrement'),
		),
              ],
            )
          ],
        ),
      ),
    );
  }
}

まとめ

今回は基本的な部分を記載してみました。
サンプルコードなどを拝見すると、応用を効かせたコーディングや
Reducerを用いたAtomsパターンなど興味深い内容が詳細に記載されています。
Flutter自体、様々な状態管理手法が提供されていますが、この「asp」も今後、その中の一つとして存在していく可能性もありますので、ご興味のある方は手元でぜひご確認いただけたらと思います。

参考

https://pub.dev/packages/asp
https://github.com/Flutterando/asp

Discussion