0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

どうしてもriverpodを使って自分自身を削除するボタンを作りたかったメモ【Flutter】

Posted at

間違っている箇所、謎な箇所があったら指摘してください。

動機

riverpodの勉強がしたかった(建前)
自滅するWidgetが欲しかった(本音)

前提知識

riverpodをほんの少しだけ知っている。

バージョン

バージョン
Flutter 3.3.0
Dart 2.18.0
flutter_riverpod ^2.2.0
Xcode 14.2

概要

ElevatedButtonStateNotifierのサブクラス越しに挿入する。
StateNotifierProviderを使って、ボタンをタップした時にWidgetをnullにする。 
今回はiOSシミュレーターを使ってる。

重要な部分だけ軽く抑えて、最後に全体のコードを貼る。

実装

provider辺りの部分

final buttonTogglerProvider =
    StateNotifierProvider<ButtonToggler, VanishButton?>(
        (ref) => ButtonToggler());

class ButtonToggler extends StateNotifier<VanishButton?> {
  ButtonToggler() : super(const VanishButton());

  void toggle() => state = state == null ? const VanishButton() : null;
}

Widgetとnullで切り替えたかったので、型はVanishButton?を指定している。 

ButtonToggler内にtoggle()を定義することで、自発的にWidgetを削除し、任意のタイミングで再構築できる。(←まさにこれがしたかった!!)
StateNotifierProviderにしたのは、Widgetとnullの切り替えを自分で作りたかったから。他のでもいいかもしれないけど、公式パッと見た感じこれがベストっぽかった。 

プロバイダとはでproviderの種類が確認できる。

stateのとこにButtonToggerの状態が格納されてるイメージ。基本的にこのstateを参照していくっぽい。stateはもともとあるものなので自分で宣言したりしなくていい、てかしない。 
 

ElevatedButtonの挙動のとこ 

class VanishButton extends ConsumerWidget {
  const VanishButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(buttonTogglerProvider.notifier).toggle();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.amber,
      ),
      child: Text(
        ref.watch(buttonTogglerProvider).toString(),
      ),
    );
  }
}

refで監視とかできるようにConsumerWidgetを継承する。

前項のButtonTogglerVanishButtonを渡してるのは、このボタンを消したいから。そのため、このボタンのonPress内でtoggle()を呼んで、間接的に自分をnullにして消す。(逆に渡したいのでクラスを定義するみたいなとこある)

onPressの様な、ユーザーのアクションに起因する処理の中にはwatch()ではなくread()を使うらしい。read()にはbuttonTogglerProvider.notifierを渡すところがwatch()と異なる。
watchは監視をするだけ?っぽい。

watch メソッドは ElevatedButton の onPressed 内など、非同期的な場面で呼び出さないでください。 また initState を始め、State のライフサイクルメソッド内での使用も避けてください。
これらの場合は代わりに ref.read を使用してください。

ref.read はリアクティブではないため、可能な限り使用を避けてください。
watch や listen の使用では問題が生じる場合の回避策として存在しています。 ほとんどの場面では watch や listen の使用、特に watch の使用がベターなはずです。
【重要】 ref.read は build メソッドの中で使わない

らしい。1

ref.watch()は今回対して関係ないが、これで状態を監視できる。

スコープ

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

main()でちゃんとスコープを決めとるのを忘れないのが大事。

実行結果 (GIF)

Simulator_Screen_Recording_-iPhone_14_Pro_Max-_2023-02-14_at_11_11_28_AdobeExpress.gif

vanishButton消えちゃうので復活用のボタンも用意してた。

状態の変化がわかりやすいように一応テキストも変えた。

何もない時に復活ボタンを押しても何も起きない。

全体コード

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

final buttonTogglerProvider =
    StateNotifierProvider<ButtonToggler, VanishButton?>(
        (ref) => ButtonToggler());

class ButtonToggler extends StateNotifier<VanishButton?> {
  ButtonToggler() : super(const VanishButton());

  void toggle() => state = state == null ? const VanishButton() : null;
}

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CountPage(),
    );
  }
}

class CountPage extends ConsumerWidget {
  const CountPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("title"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const SizedBox(height: 50),
            Container(
              height: 50,
              child: ref.watch(buttonTogglerProvider),
            ),
            const SizedBox(height: 50),
            Container(
              height: 50,
              child: ElevatedButton(
                onPressed: () {
                  ref.watch(buttonTogglerProvider) ??
                      ref.read(buttonTogglerProvider.notifier).toggle();
                },
                child: Text(
                  ref.watch(buttonTogglerProvider) == null
                      ? "restore button"
                      : "do noting",
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class VanishButton extends ConsumerWidget {
  const VanishButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(buttonTogglerProvider.notifier).toggle();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.amber,
      ),
      child: Text(
        ref.watch(buttonTogglerProvider).toString(),
      ),
    );
  }
}

参考

  1. https://riverpod.dev/ja/docs/concepts/reading

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?