🚦

Firebaseでユーザーの退会機能を作る

2022/08/21に公開約8,000字

これがないとAppleさんから、リジェクトを食らうらしい😇

FirebaseAuthenticationには、ユーザーを削除する機能があるそうです。
公式ドキュメント

https://firebase.flutter.dev/docs/auth/manage-users

こちらを書けば良いとのこと

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は切り分けてます。
ダイアログの作り方は、こちらのページを参考にいたしました。

https://www.kamo-it.org/blog/flutter-dialog/
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を使う方法もあるのですが、お金をかけたくないので、リリースしたアプリにはこちらの再認証させる方法を使いました。

Discussion

ログインするとコメントできます