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

1. 시작하기 전에

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

전제조건

이 Codelab에서는 개발자가 Flutter 경험이 있다고 가정합니다. 그렇지 않다면 먼저 기본 사항을 배우는 것이 좋습니다. 다음 링크가 도움이 됩니다:

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

당신이 만들 것

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

cd5c4753bbee8af.png8cb4d21f656540bf.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 콘솔에 로그인합니다.
  2. Firebase 콘솔에서 프로젝트 추가 (또는 프로젝트 만들기 )를 클릭하고 Firebase 프로젝트 이름(예: ' Firebase-Flutter-Codelab') 을 입력합니다.

fe6aeab3b91965ed.png

  1. 프로젝트 생성 옵션을 클릭하세요. 메시지가 표시되면 Firebase 약관에 동의하세요. 이 앱에서는 Analytics를 사용하지 않으므로 Google Analytics 설정을 건너뛰세요.

d1fcec48bf251eaa.png

Firebase 프로젝트에 대해 자세히 알아보려면 Firebase 프로젝트 이해를 참조하세요.

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

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

이 두 제품은 특별한 구성이 필요하거나 Firebase 콘솔을 사용하여 활성화해야 합니다.

Cloud Firestore 활성화

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

Cloud Firestore를 활성화합니다.

  1. Firebase 콘솔의 빌드 섹션에서 Cloud Firestore를 클릭합니다.
  2. 데이터베이스 생성 을 클릭합니다. 99e8429832d23fa3.png
  3. 테스트 모드에서 시작 옵션을 선택합니다. 보안 규칙에 대한 고지 사항을 읽어보세요. 테스트 모드에서는 개발 중에 데이터베이스에 자유롭게 쓸 수 있습니다. 다음 을 클릭합니다. 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 프로젝트입니다. 하나는 complete 라고 하며, 건너뛰거나 자신의 코드를 상호 참조하려는 경우 참조할 수 있습니다. 다른 프로젝트는 start 라고 합니다.

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

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

Firebase CLI 설치

Firebase CLI는 Firebase 프로젝트 관리를 위한 도구를 제공합니다. Emulator Suite를 사용하려면 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 콘솔 에 나열된 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)을 설정하고, 에뮬레이터를 다운로드하고, 몇 개의 터미널 명령만으로 Flutter 앱에 Firebase를 추가할 수 있습니다.

먼저 다음을 실행하여 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를 사용할지 묻는 메시지가 나타나면 기본값인 예를 선택해야 합니다.

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

중요 : 아래 스크린샷에서 볼 수 있듯이 귀하의 출력은 내 결과와 약간 다를 수 있습니다. 왜냐하면 에뮬레이터를 이미 다운로드한 경우 최종 질문의 기본값은 "아니요"이기 때문입니다.

8544e41037637b07.png

FlutterFire 구성

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

flutterfire configure

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

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

619b7aca6dc15472.png301c9534f594f472.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 코드에 지시해야 합니다.

먼저, 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

이 명령은 에뮬레이터를 부팅하고 상호 작용할 수 있는 로컬 호스트 포트를 노출합니다. 해당 명령을 실행하면 다음과 유사한 출력이 표시됩니다.

bb7181eb70829606.png

이 출력은 실행 중인 에뮬레이터와 에뮬레이터를 보기 위해 이동할 수 있는 위치를 알려줍니다. 먼저 localhost:4000 에서 에뮬레이터 UI를 확인하세요.

11563f4c7216de81.png

로컬 에뮬레이터 UI의 홈페이지입니다. 여기에는 사용 가능한 모든 에뮬레이터가 나열되어 있으며 각 에뮬레이터에는 켜짐 또는 꺼짐 상태가 표시되어 있습니다.

5. Firebase 인증 에뮬레이터

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

3c1bfded40733189.png

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

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

사용자 추가

"사용자 추가" 버튼을 클릭하고 다음 정보로 양식을 작성하세요.

  • 표시 이름: 대시
  • 이메일: 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 문자열을 인증 에뮬레이터에서 생성한 이메일과 비밀번호로 대체합니다. 그리고 다음 줄에서는 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 Auth에서 제공되며 잠시 논의할 User.displayName 과 같은 필요한 정보를 제공합니다.

Firebase Auth에서 이메일과 비밀번호로 사용자를 로그인하기 위해 필요한 기본 코드입니다. 로그인을 위해 FirebaseAuth를 호출하고 Future<UserCredential> 객체를 반환합니다. 미래가 완료되면 이 코드는 UserCredential 에 연결된 User 있는지 확인합니다. 자격 증명 개체에 사용자가 있으면 사용자가 성공적으로 로그인한 것이므로 AppState.user 속성을 설정할 수 있습니다. 없으면 오류가 발생하여 인쇄됩니다.

이 메서드에서 일반 FirebaseAuth 코드가 아닌 이 앱과 관련된 유일한 코드 줄은 _listenForEntries 메서드에 대한 호출이며, 이에 대해서는 다음 단계에서 설명합니다.

TODO: 작업 아이콘 – 앱을 다시 로드한 다음 렌더링되면 로그인 버튼을 누릅니다. 그러면 앱이 "Welcome Back, Person!"이라는 페이지로 이동하게 됩니다. 상단에. 이 페이지로 이동할 수 있으므로 인증이 작동해야 합니다. 하지만 사용자의 실제 이름을 표시하려면 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 첫 번째 사용자를 정의할 때 에뮬레이터에서 설정되었습니다. 이제 앱에 "Welcome back, Dash!"가 표시됩니다. TODO 대신 로그인할 때.

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

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

에뮬레이터:

791fce7dc137910a.png

Firebase 콘솔:

e0dde9aea34af050.png

Firestore를 사용해 본 적이 있다면 이 페이지가 Firebase 콘솔 Firestore 페이지와 유사하다는 것을 알 수 있을 것입니다. 그러나 몇 가지 주목할만한 차이점이 있습니다.

  1. 버튼 하나만 누르면 모든 데이터를 지울 수 있습니다. 이는 프로덕션 데이터에서는 위험할 수 있지만 빠른 반복에는 도움이 됩니다! 새 프로젝트를 진행 중이고 데이터 모델이 변경된 경우 쉽게 정리할 수 있습니다.
  2. '요청' 탭이 있습니다. 이 탭을 사용하면 이 에뮬레이터에 대한 수신 요청을 볼 수 있습니다. 이 탭에 대해서는 잠시 후에 더 자세히 설명하겠습니다.
  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의 'Entries' 컬렉션이 존재하지 않았기 때문에 Firestore가 컬렉션을 만들었습니다.

해당 코드를 추가한 후 앱을 핫 리로드하거나 다시 시작하고 로그인한 후 EntryForm 보기로 이동하세요. 원하는 Strings 로 양식을 채울 수 있습니다. (Date 필드는 이 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);
   });
 }
 // ...
}

이 코드는 Firestore의 'Entries' 컬렉션을 수신합니다. Firestore가 이 클라이언트에 새 데이터가 있음을 알리면 해당 데이터를 전달하고 _listenForEntries 의 코드는 모든 하위 문서를 앱에서 사용할 수 있는 객체( Entry )로 변경합니다. 그런 다음 _entriesStreamController (UI가 수신 대기 중)라는 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
  • 파이어베이스 CLI
  • Firebase 인증 에뮬레이터
  • Firebase Firestore 에뮬레이터
  • 에뮬레이터 데이터 가져오기 및 내보내기

다음 단계

더 알아보기

Sparky는 당신을 자랑스럽게 생각합니다!

2a0ad195769368b1.gif