❤️

【Flutter】state_notifier + flutter_hooks + freezedでお気に入りボタンを作る

6 min read

freezedの使用手順メモ。
押すと色が変わるIconButton(お気に入りボタンっぽいもの)を作ってみる。

この方法で作れば、いろんなところから状態を呼び出せるので、複数ページでお気に入りボタンを表示したい時とかに便利!

1. こんなんができるよ

https://github.com/nasubibocchi/favo_statenotifier_freezed

2. 導入するPackage

  • hooks_riverpod
  • flutter_hooks
  • freezed
  • build_runner

build_runnnerはdev_dependenciesに入れる。
私はHookConsumerWidgetが使いたいので、hooks_riverpodはPrereleaseを使用。

追記:hooks_riverpodは1.0.0としてリリースされました🌸

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  hooks_riverpod: ^1.0.0-dev.11
  flutter_hooks: ^0.18.0
  freezed: ^0.15.0+1

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.1.4

3. UIを作る

個人的に先に見た目を作っておくと進めやすいと思う。
今回は、ダミーデータとして'title'と'isFavo'というkeyを持ったマップのリストを作成。

class FavoWidget extends HookConsumerWidget {
  List<Contents> dammyList = dammyData
      .map((e) => Contents(title: e['title'], isFavo: e['isFavo']))
      .toList();

  
  Widget build(BuildContext context, WidgetRef ref) {
    final size = MediaQuery.of(context).size;

    return Scaffold(
      body: SingleChildScrollView(
        child: ListView.builder(
            shrinkWrap: true,
            itemCount: 3, //あとで変更
            itemBuilder: (BuildContext context, int index) {
              return Card(
                child: SizedBox(
                  height: size.height * 0.1,
                  child: Row(children: [
                    SizedBox(
                      width: size.width * 0.8,
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Text('ダミー'), //あとで変更
                      ),
                    ),
                    IconButton(
                      onPressed: () {
                        //あとで入れる
                      },
                      icon: Icon(Icons.favorite,
                          color: Colors.grey), //あとで変更
                    ),
                  ]),
                ),
              );
            }),
      ),
    );
  }
}

4. stateのクラスを作る

状態を持たせたい変数(など)の定義をする。

import 'package:favo_statenotifier_freezed/entities/contents.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'favo_state.freezed.dart'; 


class FavoState with _$FavoState {
  const factory FavoState(
      {@Default([]) List<Contents> data}) = _FavoState;
}

6行目”part 'favo_state.freezed.dart';” を入れておく。
この段階ではエラーが出たままでOK。

5. favo_state.freezed.dartを生成するためのコマンドを実行する

コマンドラインで以下を実行

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

うまくいけば、4.のエラーが消えてfavo_state.freezed.dartが生成される。

6. 状態を変化させるためのメソッドを作る

4で作った状態を変化させるメソッドを作る。
今回は状態の初期化(initState)と変更(favoriteChange)の2つ。

import 'package:favo_statenotifier_freezed/entities/contents.dart';
import 'package:favo_statenotifier_freezed/pages/favo_state.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class FavoController extends StateNotifier<FavoState> {
  FavoController() : super(const FavoState());

  void initState ({required List<Contents> data}) {
    state = state.copyWith(data: data);
  }

  void favoriteChange ({required List<Contents> data, required int index}) {
    data[index].isFavo = !data[index].isFavo;
    state = state.copyWith(data: data);
  }

}

Listの中身を変化させた上で、stateのコンストラクタに代入する。

7. プロバイダを作る

4、6の状態とメソッドをUI部分で呼び出すためのプロバイダを作る。

import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'favo_controller.dart';
import 'favo_state.dart';

final favoProvider =
    StateNotifierProvider<FavoController, FavoState>(
        (ref) => FavoController());

8. UI部分のコードで状態とメソッドを呼び出すように修正する

  • 初期化
class FavoWidget extends HookConsumerWidget {
  List<Contents> dammyList = dammyData
      .map((e) => Contents(title: e['title'], isFavo: e['isFavo']))
      .toList();

  
  Widget build(BuildContext context, WidgetRef ref) {
    final size = MediaQuery.of(context).size;

    // ここ。4で作った状態を初期化する。
    useEffect(() {
      ref.read(favoProvider.notifier).initState(data: dammyList);
    }, const []);
    final _isFavoList = ref.watch(favoProvider).data;
    
    //続く
  • ListViewの中身を変更
return Scaffold(
      body: SingleChildScrollView(
        child: ListView.builder(
            shrinkWrap: true,
            itemCount: dammyList.length, //ここ
            itemBuilder: (BuildContext context, int index) {
              return Card(
                child: SizedBox(
                  height: size.height * 0.1,
                  child: Row(children: [
                    SizedBox(
                      width: size.width * 0.8,
                      child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Text(_isFavoList[index].title.toString()), //ここ
                      ),
                    ),
                    IconButton(
                      onPressed: () {
		      //ここから
                        ref
                            .read(favoProvider.notifier)
                            .favoriteChange(data: _isFavoList, index: index);
		     //ここまで
                      },
                      icon: Icon(Icons.favorite,
		      //ここから
                          color: _isFavoList[index].isFavo == true
                              ? Colors.red
                              : Colors.grey),
		    //ここまで
                    ),
                  ]),
                ),
              );
            }),
      ),
    );

Discussion

ログインするとコメントできます