본문 바로가기

플러터(Flutter)

코드랩 플러터 앱을 파이어베이스에 연동하는 예제 따라해보기 1탄 안드로이드

구글 Codelabs에 보면 Flutter app을 Firebase에 연결하고 Firestore DB에 테이블을 생성하여 데이타를 업데이트하고 또 받아보는 간단한 튜토리얼이 있다. 아기 이름 후보를 셋 정하고 마음에 드는 이름에 투표를 하는 앱이다. 프로그래밍이란 항상 문서를 보는 것만으로는 되지 않는다. 우리가 흔히 이것을 '삽질'이라고 부르는데, 시간을 무진장 잡아먹는 부분들이 늘 생기기 마련이다. 이런 '삽질'을 통해서 '내공'을 쌓는다고 하는데 아무리 타 언어에 도가 텃다고 하더라도 새로 배우는 이 프로그래밍 언어에 삽질은 피해갈 수가 없다.

 

Firebase for Flutter

코드랩의 플러터를 위한 파이어베이스 튜토리얼

https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html#0

 

Firebase for Flutter

It's hard to spot when testing on a single device, but our current code creates a subtle race condition. If two people with your app vote at the same time, then the value of the votes field would increase by only one -- even though two people voted for the

codelabs.developers.google.com

 

개발 환경 소개

OS: Mac OS X Catalina 10.15.4

IDE: VSCode 1.45.1

Flutter: 1.12.13+hotfix.9

Dart: 2.7.2

테스트일자: 2020년 6월 7일

 

시작하려면 다음과 같은 요건이 갖춰져야 한다.

- 플러터 프로젝트를 생성할 줄 안다.

- 기본으로 생성되는 프로젝트를 빌드하여 안드로이드 에뮬레이터에 실행이 가능한다.

 

여기선 VSCode를 썼지만 디테일이 다를 뿐 안드로이드 스튜디오로 해도 똑같으므로 상관없다.

나의 삽질 포인트

나의 삽질 포인트가 남에게도 삽질 포인트일 거라고 믿고 있다. 시작하기 전에 이 설정이 올바른지 확인해 보길.

https://darkeng.tistory.com/13

 

VSCode로 플러터 프로젝트 Organization 바꾸기 또는 새로 설정하기

방금 VSCode와 플러터를 설치하고 튜토리얼을 따라 새 프로젝트를 생성했다면 회사 도메인이 기본 com.example로 설정될 것이다. 새 프로젝트 이름과 합쳐져서 많은 부분에 하드코딩 되어 버리는데 �

darkeng.tistory.com

사실 그냥 디폴트 Application Id (com.example.baby_names)를 쓰면 문제 없는데 이걸 쓸 때 쯤 나의 Application Id를 제대로 만들고 싶다는 생각이 들더라. 그래서 이 Organization을 바꾸려 들다보면 해결책을 검색하게 되고 그러다보면 시간이 든다.

만들려는 예제 프로그램이란

간단한 투표 앱이다. 여기서는 아기 이름을 투표로 결정하는 예제를 제공한다. 아이를 가졌는데 이 아기를 위한 이름을 뭘로 할지 후보를 정하고 친구나 지인으로 부터 투표를 받는 방식이다. 예제니 만큼 간단하다. 인증과정도 없고 중복투표도 가능하다.

 

플러터 앱 생성 후 dependency 업데이트

baby_names라는 이름으로 새 프로젝트를 생성한다. pubspec.yaml을 열어서 cloud_firestore를 사용하도록 dependencies에 추가한다.

pub.dartlang.org/packages/cloud_firestore

현재 버전 넘버는 위 링크에서 확인가능하다. 지금은 버전 0.13.6 이다.

이렇게 설정한 후 안드로이드 앱으로 빌드를 시도하면 "Cannot fit requested classes in a single dex file." 에러가 발생한다. multidex지원이 롤리팝 버전에서 부터 이루어져서 젤리빈(16)에서 에러가 난다. minSdkVersion을 21로 설정해줘야 한다.

 

다음과 같이 수정해주면 문제가 사라진다.

