Flutter용 Firebase 알아보기

1. 시작하기 전에

이 코드랩에서는 Android 및 iOS용 Flutter 모바일 앱을 만들기 위한 Firebase 의 몇 가지 기본 사항을 배웁니다.

전제 조건

이 코드랩은 사용자가 Flutter에 익숙하고 Flutter SDK편집기 를 설치했다고 가정합니다.

당신이 만들 것

이 코드랩에서는 Flutter를 사용하여 Android, iOS, 웹 및 macOS에서 이벤트 RSVP 및 방명록 채팅 앱을 빌드합니다. Firebase 인증으로 사용자를 인증하고 Cloud Firestore를 사용하여 데이터를 동기화합니다.

필요한 것

다음 기기 중 하나를 사용하여 이 코드랩을 실행할 수 있습니다.

  • 컴퓨터에 연결되고 개발자 모드로 설정된 물리적 장치(Android 또는 iOS).
  • iOS 시뮬레이터. ( Xcode 도구를 설치 해야 합니다.)
  • 안드로이드 에뮬레이터. ( Android Studio 에서 설정이 필요합니다.)

위 항목 외에도 다음이 필요합니다.

  • Chrome과 같이 선택한 브라우저.
  • Dart 및 Flutter 플러그인으로 구성된 Android Studio 또는 VS Code 와 같은 원하는 IDE 또는 텍스트 편집기.
  • 최신 stable 버전의 Flutter (또는 엣지에서 생활하는 것을 즐기는 경우 beta 버전).
  • Firebase 프로젝트를 만들고 관리하기 위한 Gmail 계정과 같은 Google 계정입니다.
  • firebase 명령줄 도구 , Gmail 계정에 로그인했습니다.
  • Codelab의 샘플 코드입니다. 코드를 얻는 방법은 다음 단계를 참조하십시오.

2. 샘플 코드 받기

GitHub에서 프로젝트의 초기 버전을 다운로드하여 시작하겠습니다.

명령줄에서 GitHub 리포지토리 를 복제합니다.

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

또는 GitHub의 cli 도구가 설치된 경우:

gh repo clone flutter/codelabs flutter-codelabs

샘플 코드는 codelab 컬렉션에 대한 코드가 포함된 flutter-codelabs 디렉토리에 복제되어야 합니다. 이 코드랩의 코드는 flutter-codelabs/firebase-get-to-know-flutter 있습니다.

flutter-codelabs/firebase-get-to-know-flutter 아래의 디렉토리 구조는 각 명명된 단계의 끝에 있어야 하는 위치에 대한 일련의 스냅샷입니다. 이것은 2단계이므로 일치하는 파일을 찾는 것은 다음과 같이 쉽습니다.

cd flutter-codelabs/firebase-get-to-know-flutter/step_02

앞으로 건너뛰고 싶거나 단계 후의 모습을 보려면 관심 있는 단계의 이름을 따서 명명된 디렉토리를 살펴보십시오.

시작 앱 가져오기

flutter-codelabs/firebase-get-to-know-flutter/step_02 디렉토리를 열거나 원하는 IDE로 가져옵니다. 이 디렉토리에는 아직 작동하지 않는 Flutter 모임 앱으로 구성된 코드랩의 시작 코드가 포함되어 있습니다.

작업할 파일 찾기

이 앱의 코드는 여러 디렉터리에 분산되어 있습니다. 이 기능 분할은 기능별로 코드를 그룹화하여 작업하기 쉽도록 설계되었습니다.

프로젝트에서 다음 파일을 찾습니다.

  • lib/main.dart : 이 파일에는 주요 진입점과 애플리케이션 위젯이 포함되어 있습니다.
  • lib/src/widgets.dart : 이 파일에는 애플리케이션의 스타일을 표준화하는 데 도움이 되는 몇 가지 위젯이 포함되어 있습니다. 이들은 스타터 앱의 화면을 구성하는 데 사용됩니다.
  • lib/src/authentication.dart : 이 파일에는 Firebase 이메일 기반 인증을 위한 로그인 사용자 경험을 생성하기 위한 위젯 세트와 함께 FirebaseUI 인증 의 부분 구현이 포함되어 있습니다. 인증 흐름을 위한 이러한 위젯은 아직 시작 앱에서 사용되지 않지만 곧 연결할 것입니다.

