flutter

플러터용 파이어 베이스 1부

paulaner80 2019. 4. 11. 01:02
반응형


1-1. 새 플러터앱 생성 : baby_names으로 프로젝트를 생성합니다.

1-2. pubspec.yaml 파일에 cloud_firestore 디펜던시를 추가합니다.

 

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  cloud_firestore: ^0.11.0+1  #new



2-1. 아래 코드들을 입력합니다.

 


import 'package:cloud_firestore/cloud_firestore.dart';
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 {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Baby name bote"),
        ),
        body: _buildBody(context));
  }

  Widget _buildBody(BuildContext context) {
    return _buildList(context, dummySnapshot);
  }

  Widget _buildList(BuildContext context, List<Map> 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);

    return Padding(
      key: ValueKey(record.name),
      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      child: Container(
        decoration: BoxDecoration(
            border: Border.all(color: Colors.grey),
            borderRadius: BorderRadius.circular(5.0)),
        child: ListTile(
          title: Text(record.name),
          trailing: Text(record.vote.toString()),
          onTap: () {
            print(record);
          },
        ),
      ),
    );
  }
}

class Record {
  final String name;
  final int vote;
  final DocumentReference reference;

  Record.fromMap(Map<String, dynamic> map, {this.reference})
      : name = map['name'?? '',
        vote = map['votes'?? 0;

  Record.fromSnapShot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  @override
  String toString() {
    return 'Record<$name, $vote>';
  }
}



[실행화면]





6-1. 파이어베이스에 로그인합니다.

6-2. 파이어베이스 콘솔에서 프로젝트 추가 버튼을 클릭합니다.

6-3. 아래 크림 처럼 프로젝트이름을 입력하고 리전을 선택해줍니다.



 

7. 플랫폼별 파이어베이스 설정

Firebase 프로젝트를 생성 한 후에는 Firebase 프로젝트를 사용할 하나 이상의 어플리케이션을 구성 할 수 있습니다. 




이제 Firebase에 앱의 플랫폼 고유 ID 등록하고, 

앱의 구성 파일을 다운받아 프로젝트 폴더에 추가해야합니다.

 

안드로이드와 IOS 둘 다 개발하려면 두 가지 버전 모두 등록해야합니다.

한가지 플랫폼만 개발한다면 불필요한 것을 건너뛰어도 됩니다.

 

안드로이드 설정



1. 안드로이드 앱 추가 버튼을 클릭합니다.

2. 패키지 이름이 중요합니다.

3. android/app/src/main/AndroidManifest.xml 파일을 엽니다.

4. 패키지 속성 값을 찾아냅니다. 그 값을 복사합니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.baby_names">


5. 파이어베이스 다이얼로그에서 그 값을 붙여 넣습니다




6. (선택사항)

7. 앱등록을 클릭하세요

8. Firebase에서 계속 진행하여  google-services.json 파일을 다운로드하십시오.


9. android/app 디렉토리로 다운로드한 google-services.json파일을 이동시키세요.



10. Firebase 콘솔로 돌아가서 나머지 단계를 건너 뛰고 Firebase 콘솔의 메인 페이지로 돌아갑니다.

 



11. 마지막으로 google-services.json 파일을 읽으려면 Google Services Gradle 플러그인이 필요합니다.

 


12. android/app/build.gradle 파일을 열어 아래 코드를 추가합니다.

apply plugin: 'com.google.gms.google-services'



13. android/app/build.gradle 파일을 열어 buildscript 태그 안에 디펜던시를 추가합니다.

classpath 'com.google.gms:google-services:3.2.1'

 



14, Android 용 Flutter 앱 구성이 완료되었습니다.

 

Flutter는 FlutterFire라고 불리는 각 Firebase 제품에 액세스하기위한 자체 플러그인 세트를 제공합니다. FlutterFire 플러그인의 최신 목록은 FlutterFire GitHub 페이지를 확인하십시오.

 

 

8. Cloud Firestore 데이터베이스를 만들기

이제 앱을 만들 준비가 되었습니다.

 

클라우드 파이어스토어를 세팅하고 초기 값들을 넣어 줄 것입니다.

 

8-1. 파이어베이스 콘솔을 열고 셋업하는 동안 생성했던 파이어베이스프로젝트를  선택하세요

8-2. 왼쪽 네비에서 데이터베이스를 선택하세요.


8-3. 클라우트 파이어 스토어 팬에서 데이터베이스 만들기를 클릭합니다.

8-4. 클라우드 파이어스토어 보안 규칙에서 테스트모드로 시작을 선택하고  사용설저을 클릭합니다.

 

데이터베이스가 하나의 콜렉션을 만들 것입니다. 이름을 "baby"로 합니다. 콜렉션에는 이름과 투표가 저장되는 곳이 있습니다.

 

 

8-5. 컬렉션 추가를 클릭하고 이름을 'baby'로 합니다.

이제 문서에 도큐먼트를 추가할 수 있습니다. 각각의 도큐먼트들은 Document ID를 갖습니다. 그리고 'name''vote'필드들을 갖습니다.

 

8-6.  아기 이름을 소문자로 입력합니다. 예제에서는 'dana'로 해보겠습니다.

각각의 document ID로 이름을 사용하는 것은 이름에 따른 알파베 순서가 될 것입니다.

기본적으로 document ID는 타임스탬프로자동생성 되고, 그러면 보여지는 순서는 생성된 순서가 됩니다.

 

8-7,8,9 아래처럼입력하고 저장을 누릅니다.



 

8-10. 문서추가를 클릭해서 아기 이름을 더 입력합니다.



 

9.  플러터 앱을 클라우드 파이어베이스에 접속하기

이제 컬렉션(baby)을 가져와서 dummySnapshot대신 사용할 것입니다.

Firestore.instance를 호출하여 클라우드 파이어스토어에 대한 참조를 얻습니다.

특히 baby names 콜렉션에서, Firestore.instance.collection('baby').snapshots() 은 스냅샷들의 스트림을 리턴합니다.  StreamBuilder 위젯을 사용하여 UI에 데이터 스트림을 연결합니다.

 


  Widget _buildBody(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
        stream: Firestore.instance.collection("baby").snapshots(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return LinearProgressIndicator();
          } else {
            return _buildList(context, snapshot.data.documents);
          }
        });
  }

 

 

