🫠
riverpod generatorで.familyを使ったらハマった!
これは何?
いつもの様に、技術の探究をしてそれを新しいコードに書き換えたらエラーでハマった方法を解消する記事です。これであってるのかな....
このスクラップを使って遊んでました。
これが書き換える前のコードです。昔のプロバイダーを定義するriverpodの書き方。
プロバイダーを書くコード
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:todo_fire/model/task_state.dart';
final firebaseProvider =
Provider<FirebaseFirestore>((ref) => FirebaseFirestore.instance);
// タスク用のwithConverterを作成
final taskConverterProvider = Provider((ref) {
final firebase = ref.watch(firebaseProvider);
return firebase.collection('tasks').withConverter<TaskState>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return TaskState.fromJson(data).copyWith(id: snapshot.id);
},
toFirestore: (value, _) => value.toJson(),
);
});
final taskContentConverterProvider = Provider.family((ref, String taskId) {
final firebase = ref.watch(firebaseProvider);
return firebase.collection('tasks').doc(taskId).collection('content').withConverter<TaskStateContent>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return TaskStateContent.fromJson(data);
},
toFirestore: (value, _) => value.toJson(),
);
});
// タスク用のStreamProviderを作成
final taskStreamProvider = StreamProvider.autoDispose<List<TaskState>>((ref) {
final todoConverter = ref.watch(taskConverterProvider);
return todoConverter
.snapshots()
.map((e) => e.docs.map((e) => e.data()).toList());
});
// サブコレクションのcontent用のwithConverterを作成
final contentConverterProvider = Provider.family((ref, String taskId) {
final firebase = ref.watch(firebaseProvider);
return firebase.collection('tasks').doc(taskId).collection('contents').withConverter<TaskStateContent>(
fromFirestore: (snapshot, _) => TaskStateContent.fromJson(snapshot.data()!),
toFirestore: (value, _) => value.toJson(),
);
});
// サブコレクションのcontent用のStreamProviderを作成
final contentStreamProvider = StreamProvider.autoDispose.family<List<TaskStateContent>, String>((ref, String taskId) {
final contentConverter = ref.watch(contentConverterProvider(taskId));
return contentConverter
.snapshots()
.map((e) => e.docs.map((e) => e.data()).toList());
});
riverpod generator
に書き換えたコード
自動生成するコード
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:todo_fire/model/task_state.dart';
part 'firebase_provider.g.dart';
FirebaseFirestore firebaseFirestore(FirebaseFirestoreRef ref) {
return FirebaseFirestore.instance;
}
// タスク用のwithConverterを作成
CollectionReference<TaskState> taskConverter(TaskConverterRef ref) {
final firebase = ref.watch(firebaseFirestoreProvider);
return firebase.collection('tasks').withConverter<TaskState>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return TaskState.fromJson(data).copyWith(id: snapshot.id);
},
toFirestore: (value, _) => value.toJson(),
);
}
CollectionReference<TaskStateContent> taskContentConverter(TaskContentConverterRef ref,
{required String taskId}) {
final firebase = ref.watch(firebaseFirestoreProvider);
return firebase.collection('tasks').doc(taskId).collection('content').withConverter<TaskStateContent>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return TaskStateContent.fromJson(data);
},
toFirestore: (value, _) => value.toJson(),
);
}
// タスク用のStreamProviderを作成
Stream<List<TaskState>> taskStream(TaskStreamRef ref) {
final todoConverter = ref.watch(taskConverterProvider);
return todoConverter
.snapshots()
.map((e) => e.docs.map((e) => e.data()).toList());
}
// サブコレクションのcontent用のwithConverterを作成
CollectionReference<TaskStateContent> contentConverter(ContentConverterRef ref, {required String taskId}) {
final firebase = ref.watch(firebaseFirestoreProvider);
return firebase.collection('tasks').doc(taskId).collection('contents').withConverter<TaskStateContent>(
fromFirestore: (snapshot, _) => TaskStateContent.fromJson(snapshot.data()!),
toFirestore: (value, _) => value.toJson(),
);
}
// サブコレクションのcontent用のStreamProviderを作成
Stream<List<TaskStateContent>> contentStream(ContentStreamRef ref, {required String taskId}) {
final contentConverter = ref.watch(contentConverterProvider(taskId: taskId));
return contentConverter
.snapshots()
.map((e) => e.docs.map((e) => e.data()).toList());
}
riverpod generatorに変更すると、引数の渡し方が変わってるみたい???
taskId: taskId
と書く!
final contentConverter = ref.watch(contentConverterProvider(taskId: taskId));
コードジャンプして内部のコードをというか、自動生成されたコードを見るとこんなのができてる。これを使えということらしい。
ないとどうなるかというと、エラーが出る!
コードアンドレで紹介されてた。この方法でやるみたいだ。
公式にもそれっぽいの書いてあるけど、rivepod generator
じゃないよ🫠
pab.devには書いてある?
AsyncValue<List<Product>> products = ref.watch(fetchProductProvider(page: 1));
View側のコードも修正しておこう
フォルダのページ:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:todo_fire/provider/firebase_provider.dart';
import 'package:todo_fire/view/conten_view.dart';
import 'package:todo_fire/view/create_task_view.dart';
class TaskView extends ConsumerWidget {
const TaskView({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final taskState = ref.watch(taskStreamProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Task'),
),
body: taskState.when(
data: (tasks) {
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return ListTile(
// 画面左にフォルダのアイコンを表示
leading: const Icon(Icons.folder),
title: Text(task.title),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ContentView(task: task),
),
);
},
);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, s) => Center(child: Text(e.toString())),
),
// tasksにドキュメントを作成するForm付きのDialogを表示
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const CreateTaskView(),
),
);
},
child: const Icon(Icons.add),
),
);
}
}
フォルダの中のページ:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:todo_fire/model/task_state.dart';
import 'package:todo_fire/provider/firebase_provider.dart';
class ContentView extends ConsumerWidget {
const ContentView({super.key, required this.task});
final TaskState task;
Widget build(BuildContext context, WidgetRef ref) {
final content = ref.watch(contentStreamProvider(taskId: task.id));
final contentController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text(task.title),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: contentController,
decoration: const InputDecoration(
labelText: 'Content',
hintText: 'Input content',
),
),
),
Expanded(
child: content.when(
data: (contents) {
return ListView.builder(
itemCount: contents.length,
itemBuilder: (context, index) {
final content = contents[index];
return ListTile(
// 左にファイルのiconを表示
leading: const Icon(Icons.file_copy),
title: Text(content.content),
);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, s) => Center(child: Text(e.toString())),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// doumentIDを取得する
final id = ref.read(contentConverterProvider(taskId: task.id)).doc().id;
final content = TaskStateContent(
id: id,
content: contentController.text,
createdAt: Timestamp.now().toDate(),
);
// contentを作成する
ref.read(contentConverterProvider(taskId: task.id)).add(content);
},
child: const Icon(Icons.add),
),
);
}
}
まとめ
まあ、こん感じでエラーにハマって苦しみました💦
自動生成形はこれだから嫌いなんですよ。SwiftUIとJetpack Composeは自動生成ないから、そんな人たちがFlutterやるとエラーで混乱するらしいです。
Discussion
この辺りでしょうか。
これです。
いえ、そうではなく。。。
とありますよね?
そして上記はNamed Parameterを使用しているのですから、
となりますが、以下のように
とすれば、
ではないのでしょうか。
つまり、riverpod_generatorを使用したことでNamed Parameterで呼び出さなければならなくなったわけではなく、contentConverterProviderがNamed Parameterを使用しているから、なのではありませんか?
required
これつけてるからかな。まあ、以前taskId: taskId
なりませんでしたからね。Named Parameterか否かの違いだと思います。