나머지 응용 프로그램을 구축하는 데 필요한 추가 파일을 추가합니다.

lib/main.dart 파일 검토

이 앱은 google_fonts 패키지를 활용하여 전체 앱에서 Roboto를 기본 글꼴로 만들 수 있습니다. 동기가 부여된 독자를 위한 연습은 fonts.google.com 을 탐색하고 앱의 다른 부분에서 찾은 글꼴을 사용하는 것입니다.

Header , ParagraphIconAndDetail 형식으로 lib/src/widgets.dart 의 도우미 위젯을 활용하고 있습니다. 이러한 위젯은 중복 코드를 제거하여 HomePage 에 설명된 페이지 레이아웃의 혼란을 줄입니다. 이것은 일관된 모양과 느낌을 가능하게 하는 추가적인 이점이 있습니다.

Android, iOS, 웹 및 macOS에서 앱이 어떻게 표시되는지는 다음과 같습니다.

앱 미리보기

3. Firebase 프로젝트 생성 및 설정

이벤트 정보를 표시하는 것은 손님에게 유용하지만 이벤트를 표시하는 것만으로는 누구에게나 그다지 유용하지 않습니다. 이 앱에 몇 가지 동적 기능을 추가해 보겠습니다. 이를 위해서는 Firebase를 앱에 연결해야 합니다. Firebase를 시작하려면 Firebase 프로젝트를 만들고 설정해야 합니다.

Firebase 프로젝트 만들기

  1. Firebase 에 로그인합니다.
  2. Firebase 콘솔에서 프로젝트 추가 (또는 프로젝트 생성 )를 클릭하고 Firebase 프로젝트의 이름을 Firebase-Flutter-Codelab 으로 지정합니다.

4395e4e67c08043a.png

  1. 프로젝트 생성 옵션을 클릭합니다. 메시지가 표시되면 Firebase 약관에 동의합니다. 이 앱에 Analytics를 사용하지 않을 것이기 때문에 Google Analytics 설정을 건너뛰십시오.

b7138cde5f2c7b61.png

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

빌드 중인 앱은 웹 앱에 사용할 수 있는 여러 Firebase 제품을 사용합니다.

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

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

Firebase 인증을 위한 이메일 로그인 활성화

사용자가 웹 앱에 로그인할 수 있도록 하려면 이 코드랩에서 이메일/비밀번호 로그인 방법을 사용합니다.

  1. Firebase 콘솔의 왼쪽 패널에서 빌드 메뉴를 확장합니다.
  2. 인증 을 클릭한 다음 시작하기 버튼을 클릭한 다음 로그인 방법 탭을 클릭합니다(또는 여기를 클릭 하여 로그인 방법 탭으로 직접 이동).
  3. 로그인 공급자 목록에서 이메일/비밀번호 를 클릭하고 활성화 스위치를 켜짐 위치로 설정한 다음 저장 을 클릭합니다. 58e3e3e23c2f16a4.png

Cloud Firestore 활성화

웹 앱은 Cloud Firestore 를 사용하여 채팅 메시지를 저장하고 새 채팅 메시지를 받습니다.

Cloud Firestore 활성화:

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

4. Firebase 구성

Firebase를 Flutter와 함께 사용하려면 FlutterFire 라이브러리를 올바르게 활용하도록 Flutter 프로젝트를 구성하는 프로세스를 따라야 합니다.

  • 프로젝트에 FlutterFire 종속성 추가
  • Firebase 프로젝트에 원하는 플랫폼 등록
  • 플랫폼별 구성 파일을 다운로드하고 코드에 추가합니다.

Flutter 앱의 최상위 디렉토리에는 android , ios , macosweb 이라는 하위 디렉토리가 있습니다. 이 디렉토리에는 iOS 및 Android 각각에 대한 플랫폼별 구성 파일이 있습니다.

종속성 구성

이 앱에서 사용 중인 두 가지 Firebase 제품(Firebase Auth 및 Cloud Firestore)에 FlutterFire 라이브러리를 추가해야 합니다. 다음 세 가지 명령을 실행하여 종속성을 추가합니다.

$ flutter pub add firebase_core 
Resolving dependencies...
+ firebase_core 1.10.5
+ firebase_core_platform_interface 4.2.2
+ firebase_core_web 1.5.2
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3
  test_api 0.4.3 (0.4.8 available)