StreamBuilder위젯은 데이터베이스가 업데이트되는지 청취하고 있고, 데이터가 바뀌면 갱신합니다. 데이터가 없으면, 프로그레스 인디케이터를 보여줍니다.

   3. 코드는 에러가 있습니다.  _buildList를 찾고 서명을 다음과 같이 변경하십시오.

 


  Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {

 

 

  1.  _buildListItem 메소드는 여전히 map을 얻는 다고 생각할 수있습니다. 메소드 시작점을 찾아 다음과 같이 바꿈니다.

  Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
    final record = Record.fromSnapshot(data);

 

Map 대신 DocumentSnapshot을 가져와서 Record.fromsnapshot() 네임드 생성자를 사용하여 Record를 생성하세요.

   5. (옵셔널) dumysnapshot 필더를 지웁니다. 더 이상 필요 없습니다.

   6. 파일을 저장하고, 핫 리로드 합니다.

 

 

 

금방  데이터베이스에서 읽어 왔습니다. 파이어베이스 콘솔에가서 데이터베이스를 변경할 수도 있습니다. 앱은 데이터 베이스의 변경을 즉시 반영할 것입니다.



 

빌드에러

Plugin project :firebase_core_web not found. Please update settings.gradle.

[DEFAULT]' has been created - call Firebase.initializeApp() in Flutter and Firebase

 

>해결방법

https://firebase.flutter.dev/docs/overview

에서 플러터 파이어 설정과정을 봐야합니다.

 

 

과정1


파이어베이스 서비를 이용하기위해서는 firebase_core 플러그인 있어야합니다.

firebase_core: "0.5.3"

 


 과정2


/android/app/build.gradle에서 버전을 넣어준다.

rootProject.ext {

  set('FlutterFire', [

    FirebaseSDKVersion: '25.12.0'

  ])

}


과정 3


플러터파이어를 이니셜라이즈하기위해서

await Firebase.initializeApp();  호출 해야다.


FutureBuilder 사용하거나. initState에서 호출해주어야합니다.


아래 전체 소스의 class App extends StatefulWidget 부분을 참고하면 되겠습니다.



 


10. 상호작용 추가

 

vote기능 추가

 onTap() 의 함수를 아래 처럼 변경합니다.

 

 

단순히 레코드를 콘솔에 출력하는 대신, 데이터베이스에 투표수를 1개 증가시킬 것입니다.

 

 

 

어떻게 동작하는 거일 까요? 이름 타일을 탭했을 때 데이터의 레퍼런스를 업데이트하라고 말하고 있는 것입니다. 이렇게하면 클라우드 파이어가 모든 청취자에게 업데이트된 스냅샷을 알립니다.  위에서 구현한 StreamBuilder를 통해 청취할 때 새로운 데이터로 업데이트 됩니다.

 

 

 

 

11. 클라우드 파이어 트랜잭션 사용하기

하나의 기기에서 테스트할 때는 찾아내기 어렵겠지만 현재코드는 알아채기 힘든 경쟁조건을 만듭니다. 사람이 동시에 동시에 투표를 한다면, 비록 두 사람이 투표했지만 투표 값은 한 번 만 증가 될 것입니다. 두 사람의 앱이 동시에 현재 값을 읽었고, 1을 증가시키고 똑같은 값으로 데이터베이스에 쓰기 때문입니다. 두 사람 모두 투표값이 증가 된 것을 보았기 때문에 잘못 된 것을 알아차리기 어렵습니다. 이것은 거의 동시에 이루어져야하기 때문에 테스트로 감지해내기 어렵습니다.

 

투표값은 공유자원입니다. 공유자원을 업데이트할 때 경쟁조건을 만들 위험이 있습니다. (특히 새값이 이전 값과 달라질때)  대신 모든 데이터베이스에 값을 업데이트할 때 트랜잭션을 사용해야 합니다.

 

 

  1.  main.dart의  onTap 을 아래코드로 바꿔줍니다.
  2. 저장하고 핫리로드합니다.

 

투표 상호작용이 완료되는데 시간이 조금더 걸립니다.  하지만 경쟁 상태가 해결되어 각각의 투표가 카운트됩니다.

 

어떻게 동작한는 것일까요? 한 트랜잭션에서 일기와 쓰기 작업 래핑을 통해서 트랜잭션이 동작하는 동안 기본데이터에 대한 외부변경사항이 없는 경우에만 변경사항을 커밋하라고 알려줍니다. 두 명의 사용자가 특정 이름으로 동시에 투표하지 않으면 트랜잭션이 정확히 한 번 실행됩니다. 그러나 transaction.get (...)과 transaction.update (...) 호출 사이에서 득표 수가 변경되면 현재 실행이 커밋되지 않고 트랜잭션이 재 시도됩니다. 5 번의 재 시도가 실패하면 트랜잭션은 실패합니다.




[최종소스]

/android/app/build.gradle 에 FirebaseSDKVersion: '25.12.0'를 설정해주어야 합니다.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

void main() => runApp(App());



class App extends StatefulWidget {
_AppState createState() => _AppState();
}

class _AppState extends State<App> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;

// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}

@override
void initState() {
initializeFlutterFire();
super.initState();
}

@override
Widget build(BuildContext context) {
// Show error message if initialization failed
if(_error) {
return SomethingWentWrong();
}

// Show a loader until FlutterFire is initialized
if (!_initialized) {
return Loading();
}

return MyApp();
}
}

