Firebaseでユーザーの退会機能を作る
これがないとAppleさんから、リジェクトを食らうらしい😇
FirebaseAuthenticationには、ユーザーを削除する機能があるそうです。
公式ドキュメント
こちらを書けば良いとのこと
await user?.delete();
メソッドにするとこんな感じですね。userコレクションと、messagesサブコレクションも削除するように、書いています。
void deleteUser() async {
final user = FirebaseAuth.instance.currentUser;
final uid = user?.uid;
// userコレクションを削除
final msg =
await FirebaseFirestore.instance.collection('users').doc(uid).delete();
// messagesサブコレクションを削除
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.collection('messages')
.doc(uid)
.delete();
// ユーザーを削除
await user?.delete();
await FirebaseAuth.instance.signOut();
print('ユーザーを削除しました!');
}
認証だけだと、こんな感じになるのでしょうね。関数を作るか、ボタンの中に直接書く。
// 定数を定義する。
final user = FirebaseAuth.instance.currentUser;
// uidを取得する
final uid = user?.uid;
// ?つけないとエラーが出る!
await user?.delete();
で、他の記事で使っているコードに機能を追加して、プログラムを作ってみました。
ダイアログが使えるクラスを使ってWidgetは切り分けてます。
ダイアログの作り方は、こちらのページを参考にいたしました。
import 'package:auth_crud/main.dart';
import 'package:auth_crud/page/group_info.dart';
import 'package:auth_crud/page/home_page.dart';
import 'package:auth_crud/page/update.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class DeleteUserPage extends StatefulWidget {
DeleteUserPage({Key? key}) : super(key: key);
State<DeleteUserPage> createState() => _DeleteUserPageState();
}
class _DeleteUserPageState extends State<DeleteUserPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HOME'),
),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () async {
final String? selectedText = await showDialog<String>(
context: context,
builder: (_) {
return SimpleDialogSample();
});
print('ユーザーを削除しました!');
Navigator.push(context,
MaterialPageRoute(builder: (context) => GroupInfoPage()));
},
child: Text('ユーザーを削除')),
],
),
),
);
}
}
class SimpleDialogSample extends StatefulWidget {
SimpleDialogSample({Key? key}) : super(key: key);
State<SimpleDialogSample> createState() => _SimpleDialogSampleState();
}
class _SimpleDialogSampleState extends State<SimpleDialogSample> {
void deleteUser() async {
final user = FirebaseAuth.instance.currentUser;
final uid = user?.uid;
final msg =
await FirebaseFirestore.instance.collection('users').doc(uid).delete();
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.collection('messages')
.doc(uid)
.delete();
// ユーザーを削除
await user?.delete();
await FirebaseAuth.instance.signOut();
print('ユーザーを削除しました!');
}
Widget build(BuildContext context) {
return SimpleDialog(
title: Text('退会してもよろしいですか?'),
children: [
SimpleDialogOption(
child: Text('退会する'),
onPressed: () async {
deleteUser();
print('ユーザーを削除しました!');
Navigator.push(context,
MaterialPageRoute(builder: (context) => GroupInfoPage()));
},
),
SimpleDialogOption(
child: Text('退会しない'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => HomePage()));
print('キャンセルされました!');
},
)
],
);
}
}
退会したら移動する静的ページのコード
import 'package:auth_crud/main.dart';
import 'package:flutter/material.dart';
class GroupInfoPage extends StatelessWidget {
const GroupInfoPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('退会完了'),
),
body: Center(
child: Column(
children: [
Text('退会手続きを完了いたしました!'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SignInPage()));
},
child: Text('新規登録へ戻る')),
],
),
),
);
}
}
で戻ると、Widgetのエラーが発生した!
原因は、MaterialAppと、Scaffoldが必要でした。
他の記事のコードを修正
main.dartにログインのページを作ったので、こんな感じになってます。
import 'package:auth_crud/firebase_options.dart';
import 'package:auth_crud/page/home_page.dart';
import 'package:auth_crud/page/sign_up.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.blue),
title: "auth_crud",
home: SignInPage());
}
}
class SignInPage extends StatefulWidget {
const SignInPage({Key? key}) : super(key: key);
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
String? email;
String? password;
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FlutterZero'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
TextField(
controller: _emailController,
onChanged: (email) {
this.email = email;
},
decoration: InputDecoration(hintText: 'Email'),
),
TextField(
controller: _passwordController,
onChanged: (password) {
this.password = password;
},
obscureText: true,
decoration: InputDecoration(hintText: 'Password'),
),
ElevatedButton(
child: Text('Sign In'),
onPressed: () async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
final user = FirebaseAuth.instance.currentUser!;
final snackBar = SnackBar(
content: Text(user.email!),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.push(context,
MaterialPageRoute(builder: (context) => HomePage()));
} catch (e) {
print(e);
}
},
),
TextButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SignUpPage()));
},
child: Text('新規登録'))
],
),
),
);
}
}
成功するとこんな感じで、ログ出ました。
Restarted application in 595ms.
flutter: キャンセルされました!
2
flutter: ユーザーを削除しました!
Application finished.
Exited
補足情報
ログインして時間が経つとユーザーを削除する機能が使えなくなってしまうので、その場合どのように対処したかというと、またログインをさせて再認証するとことによって、deleteメソッドを使えるようにしました。
Cloud Functionsを使う方法もあるのですが、お金をかけたくないので、リリースしたアプリにはこちらの再認証させる方法を使いました。
追加情報
最近、Cloud Functionsの使い方がわかってきたので、ロジックを書いておきます。
こちらの記事を見て、退会機能の作り方が理解できました。
- やること
- 退会コレクションを作る.
- 退会コレクションに、uidとTimestampを保存する.
- ドキュメントidとuidが一致してればユーザーは削除される.
ソースコード
Cloud Functionsでプロジェクトを作る
Flutterのログイン機能を持っているアプリにコードを追加。退会ページに設置してください。
userのコードは、こんな感じになると思います。?つけないと怒れるかも。
delete_usersが作成されると、メールアドレスとパスワードログインで登録した、ユーザー情報が削除されます。
final user = FirebaseAuth.instance.currentUser;
ElevatedButton(
onPressed: () async {
final data = {
"uid": user.uid,
"deletedAt": Timestamp.now(),
};
await FirebaseFirestore.instance
.collection('delete_users')
.add(data)
.then((value) async => {
await FirebaseAuth.instance.signOut(),
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) {
return LoginPage();
}))
})
.catchError((e) => print("Failed to add user: $e"));
},
child: Text('退会する'),
)
Cloud Functionsのコード
TypeScriptで作成してます。関数は使う前にDeployしておいてください!
// Cloud Functions for Firebase SDKでCloud Functionsを作成し、トリガーを設定します。
import * as functions from "firebase-functions";
// FirestoreにアクセスするためのFirebase Admin SDKです。
import * as admin from "firebase-admin";
admin.initializeApp();
// 削除するユーザーを登録するコレクションが作成されたら、FirebaseAuthenticatonのユーザーデータを削除する.
// リージョンは、asia-northeast1を設定。自分の設定したリージョンに合わせる。
exports.deleteUser = functions.region("asia-northeast1").firestore.document("deleted_users/{docId}").onCreate(async (snap, context) => {
const deleteDocument = snap.data();
const uid = deleteDocument.uid;
await admin.auth().deleteUser(uid);
});
Discussion