플러터 로그인 로그아웃 구현 – Firebase Authentication 파이어베이스 로그인 로그아웃 예제 (2)

Flutter를 사용하면, Firebase Authentication을 활용하여 로그인 기능을 구현하는 것이 매우 간단합니다. 이 글에서는 Firebase을 사용하여 플러터 로그인 로그아웃 기능을 구현하는 과정에 대해 작성합니다.

회원 가입 구현

Flutter 앱에서 Firebase Authentication을 사용하여 안전하고 효율적인 회원가입 시스템을 구현하는 방법은 아래글을 참고하세요.

Firebase 초기화

Flutter 앱에서 Firebase를 사용하기 위해서는 앱이 시작될 때 Firebase를 초기화해야 합니다. main 함수에서 Firebase.initializeApp()를 호출하여 Firebase를 초기화합니다.

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

사용자 인증 상태 관리

MyApp 클래스는 앱의 루트 위젯입니다. 여기서 StreamBuilder를 사용하여 Firebase 인증 상태 변경을 실시간으로 감지하고, 사용자의 로그인 상태에 따라 적절한 화면을 표시합니다.

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // StreamBuilder를 사용하여 FirebaseAuth의 인증 상태 변경을 감지
      home: StreamBuilder<User?>(
        // FirebaseAuth.instance.authStateChanges()는 사용자 인증 상태가 변경될 때마다 스트림을 통해 알림
        stream: FirebaseAuth.instance.authStateChanges(),
        // 스트림의 데이터(인증 상태 변경)가 업데이트 될 때마다 builder 함수가 호출됩니다.
        builder: (context, snapshot) {
          // snapshot.connectionState가 active 상태인 경우, 인증 상태 변경 정보가 준비
          if (snapshot.connectionState == ConnectionState.active) {
            // user 변수에 현재 인증된 사용자 정보를 할당
            User? user = snapshot.data;
            // user가 null인 경우, 사용자가 로그아웃 상태임을 의미하므로 SignInPage()를 반환
            if (user == null) {
              return SignInPage();
            }
            // user가 null이 아닌 경우, 사용자가 로그인 상태임을 의미하므로 HomePage()를 반환
            return HomePage(user: user);
          }
          // 스트림의 연결 상태가 active 상태가 아닌 경우 로딩 인디케이터를 표시
          return Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        },
      ),
    );
  }
}

로그인 페이지 구현

SignInPage는 사용자가 이메일과 비밀번호를 입력하여 로그인할 수 있는 페이지입니다. 사용자가 로그인 버튼을 누르면 _signIn 메서드가 호출되고, Firebase 인증을 사용하여 로그인을 시도합니다.

class SignInPage extends StatefulWidget {
  @override
  _SignInPageState createState() => _SignInPageState();
}

class _SignInPageState extends State<SignInPage> {
  // 사용자 이메일과 비밀번호 입력을 위한 TextEditingController 인스턴스 생성
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  // FirebaseAuth 인스턴스를 사용하여 인증 처리
  final _auth = FirebaseAuth.instance;

  // 사용자에게 로그인 상태 또는 오류 메시지를 표시하기 위한 문자열 변수
  String _statusMessage = '';