class SomethingWentWrong extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "SomethingWentWrong",
home:Scaffold(
appBar: AppBar(title:Text("SomethingWentWrong!!")),
body:Text("SomethingWentWrong!!!!")

)

);
}
}

class Loading extends StatelessWidget{
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Loading",
home:Scaffold(
appBar: AppBar(title:Text("Loading!!")),
body:Text("Loading!!!!")

)

);
}
}


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) {
return MaterialApp(
title: 'Baby Names',
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Baby Name Votes')),
body: _buildBody(context),
);
}

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.docs);
},
);
}

Widget _buildList(BuildContext context,
List<QueryDocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}

Widget _buildListItem(BuildContext context, QueryDocumentSnapshot data) {
final record = Record.fromSnapshot(data);

return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.votes.toString()),
onTap: () => record.reference.update({'vote':FieldValue.increment(1)})
),
),
);
}
}

class Record {
final String name;
final int votes;
final DocumentReference reference;

Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
assert(map['vote'] != null),
name = map['name'],
votes = map['vote'];

Record.fromSnapshot(QueryDocumentSnapshot snapshot)
: this.fromMap(snapshot.data(), reference: snapshot.reference);

@override
String toString() => "Record<$name:$votes>";
}


'flutter' 카테고리의 다른 글

bloc todo  (0) 2019.04.19
Widget, State, BuildContext 그리고 InheritedWidget  (1) 2019.04.16
Dart에서 stream 만들기  (0) 2019.04.04
dart  (0) 2019.04.03
Dart mixin 이란?  (0) 2019.04.03