Changed 5 dependencies!

firebase_core 는 모든 Firebase Flutter 플러그인에 필요한 공통 코드입니다.

$ flutter pub add firebase_auth
Resolving dependencies...
+ firebase_auth 3.3.3
+ firebase_auth_platform_interface 6.1.8
+ firebase_auth_web 3.3.4
+ intl 0.17.0
  test_api 0.4.3 (0.4.8 available)
Changed 4 dependencies!

firebase_auth 를 사용하면 Firebase의 인증 기능과 통합할 수 있습니다.

$ flutter pub add cloud_firestore
Resolving dependencies...
+ cloud_firestore 3.1.4
+ cloud_firestore_platform_interface 5.4.9
+ cloud_firestore_web 2.6.4
  test_api 0.4.3 (0.4.8 available)
Changed 3 dependencies!

cloud_firestore 를 사용하면 Cloud Firestore 데이터 저장소에 액세스할 수 있습니다.

$ flutter pub add provider
Resolving dependencies...
+ nested 1.0.0
+ provider 6.0.1
  test_api 0.4.3 (0.4.8 available)
Changed 2 dependencies!

필수 패키지를 추가하는 동안 iOS, Android, macOS 및 웹 러너 프로젝트도 Firebase를 적절하게 활용하도록 구성해야 합니다. 또한 디스플레이 논리에서 비즈니스 논리를 분리할 수 있는 provider 패키지를 사용하고 있습니다.

flutterfire 설치하기

FlutterFire CLI는 기본 Firebase CLI에 따라 다릅니다. 아직 설치하지 않았다면 Firebase CLI 가 컴퓨터에 설치되어 있는지 확인합니다.

다음으로 다음 명령을 실행하여 FlutterFire CLI를 설치합니다.

$ dart pub global activate flutterfire_cli

설치가 완료되면 flutterfire 명령을 전 세계적으로 사용할 수 있습니다.

앱 구성

CLI는 Firebase 프로젝트 및 선택한 프로젝트 애플리케이션에서 정보를 추출하여 특정 플랫폼에 대한 모든 구성을 생성합니다.

애플리케이션 루트에서 configure 명령을 실행합니다.

$ flutterfire configure

구성 명령은 다음과 같은 여러 프로세스를 안내합니다.

  1. Firebase 프로젝트 선택(.firebaserc 파일 또는 Firebase 콘솔 기반)
  2. 구성하려는 플랫폼(예: Android, iOS, macOS 및 웹)을 묻습니다.
  3. 선택한 플랫폼에 대해 구성을 추출하는 데 사용해야 하는 Firebase 애플리케이션을 식별합니다. 기본적으로 CLI는 현재 프로젝트 구성을 기반으로 Firebase 앱을 자동으로 일치시키려고 시도합니다.
  4. 프로젝트에서 firebase_options.dart 파일을 생성합니다.

macOS 구성

macOS의 Flutter는 완전히 샌드박스 처리된 애플리케이션을 빌드합니다. 이 애플리케이션은 Firebase 서버와 통신하기 위해 네트워크를 사용하여 통합되므로 네트워크 클라이언트 권한으로 애플리케이션을 구성해야 합니다.

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

자세한 내용은 권한 및 앱 샌드박스 를 참조하십시오.

5. 사용자 로그인 추가(RSVP)

앱에 Firebase를 추가했으므로 이제 Firebase 인증 을 사용하여 사람을 등록하는 RSVP 버튼을 설정할 수 있습니다. Android 네이티브, iOS 네이티브 및 웹의 경우 사전 빌드된 FirebaseUI 인증 패키지가 있지만 Flutter의 경우 이 기능을 빌드해야 합니다.

2단계에서 검색한 프로젝트에는 대부분의 인증 흐름에 대한 사용자 인터페이스를 구현하는 위젯 세트가 포함되어 있습니다. Firebase 인증을 애플리케이션에 통합하는 비즈니스 로직을 구현합니다.

공급자와 비즈니스 로직

provider 패키지를 사용하여 Flutter 위젯의 응용 프로그램 트리 전체에서 중앙 집중식 응용 프로그램 상태 개체를 사용할 수 있도록 할 것입니다. 시작하려면 lib/main.dart 상단에서 가져오기를 수정하십시오.

