목차

  1. Firebase 설치
    1. CLI 설치 (Firebase & FlutterFIre) 
  2. Firebase 연동
    1. CLI 로 firebase와 프로젝트 연동 
    2. 연동 확인
    3. 기타 설정
  3. 페이지별 코드

 

 

 

✅ Firebase 설치

 

1️⃣ CLI 설치 (Firebase & FlutterFIre) 

  • Firebase 설치 방식 (2종류)
    1. 실행할 앱의 운영체제 별 설치 (ios / android) (각 운영체제 별 설치 방법이 상이)
    2. 운영체제 통합 버젼인 CLI 설치 (** 해당 게시글은 CLI 설치로 진행)
      ** Firebase CLI 와 FlutterFire CLI 둘 다 설치
  • 명령어
    1. brew install firebase-cli : Firebase CLI 설치
    2. firebase login
      • 기타 설정
        • “Gemini in Firebase”(AI 지원 기능) 사용 여부
        • Firebase CLI에서 로그인한 직후 “사용 통계 수집 허용 여부”
    3. dart pub global activate flutterfire_cli : FlutterFire CLI 설치
    4. flutterfire --version : CLI 버젼확인 (정상설치 여부)
      • 에러 메세지 : zsh: command not found: flutterfire
        • FlutterFire CLI가 설치되지 않아서 생긴 에러
        • Firebase CLI는 설치했지만 Flutter 프로젝트와 Firebase를 연결하는 FlutterFire CLI는 아직 없는 상태
    5. PATH 재설정
      1. echo 'export PATH="$PATH:$HOME/.pub-cache/bin"' >> ~/.zshrc
      2. source ~/.zshrc
      3. which flutterfire
      4. flutterfire --version

 



 

✅ Firebase 연동

 

1️⃣ CLI 로 firebase와 프로젝트 연동

  1. flutterfire configure : firebase와 프로젝트 계정 연동

 

 

2️⃣ 연동 확인

  • flutter 프로젝트 lib 파일 내에 'firebase_options.dart' 파일이 생성되면 정상 연결

 

 

3️⃣ 기타 설정

  1. firebase에서 로그인 권한 설정(Authentication)
  2. pubspec.yaml 설정
  3. ios 플랫폼 버젼 설정
  4. 추가 설정된 내용 터미널로 적용

 

 

 

 

✅ 페이지별 코드

 

1️⃣ main.dart

  • WidgetsFlutterBinding : Firebase 같은 비동기 초기화를 해야 할 때 사용
  • .ensureInitialized() : 초기화가 완료될 때까지 기다림 (Firebase, SharedPreferences, 카메라 등 앱 실행 전에 초기화가 필요한 서비스와 함께 사용)
  • Firebase.initializeApp() : firebase 초기화
  • primarySwatch : 앱 전체의 테마(색상, 폰트, 버튼 스타일 등)를 정의
import 'package:firebase_core/firebase_core.dart';
import 'package:프로젝트명/home.dart';
import 'package:프로젝트명/login.dart';
import 'package:프로젝트명/signup.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  // WidgetsFlutterBinding : Firebase 같은 비동기 초기화를 해야 할 때 사용
  // .ensureInitialized() : 초기화가 완료될 때까지 기다림 (Firebase, SharedPreferences, 카메라 등 앱 실행 전에 초기화가 필요한 서비스와 함께 사용)
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // firebase 초기화
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue), // primarySwatch : 앱 전체의 테마(색상, 폰트, 버튼 스타일 등)를 정의
      initialRoute: '/',
      routes: {
        '/': (context) => const HomePage(),
        '/login': (context) => const LoginPage(),
        '/signup': (context) => const SignupPage(),
      },
    );
  }
}

 

 

