Firebase 에뮬레이터 도구 모음을 사용한 Flutter 앱 로컬 개발

1. 시작하기 전에

이 Codelab에서는 로컬 개발 중에 Flutter와 함께 Firebase 에뮬레이터 도구 모음을 사용하는 방법을 알아봅니다. 에뮬레이터 도구 모음을 통해 이메일-비밀번호 인증을 사용하는 방법과 Firestore 에뮬레이터에서 데이터를 읽고 쓰는 방법을 알아봅니다. 마지막으로 에뮬레이터에서 데이터를 가져오고 내보내서 개발로 돌아올 때마다 동일한 가짜 데이터를 사용합니다.

기본 요건

이 Codelab에서는 Flutter를 사용한 경험이 있다고 가정합니다. 그렇지 않은 경우 먼저 기본사항을 학습하는 것이 좋습니다. 다음 링크가 도움이 됩니다.

Firebase를 사용해 본 경험도 있지만 Flutter 프로젝트에 Firebase를 추가해 본 적이 없어도 괜찮습니다. Firebase Console에 익숙하지 않거나 Firebase를 처음 사용하는 경우 먼저 다음 링크를 참조하세요.

생성할 항목

이 Codelab에서는 간단한 저널링 애플리케이션을 빌드하는 방법을 안내합니다. 애플리케이션에는 로그인 화면과 과거의 업무 일지 항목을 읽고 새 항목을 만들 수 있는 화면이 있습니다.

cd5c4753bbee8af.png 8cb4d21f656540bf.png

학습할 내용

Firebase 사용을 시작하는 방법과 Firebase 에뮬레이터 도구 모음을 Flutter 개발 워크플로에 통합하고 사용하는 방법을 배웁니다. 다음 Firebase 관련 주제를 다룹니다.

이러한 주제는 Firebase 에뮬레이터 도구 모음을 다루는 데 필요하므로 다룹니다. 이 Codelab은 Flutter 앱에 Firebase 프로젝트를 추가하고 Firebase 에뮬레이터 도구 모음을 사용한 개발에 중점을 둡니다. Firebase 인증 또는 Firestore에 대한 심층적인 논의는 없습니다. 이러한 주제에 익숙하지 않은 경우 Flutter용 Firebase 알아보기 Codelab부터 시작하는 것이 좋습니다.

