본문 바로가기
개발 공부/Flutter

[Flutter]Flutter - 1. Firebase 로그인 구현하기

by 깐테 2022. 1. 25.

이번 포스팅 역시 이전에 간단하게 진행했던 프로젝트를 바탕으로 포스팅을 해보려 한다.

 

Flutter의 경우에는 라이브러리를 다운받아 진행하는 경우가 많으므로 pub.dev 사이트를 자주 이용한다.

 

Dart packages (pub.dev)

 

Dart packages

Pub is the package manager for the Dart programming language, containing reusable libraries & packages for Flutter, AngularDart, and general Dart programs.

pub.dev

 

1. Firebase 서비스 이용하기

Firebase 관련 서비스를 이용하기 위해서는 파이어베이스 콘솔에 앱을 등록하고,

Firebase_core라는 라이브러리가 필요하다.

 

파이어베이스 콘솔에 앱을 등록하는 과정은 아래 포스팅을 참조하면 된다.

 

2021.07.16 - [Android] - [Android] 구글 Firebase(파이어베이스) 연동하기

 

[Android] 구글 Firebase(파이어베이스) 연동하기

아마 요즘 가장 많이 사용하는 데이터베이스가 아닌가 할 생각이 들 정도로 많이들 사용하는 Google Firebase에 대해 포스팅하고 간단하게 연결해보도록 하겠다. 1. Firebase https://firebase.google.com/?hl=ko.

kante-kante.tistory.com

 

이후 콘솔에서 앱 등록을 완료했다면, 다음 과정을 따라하면 된다.

 

Migration Guide | FlutterFire

 

Migration Guide | FlutterFire

We changed the way developers integrate Flutter applications with Firebase in mid 2020. If you're currently using FlutterFire and wish

firebase.flutter.dev

 

pubspec.yaml 파일

pubspec.yaml 파일 내부에 firebase_core 라이브러리를 다음과 같이 등록한다.

 

실제 본인이 사용한 버전과 최신 버전에는 차이가 있을 수 있기 때문에 최신 버전은 pub.dev 홈페이지의 versions 탭을 참조하면 된다.

 

이후 사용중인 개발 도구의 우측 상단에서 pub get을 클릭해주거나, 콘솔에서 flutter pub get을 입력해주면 된다.

$ flutter pub get

 

2. 간단한 로그인 페이지 구현하기.

FlutterFire Overview | FlutterFire

 

FlutterFire Overview | FlutterFire

<img

firebase.flutter.dev

기본적인 구현 방식은 Firebase Flutter 설명 페이지에 initialization 부분을 참조하여 구현하였다.

 

1. main.dart

// main.dart

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

var logger = Logger(
  printer: PrettyPrinter(),
);

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


class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

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

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: '로그인 앱',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: const MyHomePage(),
    );
  }

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

  }

  @override
  dispose() async {
    super.dispose();

  }

}

main.dart 페이지에 다음과 같이 처리해주었다.

 

home 부분에는 MyHomePage로 처리해 두었는데 이 페이지에서는 로그인 사용자를 구별하여 회원 가입 페이지로 보낼 지, 로그인 한 다음 완료 페이지로 보낼 지 처리하기 위해 작성하였다.

 

혹시라도 GetMaterialApp을 찾지 못하는 경우에는 pubspec.yaml에서 get을 추가해주면 된다.

(getX 패키지에 대한 설명은 추후 포스팅 예정)

 

 

2. my_home_page.dart

// my_home_page.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:logintest/login_page.dart';
import 'package:logintest/market_page.dart';

import 'main.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

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

class _TmpPageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context){
    return const Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }

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

    _permission();
    _logout();
    _auth();

  }

  @override
  void dispose(){
    super.dispose();
  }

  // 제거해도 되는 부분이나, 추후 권한 설정과 관련된 포스팅 예정
  _permission() async{
    Map<Permission, PermissionStatus> statuses = await [
        Permission.storage,
    ].request();
    //logger.i(statuses[Permission.storage]);
  }

  _auth(){
    // 사용자 인증정보 확인. 딜레이를 두어 확인
    Future.delayed(const Duration(milliseconds: 100),() {
      if(FirebaseAuth.instance.currentUser == null){
        Get.off(() => const LoginPage());
      } else {
        Get.off(() => const MarketPage());
      }
    });
  }

  _logout() async{
    await FirebaseAuth.instance.signOut();
  }

}

_permission, _auth, _logout 세 부분으로 구별해두었다.

_permission 부분은 추후 Firebase Storage를 사용하고, 휴대폰 내부 저장소를 사용하기 위해 권한을 요청하는 부분이므로 해당 부분은 제거해도 된다.

 

_auth:

사용자 인증정보를 확인하는 부분이다.

만약 Firebase 데이터베이스에 현재 사용자 정보가 존재하지 않는다면 로그인 페이지로 이동하도록 처리하고

로그인된 사용자정보가 존재한다면 완료 페이지로 이동하도록 처리하였다.

 

pubspec.yaml에 firebase_auth를 추가해야 사용할 수 있다.

firebase_auth | Flutter Package (pub.dev)

 

firebase_auth | Flutter Package

Flutter plugin for Firebase Auth, enabling Android and iOS authentication using passwords, phone numbers and identity providers like Google, Facebook and Twitter.

pub.dev

 

해당 페이지를 참조하거나

 

