글 작성자: 취업중인 피터팬
728x90

0 - 기본적으로 앞의 글과 이어지며 firebase 이메일 인증과 구글 인증이  어플에 허용되어 있어야 합니다.

 

1. 목표

 

 

auth.dart을 구현함으로 firebase에서 지원하는 메일 인증과 구글 인증을 실행할 수 있도록 한다.

toast.dart을 구현해 오류를 출력한다.

 

 

 

2. 구현 내용

auth.dart

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:graduationproject/methods/toast.dart';
import 'backPage.dart';

class AuthPage extends StatefulWidget {
  AuthPage({Key key}) : super(key: key);
  static const routeName = '/auth';

  @override
  _AuthPageState createState() => _AuthPageState();
}

firebase_auth을 임포트 해주고 오류를 출력하는 파일인 toast.dart을 임포트 해준다. 앞의 게시글에서 설명했듯이 routeName을 지정해 준다. 

 

*이메일 인증을 구현하기 전에 먼저 어떻게 구현할 것지 설명하겠습니다. 

사용자가 이메일을 올바르게 입력하고 인증을 원한다. -> 어플에서 firebase에게 인증을 요청한다. -> firebase가 해당 매일로 이메일을 보낸다. -> 어플에서 사용자에게 이메일 인증을 요청한다. -> 사용자가 이메일을 확인하고 링크를 누른다. -> 사용자가 어플에서 이메일 인증을 했음을 알려준다. -> 어플은 firebase에 사용자 인증을 허가한다.

좀 더 자세한 구현 내용은 Sign_up에서 설명하도록 하겠습니다. 

 

  String _message = '이메일이을 보냈습니다. 이메일을 인증해주세요.';
  FirebaseUser _firebaseUser;
  final _scaffoldKey = GlobalKey<ScaffoldState>();


  @override
  void initState() {
    super.initState();
    _streamOpen();
  }

너무 쉬운 코드입니다. 클래스가 시작하면 가장 먼저 들리는 함수로 _streamOpen() 함수를 호출합니다.

당연히 FirebaseUser와 이메일 인증을 위한 GlobalKey을 선언해 준다.

 

_streamOpen() {
    FirebaseAuth.instance.onAuthStateChanged.listen((user) {
      if (user == null) {
        Navigator.pushReplacementNamed(context, '/backPage');
        return;
      }
      //이메일 확인을 누루지 않으면 Verified가 false이다.
      setState(() => _firebaseUser = user);
      if (!user.isEmailVerified) {
        setState(() => _message = '이메일이을 보냈습니다. 이메일을 인증해주세요.');
        return;
      }
      Navigator.pushReplacementNamed(context, '/backPage');
    });
  }

 

_streamOpen() 함수 코드입니다. onAuthStateChange 메서드는 Listener가 등록돼 직후, user가 로그인한 경우, 현재 user가 로그 아웃한 경우, 현재 user가 변경될 때 UI 스레드에서 호출됩니다. user가 null일 때 backPage로 이동한다.(backPage는 원래 main.dart에 있던 코드이다. 사용자가 기본적으로 보는 UI라고 생각하면 된다.) 이렇게 구현한 이유는 Splash.dart(앞의 글 참고)가 끝나면 auth.dart 페이지로 넘어가는데 auth에서 사용자 인증이 되어 있지 않으면 로그인 회원가입이 가능한 backPage로 이동할 수 있도록 한 것이다. 만약 user가 null이 아니라면 인증이 되어 있는 것임으로 메일 인증이 되었는지 확인해야 한다. 이메일 인증이 되어 있지 않다면 user.isEmailVerified 가 false가 될 것이다. 이때 이메일을 인증해 달라는 메시지를 보낸다. 만약 모든 인증이 완료되었다면 backPage로 이동한다.(사실 backPage을 이동할 때 사용자의 정보를 넘겨줘서 backPage에서 권한을 주는 느낌으로 로그인 안 했을 때와 다른 화면을 보여주어야 하는데 그건 page로 arguments을 넘기는 코드를 구현할 때 하도록 하자)

 

@override
  Widget build(BuildContext context) {
    if(app.loading) return _loading();
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
          title: Text('이메일 확인')
      ),
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              SizedBox(height: 50,),
              Text(
                  _message,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 30,
                  fontFamily: 'Nanum Barumpen',
                ),
              ),
              SizedBox(height: 20,),
              _firebaseUser == null ? CircularProgressIndicator() : _buildReloadButton()
            ],
          ),
        ),
      ),
    );
  }