diff --git a/android/app/build.gradle b/android/app/build.gradle
index d10186c..e014378 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -39,7 +39,7 @@ android {
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "com.moley.baby_names"
-        minSdkVersion 16
+        minSdkVersion 21
         targetSdkVersion 28
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName

 

baby_names 앱 코드 추가

빌드가 되면 lib/main.dart 내용을 싹 지우고 아래 링크에 있는 소스를 copy & paste 한다. 빌드해보면 리스트뷰로 이루어진 간단한 앱이 뜬다. 코드 길이가 길어 링크만 제공한다.

https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html#4

 

파이어베이스 계정 생성

일단 무료로 쓸 수 있는 스파크요금제로 '지금 시작' 버튼을 누른다.

 

프로젝트 만들기를 누른다.

 

프로젝트 이름에 적당히 'baby names app db'라고 쓰고 약관에 동의한 후 계속 버튼을 누른다.

 

애널리틱스 설정을 한다. 추후에 정식으로 서비스를 론칭하게 되면 확실히 정보분석에 도움이 될 것이다.

 

이제 새 프로젝트가 준비되었다. 이제 ios나 android 또는 웹용 앱을 추가해 줄 차례다.

당장은 안드로이드 용으로 테스트 해본다. 튜토리얼에 ios 설정방법에 대해서도 기술하고 있다.

앱에 Firebase를 추가하여 시작하기 바로 아래에 안드로이드 이미지 아이콘을 눌러서 안드로이드 앱을 추가해준다.

 

안드로이드 패키지 이름을 자신이 설정한 organization.appname으로 설정한다. Organization 설정 방법은 앞부분 참조.

 

앞서 설정해준 Organization.appname을 바탕으로 google-services.json을 자동 생성해서 다운로드 받게 해준다. 다운로드를 받아서 android/app/ 디렉토리에 넣어준다.

 

android/build.gradle과 android/app/build.gradle파일을 열어서 다음과 같이 수정해준다.

 

이제 다시 빌드/실행을 하거나 핫 리로드를 해서 디바이스에 앱을 띄워본다.

 

앱이 실행되고 설치가 확인되었다.

 

 

이제 데이타베이스를 손볼 차례다. 왼쪽의 '개발' 탭을 누른다.

 

'개발'탭 아래 'Database'를 클릭한 후 '데이타베이스 만들기'를 한다.

간단히 테스트 모드로 시작해본다.

 

클라우드 파이어스토어 위치를 선택한다.

 

'데이터'탭의 '+ 컬렉션 시작' 을 클릭한다.

 

컬렉션 ID를 쓴다. DB라면 아마도 테이블의 개념일 것이다.

 

컬렉션에 들어가는 레코드를 입력한다.

 

'+ 문서추가'로 dana외에 filip, richard를 입력하였다.

 

이제 파이어베이스의 baby 컬렉션을 가져오도록 lib/main.dart파일을 수정해보자.

diff --git a/lib/main.dart b/lib/main.dart
index 7be7817..fe3f49a 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,14 +3,6 @@ import 'package:flutter/material.dart';
 
 void main() => runApp(MyApp());
 
-final dummySnapshot = [
- {"name": "Filip", "votes": 15},
- {"name": "Abraham", "votes": 14},
- {"name": "Richard", "votes": 11},
- {"name": "Ike", "votes": 10},
- {"name": "Justin", "votes": 1},
-];
-
 class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
@@ -38,19 +30,25 @@ class _MyHomePageState extends State<MyHomePage> {
  }
 
  Widget _buildBody(BuildContext context) {
-   // TODO: get actual snapshot from Cloud Firestore
-   return _buildList(context, dummySnapshot);
+   return StreamBuilder<QuerySnapshot>(
+     stream: Firestore.instance.collection('baby').snapshots(),
+     builder: (context, snapshot) {
+       if (!snapshot.hasData) return LinearProgressIndicator();
+
+       return _buildList(context, snapshot.data.documents);
+     },
+   );
  }
 
- Widget _buildList(BuildContext context, List<Map> snapshot) {
+ Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
    return ListView(
      padding: const EdgeInsets.only(top: 20.0),
      children: snapshot.map((data) => _buildListItem(context, data)).toList(),
    );
  }
 
- Widget _buildListItem(BuildContext context, Map data) {
-   final record = Record.fromMap(data);
+ Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
+   final record = Record.fromSnapshot(data);
 
    return Padding(
      key: ValueKey(record.name),

 

 

어플리케이션을 실행하여 파이어스토어에서 컬렉션의 데이타를 잘 가져오는지 확인한다.

소스를 약간 더 수정하여 ListItem을 클릭했을때 투표값이 증가하도록 수정해보자.

diff --git a/lib/main.dart b/lib/main.dart
index fe3f49a..edebab6 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -61,7 +61,7 @@ class _MyHomePageState extends State<MyHomePage> {
        child: ListTile(
          title: Text(record.name),
          trailing: Text(record.votes.toString()),
-         onTap: () => print(record),
+         onTap: () => record.reference.updateData({'votes': record.votes + 1})
        ),
      ),
    );

 

이름을 클릭하여 투표값이 증가하는지 확인한다.

 

데이타를 atomic하게 업데이트

하지만, 위와 같은 코드로는 race condition이 존재하여 두명 이상의 사람이 동시에 클릭을 하게되면 동시에 증가된 하나의 값만 쓰게되어 atomic하지 않다. 다음과 같이 FieldValue.increment(1)로 문제를 해결한다.

diff --git a/lib/main.dart b/lib/main.dart
index edebab6..c5a0601 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -61,7 +61,7 @@ class _MyHomePageState extends State<MyHomePage> {
        child: ListTile(
          title: Text(record.name),
          trailing: Text(record.votes.toString()),
-         onTap: () => record.reference.updateData({'votes': record.votes + 1})
+         onTap: () => record.reference.updateData({'votes': FieldValue.increment(1)})
        ),
      ),
    );

 

여기 링크에 또다른 해결 방법인 transaction 사용에 관한 설명도 같이 있으니 참고. 그리고, 맨 마지막에 모든 수정이 반영된 lib/main.dart의 전체 소스코드도 얻을 수 있다.

https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html#10

 

이제 기본적이지만 플러터 앱의 백엔드로 파이어베이스 사용을 어떻게 하는지 이해하게 되었다. 다 안다고 생각할 때 아는게 별로 없다. 더 많은 코드를 보도록 노력하자.