필요한 사항

  • Flutter에 관한 실무 지식 및 설치된 SDK
  • Intellij JetBrains 또는 VS Code 텍스트 편집기
  • Chrome 브라우저 (또는 다른 Flutter용 개발 타겟. 이 Codelab의 일부 터미널 명령어는 앱이 Chrome에서 실행된다고 가정합니다.

2. Firebase 프로젝트 만들기 및 설정

가장 먼저 완료해야 하는 작업은 Firebase 웹 콘솔에서 Firebase 프로젝트를 만드는 것입니다. 이 Codelab의 대부분은 로컬에서 실행되는 UI를 사용하는 에뮬레이터 도구 모음을 중점적으로 다루지만 먼저 전체 Firebase 프로젝트를 설정해야 합니다.

Firebase 프로젝트 만들기

  1. Firebase Console에 로그인합니다.
  2. Firebase Console에서 프로젝트 추가 (또는 프로젝트 만들기)를 클릭하고 Firebase 프로젝트의 이름 (예: 'Firebase-Flutter-Codelab')을 입력합니다.

fe6aeab3b91965ed.png

  1. 프로젝트 만들기 옵션을 클릭합니다. 메시지가 표시되면 Firebase 약관에 동의합니다. 이 앱에는 애널리틱스를 사용하지 않으므로 Google 애널리틱스 설정을 건너뛰세요.

d1fcec48bf251eaa.png

Firebase 프로젝트에 대한 자세한 내용은 Firebase 프로젝트 이해를 참조하세요.

빌드 중인 앱은 Flutter 앱에 사용할 수 있는 두 가지 Firebase 제품을 사용합니다.

  • Firebase 인증 - 사용자가 앱에 로그인할 수 있습니다.
  • Cloud Firestore: 클라우드에 구조화된 데이터를 저장하고 데이터가 변경되면 즉시 알림을 받습니다.

이 두 제품은 특별한 구성이 필요하거나 Firebase Console을 사용하여 사용 설정해야 합니다.

Cloud Firestore 사용 설정

Flutter 앱은 Cloud Firestore를 사용하여 저널 항목을 저장합니다.

Cloud Firestore를 사용 설정합니다.

  1. Firebase Console의 빌드 섹션에서 Cloud Firestore를 클릭합니다.
  2. 데이터베이스 만들기를 클릭합니다. 99e8429832d23fa3.png
  3. 테스트 모드에서 시작 옵션을 선택합니다. 보안 규칙에 대한 면책조항을 읽습니다. 테스트 모드를 사용하면 개발 중에 데이터베이스에 자유롭게 쓸 수 있습니다. Next를 클릭합니다. 6be00e26c72ea032.png
  4. 데이터베이스 위치를 선택합니다. 기본값을 사용해도 됩니다. 이 위치는 나중에 변경할 수 없습니다. 278656eefcfb0216.png
  5. 사용 설정을 클릭합니다.

3. Flutter 앱 설정

시작하기 전에 시작 코드를 다운로드하고 Firebase CLI를 설치해야 합니다.

시작 코드 가져오기

명령줄에서 GitHub 저장소를 클론합니다.

git clone https://github.com/flutter/codelabs.git flutter-codelabs

또는 GitHub의 CLI 도구가 설치된 경우 다음 안내를 따릅니다.

gh repo clone flutter/codelabs flutter-codelabs

샘플 코드는 Codelab 컬렉션 코드가 포함된 flutter-codelabs 디렉터리에 클론해야 합니다. 이 Codelab의 코드는 flutter-codelabs/firebase-emulator-suite에 있습니다.

flutter-codelabs/firebase-emulator-suite 아래의 디렉터리 구조는 Flutter 프로젝트 2개입니다. 하나는 complete이며, 계속 건너뛰거나 자체 코드를 교차 참조하려는 경우 참조할 수 있습니다. 다른 프로젝트는 start라고 합니다.

시작하려는 코드는 flutter-codelabs/firebase-emulator-suite/start 디렉터리에 있습니다. 해당 디렉터리를 열거나 원하는 IDE로 가져옵니다.

cd flutter-codelabs/firebase-emulator-suite/start

Firebase CLI 설치

Firebase CLI는 Firebase 프로젝트를 관리하는 도구를 제공합니다. 에뮬레이터 도구 모음을 사용하려면 CLI가 필요하므로 CLI를 설치해야 합니다.

CLI를 설치하는 방법에는 여러 가지가 있습니다. MacOS 또는 Linux를 사용하는 경우 가장 간단한 방법은 터미널에서 다음 명령어를 실행하는 것입니다.

curl -sL https://firebase.tools | bash

CLI를 설치한 후에는 Firebase에 인증해야 합니다.

  1. 다음 명령어를 실행하여 Google 계정으로 Firebase에 로그인합니다.
firebase login
  1. 이 명령어는 로컬 머신을 Firebase에 연결하고 Firebase 프로젝트에 대한 액세스 권한을 부여합니다.
  1. Firebase 프로젝트를 나열하여 CLI가 올바르게 설치되었고 계정에 액세스할 수 있는지 테스트합니다. 다음 명령어를 실행합니다.
firebase projects:list
  1. 표시된 목록은 Firebase Console에 나열된 Firebase 프로젝트와 같아야 합니다. 최소한 firebase-flutter-codelab을 볼 수 있습니다.

FlutterFire CLI 설치

FlutterFire CLI는 Firebase CLI를 기반으로 빌드되며 Firebase 프로젝트를 Flutter 앱과 더 쉽게 통합할 수 있게 해줍니다.

먼저 CLI를 설치합니다.

dart pub global activate flutterfire_cli

CLI가 설치되었는지 확인합니다. Flutter 프로젝트 디렉터리 내에서 다음 명령어를 실행하고 CLI가 도움말 메뉴를 출력하는지 확인합니다.

flutterfire --help

Firebase CLI 및 FlutterFire CLI를 사용하여 Flutter 앱에 Firebase 프로젝트 추가

CLI 두 개를 설치한 상태에서 몇 가지 터미널 명령어로 개별 Firebase 제품 (예: Firestore)을 설정하고 에뮬레이터를 다운로드하며 Firebase를 Flutter 앱에 추가할 수 있습니다.

먼저 다음을 실행하여 Firebase 설정을 완료합니다.

firebase init

이 명령어는 프로젝트를 설정하는 데 필요한 일련의 질문을 안내합니다. 다음 스크린샷은 흐름을 보여줍니다.

  1. 기능을 선택하라는 메시지가 표시되면 'Firestore'와 '에뮬레이터'를 선택합니다. (Flutter 프로젝트 파일에서 수정할 수 있는 구성을 사용하지 않으므로 인증 옵션은 없습니다.) fe6401d769be8f53.png
  2. 그런 다음 메시지가 표시되면 '기존 프로젝트 사용'을 선택합니다.

F11dcab439e6ac1e.png

  1. 이제 이전 단계에서 만든 프로젝트인 flutter-firebase-codelab을 선택합니다.

3bdc0c6934991c25.png

  1. 다음으로 생성될 파일의 이름 지정에 대한 몇 가지 질문을 받게 됩니다. 각 질문에서 'Enter'를 눌러 기본값을 선택하는 것이 좋습니다. 9bfa2d507e199c59.png
  2. 마지막으로 에뮬레이터를 구성해야 합니다. 목록에서 Firestore 및 인증을 선택한 다음 각 에뮬레이터에 사용할 특정 포트에 관한 각 질문에 'Enter' 키를 누릅니다. 에뮬레이터 UI를 사용할지 묻는 메시지가 표시되면 기본값(예)을 선택해야 합니다.

프로세스가 끝나면 다음 스크린샷과 같은 출력이 표시됩니다.

중요: 아래 스크린샷과 같이 출력이 저와 약간 다를 수 있습니다. 에뮬레이터를 이미 다운로드한 경우 마지막 질문의 기본값이 'No'로 설정되기 때문입니다.

8544e41037637b07.png

FlutterFire 구성

다음으로 FlutterFire를 사용하여 Flutter 앱에서 Firebase를 사용하는 데 필요한 Dart 코드를 생성할 수 있습니다.

flutterfire configure

이 명령어를 실행하면 사용할 Firebase 프로젝트와 설정할 플랫폼을 선택하라는 메시지가 표시됩니다. 이 Codelab에서는 Flutter 웹을 사용하지만 모든 옵션을 사용하도록 Firebase 프로젝트를 설정할 수도 있습니다.

다음 스크린샷은 응답해야 하는 프롬프트를 보여줍니다.

619b7aca6dc15472.png 301c9534f594f472.png

이 스크린샷은 프로세스 마지막의 출력을 보여줍니다. Firebase에 익숙하다면 콘솔에서 애플리케이션을 만들지 않아도 되며 FlutterFire CLI가 알아서 생성했음을 알 수 있습니다.

12199a85ade30459.png

Flutter 앱에 Firebase 패키지 추가

마지막 설정 단계는 관련 Firebase 패키지를 Flutter 프로젝트에 추가하는 것입니다. 터미널에서 flutter-codelabs/firebase-emulator-suite/start에 있는 Flutter 프로젝트의 루트에 있는지 확인합니다. 그런 후 다음 세 가지 명령어를 실행합니다.

flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add cloud_firestore

이 애플리케이션에서는 이러한 패키지만 사용합니다.

4. Firebase 에뮬레이터 사용 설정

지금까지 Flutter 앱과 Firebase 프로젝트는 에뮬레이터를 사용할 수 있도록 설정되었지만 발신 Firebase 요청을 로컬 포트로 다시 라우팅하도록 Flutter 코드에 지시해야 합니다.

먼저 Firebase 초기화 코드와 에뮬레이터 설정 코드를 main.dart.main 함수에 추가합니다.

main.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'app_state.dart';
import 'firebase_options.dart';
import 'logged_in_view.dart';
import 'logged_out_view.dart';


void main() async {
 WidgetsFlutterBinding.ensureInitialized();
 await Firebase.initializeApp(
   options: DefaultFirebaseOptions.currentPlatform,
 );

 if (kDebugMode) {
   try {
     FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
     await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
   } catch (e) {
     // ignore: avoid_print
     print(e);
   }
 }

 runApp(MyApp());
}

처음 몇 줄의 코드로 Firebase가 초기화됩니다. 거의 보편적으로, Flutter 앱에서 Firebase를 사용하는 경우 먼저 WidgetsFlutterBinding.ensureInitializedFirebase.initializeApp를 호출하는 것이 좋습니다.

그 다음에 if (kDebugMode) 줄로 시작되는 코드는 프로덕션 Firebase 프로젝트가 아닌 에뮬레이터를 타겟팅하도록 앱에 지시합니다. kDebugMode는 개발 환경에 있는 경우에만 에뮬레이터 타겟팅이 발생하도록 합니다. kDebugMode는 상수 값이므로 Dart 컴파일러는 출시 모드에서 코드 블록을 모두 삭제할 수 있습니다.

에뮬레이터 시작

Flutter 앱을 시작하기 전에 에뮬레이터를 시작해야 합니다. 먼저 터미널에서 다음을 실행하여 에뮬레이터를 시작합니다.

firebase emulators:start

이 명령어는 에뮬레이터를 부팅하고 상호작용할 수 있는 localhost 포트를 노출합니다. 이 명령어를 실행하면 다음과 비슷한 출력이 표시됩니다.

bb7181eb70829606.png

이 출력을 통해 실행 중인 에뮬레이터와 에뮬레이터를 볼 수 있는 위치를 알 수 있습니다. 먼저 localhost:4000에서 에뮬레이터 UI를 확인합니다.

11563f4c7216de81.png

이는 로컬 에뮬레이터 UI의 홈페이지입니다. 사용 가능한 모든 에뮬레이터가 나열되며 각 에뮬레이터에는 상태 on 또는 off라는 라벨이 지정되어 있습니다.

5. Firebase 인증 에뮬레이터

사용할 첫 번째 에뮬레이터는 인증 에뮬레이터입니다. UI의 인증 카드에서 '에뮬레이터로 이동'을 클릭하여 인증 에뮬레이터를 시작하면 다음과 같은 페이지가 표시됩니다.

3c1bfded40733189.png

이 페이지는 인증 웹 콘솔 페이지와 유사합니다. 온라인 콘솔과 같은 사용자가 나열된 표가 있으며, 이를 통해 사용자를 수동으로 추가할 수 있습니다. 여기서 큰 차이점은 에뮬레이터에서 사용할 수 있는 유일한 인증 방법 옵션이 이메일과 비밀번호를 통하는 것뿐이라는 것입니다. 로컬 개발에는 이 정도면 충분합니다.

다음으로, Firebase 인증 에뮬레이터에 사용자를 추가하고 Flutter UI를 통해 해당 사용자를 로그인하는 프로세스를 살펴봅니다.

사용자 추가하기

'사용자 추가' 버튼을 클릭하고 다음 정보로 양식을 작성합니다.

  • 표시 이름: Dash
  • 이메일: dash@email.com
  • 비밀번호: 대시워드

양식을 제출하면 표에 사용자가 포함된 것이 표시됩니다. 이제 코드를 업데이트하여 해당 사용자로 로그인할 수 있습니다.

logged_out_view.dart

LoggedOutView 위젯에서 업데이트해야 하는 유일한 코드는 사용자가 로그인 버튼을 누를 때 트리거되는 콜백에 있습니다. 다음과 같이 코드를 업데이트합니다.

class LoggedOutView extends StatelessWidget {
 final AppState state;
 const LoggedOutView({super.key, required this.state});
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Firebase Emulator Suite Codelab'),
     ),
     body: Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
          Text(
           'Please log in',
            style: Theme.of(context).textTheme.displaySmall,
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
             onPressed: () async {
              await state.logIn('dash@email.com', 'dashword').then((_) {
                if (state.user != null) {
                 context.go('/');
                }
              });
              },
              child: const Text('Log In'),
          ),
        ),
      ],
    ),
   ),
  );
 }
}

