☄️

buildメソッドの中に処理を書くのはよくないらしい?

2023/10/23に公開

Overview

この記事を見て、buildメソッドの中に処理を書くのってよくないんだな〜と思いつつも書かないといけない場面がよくあります。
https://qiita.com/chooyan_eng/items/976adeea8eed1b5ebad4

普段は私は、flutter_hooksを使っているのですが、あれもbuildメソッドの中に処理を書いているのでよくないのだろうかと考えたりします???

summary

今回は、ReactのカスタムHookのようなことをして、これ防げないだろうかと考えてStreamコントローラーを制御するロジックを作ってみました。

⚓️HookWidgetを使った例

普段から気に入って使ってるuseなんちゃらを使ってみました。これがReactみたいな書き方になって、メリットになっているかは分からないですが🤔

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'dart:async';

void main() => runApp(MyApp());

class MyApp extends HookWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Hooks Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends HookWidget {
  
  Widget build(BuildContext context) {
    final counter = useCounter();
    final streamValue = useStream(counter.stream, initialData: counter.value).data;

    return Scaffold(
      appBar: AppBar(title: Text('Flutter Hooks Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text(
              '$streamValue',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

// Custom Hook
CounterController useCounter() {
  final controller = useMemoized(() => CounterController());
  useEffect(() {
    return controller.dispose; // dispose when widget is removed from tree
  }, []);
  return controller;
}

class CounterController {
  final _counter = StreamController<int>.broadcast();
  int _value = 0;

  int get value => _value;
  Stream<int> get stream => _counter.stream;

  void increment() {
    _value++;
    _counter.sink.add(_value);
  }

  void dispose() {
    _counter.close();
  }
}

📡Riverpodを使った例

カウンターしかやらないので、プロバイダーでやる必要あるのかなと思いつつ、StreamコントローラーとonDispose使ったことないので、試してみたいなと思ってこんなの作りました。

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

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';

part 'myapp.g.dart';

void main() => runApp(const ProviderScope(child: MyApp()));

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    final counterStream = ref.watch(streamProvider);

    return MaterialApp(
      title: 'StreamProvider Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('StreamProvider Demo')),
        body: Center(
          child: counterStream.when(
            data: (value) => Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text('You have pushed the button this many times:'),
                Text(
                  '$value',
                  style: const TextStyle(fontSize: 30),
                ),
              ],
            ),
            loading: () => const CircularProgressIndicator(),
            error: (error, stack) => Text('Error: $error'),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => ref.read(counterProvider).increment(),
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

// StreamCounterControllerを使ってカウントするためのクラス
class CounterController {
  // refメソッドを使うために必要
  final Ref ref;
  // コンストラクター内にコントローラーが破棄された時の処理を記述
  CounterController(this.ref) {
    ref.onDispose(() => _counter.close());
  }
  // broadcastは、複数のリスナーに対してイベントを通知するためのもの
  final _counter = StreamController<int>.broadcast();
  // カウンターの初期値
  int _value = 0;
  // Streamを公開するためのgetter
  Stream<int> get stream => _counter.stream;
  // カウントアップするためのメソッド
  void increment() {
    _value++;
    _counter.sink.add(_value);
  }
}
// 状態破棄したくないときは、keepAlive: trueをつける
(keepAlive: true)
// このクラスは、CounterControllerを継承している
CounterController counter(CounterRef ref) {
  return CounterController(ref);
}
// StreamProviderを使うための関数

Stream<int>  stream(StreamRef ref) {
  final controller = ref.watch(counterProvider);
  return controller.stream;
}

thoughts

buildメソッドの中に処理を書かないように最近は、気をつけようと意識するのですが、いい方ないのかな〜と考えております。
書いたらやばいな〜って処理は、FirestoreとAPIに接続してる処理だと思います。ReactでもuseEffectの中に処理を書いて、無限ループが走ったことがあることをTwitterで以前見かけたことがあります。

怖いですね〜、気をつけないと😅

Jboy王国メディア

Discussion