라이브러리/메인.다트

import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

import 'firebase_options.dart';                    // new
import 'src/authentication.dart';                  // new
import 'src/widgets.dart';

import 라인은 Firebase Core 및 Auth를 소개하고, 위젯 트리를 통해 애플리케이션 상태 객체를 사용할 수 있도록 하는 데 사용하는 provider 패키지를 가져오고, lib/src 의 인증 위젯을 포함합니다.

이 응용 프로그램 상태 개체인 ApplicationState 에는 이 단계에 대한 두 가지 주요 책임이 있지만 이후 단계에서 응용 프로그램에 더 많은 기능을 추가함에 따라 추가 책임을 갖게 됩니다. 첫 번째 책임은 Firebase.initializeApp() 을 호출하여 Firebase 라이브러리를 초기화하는 것입니다. 그런 다음 승인 흐름을 처리합니다. lib/main.dart 끝에 다음 클래스를 추가합니다.

라이브러리/메인.다트

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods =
          await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  Future<void> signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> registerAccount(
      String email,
      String displayName,
      String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();
  }
}

이 수업에서 몇 가지 핵심 사항을 언급할 가치가 있습니다. 사용자가 인증되지 않은 상태에서 시작하고 앱은 사용자의 이메일 주소를 요청하는 양식을 표시합니다. 해당 이메일 주소가 파일에 있는지 여부에 따라 앱은 사용자 등록을 요청하거나 비밀번호를 요청한 다음 모든 것이 잘 작동한다고 가정하면 사용자 인증됩니다.

로그인에 문제가 있는 기존 계정이 있는 사용자의 경우를 처리하지 않기 때문에 이것이 FirebaseUI 인증 흐름의 완전한 구현이 아니라는 점에 유의해야 합니다. 이 추가 기능을 구현하는 것은 동기부여 독자.

인증 흐름 통합

이제 애플리케이션 상태가 시작되었으므로 애플리케이션 상태를 앱 초기화에 연결하고 인증 흐름을 HomePage 에 추가할 차례입니다. provider 패키지를 통해 응용 프로그램 상태를 통합하도록 기본 진입점을 업데이트합니다.

라이브러리/메인.다트

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

main 기능을 수정하면 공급자 패키지가 ChangeNotifierProvider 위젯을 사용하여 응용 프로그램 상태 개체를 인스턴스화하는 책임이 있습니다. 응용 프로그램 상태 개체가 ChangeNotifier 를 확장하고 provider 패키지가 종속 위젯을 다시 표시할 시기를 알 수 있도록 하기 때문에 이 특정 공급자 클래스를 사용하고 있습니다. 마지막으로 HomePagebuild 방법을 업데이트하여 애플리케이션 상태를 Authentication 과 통합합니다.

라이브러리/메인.다트

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Authentication 위젯을 인스턴스화하고 Consumer 위젯에 래핑합니다. 소비자 위젯은 응용 프로그램 상태가 변경될 때 provider 패키지를 사용하여 트리의 일부를 다시 빌드하는 데 사용할 수 있는 일반적인 방법입니다. Authentication 위젯은 이제 테스트할 인증 UI입니다.

인증 흐름 테스트

cdf2d25e436bd48d.png

다음은 사용자가 RSVP 버튼을 탭하여 이메일 양식을 시작할 수 있는 인증 흐름의 시작입니다.

2a2cd6d69d172369.png

이메일을 입력하면 시스템은 사용자가 이미 등록되어 있는지 확인합니다. 이 경우 사용자에게 암호를 묻는 메시지가 표시되고, 사용자가 등록되지 않은 경우 등록 양식을 진행합니다.

e5e65065dba36b54.png

오류 처리 흐름을 확인하려면 짧은 암호(6자 미만)를 입력해 보십시오. 사용자가 등록된 경우 대신 암호가 표시됩니다.

fbb3ea35fb4f67a.png

이 페이지에서 잘못된 암호를 입력하여 이 페이지의 오류 처리를 확인하십시오. 마지막으로, 사용자가 로그인하면 사용자에게 다시 로그아웃할 수 있는 기능을 제공하는 로그인 경험이 표시됩니다.