업데이트된 코드가 TODO 문자열을 인증 에뮬레이터에서 만든 이메일과 비밀번호로 바꿉니다. 다음 줄은 state.user가 null인지 확인하는 코드로 if(true) 줄을 대체했습니다. AppClass의 코드가 이를 자세히 보여줍니다.

app_state.dart

AppState에 있는 코드의 두 부분을 업데이트해야 합니다. 먼저 클래스 멤버 AppState.user에 Object 유형이 아닌 firebase_auth 패키지의 User 유형을 지정합니다.

둘째, 아래와 같이 AppState.login 메서드를 작성합니다.

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user; // <-- changed variable type
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 } 
 // ...
}

사용자의 유형 정의는 이제 User?입니다. 이 User 클래스는 Firebase 인증에서 가져오며 User.displayName과 같은 필요한 정보를 제공합니다. 이에 대해서는 잠시 후에 설명합니다.

Firebase 인증에서 이메일과 비밀번호로 사용자를 로그인하는 데 필요한 기본 코드입니다. FirebaseAuth를 호출하여 로그인하면 Future<UserCredential> 객체가 반환됩니다. Future가 완료되면 이 코드는 UserCredential에 연결된 User가 있는지 확인합니다. 사용자 인증 정보 객체에 사용자가 있으면 사용자가 정상적으로 로그인한 것이며 AppState.user 속성을 설정할 수 있습니다. 존재하지 않으면 오류가 발생한 것이며 출력됩니다.

