🚔

Readerが無くなった!

2022/11/03に公開

Readerが非推奨になったようです!

最近、Flutterやってなくて技術が追えていなかった。
TwitterやFlutter大学さんの情報を見ていると、Riverpod2.0から、Readerがなくなって、Refを使うことになるそうです。

puv.devの変更ログにも書いてありました。
https://pub.dev/packages/flutter_riverpod/changelog

  • The Reader typedef is removed. Use Ref instead.
  • Readerのtypedefが削除されました。代わりにRefを使用します。

カウンターアプリだと面白くないので、Firebaseに文字を保存するだけですが、サンプルコード作ってみました。
綺麗なコードではないと思います😅
他にいい書き方があると思うのですが、あれば教えて欲しいです。StateNotifier使うのに、こだわりすぎてるのかもしれないですね...

今回は、アンドレアさんという有名な海外のエンジニアさんの情報も参考に勉強しました。
https://codewithandrea.com/articles/flutter-state-management-riverpod/

サンプルコード

https://github.com/sakurakotubaki/Riverpod2.0-APP

フォルダ構成はこのようになっています。mvvmじゃなくてもいいという人もいるが、私のコードがmvvmなのかも怪しいですね😇

lib/
├── firebase_options.dart
├── main.dart
├── provider
│   └── firebase_provider.dart
├── screen
│   └── main_page.dart
└── state
    └── text_state.dart

多分違うな...
monoさんのGithubが参考になると、札幌の知人が言っていたので、今後はmonoさんのフォルダ構成や命名規則を参考にしていこうと思います。

providerフォルダには、FirebaseとtextEditingControllerを使うための、Providerを定義しています。他のページで呼び出して使います。

provider/firebase_provider.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Firebaseを使うためのProvider
final firebaseProvider =
    Provider<FirebaseFirestore>((ref) => FirebaseFirestore.instance);

// StateProvider は外部から変更が可能なステート(状態)を公開するプロバイダです。
// TextEditingControllerを使うためのProvider
final textProvider = StateProvider.autoDispose((ref) {
  // riverpodで使うには、('')が必要
  return TextEditingController(text: '');
});

stateフォルダには、providerを呼び出して、ロジックを使うためのStateNotifierクラスを定義しています。今回は、データを保存するだけなので、なんだか物足りないですが、今回はRefで書くとどうなるのかを実験しただけなので、編集と削除は作ってません。

state/text_state.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_ref_app/provider/firebase_provider.dart';

final textStateProvider = StateNotifierProvider<TextState, dynamic>((ref) {
  return TextState(ref);
});

class TextState extends StateNotifier<dynamic> {
  // Refを使うと他のファイルのProviderを呼び出せる
  // Riverpod2.0以前は、Reader _readerと書いていた。
  final Ref _ref;
  // superは、親クラスのコンストクラスターを呼び出す
  TextState(this._ref) : super([]);
  // FireStoreにデータを追加するメソッド
  Future<void> textAdd(String text) async {
    // _ref.read()と書いて、firebaseProviderを呼び出す
    final ref = await _ref
        .read(firebaseProvider)
        .collection('users')
        .add({'text': text});
  }
}

アプリの画面は、screenフォルダの中に配置しました。命名規則ですけど、人によってviewとかuiとか違いますね?
好みの問題ですかね?

screen/main_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_ref_app/provider/firebase_provider.dart';
import 'package:riverpod_ref_app/state/text_state.dart';

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    // textProviderを呼び出す定数を定義
    final controllerProvider = ref.watch(textProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Riverpod2.0'),
      ),
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextFormField(
                controller: controllerProvider, //providerに定義したコントローラーを使う
                decoration: const InputDecoration(
                  border: UnderlineInputBorder(),
                  labelText: '文字を入力してください',
                ),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                  onPressed: () async {
                    // ref.readでStateNotifierを呼び出す.
                    // controllerProvider.textと書いて
                    ref
                        .read(textStateProvider.notifier)
                        .textAdd(controllerProvider.text);
                  },
                  child: Text('新規登録')),
            ],
          ),
        ),
      ),
    );
  }
}

アプリを実行するmain.dartのコード

main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_ref_app/firebase_options.dart';
import 'package:riverpod_ref_app/screen/main_page.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(
    ProviderScope(child: MyApp()),
  );
}

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

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MainPage(),
    );
  }
}

アプリを実行して、入力フォームから文字を送信するとFireStoreにデータが保存されました。以前書いたコードと変更点はあって、苦労したのは、StateNotifierProviderのreturn TextState();のところで、エラーが出ていたところでした🤔
()の中に、(ref)と書いたら、エラーが消えました。これでいいのだろうかと、まだ疑問は残りますが...

final textStateProvider = StateNotifierProvider<TextState, dynamic>((ref) {
  return TextState(ref);
});

アンドレアさんによるとrefの役割は

すべての Flutter ウィジェットには、ウィジェット ツリー内のBuildContextものにアクセスするために使用できるオブジェクトがあります( など)。Theme.of(context)
しかし、Riverpod プロバイダーはウィジェット ツリーの外にあり、それらを読み取るには追加のrefオブジェクトが必要です。ここでは、それを取得する 3 つの異なる方法を示します

refを書いたから、読み取れるようになったということですかね?
まだまだ、勉強不足なことがあるので、これからもFlutterのキャッチアップを続けて行きます🧑‍💻

Discussion