4ed811a25b0cf816.png

이를 통해 인증 흐름을 구현했습니다. 축하해요!

6. Cloud Firestore에 메시지 쓰기

사용자가 온다는 사실을 알고 있으면 좋겠지만 게스트에게 앱에서 할 수 있는 다른 일을 주자. 방명록에 메시지를 남길 수 있다면 어떨까요? 그들은 왜 그들이 오고 싶어하는지 또는 누구를 만나고 싶은지 공유할 수 있습니다.

사용자가 앱에 작성한 채팅 메시지를 저장하려면 Cloud Firestore 를 사용합니다.

데이터 모델

Cloud Firestore는 NoSQL 데이터베이스이며 데이터베이스에 저장된 데이터는 컬렉션, 문서, 필드, 하위 컬렉션으로 나뉩니다. 채팅의 각 메시지를 guestbook 이라는 최상위 컬렉션에 문서로 저장합니다.

7c20dc8424bb1d84.png

Firestore에 메시지 추가

이 섹션에서는 사용자가 데이터베이스에 새 메시지를 작성하는 기능을 추가합니다. 먼저 UI 요소(양식 필드 및 보내기 버튼)를 추가한 다음 이러한 요소를 데이터베이스에 연결하는 코드를 추가합니다.

먼저 cloud_firestore 패키지 및 dart:async 에 대한 가져오기를 추가합니다.

라이브러리/메인.다트

import 'dart:async';                                    // new

import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';

메시지 필드와 보내기 버튼의 UI 요소를 구성하려면 lib/main.dart 하단에 새로운 상태 저장 위젯 GuestBook 을 추가하세요.

라이브러리/메인.다트

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage});
  final FutureOr<void> Function(String message) addMessage;

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

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

여기에 몇 가지 관심 지점이 있습니다. 먼저, 메시지에 실제로 일부 콘텐츠가 있는지 확인하고 콘텐츠가 없는 경우 사용자에게 오류 메시지를 표시할 수 있도록 양식을 인스턴스화합니다. 양식의 유효성을 검사하는 방법에는 양식 뒤의 양식 상태에 액세스하는 것이 포함되며 이를 위해 GlobalKey 를 사용합니다. 키와 사용 방법에 대한 자세한 내용은 Flutter Widgets 101 에피소드 "키를 사용하는 시기" 를 참조하세요.

또한 위젯이 배치되는 방식에 주목하세요. TextFormField 가 있는 Row 와 자체적으로 Row 를 포함하는 StyledButton 이 있습니다. 또한 TextFormFieldExpanded 위젯에 래핑되므로 TextFormField 가 행에서 추가 공간을 차지하도록 합니다. 이것이 필요한 이유를 더 잘 이해하려면 제약 조건 이해 를 읽어보십시오.

이제 사용자가 방명록에 추가할 일부 텍스트를 입력할 수 있는 위젯이 있으므로 화면에 가져와야 합니다. 이렇게 하려면 HomePage 본문을 편집하여 ListView 의 자식 하단에 다음 두 줄을 추가합니다.

const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

위젯을 표시하는 데는 충분하지만 유용한 작업을 수행하는 데는 충분하지 않습니다. 이 코드가 작동하도록 곧 업데이트할 것입니다.

앱 미리보기

사용자가 SEND 버튼을 클릭하면 아래 코드 스니펫이 실행됩니다. 데이터베이스의 guestbook 컬렉션에 메시지 입력 필드의 내용을 추가합니다. 특히 addMessageToGuestBook 메서드는 메시지 내용을 새 문서(자동 생성된 ID 포함)에 guestbook 컬렉션에 추가합니다.

FirebaseAuth.instance.currentUser.uid 는 Firebase 인증이 로그인한 모든 사용자에게 제공하는 자동 생성 고유 ID에 대한 참조입니다.

lib/main.dart 파일을 다시 변경합니다. addMessageToGuestBook 메소드를 추가하십시오. 다음 단계에서 사용자 인터페이스와 이 기능을 함께 연결합니다.

라이브러리/메인.다트

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (_loginState != ApplicationLoginState.loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // To here
}

UI를 데이터베이스에 연결하기