이 메서드에서 일반적인 FirebaseAuth 코드가 아니라 이 앱과 관련된 유일한 코드 줄은 _listenForEntries 메서드 호출이며, 이 내용은 다음 단계에서 다룹니다.

TODO: 작업 아이콘 – 앱을 새로고침한 후 렌더링되면 로그인 버튼을 누릅니다. 이렇게 하면 앱이 상단에 '환영합니다, 사람!'이라는 메시지가 있는 페이지로 이동합니다. 이 페이지로 이동할 수 있으므로 인증이 작동 중이어야 하지만 사용자의 실제 이름을 표시하려면 logged_in_view.dart을(를) 약간 업데이트해야 합니다.

logged_in_view.dart

LoggedInView.build 메서드의 첫 번째 줄을 변경합니다.

class LoggedInView extends StatelessWidget {
 final AppState state;
 LoggedInView({super.key, required this.state});

 final PageController _controller = PageController(initialPage: 1);

 @override
 Widget build(BuildContext context) {
   final name = state.user!.displayName ?? 'No Name';

   return Scaffold(
 // ...

이제 이 줄은 AppState 객체의 User 속성에서 displayName를 가져옵니다. 이 displayName는 첫 번째 사용자를 정의할 때 에뮬레이터에서 설정되었습니다. 이제 로그인할 때 앱에 TODO이 아닌 'Welcome back, Dash!'가 표시됩니다.

6. Firestore 에뮬레이터에서 데이터 읽기 및 쓰기

먼저 Firestore 에뮬레이터를 확인합니다. 에뮬레이터 UI 홈페이지 (localhost:4000)에서 Firestore 카드의 '에뮬레이터로 이동'을 클릭합니다. 다음과 같이 표시됩니다.

에뮬레이터:

791fce7dc137910a.png

Firebase Console:

E0dde9aea34af050.png

Firestore를 사용해 본 경험이 있다면 이 페이지는 Firebase Console Firestore 페이지와 비슷하다는 것을 알 수 있습니다. 하지만 몇 가지 주목할 만한 차이점이 있습니다.

  1. 버튼 하나만 탭하면 모든 데이터를 삭제할 수 있습니다. 이는 프로덕션 데이터의 경우 위험할 수 있지만 빠른 반복에 유용합니다. 새 프로젝트를 작업하고 있는데 데이터 모델이 변경되면 쉽게 삭제할 수 있습니다.
  2. '요청' 탭이 있습니다. 이 탭을 사용하면 이 에뮬레이터로 수신되는 요청을 볼 수 있습니다. 이 탭에 대해서는 나중에 자세히 설명하겠습니다.
  3. Rules, Indexes, Usage에 대한 탭이 없습니다. 보안 규칙 작성을 도와주는 도구가 있지만 (다음 섹션에서 설명) 로컬 에뮬레이터에는 보안 규칙을 설정할 수 없습니다.

요약하자면, 이 Firestore 버전은 개발 중에 더 많은 유용한 도구를 제공하며 프로덕션에 필요한 도구를 제거합니다.

Firestore에 쓰기

에뮬레이터의 'Requests' 탭에 관해 논의하기 전에 먼저 요청합니다. 이를 위해서는 코드를 업데이트해야 합니다. 먼저 앱에서 양식을 연결하여 Firestore에 새 저널 Entry을 작성합니다.

Entry를 제출하는 대략적인 흐름은 다음과 같습니다.

  1. 사용자가 양식을 작성하고 Submit 버튼을 누름
  2. UI에서 AppState.writeEntryToFirebase를 호출합니다.
  3. AppState.writeEntryToFirebase는 Firebase에 항목을 추가합니다.

1단계나 2단계와 관련된 코드는 변경할 필요가 없습니다. 3단계에서 추가해야 하는 유일한 코드는 AppState 클래스에 추가됩니다. AppState.writeEntryToFirebase를 다음과 같이 변경합니다.

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }
 // ...
}

WriteEntryToFirebase 메서드의 코드는 Firestore에서 'Entry'라는 컬렉션에 대한 참조를 가져옵니다. 그런 다음 Map<String, String> 유형이어야 하는 새 항목을 추가합니다.

이 경우 Firestore에 'Entries' 컬렉션이 없으므로 Firestore에서 컬렉션을 만들었습니다.

이 코드를 추가하고 핫 리로드하거나 앱을 다시 시작하고 로그인하고 EntryForm 뷰로 이동합니다. 원하는 Strings을 사용하여 양식을 작성할 수 있습니다. 날짜 필드는 이 Codelab에서 간소화한 모든 문자열을 사용합니다. 어떤 식으로든 DateTime 객체에 대한 강력한 유효성 검사나 관심 사항이 없습니다.)

