😽

Flutter+Firebase+RiverPodでCount Appを作ってみた。

2022/01/23に公開

細かいことは抜きにしてコード貼ります。

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

final userProvider = StateProvider((ref) {
  return FirebaseAuth.instance.currentUser;
});

final infoTextProvider = StateProvider.autoDispose((ref) {
  return "";
});

final emailProvider = StateProvider.autoDispose((ref) {
  return "";
});

final passwordProvider = StateProvider.autoDispose((ref) {
  return "";
});

final messageTextProvider = StateProvider.autoDispose((ref) {
  return "";
});

final postsQueryProvider = StreamProvider.autoDispose((ref) {
  return FirebaseFirestore.instance
      .collection("posts")
      .orderBy("date")
      .snapshots();
});

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    const ProviderScope(
      child: ChatApp(),
    ),
  );
}

class ChatApp extends StatelessWidget {
  const ChatApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

class LoginPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final infoText = ref.watch(infoTextProvider);
    final email = ref.watch(emailProvider);
    final password = ref.watch(passwordProvider);

    return Scaffold(
        body: Center(
            child: Container(
                padding: const EdgeInsets.all(24),
                child: Column(
                  children: <Widget>[
                    TextFormField(
                        decoration: const InputDecoration(labelText: "メールアドレス"),
                        onChanged: (String value) {
                          ref.read(emailProvider.state).state = value;
                        }),
                    TextFormField(
                        decoration: const InputDecoration(labelText: "パスワード"),
                        obscureText: true,
                        onChanged: (String value) {
                          ref.read(passwordProvider.state).state = value;
                        }),
                    Container(
                      padding: const EdgeInsets.all(8),
                      child: Text(infoText),
                    ),
                    SizedBox(
                        width: double.infinity,
                        child: ElevatedButton(
                            onPressed: () async {
                              try {
                                final FirebaseAuth auth = FirebaseAuth.instance;
                                final result =
                                    await auth.createUserWithEmailAndPassword(
                                        email: email, password: password);
                                // user情報を更新
                                ref.read(userProvider.state).state =
                                    result.user;
                                // 画面遷移
                                await Navigator.of(context)
                                    .pushReplacement(MaterialPageRoute(
                                  builder: (context) {
                                    return ChatPage();
                                  },
                                ));
                              } catch (e) {
                                ref.read(infoTextProvider.state).state =
                                    "登録に失敗しました: ${e.toString()}";
                              }
                            },
                            child: const Text("新規登録"))),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: OutlinedButton(
                          onPressed: () async {
                            try {
                              FirebaseAuth auth = FirebaseAuth.instance;
                              final result =
                                  await auth.signInWithEmailAndPassword(
                                      email: email, password: password);
                              // ref.read(userProvider.)
                              await Navigator.of(context).pushReplacement(
                                  MaterialPageRoute(builder: ((context) {
                                return ChatPage();
                              })));
                            } catch (e) {
                              ref.read(infoTextProvider.state).state =
                                  "ログインに失敗しました: ${e.toString()}";
                            }
                          },
                          child: const Text("ログイン")),
                    )
                  ],
                ))));
  }
}

class ChatPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(userProvider)!;
    final AsyncValue<QuerySnapshot> asyncPostQuery =
        ref.watch(postsQueryProvider);
    return Scaffold(
        appBar: AppBar(
          title: const Text("チャット"),
          actions: <Widget>[
            IconButton(
                onPressed: () async {
                  await FirebaseAuth.instance.signOut();
                  await Navigator.of(context).pushReplacement(MaterialPageRoute(
                    builder: (context) {
                      return LoginPage();
                    },
                  ));
                },
                icon: const Icon(Icons.logout))
          ],
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(8),
                child: Text(
                  "ログイン情報: ${user.email}",
                ),
              ),
              Expanded(
                  child: asyncPostQuery.when(data: (QuerySnapshot query) {
                return ListView(
                  children: query.docs.map((document) {
                    return Card(
                        child: ListTile(
                      title: Text(document["text"]),
                      subtitle: Text(document["email"]),
                      trailing: document["email"] == user.email
                          ? IconButton(
                              icon: const Icon(Icons.delete),
                              onPressed: () async {
                                await FirebaseFirestore.instance
                                    .collection("posts")
                                    .doc(document.id)
                                    .delete();
                              },
                            )
                          : null,
                    ));
                  }).toList(),
                );
              }, error: (e, stackTrace) {
                return Center(
                  child: Text(e.toString()),
                );
              }, loading: () {
                return const Center(
                  child: Text("読み込み中"),
                );
              })),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            Navigator.of(context).push(MaterialPageRoute(builder: ((context) {
              return AddPostPage();
            })));
          },
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ));
  }
}

class AddPostPage extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final messageText = ref.watch(messageTextProvider);
    final user = ref.watch(userProvider)!;

    return Scaffold(
        appBar: AppBar(
          title: const Text('チャット投稿'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(32),
          child: Column(
            children: <Widget>[
              TextFormField(
                decoration: const InputDecoration(labelText: "投稿メッセージ"),
                keyboardType: TextInputType.multiline,
                maxLines: 3,
                onChanged: (String value) {
                  ref.read(messageTextProvider.state).state = value;
                },
              ),
              const SizedBox(height: 8),
              SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    child: const Text('投稿'),
                    onPressed: () async {
                      final date = DateTime.now().toLocal().toIso8601String();
                      final email = user.email;
                      await FirebaseFirestore.instance
                          .collection("posts")
                          .doc()
                          .set({
                        "text": messageText,
                        "email": email,
                        "date": date
                      });
                      Navigator.of(context).pop();
                    },
                  ))
            ],
          ),
        ));
  }
}

参考
Riverpodで状態管理

Discussion