사용자가 방명록에 추가하려는 텍스트를 입력할 수 있는 UI가 있고 Cloud Firestore에 항목을 추가하는 코드가 있습니다. 이제 두 개를 연결하기만 하면 됩니다. lib/main.dart 에서 HomePage 위젯을 다음과 같이 변경합니다.

라이브러리/메인.다트

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loginState == ApplicationLoginState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

이 단계를 시작할 때 다시 추가한 두 줄을 전체 구현으로 교체했습니다. 다시 Consumer<ApplicationState> 를 사용하여 렌더링 중인 트리 부분에서 응용 프로그램 상태를 사용할 수 있도록 합니다. 이를 통해 UI에 메시지를 입력하는 누군가에 반응하고 이를 데이터베이스에 게시할 수 있습니다. 다음 섹션에서는 추가된 메시지가 데이터베이스에 게시되는지 테스트합니다.

메시지 전송 테스트

  1. 앱에 로그인했는지 확인합니다.
  2. "Hey there!"와 같은 메시지를 입력한 다음 SEND 를 클릭합니다.

이 작업은 메시지를 Cloud Firestore 데이터베이스에 씁니다. 그러나 아직 데이터 검색을 구현해야 하기 때문에 실제 Flutter 앱에서 메시지를 볼 수 없습니다. 다음 단계에서 수행합니다.

하지만 Firebase 콘솔에서 새로 추가된 메시지를 볼 수 있습니다.

Firebase 콘솔의 데이터베이스 대시보드 에서 새로 추가된 메시지와 함께 guestbook 컬렉션이 표시되어야 합니다. 메시지를 계속 보내면 방명록 컬렉션에 다음과 같은 많은 문서가 포함됩니다.

Firebase 콘솔

713870af0b3b63c.png

7. 메시지 읽기

손님이 데이터베이스에 메시지를 쓸 수 있다는 것은 멋진 일이지만 아직 앱에서는 볼 수 없습니다. 수정하자!

메시지 동기화

메시지를 표시하려면 데이터가 변경될 때 트리거되는 리스너를 추가한 다음 새 메시지를 표시하는 UI 요소를 만들어야 합니다. 앱에서 새로 추가된 메시지를 수신 대기하는 코드를 애플리케이션 상태에 추가합니다.

다음 값 클래스는 GuestBook 위젯 바로 위에 있습니다. 이 클래스는 Cloud Firestore에 저장 중인 데이터의 구조화된 보기를 제공합니다.

라이브러리/메인.다트

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});
  final String name;
  final String message;
}

상태 및 getter를 정의하는 ApplicationState 섹션에서 다음 새 줄을 추가합니다.

라이브러리/메인.다트

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

그리고 마지막으로 ApplicationState 의 초기화 섹션에서 다음을 추가하여 사용자가 로그인할 때 문서 컬렉션에 대한 쿼리를 구독하고 로그아웃할 때 구독을 취소합니다.

라이브러리/메인.다트

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        // Add from here
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // to here.
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        // Add from here
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      notifyListeners();
    });
  }

이 섹션은 guestbook 컬렉션에 대한 쿼리를 구성하고 이 컬렉션에 대한 구독 및 구독 취소를 처리하는 곳이므로 중요합니다. 스트림을 듣고 guestbook 컬렉션에 있는 메시지의 로컬 캐시를 재구성하고 나중에 구독을 취소할 수 있도록 이 구독에 대한 참조도 저장합니다. 여기에서 많은 일이 진행 중이며 보다 명확한 멘탈 모델을 얻을 때 어떤 일이 발생하는지 검사하는 디버거에서 시간을 할애할 가치가 있습니다.

자세한 내용은 Cloud Firestore 문서 를 참조하세요.

GuestBook 위젯에서 이 변화하는 상태를 사용자 인터페이스에 연결해야 합니다. 구성의 일부로 메시지 목록을 추가하여 위젯을 수정합니다.

라이브러리/메인.다트

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({required this.addMessage, required this.messages});
  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

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

다음으로 다음과 같이 build 메서드를 수정하여 _GuestBookState 에 이 새 구성을 노출합니다.