양식에서 제출을 누릅니다. 앱에서는 아무 일도 일어나지 않지만 에뮬레이터 UI에 새 항목이 표시됩니다.

Firestore 에뮬레이터의 요청 탭

UI에서 Firestore 에뮬레이터로 이동하여 '데이터' 탭을 확인합니다. 이제 데이터베이스 루트에 'Entry'라는 컬렉션이 있습니다. 양식에 입력한 것과 동일한 정보가 포함된 문서가 있어야 합니다.

A978fb34fb8a83da.png

AppState.writeEntryToFirestore가 작동한 것을 확인했으므로 이제 요청 탭에서 요청을 더 자세히 살펴볼 수 있습니다. 지금 해당 탭을 클릭합니다.

Firestore 에뮬레이터 요청

그러면 다음과 비슷한 목록이 표시됩니다.

F0b37f0341639035.png

해당 목록 항목을 클릭하면 유용한 정보를 볼 수 있습니다. 새 저널 항목 만들기 요청에 해당하는 CREATE 목록 항목을 클릭합니다. 다음과 같은 새로운 표가 표시됩니다.

385d62152e99aad4.png

앞서 언급했듯이 Firestore 에뮬레이터는 앱의 보안 규칙을 개발하는 도구를 제공합니다. 이 보기에는 이 요청이 통과했거나 실패한 경우 실패한 보안 규칙의 행이 정확히 표시됩니다. 더 강력한 앱에서는 보안 규칙이 확장되고 여러 승인 확인이 포함될 수 있습니다. 이 보기는 이러한 승인 규칙을 작성하고 디버그하는 데 사용됩니다.