아래와 같이 pubspec.yaml에 추가해주면 된다.

 

 

_logout():

현재 로그인한 사용자를 로그아웃하는 코드

 

 

3. login_page.dart

import 'package:logintest/join_page.dart';
import 'package:logintest/main.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logintest/market_page.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _emailController = TextEditingController(); //입력되는 값을 제어
  final TextEditingController _passwordController = TextEditingController();

  // 로그인 폼 상단에 이미지가 표시된다. 이미지가 없어도 동작은 하나, X표시 처리.
  String _imageFile = 'assets/images/main.png';

  Widget _userIdWidget(){
    return TextFormField(
      controller: _emailController,
      keyboardType: TextInputType.emailAddress,
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        labelText: '이메일',
      ),
      validator: (String? value){
        if (value!.isEmpty) {// == null or isEmpty
          return '이메일을 입력해주세요.';
        }
        return null;
      },
    );
  }

  Widget _passwordWidget(){
    return TextFormField(
      controller: _passwordController,
      obscureText: true,
      keyboardType: TextInputType.number,
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        labelText: '비밀번호',
      ),
      validator: (String? value){
        if (value!.isEmpty) {// == null or isEmpty
          return '비밀번호를 입력해주세요.';
        }
        return null;
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: const Text("로그인"),
        centerTitle: true,
      ),
      body: Form(
        key: _formKey,
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            children: [
              Image(width: 400.0, height: 250.0, image: AssetImage(_imageFile)),
              const SizedBox(height: 20.0),
              _userIdWidget(),
              const SizedBox(height: 20.0),
              _passwordWidget(),
              Container(
                height: 70,
                width: double.infinity,
                padding: const EdgeInsets.only(top: 8.0), // 8단위 배수가 보기 좋음
                child: ElevatedButton(
                    onPressed: () => _login(),
                    child: const Text("로그인")
                ),
              ),
              const SizedBox(height: 20.0),
              GestureDetector(
                child: const Text('회원 가입'),
                onTap: (){
                  Get.to(() => const JoinPage());
                },
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void initState() {
    //해당 클래스가 호출되었을떄
    super.initState();
  }
  @override
  void dispose() {
    // 해당 클래스가 사라질떄
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  _login() async {
    //키보드 숨기기
    if (_formKey.currentState!.validate()) {
      FocusScope.of(context).requestFocus(FocusNode());

      // Firebase 사용자 인증, 사용자 등록
      try {
        await FirebaseAuth.instance.signInWithEmailAndPassword(
          email: _emailController.text,
          password: _passwordController.text,
        );

        Get.offAll(() => const MarketPage());
      } on FirebaseAuthException catch (e) {
        logger.e(e);
        String message = '';

        if (e.code == 'user-not-found') {
          message = '사용자가 존재하지 않습니다.';
        } else if (e.code == 'wrong-password') {
          message = '비밀번호를 확인하세요';
        } else if (e.code == 'invalid-email') {
          message = '이메일을 확인하세요.';
        }

        /*final snackBar = SnackBar(
          content: Text(message),
          backgroundColor: Colors.deepOrange,
      );
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
      */

        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(message),
            backgroundColor: Colors.deepOrange,
          ),
        );
      }

    }
  }

}

 

validator: (String? value){
        if (value!.isEmpty) {// == null or isEmpty
          return '이메일을 입력해주세요.';
        }
        return null;
      },

이메일 부분과 비밀번호 부분을 입력할 수 있는 폼을 두개를 만들어 두었고

이 부분에서 validator를 통해 입력값을 검증받는 과정을 거치도록 처리하였다.

(이메일이나 비밀번호가 입력되지 않으면 입력해달라는 문구를 출력하도록 처리.)

 

 

_login() 부분: 

_login() async {
    //키보드 숨기기
    if (_formKey.currentState!.validate()) {
      FocusScope.of(context).requestFocus(FocusNode());

      // Firebase 사용자 인증, 사용자 등록
      try {
        await FirebaseAuth.instance.signInWithEmailAndPassword(
          email: _emailController.text,
          password: _passwordController.text,
        );

        Get.offAll(() => const MarketPage());
      } on FirebaseAuthException catch (e) {
        logger.e(e);
        String message = '';

        if (e.code == 'user-not-found') {
          message = '사용자가 존재하지 않습니다.';
        } else if (e.code == 'wrong-password') {
          message = '비밀번호를 확인하세요';
        } else if (e.code == 'invalid-email') {
          message = '이메일을 확인하세요.';
        }

        /*final snackBar = SnackBar(
          content: Text(message),
          backgroundColor: Colors.deepOrange,
      );
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
      */

        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(message),
            backgroundColor: Colors.deepOrange,
          ),
        );
      }

    }
  }

입력 폼의 값과 데이터베이스의 정보를 비교하여 입력된 값이 올바르다면 로그인 성공 처리하고 다음 페이지로 이동하도록 구성하였다.

 

만약 로그인에 실패했을 경우, 로그 메시지와 비교하여 사용자 화면에 오류 메시지를 띄워주도록 처리하였다.

 

 

 

3. 결과값

이메일 값이 올바르지 않거나 비밀번호가 올바르지 않으면 다음과 같이 하단에 메시지를 띄우게 된다.

 

 

만약 로그인에 성공했다면, 만들어 두었던 완료 페이지로 이동하도록 처리한다.

 

반응형