2️⃣ login.dart

  • createState() : LoginPage의 상태를 관리하는 _LoginPageState 객체 생성
  • key : Form 위젯의 상태를 구분하거나 추적하는 “식별자” 속성. (어떤 키로 식별될지 지정)
  • GlobalKey 변수.currentState : FormState 객체에 접근 (validate, save 등 호출 가능)
  • GlobalKey 변수.currentState.validate() : 모든 FormField 검증
  • GlobalKey 변수.currentState.save() : 입력값 저장
  • FirebaseAuth.instance : Firebase Auth 객체 접근
  • .signInWithEmailAndPassword : Firebase Authentication으로 이메일/비밀번호 로그인
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();  // createState() : LoginPage의 상태를 관리하는 _LoginPageState 객체 생성
}

class _LoginPageState extends State<LoginPage> {
  final _key = GlobalKey<FormState>();  //
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _pwdController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Firebase App")),
      body: Container(
        padding: const EdgeInsets.all(15),
        child: Center(
          child: Form(
            key: _key,  // key : Form 위젯의 상태를 구분하거나 추적하는 “식별자” 속성. (어떤 키로 식별될지 지정)
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                emailInput(),
                const SizedBox(height: 15),
                passwordInput(),
                const SizedBox(height: 15),
                loginButton(),
                const SizedBox(height: 15),
                TextButton(
                  onPressed: () => Navigator.pushNamed(context, '/signup'),
                  child: const Text("Sign Up"),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  TextFormField emailInput() {
    return TextFormField(
      controller: _emailController,
      autofocus: true,
      validator: (val) {
        if (val!.isEmpty) {
          return 'The input is empty.';
        } else {
          return null;
        }
      },
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your email address.',
        labelText: 'Email Address',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  TextFormField passwordInput() {
    return TextFormField(
      controller: _pwdController,
      obscureText: true,
      autofocus: true,
      validator: (val) {
        if (val!.isEmpty) {
          return 'The input is empty.';
        } else {
          return null;
        }
      },
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your password.',
        labelText: 'Password',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  ElevatedButton loginButton() {
    return ElevatedButton(
      onPressed: () async { // 버튼 누르면 내부 기능을 비동기로 실행
        // _key.currentState.validate() : 모든 FormField 검증
        // _key.currentState.save() : 입력값 저장
        if (_key.currentState!.validate()) {  // _key.currentState : FormState 객체에 접근 (validate, save 등 호출 가능)
          try {
            await FirebaseAuth.instance // FirebaseAuth.instance : Firebase Auth 객체 접근
                .signInWithEmailAndPassword(  // Firebase Authentication으로 이메일/비밀번호 로그인
                  email: _emailController.text,
                  password: _pwdController.text,
                )
                .then(
                    (_) => Navigator.pushNamed(context, "/"));  // Navigator : 화면 전환을 담당하는 라우터 (push, pushNamed, pop, pushReplacement 등으로 동작)
          } on FirebaseAuthException catch (e) {
            if (e.code == 'user-not-found') {
              debugPrint('No user found for that email.');
            } else if (e.code == 'wrong-password') {
              debugPrint('Wrong password provided for that user.');
            }
          }
        }
      },
      child: Container(
        padding: const EdgeInsets.all(15),
        child: const Text("Login", style: TextStyle(fontSize: 18)),
      ),
    );
  }
}

 

 

3️⃣ signup.dart

  • GlobalKey<FormState> : Form 위젯을 고유하게 식별하고, Form의 상태(FormState)에 접근할 수 있게 해주는 키
  • .createUserWithEmailAndPassword() : email과 password로 유저 계정 생성
  • weak-password : Firebase 전용 코드 (Firebase의 기본 기준으로 보통 최소 길이(6자 미만)일 경우 발생)
  • email-already-in-use : Firebase 전용 코드 (이미 존재하는 이메일)
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class SignupPage extends StatefulWidget {
  const SignupPage({super.key});

  @override
  State<SignupPage> createState() => _SignupPageState();
}

class _SignupPageState extends State<SignupPage> {
  final _key = GlobalKey<FormState>();  // GlobalKey<FormState> : Form 위젯을 고유하게 식별하고, Form의 상태(FormState)에 접근할 수 있게 해주는 키
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _pwdController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Firebase App")),
      body: Container(
        padding: const EdgeInsets.all(15),
        child: Center(
          child: Form(
            key: _key,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                emailInput(),
                const SizedBox(height: 15),
                passwordInput(),
                const SizedBox(height: 15),
                submitButton(),
                const SizedBox(height: 15),
              ],
            ),
          ),
        ),
      ),
    );
  }

  TextFormField emailInput() {
    return TextFormField(
      controller: _emailController,
      autofocus: true,
      validator: (val) {
        if (val!.isEmpty) {
          return 'The input is empty.';
        } else {
          return null;
        }
      },
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your email address.',
        labelText: 'Email Address',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  TextFormField passwordInput() {
    return TextFormField(
      controller: _pwdController,
      obscureText: true,
      autofocus: true,
      validator: (val) {
        if (val!.isEmpty) {
          return 'The input is empty.';
        } else {
          return null;
        }
      },
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your password.',
        labelText: 'Password',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  ElevatedButton submitButton() {
    return ElevatedButton(
      onPressed: () async {
        if (_key.currentState!.validate()) {
          try {
            final credential = await FirebaseAuth.instance  // FirebaseAuth.instance : Firebase Auth 객체 접근
                .createUserWithEmailAndPassword(  // email과 password로 유저 계정 생성
                  email: _emailController.text, // _emailController.text : TextEditingController 객체를 통해 사용자가 입력한 텍스트를 가져옴
                  password: _pwdController.text,
                )
                .then(
                    (_) => Navigator.pushNamed(context, "/"));  // Navigator : 화면 전환을 담당하는 라우터 (push, pushNamed, pop, pushReplacement 등으로 동작)
          } on FirebaseAuthException catch (e) {
            if (e.code == 'weak-password') {  // weak-password : Firebase의 기본 기준으로 보통 최소 길이(6자 미만)일 경우 발생
              print('The password provided is too weak.');
            } else if (e.code == 'email-already-in-use') {  // email-already-in-use : Firebase 전용 코드 (이미 존재하는 이메일)
              print('The account already exists for that email.');
            }
          } catch (e) {
            print(e.toString());
          }
        }
      },
      child: Container(
        padding: const EdgeInsets.all(15),
        child: const Text("Sign Up", style: TextStyle(fontSize: 18)),
      ),
    );
  }
}

 

 

4️⃣ home.dart

  • StreamBuilder() : 실시간 데이터 스트림을 구독하고 UI를 자동 업데이트 (ex. 로그인 상태 변화, Firestore 데이터 변경 등)
  • authStateChanges() : 로그인/로그아웃 등 인증 상태 변화
  • await FirebaseAuth.instance.signOut() : Firebase로 접속한 계정 로그아웃
import 'package:firebase_auth/firebase_auth.dart';
import 'package:프로젝트명/login.dart';
import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder( // StreamBuilder() : 실시간 데이터 스트림을 구독하고 UI를 자동 업데이트 (ex. 로그인 상태 변화, Firestore 데이터 변경 등)
      stream: FirebaseAuth.instance.authStateChanges(),   // authStateChanges() : 로그인/로그아웃 등 인증 상태 변화
      builder: (BuildContext con, AsyncSnapshot<User?> user) {
        if (!user.hasData) {
          return const LoginPage();
        } else {
          return Scaffold(
            appBar: AppBar(
              title: const Text("Firebase App"),
              actions: [
                IconButton(
                  icon: const Icon(Icons.logout),
                  onPressed: () async => await FirebaseAuth.instance.signOut()  // await FirebaseAuth.instance.signOut() : Firebase로 접속한 계정 로그아웃
                    .then(
                        (_) => Navigator.pushNamed(context, "/login"),
                  ),
                ),
              ],
            ),
            body: const Center(child: Text("Successfully logged in!")),
          );
        }
      },
    );
  }
}

 

 

5️⃣ 테스트

 

 

+ Recent posts