라이브러리/메인.다트

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  // Modify from here
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                const SizedBox(width: 8),
                StyledButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      await widget.addMessage(_controller.text);
                      _controller.clear();
                    }
                  },
                  child: Row(
                    children: const [
                      Icon(Icons.send),
                      SizedBox(width: 4),
                      Text('SEND'),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        // Modify from here
        const SizedBox(height: 8),
        for (var message in widget.messages)
          Paragraph('${message.name}: ${message.message}'),
        const SizedBox(height: 8),
      ],
      // to here.
    );
  }
}

Column 위젯으로 빌드 메서드의 이전 내용을 래핑한 다음 Column 의 자식의 꼬리 부분에 컬렉션을 추가하여 메시지 목록의 각 메시지에 대해 새 Paragraph 을 생성합니다.

마지막으로, 이제 새 messages 매개변수로 GuestBook 을 올바르게 구성하기 위해 HomePage 의 본문을 업데이트해야 합니다.

라이브러리/메인.다트

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loginState == ApplicationLoginState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

메시지 동기화 테스트

Cloud Firestore는 데이터베이스에 가입한 클라이언트와 데이터를 자동으로 즉시 동기화합니다.

  1. 데이터베이스에서 이전에 생성한 메시지가 앱에 표시되어야 합니다. 새 메시지를 자유롭게 작성하십시오. 즉시 나타나야 합니다.
  2. 여러 창이나 탭에서 작업 공간을 열면 메시지가 탭 간에 실시간으로 동기화됩니다.
  3. (선택 사항) Firebase 콘솔의 데이터베이스 섹션에서 직접 새 메시지를 수동으로 삭제, 수정 또는 추가할 수 있습니다. 모든 변경 사항이 UI에 표시되어야 합니다.

축하합니다! 앱에서 Cloud Firestore 문서를 읽고 있습니다!

리뷰

8. 기본 보안 규칙 설정

처음에 테스트 모드를 사용하도록 Cloud Firestore를 설정했습니다. 즉, 데이터베이스가 읽기 및 쓰기를 위해 열려 있습니다. 그러나 개발 초기 단계에서만 테스트 모드를 사용해야 합니다. 모범 사례로 앱을 개발할 때 데이터베이스에 대한 보안 규칙을 설정해야 합니다. 보안은 앱의 구조와 동작에 필수적이어야 합니다.

보안 규칙을 사용하면 데이터베이스의 문서 및 컬렉션에 대한 액세스를 제어할 수 있습니다. 유연한 규칙 구문을 사용하면 전체 데이터베이스에 대한 모든 쓰기에서 특정 문서에 대한 작업에 이르기까지 모든 항목과 일치하는 규칙을 만들 수 있습니다.

Firebase 콘솔에서 Cloud Firestore에 대한 보안 규칙을 작성할 수 있습니다.

  1. Firebase 콘솔의 개발 섹션에서 데이터베이스 를 클릭한 다음 규칙 탭을 선택합니다(또는 여기를 클릭 하여 규칙 탭으로 직접 이동).
  2. 규칙이 공개된다는 경고와 함께 다음 기본 보안 규칙이 표시되어야 합니다.

7767a2d2e64e7275.png

컬렉션 식별

먼저 앱이 데이터를 쓰는 컬렉션을 식별합니다.

match /databases/{database}/documents 에서 보호하려는 컬렉션을 식별합니다.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

보안 규칙 추가

인증 UID를 각 방명록 문서의 필드로 사용했기 때문에 인증 UID를 얻고 문서에 쓰려는 사람이 인증 UID와 일치하는지 확인할 수 있습니다.

아래와 같이 규칙 세트에 읽기 및 쓰기 규칙을 추가합니다.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

이제 방명록의 경우 로그인한 사용자만 메시지(모든 메시지!)를 읽을 수 있지만 메시지 작성자만 메시지를 편집할 수 있습니다.

유효성 검사 규칙 추가

모든 예상 필드가 문서에 있는지 확인하기 위해 데이터 유효성 검사를 추가합니다.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. 보너스 단계: 배운 내용을 연습하세요

참석자의 RSVP 상태 기록

현재 앱은 이벤트에 관심이 있는 사람들이 채팅을 시작할 수 있도록 허용합니다. 또한 누군가가 오는지 알 수 있는 유일한 방법은 채팅에 게시하는 것입니다. 얼마나 많은 사람들이 오는지 조직하고 사람들에게 알리도록 합시다.