또한 메타데이터와 인증 데이터를 비롯하여 이 요청의 모든 부분을 간편하게 검사할 수 있는 방법을 제공합니다. 이 데이터는 복잡한 승인 규칙을 작성하는 데 사용됩니다.

Firestore에서 읽기

Firestore는 데이터 동기화를 사용하여 업데이트된 데이터를 연결된 기기로 푸시합니다. Flutter 코드에서 Firestore 컬렉션과 문서를 수신 대기 (또는 구독)할 수 있으며 데이터가 변경될 때마다 코드에 알림이 전송됩니다. 이 앱에서는 Firestore 업데이트 수신 대기가 AppState._listenForEntries라는 메서드에서 실행됩니다.

이 코드는 각각 AppState._entriesStreamControllerAppState.entries라는 StreamControllerStream와 함께 작동합니다. Firestore의 데이터를 표시하는 데 UI에 필요한 모든 코드와 마찬가지로 이 코드는 이미 작성되어 있습니다.

아래 코드와 일치하도록 _listenForEntries 메서드를 업데이트합니다.

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }

 void _listenForEntries() {
   FirebaseFirestore.instance
       .collection('Entries')
       .snapshots()
       .listen((event) {
     final entries = event.docs.map((doc) {
       final data = doc.data();
       return Entry(
         date: data['date'] as String,
         text: data['text'] as String,
         title: data['title'] as String,
       );
     }).toList();

     _entriesStreamController.add(entries);
   });
 }
 // ...
}

