😽
Flutter+Firebase+RiverPodでCount Appを作ってみた。
細かいことは抜きにしてコード貼ります。
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();
},
))
],
),
));
}
}
Discussion