애플리케이션 상태에 몇 가지 새로운 기능을 추가할 것입니다. 첫 번째는 로그인한 사용자가 참석 여부를 지정할 수 있는 기능입니다. 두 번째 기능은 실제로 몇 명이 참석하는지에 대한 카운터입니다.

lib/main.dart 에서 다음을 접근자 섹션에 추가하여 UI 코드가 이 상태와 상호 작용할 수 있도록 합니다.

라이브러리/메인.다트

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}

다음과 같이 ApplicationStateinit 메소드를 업데이트하십시오.

라이브러리/메인.다트

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // Add from here
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // To here

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // to here
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

위의 내용은 참석자 수를 확인하기 위해 항상 구독 중인 쿼리를 추가하고 사용자가 참석 중인지 확인하기 위해 사용자가 로그인한 동안에만 활성화되는 두 번째 쿼리를 추가합니다. 다음으로 GuestBookMessage 선언 뒤에 다음 열거형을 추가합니다.

라이브러리/메인.다트

enum Attending { yes, no, unknown }

이제 이전 라디오 버튼처럼 작동하는 새 위젯을 정의할 것입니다. 예 또는 아니오가 선택되지 않은 불확실한 상태에서 시작되지만 사용자가 참석 여부를 선택하면 채워진 버튼으로 강조 표시된 해당 옵션을 표시하고 평면 렌더링으로 후퇴하는 다른 옵션을 표시합니다.

라이브러리/메인.다트

class YesNoSelection extends StatelessWidget {
  const YesNoSelection({required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

다음으로 YesNoSelection 을 활용하도록 HomePage 의 빌드 메서드를 업데이트해야 로그인한 사용자가 참석 여부를 지명할 수 있습니다. 또한 이 이벤트의 참석자 수를 표시합니다.

라이브러리/메인.다트

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here
      if (appState.attendees >= 2)
        Paragraph('${appState.attendees} people going')
      else if (appState.attendees == 1)
        const Paragraph('1 person going')
      else
        const Paragraph('No one going'),
      // To here.
      if (appState.loginState == ApplicationLoginState.loggedIn) ...[
        // Add from here
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // To here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

규칙 추가

일부 규칙이 이미 설정되어 있으므로 버튼을 사용하여 추가하는 새 데이터는 거부됩니다. attendees 컬렉션에 추가할 수 있도록 규칙을 업데이트해야 합니다.

attendees 컬렉션의 경우 인증 UID를 문서 이름으로 사용했으므로 이를 가져와 제출자의 uid 가 작성 중인 문서와 동일한지 확인할 수 있습니다. 모든 사람이 참석자 목록을 읽을 수 있도록 허용하지만(개인 데이터가 없기 때문에) 작성자만 업데이트할 수 있습니다.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

유효성 검사 규칙 추가

모든 예상 필드가 문서에 있는지 확인하기 위해 데이터 유효성 검사를 추가합니다.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}

(선택 사항) 이제 버튼을 클릭한 결과를 볼 수 있습니다. Firebase 콘솔에서 Cloud Firestore 대시보드로 이동합니다.

앱 미리보기

10. 축하합니다!

Firebase를 사용하여 대화형 실시간 웹 애플리케이션을 구축했습니다.

우리가 다룬 내용

  • Firebase 인증
  • 클라우드 파이어스토어
  • Firebase 보안 규칙

다음 단계

  • 다른 Firebase 제품에 대해 자세히 알고 싶으십니까? 사용자가 업로드한 이미지 파일을 저장하고 싶습니까? 아니면 사용자에게 알림을 보내시겠습니까? Firebase 문서 를 확인하세요. Firebase용 Flutter 플러그인에 대해 자세히 알고 싶으십니까? 자세한 내용은 FlutterFire 를 확인하세요.
  • Cloud Firestore에 대해 자세히 알고 싶으십니까? 하위 컬렉션 및 트랜잭션에 대해 배우고 싶습니까? Cloud Firestore에 대해 더 깊이 있는 코드랩을 보려면 Cloud Firestore 웹 코드랩 으로 이동하세요. 또는 이 YouTube 시리즈를 확인하여 Cloud Firestore에 대해 알아 보세요!

더 알아보기

어떻게 되었나요?

우리는 당신의 피드백을 좋아할 것입니다! 여기 에서 (매우) 짧은 양식을 작성하십시오.