이 코드는 Firestore의 'Entries' 컬렉션을 수신 대기합니다. Firestore는 이 클라이언트에 새 데이터가 있음을 알리면 해당 데이터를 전달하고 _listenForEntries의 코드는 모든 하위 문서를 앱에서 사용할 수 있는 객체(Entry)로 변경합니다. 그런 다음 이러한 항목을 UI가 리슨하는 _entriesStreamController라는 StreamController에 추가합니다. 이 코드만 업데이트하면 됩니다.

마지막으로 AppState.logIn 메서드는 사용자가 로그인한 후 수신 대기 프로세스를 시작하는 _listenForEntries를 호출합니다.

// ...
Future<void> logIn(String email, String password) async {
 final credential = await FirebaseAuth.instance
     .signInWithEmailAndPassword(email: email, password: password);
 if (credential.user != null) {
   user = credential.user!;
   _listenForEntries();
 } else {
   print('no user!');
 }
}
// ...

이제 앱을 실행합니다. 다음과 같이 표시됩니다.

b8a31c7a8900331.gif

7. 에뮬레이터로 데이터 내보내기 및 가져오기

Firebase 에뮬레이터는 데이터 가져오기 및 내보내기를 지원합니다. 가져오기와 내보내기를 사용하면 개발을 중단했다가 재개해도 동일한 데이터로 개발을 계속할 수 있습니다. 데이터 파일을 git에 커밋할 수도 있으며, 함께 작업하는 다른 개발자에게 동일한 데이터를 사용할 수 있습니다.

