😃

【Flutter】riverpodを使ってRadioListTileからAppBarのbackgroundColorを変える

2023/01/17に公開

背景

私はFlutterの勉強として簡単なメモアプリを作っています。現在はメモの追加や編集、削除、またアプリの説明画面やあらかじめ用意しておいたGoogle formでアンケートに答えてもらうフィードバック機能を実装しており、アプリ自体は大体完成となっています。

よし!あとはデプロイだな!

しかし、1つの願望が頭に浮かんできました...。

(AppBarの色をユーザー側から変更できるようにしたいな...)

このような「やってみたい」という動機から開発に至りました。

実装したいこと

  • riverpodのStateProviderを組み合わせてColorを返すようにする
  • 設定画面の大枠を作り、ドローワーから遷移できるようにする
  • 設定画面にExpansionTileを使ってドロップダウン実装する
  • ExpansionTileの中にRadioListTileを並べる
  • RadioListTileでAppBarのbackgroundColorを変更する処理を書く

riverpodのStateProviderを組み合わせてColorを返すようにする

準備するStateProviderは以下の2つです。

  1. 列挙型(enum)を監視するStateProvider
  2. それを監視して列挙型の値に応じてColorを返すStateProvider

今回、RadioListTileを使用するので値は列挙型を使います。また、AppBarbackgroundColorはColorクラスを使って指定するので1のStateProviderを監視して、その値によってColorを返すように条件分岐してあげましょう。

以下はコードの全体です。

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

//列挙型の変数AppBarColor
enum AppBarColor { blue, red, green }

//AppBarColorを監視するStateProvider
final appBarColorProvider = StateProvider<Enum>((ref) => AppBarColor.blue);

//appBarColorProviderを監視しColorを返すStateProvider
final appBarColorStateProvider = StateProvider<Color>((ref) {
  final appBarColorState = ref.watch(appBarColorProvider);

  if (appBarColorState == AppBarColor.blue) {
    return Colors.lightBlue;
  } else if (appBarColorState == AppBarColor.red) {
    return Colors.red;
  } else {
    return Colors.green;
  }
});

解説

まずflutter_riverpodパッケージをimportしてriverpodを使えるようにしましょう。

import 'package:flutter_riverpod/flutter_riverpod.dart';

以下のリンクからriverpodのpub.devにアクセスできます。インストールの仕方等はそちらを参照してください。
https://pub.dev/packages/riverpod

次に、RadioListTileで使用する列挙型の変数AppBarColorを宣言します。
今回はblue(正確にはlightblue)、red、greenの3種類にしました。

//列挙型の変数AppBarColor
enum AppBarColor { blue, red, green }

最後に2つのStateProviderを作ります。
列挙型のAppBarColorを監視するStateProviderのrefにはAppBarColor.blueを格納してあげます。

//AppBarColorを監視するStateProvider
final appBarColorProvider = StateProvider<Enum>((ref) => AppBarColor.blue);

さらにそれを監視し列挙型の値に応じたColorを返すStateProviderを作ります。

//appBarColorProviderを監視しColorを返すStateProvider
final appBarColorStateProvider = StateProvider<Color>((ref) {
  final appBarColorState = ref.watch(appBarColorProvider);

  if (appBarColorState == AppBarColor.blue) {
    return Colors.lightBlue;
  } else if (appBarColorState == AppBarColor.red) {
    return Colors.red;
  } else {
    return Colors.green;
  }
});

設定画面の大枠を作り、ドローワーから遷移できるようにする

設定画面の大枠を作ります。
設定画面のAppBarbackgroundColorも変えたいのでConsumerStatefulWidgetで作っていきます。

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

class SettingPage extends ConsumerStatefulWidget{
  
  _SettingPageState createState() => _SettingPageState();
}

class _SettingPageState extends ConsumerState<SettingPage>{
  
  void initState() {
    super.initState();
    ref.read(appBarColorStateProvider);
  }
  
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: ref.watch(appBarColorStateProvider),
        title: const Text(
          '設定',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            color: Colors.white,
            fontSize: 30,
          ),
        ),
        bottomOpacity: 0.0,
        elevation: 0.0,
      ),
      body: Column(children: <Widget>[
        //ここにExpansionTileなどを書く
      ]),
    );
  }
}

ConsumerStatefulWidgetConsumerStateというモノがでてきましたが、これはStatefulWidgetStateに対応しています。
以下のリンクでその辺について詳しく書かれているので参照してみてください。
https://riverpod.dev/ja/docs/concepts/reading