auth.dart도 구현된 페이지 화면이 있다. 이메일을 인증하라고 요청과 , 인증을 확인하는 페이지인데 위젯으로 구현되어 있으며 _firebaseUser 가 null 일 때와 아닐 때 을 구현했는데 _firebaseUser가 null이 면 CircularProgressIndicator() 함수가 실행된다. 이 함수는 flutter에서 지원하는 로딩 중 함수 인다. 이 코드만 보면 사용자 인증이 안되면 계속 로딩 중이 되는 거 아닌가라는 의문이 생길 수 있는데 initState() 함수에 있는 _streamOpen() 함수를 생각해 보면 그렇지 않다. 애초에 user가 null이면 backPage 화면으로 넘어간다. 그럼 왜 CiircularProgressIndicator() 함수를 정의했는가? 사용자 인증은 했는데 아직 서버 데이터가 완전히 이동하지 않았을 때 즉 진짜 로딩 중일 때 로딩 중 함수가 호출되는 것이다. 위젯 빌드가 보이는 순간 이미 사용자는 인증을 하고 있는 상태이다. null이 false 일 때는 _buildReloadButton() 함수를 실행한다.

 Widget _buildReloadButton() {
    return RaisedButton(
      child: Text(
          '이메일을 확인했으면 클릭하세요',
        style: TextStyle(
          fontSize: 20,
        ),
      ),
      onPressed: () async {
        final user = await FirebaseAuth.instance.currentUser();
        await user.reload();
        if (!user.isEmailVerified) {
          toastError(_scaffoldKey, '이메일이 인증되지 않았습니다 다시 시도해주세요');
          return;
        }
        Navigator.pushReplacementNamed(context, '/backPage');
      },
    );
  }

 이 버튼 함수는 이메일 인증 함수로 버튼을 누르면 현재 사용자를 불러옵니다.  user.isEmailVerified 가 false면 toastError class을 호출해 오류를 출력합니다. null이 아니면 역시 backPage로 이동합니다.

 

3. 전제 소스 코드 - auth.dart

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:graduationproject/methods/toast.dart';
import 'backPage.dart';

class AuthPage extends StatefulWidget {
  AuthPage({Key key}) : super(key: key);
  static const routeName = '/auth';

  @override
  _AuthPageState createState() => _AuthPageState();
}

class AppState{
  bool loading;
  AppState(this.loading);
}

class _AuthPageState extends State<AuthPage> {
  //StreamSubscription<FirebaseUser> _subscriptionAuth;
  String _message = '이메일이을 보냈습니다. 이메일을 인증해주세요.';
  FirebaseUser _firebaseUser;
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final app = AppState(false);


  @override
  void initState() {
    super.initState();
    _streamOpen();
  }

//  @override
//  void dispose() {
//    //_subscriptionAuth.cancel();
//    super.dispose();
//  }


  //로그인 인증 stream
  _streamOpen() {
    FirebaseAuth.instance.onAuthStateChanged.listen((user) {
      if (user == null) {
        Navigator.pushReplacementNamed(context, '/backPage',  arguments: backPage(user: null));
        return;
      }
      //이메일 확인을 누루지 않으면 Verified가 false이다.
      setState(() => _firebaseUser = user);//?? 이코드의 의미가 무엇인가? setState을 왜 해주었는가?
      if (!user.isEmailVerified) {
        setState(() => _message = '이메일이을 보냈습니다. 이메일을 인증해주세요.');
        return;
      }
      Navigator.pushReplacementNamed(context, '/backPage', arguments: backPage(user : _firebaseUser));
    });
  }


  Widget _buildReloadButton() {
    return RaisedButton(
      child: Text(
          '이메일을 확인했으면 클릭하세요',
        style: TextStyle(
          fontSize: 20,
        ),
      ),
      onPressed: () async {
        final user = await FirebaseAuth.instance.currentUser();
        await user.reload();
        if (!user.isEmailVerified) {
          toastError(_scaffoldKey, '이메일이 인증되지 않았습니다 다시 시도해주세요');
          return;
        }
        Navigator.pushReplacementNamed(context, '/backPage', arguments: backPage(user : user));
      },
    );
  }


  @override
  Widget build(BuildContext context) {
    if(app.loading) return _loading();
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
          title: Text('이메일 확인')
      ),
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              SizedBox(height: 50,),
              Text(
                  _message,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 30,
                  fontFamily: 'Nanum Barumpen',
                ),
              ),
              SizedBox(height: 20,),
              _firebaseUser == null ? CircularProgressIndicator() : _buildReloadButton()
            ],
          ),
        ),
      ),
    );
  }

  //로딩중을 보여주는 위젯
  Widget _loading() {
    return Scaffold(
        appBar: AppBar(title: Text("loading...")),
        body: Center(child: CircularProgressIndicator())
    );
  }
}

다음 코드는 페이지가 이동할 때 데이터가 함께 이동하는 코드까지 포함되어 있습니다. 로딩 함수 설명 등 앞에 게시물과 겹치는 설명은 생략했습니다. toast.dart 또한 설명을 생략했습니다.

 

4. 구현된 화면

 

5. 앞으로 구현할 내용

 

 

 

Sign_up과 Sign_in을 구현한다.

 

 

 

출처 : 

https://flutter.dev/docs

 

Flutter Documentation

The landing page for Flutter documentation.

flutter.dev

https://fkkmemi.github.io/

 

memi’s startup

Development logs

fkkmemi.github.io