에뮬레이터 데이터 내보내기

먼저 이미 가지고 있는 에뮬레이터 데이터를 내보냅니다. 에뮬레이터가 실행되는 동안 새 터미널 창을 열고 다음 명령어를 입력합니다.

firebase emulators:export ./emulators_data

.emulators_data는 인수로, 데이터를 내보낼 위치를 Firebase에 알려줍니다. 디렉터리가 없으면 새로 만듭니다. 해당 디렉터리에 원하는 이름을 사용할 수 있습니다.

이 명령어를 실행하면 명령어를 실행한 터미널에 다음과 같은 출력이 표시됩니다.

i  Found running emulator hub for project flutter-firebase-codelab-d6b79 at http://localhost:4400
i  Creating export directory /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
i  Exporting data to: /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
✔  Export complete

에뮬레이터가 실행 중인 터미널 창으로 전환하면 다음과 같은 출력이 표시됩니다.

i  emulators: Received export request. Exporting data to /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data.
✔  emulators: Export complete.

마지막으로 프로젝트 디렉터리를 보면 ./emulators_data라는 디렉터리가 표시됩니다. 이 디렉터리에는 저장된 데이터와 함께 여러 메타데이터 파일 중에서 JSON 파일이 포함되어 있습니다.

에뮬레이터 데이터 가져오기

이제 개발 워크플로의 일부로 해당 데이터를 가져와 중단한 지점부터 시작할 수 있습니다.

먼저 터미널에서 CTRL+C를 눌러 실행 중인 에뮬레이터를 중지합니다.

다음으로, 이미 확인한 emulators:start 명령어를 실행하되 가져올 데이터를 알려주는 플래그를 포함합니다.

firebase emulators:start --import ./emulators_data

에뮬레이터가 실행되면 localhost:4000의 에뮬레이터 UI로 이동하면 이전에 작업했던 것과 동일한 데이터가 표시됩니다.

에뮬레이터를 닫을 때 자동으로 데이터 내보내기

또한 각 개발 세션이 끝날 때마다 데이터를 내보내는 대신 에뮬레이터를 종료할 때 자동으로 데이터를 내보낼 수도 있습니다.

에뮬레이터를 시작할 때 두 개의 추가 플래그를 사용하여 emulators:start 명령어를 실행합니다.

firebase emulators:start --import ./emulators_data --export-on-exit

자, 이제 이 프로젝트의 에뮬레이터로 작업할 때마다 데이터가 저장되고 다시 로드됩니다. 다른 디렉터리를 –export-on-exit flag의 인수로 지정할 수도 있지만 기본적으로 –import에 전달된 디렉터리로 지정됩니다.

이러한 옵션을 조합하여 사용할 수도 있습니다. 문서 메모: 내보내기 디렉터리는 firebase emulators:start --export-on-exit=./saved-data 플래그로 지정할 수 있습니다. --import을 사용하는 경우 내보내기 경로는 기본적으로 동일하게 설정됩니다(예: firebase emulators:start --import=./data-path --export-on-exit). 마지막으로 원하는 경우 다른 디렉터리 경로를 --import--export-on-exit 플래그에 전달합니다.

8. 수고하셨습니다.

Firebase 에뮬레이터와 Flutter로 준비 및 실행을 완료했습니다. 이 Codelab의 완성된 코드는 GitHub의 'complete' 디렉터리(Flutter Codelabs)에서 확인할 수 있습니다.

학습한 내용

  • Firebase를 사용하도록 Flutter 앱 설정
  • Firebase 프로젝트 설정
  • FlutterFire CLI
  • Firebase CLI
  • Firebase 인증 에뮬레이터
  • Firebase Firestore 에뮬레이터
  • 에뮬레이터 데이터 가져오기 및 내보내기

다음 단계

자세히 알아보기

Sparky는 회원님을 자랑스럽게 생각합니다.

2a0ad195769368b1.gif