  // 비동기 로그인 처리 메서드
  Future<void> _signIn() async {
    try {
      // 이메일과 비밀번호를 사용하여 사용자 로그인 시도
      final UserCredential userCredential =
          await _auth.signInWithEmailAndPassword(
        email: _emailController.text,
        password: _passwordController.text,
      );

      // 로그인 성공 시 이메일 인증 여부 확인
      if (userCredential.user != null) {
        if (!userCredential.user!.emailVerified) {
          // 이메일 인증이 완료되지 않은 경우 사용자에게 메시지 표시
          setState(() {
            _statusMessage = '이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요.';
          });
        } else {
          // 이메일 인증이 완료된 경우 로그인 성공 처리
          setState(() {
            _statusMessage = '로그인 성공!';
            // 홈 화면으로 이동
            Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => HomePage(user: userCredential.user!)));
          });
        }
      }
    } on FirebaseAuthException catch (e) {
      // FirebaseAuth에서 발생하는 예외 처리 및 오류 메시지 표시
      setState(() {
        _statusMessage = e.message ?? '로그인 실패';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    // MaterialApp과 Scaffold를 사용하여 로그인 페이지 UI 구성
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Sign In")),
        body: SingleChildScrollView(
          // 스크롤 가능한 컨테이너 내에 로그인 폼 구성
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: <Widget>[
              // 이메일 입력 필드
              Padding(
                padding: const EdgeInsets.only(bottom: 8.0),
                child: TextField(
                  controller: _emailController,
                  decoration: InputDecoration(labelText: "Email"),
                ),
              ),
              // 비밀번호 입력 필드 (텍스트 가림 처리)
              Padding(
                padding: const EdgeInsets.only(bottom: 20.0),
                child: TextField(
                  controller: _passwordController,
                  decoration: InputDecoration(labelText: "Password"),
                  obscureText: true,
                ),
              ),
              // 로그인 버튼
              ElevatedButton(
                onPressed: _signIn,
                child: Text("Sign In"),
              ),
              SizedBox(height: 20),
              // 로그인 상태 또는 오류 메시지 표시
              Text(_statusMessage),
            ],
          ),
        ),
      ),
    );
  }
}

플러터-로그인-ui

로그아웃 페이지 구현

로그인에 성공한 사용자를 위한 HomePage를 구현합니다. 여기서는 사용자의 이메일 주소를 표시하고, 로그아웃 버튼을 누르면 로그인 버튼으로 이동합니다.

_signOut이라는 별도의 비동기 함수를 정의하여 로그아웃 처리를 하였습니다. 이렇게 하면 로그아웃 로직을 재사용할 수 있고, 오류 처리를 위한 로직을 한 곳에 모을 수 있습니다. 만약 로그아웃 과정에서 오류가 발생하면, 사용자에게 적절한 피드백을 제공하기 위해 ScaffoldMessenger를 사용하여 스낵바 메시지를 표시합니다.

class HomePage extends StatelessWidget {
  // 사용자 정보를 담고 있는 User 객체를 final로 선언
  final User user;

  // 생성자에서 user를 필수 인자로 받음. required 키워드로 null이 아닌 값을 보장
  HomePage({required this.user});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("로그인 성공"),
        actions: [
          IconButton(
            icon: Icon(Icons.logout),
            // 로그아웃 버튼이 클릭되면 _signOut 함수를 비동기적으로 호출
            onPressed: () async {
              await _signOut(context);
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 사용자의 이메일을 화면에 표시
            Text("환영합니다, ${user.email}!", style: TextStyle(fontSize: 20)),
            SizedBox(height: 20),
            Text("로그인에 성공하였습니다.", style: TextStyle(fontSize: 16)),
            SizedBox(height: 20),
            // 로그아웃 버튼을 배치 클릭 시 _signOut 함수를 호출
            ElevatedButton(
              onPressed: () {
                _signOut(context);
              },
              child: Text("로그아웃"),
            ),
          ],
        ),
      ),
    );
  }

  // _signOut 함수는 로그아웃을 처리하는 비동기 함수
  Future<void> _signOut(BuildContext context) async {
    try {
      // FirebaseAuth의 signOut 메서드를 호출하여 사용자를 로그아웃
      await FirebaseAuth.instance.signOut();
      // 로그아웃 후 현재 페이지를 종료하고 이전 화면으로 돌아감
      Navigator.of(context).pop();
    } catch (e) {
      // 로그아웃 과정에서 오류가 발생한 경우 사용자에게 알림을 제공
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text("로그아웃 중 오류가 발생했습니다."),
        ),
      );
    }
  }
}
플러터-로그아웃-ui

마무리

Flutter와 Firebase를 사용하여 간단한 사용자 인증 시스템을 구축하는 방법을 살펴보았습니다. 이메일과 비밀번호를 사용한 로그인 기능은 앱에 있어 필수적인 부분이며, Firebase 인증을 이용하여 손쉽게 구현하였습니다. 읽어주셔서 감사합니다.

다음글은 시간 채팅 기능을 추가하는 방법에 대해서 작성하였습니다.

아래는 로그인 관련하여 참고하시면 좋은 글입니다.

Leave a Comment