さて、設定画面の大枠ができました。ドローワーからこの設定画面へ遷移できるようにしましょう。

drawer.dart
class CreateDrawer extends StatelessWidget{
  
  Widget build(BuildContext context){
    return Drawer(
      child: Padding(
        padding: const EdgeInsets.all(30.0),
	child: ListView(
	  children: [
	    //アプリの説明画面、フィードバック画面に遷移するコード
	    OutlinedButton.icon(
              onPressed: () {
	        //設定画面へ遷移するコード
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) {
                      return SettingPage();
                    },
                  ),
                );
              },
              icon: const Icon(
                Icons.settings,
              ),
              label: const Text(
                '設定',
              ),
              style: OutlinedButton.styleFrom(
                side: const BorderSide(
                  color: Colors.grey,
                  width: 1,
                ),
              ),
            ),
	  ],
	),
      ),
    );
  }
}

このCreateDrawerクラスをメイン画面にしたいStatelessWidgetScaffold内にあるdrawerに置いてあげましょう。Containerでラップしてdrawerの横幅をカスタムしてみると良いかもしれませんね。それらのコードは今回は省略します。

設定画面にExpansionTileを使ってドロップダウン実装する

ドローワーから設定画面への遷移が実装できたところで設定画面にドロップダウンを実装しましょう。
DropdownButtonというドロップダウンメニューを実装できるWidgetがありますが、今回はラジオボタンを使いたいという考えがあるので、ExpansionTileを採用しました。

setting.dart
class _SettingPageState extends ConsumerState<SettingPage>{
  
  void initState() {
    //省略
  }
  
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(
        //省略
      ),
      body: Column(children: <Widget>[
        ExpansionTile(
	  title: const Text('アプリバーのカラー'),
	  children: <Wisget>[
	    //ここにRadioListTileの値を変える処理を書く
	  ],
	),
      ]),
    );
  }
}

プロパティはシンプルにtitlechildrenのみにしました。
その他のExpansionTileに関する情報は以下のリンクを参照してください。
https://api.flutter.dev/flutter/material/ExpansionTile-class.html

RadioListTileでAppBarのbackgroundColorを変更する処理を書く

最後にRadioListTileを使ってAppBarのカラーを変更する処理を書いていきます。

setting.dart
class _SettingPageState extends ConsumerState<SettingPage>{
  
  void initState() {
    //省略
  }
  
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(
        //省略
      ),
      body: Column(children: <Widget>[
        ExpansionTile(
	  title: const Text('アプリバーのカラー'),
	  children: <Wisget>[
	    RadioListTile(
              title: const Text('ライトブルー'),
              value: AppBarColor.blue,
              groupValue: ref.watch(appBarColorProvider),
              onChanged: (Enum? value) {
                ref.watch(appBarColorProvider.notifier).state = value!;
              },
            ),
            RadioListTile(
              title: const Text('レッド'),
              value: AppBarColor.red,
              groupValue: ref.watch(appBarColorProvider),
              onChanged: (Enum? value) {
                ref.watch(appBarColorProvider.notifier).state = value!;
              },
            ),
            RadioListTile(
              title: const Text('グリーン'),
              value: AppBarColor.green,
              groupValue: ref.watch(appBarColorProvider),
              onChanged: (Enum? value) {
                ref.watch(appBarColorProvider.notifier).state = value!;
              },
            ),
	  ],
	),
      ]),
    );
  }
}

valueにはそれぞれtitleに応じた値にして、groupValueにはappBarColorProviderref.watch()した値にしています。
onChangedメソッドはvalueプロパティが列挙型なので、それと一致するように列挙型の変数valueを引数に渡してあげて

ref.watch(appBarColorProvider.notifier).state = value!;

と書いてあげます。ぶっちゃけ.notifierとか.stateの意味はよく分かっていません...。

デモンストレーション

AppBarbackgroundColorRadioListTileで変更することができました。

最後に

ここまで読んでいただきありがとうございました!間違っている情報がある場合はお伝えください。

今後の展望としては、アプリを再度立ち上げるとせっかく設定したbackgroundColorが初期値のライトブルーに戻ってしまうので、shared_preferencesというパッケージを使って端末内に設定を保存できるようにすることです。
以下はそのパッケージのリンクです。
https://pub.dev/packages/shared_preferences

Qiitaでも記事を執筆していますので、そちらもよろしければご覧ください!

https://qiita.com/Kyomu777

Discussion