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

1. 시작하기 전에

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

기본 요건

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

Firebase에 대한 경험이 어느 정도 있어야 하지만 Flutter 프로젝트에 Firebase를 추가한 적이 없다면 괜찮습니다. Firebase 콘솔에 익숙하지 않거나 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 텍스트 편집기
  • Google 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. 데이터베이스 만들기를 클릭합니다. <ph type="x-smartling-placeholder">99e8429832d23fa3.png</ph>
  3. 테스트 모드에서 시작 옵션을 선택합니다. 보안 규칙에 관한 면책 조항을 읽습니다. 테스트 모드를 사용하면 개발 중에 데이터베이스에 자유롭게 쓸 수 있습니다. Next를 클릭합니다. <ph type="x-smartling-placeholder">6be00e26c72ea032.png</ph>
  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 프로젝트로 구성됩니다. 그 중 하나가 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를 사용하여 Firebase 프로젝트를 Flutter 앱에 추가

두 CLI가 설치되면 Firestore와 같은 개별 Firebase 제품을 설정하고, 에뮬레이터를 다운로드하고, 터미널 명령어 몇 개만으로 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를 사용할지 묻는 메시지가 표시되면 기본값인 Yes를 선택해야 합니다.

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

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

8544e41037637b07.png

FlutterFire 구성

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

flutterfire configure

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

다음 스크린샷은 답변해야 하는 메시지를 보여줍니다.

619b7aca6dc15472.png 301c9534f594f472.png

이 스크린샷은 프로세스 종료 시의 출력을 보여줍니다. Firebase에 익숙한 경우 Console에서 애플리케이션을 만들지 않아도 FlutterFire CLI에서 자동으로 애플리케이션을 만들었다는 것을 알 수 있습니다.

12199a85ade30459.png

Flutter 앱에 Firebase 패키지 추가

마지막 설정 단계는 Flutter 프로젝트에 관련 Firebase 패키지를 추가하는 것입니다. 터미널에서 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 프로젝트는 에뮬레이터를 사용할 수 있도록 설정되었지만, Flutter 코드에 나가는 Firebase 요청을 로컬 포트로 다시 라우트하도록 지시해야 합니다.

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

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의 홈페이지입니다. 사용 가능한 모든 에뮬레이터가 나열되며 각 에뮬레이터에는 사용 또는 사용 중지 상태가 라벨로 지정됩니다.

5. Firebase 인증 에뮬레이터

가장 먼저 사용할 에뮬레이터는 인증 에뮬레이터입니다. 'Go to Emulator'(에뮬레이터로 이동)를 클릭하여 인증 에뮬레이터를 시작합니다. 그러면 다음과 같은 페이지가 표시됩니다.

3c1bfded40733189.png

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

이제 Firebase 인증 에뮬레이터에 사용자를 추가한 후 Flutter UI를 통해 해당 사용자를 로그인하는 과정을 살펴보겠습니다.

사용자 추가하기

'사용자 추가'를 클릭합니다. 버튼을 클릭하고 양식에 다음 정보를 입력합니다.

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

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

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 문자열을 인증 에뮬레이터에서 만든 이메일 및 비밀번호로 바꿉니다. 다음 줄에서 if(true) 줄이 state.user이 null인지 확인하는 코드로 대체되었습니다. 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와 같은 필요한 정보를 제공합니다. 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는 첫 번째 사용자를 정의할 때 에뮬레이터에서 설정되었습니다. 이제 앱에 '환영합니다, Dash!' TODO가 아니라 로그인할 때

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

먼저 Firestore 에뮬레이터를 확인합니다. 에뮬레이터 UI 홈페이지 (localhost:4000)에서 'Go to emulator'를 클릭합니다. Firestore 카드의 경우 예를 들면 다음과 같습니다.

에뮬레이터:

791fce7dc137910a.png

Firebase Console:

e0dde9aea34af050.png

Firestore를 사용해 본 적이 있다면 이 페이지가 Firebase Console Firestore 페이지와 유사하다는 것을 알 수 있습니다. 하지만 몇 가지 중요한 차이점이 있습니다.

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

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

Firestore에 쓰기

'요청'에 대해 논의하기 전에 먼저 요청합니다. 이를 위해서는 코드 업데이트가 필요합니다. 먼저 앱의 양식을 연결하여 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의 'Entries'라는 컬렉션에 대한 참조를 가져옵니다. 그런 다음 Map<String, String> 유형이어야 하는 새 항목을 추가합니다.

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

코드를 추가한 후 핫 리로드하거나 앱을 다시 시작하고 로그인한 후 EntryForm 뷰로 이동합니다. 원하는 Strings로 양식을 작성할 수 있습니다. 날짜 필드는 이 Codelab을 위해 단순화되었으므로 모든 문자열을 사용합니다. 강력한 유효성 검사가 수행되지 않으며 어떤 방식으로든 DateTime 객체를 신경 쓰지 않습니다.)

양식에서 '제출'을 누릅니다. 앱에서는 아무 일도 일어나지 않지만 에뮬레이터 UI에서 새 항목을 확인할 수 있습니다.

Firestore 에뮬레이터의 요청 탭

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

a978fb34fb8a83da.png

이는 AppState.writeEntryToFirestore가 작동했음을 확인하며 이제 요청 탭에서 요청을 자세히 살펴볼 수 있습니다. 지금 해당 탭을 클릭하세요.

Firestore 에뮬레이터 요청

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

f0b37f0341639035.png

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

385d62152e99aad4.png

앞서 언급했듯이 Firestore 에뮬레이터는 앱의 보안 규칙을 개발하는 도구를 제공합니다. 이 뷰에는 이 요청이 보안 규칙의 어느 줄을 통과했는지(또는 실패한 경우) 정확하게 표시됩니다. 더 견고한 앱에서는 보안 규칙이 확장되고 여러 번의 승인 확인이 포함될 수 있습니다. 이 뷰는 이러한 승인 규칙을 작성하고 디버그하는 데 사용됩니다.

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

Firestore에서 읽기

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

이 코드는 각각 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);
   });
 }
 // ...
}

이 코드는 '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의 완료 코드는 'complete'에서 확인할 수 있습니다. GitHub 디렉터리: Flutter Codelabs

학습한 내용

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

다음 단계

자세히 알아보기

Sparky가 자랑스러워요.

2a0ad195769368b1.gif