일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Python
- livedata
- NDK
- 안드로이드 구글맵
- 프로그래머스
- Android P
- android push
- flutter firestore
- kodility
- FLUTTER
- 안드로이드
- RxAndroid
- Android
- Flutter TextField
- Java
- Django REST
- dart
- C
- C/C++
- Kotlin
- 알고리즘
- Rxjava2
- android architecture component
- UWP
- Django REST framework
- Django REST Android
- mfc
- C++
- RxJava
- 코틀린
- Today
- Total
개발하는 두더지
Flutter - Firestore 라이브러리 사용하기 (3) 본문
지금까지 Flutter에서 Firestore dependency를 추가하고 android에서 사용할 수 있도록 환경을 구성했습니다.
이번에는 실제 flutter 코드에서 firestore를 어떻게 사용하는지 알아보겠습니다.
1편에서 본 코드와 크게 다르지 않습니다.
dummySnapshot을 이용하지 않고 StreamBuilder
를 이용하여 Firebase 쿼리 결과값을 가져오게됩니다.StreamBuilder
위젯은 데이터베이스에 대한 업데이트를 수신하고 데이터가 변경될 때마다 목록을 새로 고칩니다.Firebase Console
에서 document의 값 한개를 변경하면 컬렉션에 포함된 모든 document의 값이 호출되어 실시간으로 변경된 값을 감지하고 UI변경 작업을 진행할 수 있습니다.
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("baby").snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
//return _buildList(context, dummySnapshot); <- old
}
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, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
print("record ${record.name} ${record.votes}");
...
}
자 여기까지 App에서 firestore의 데이터를 불러오는 과정을 마쳤습니다.
이제 항목을 클릭했을 때 투표값이 계속 반영되는 작업이 필요합니다.
onTap: () => record.reference.updateData({'votes': record.votes + 1})
사용자가 하나의 타일을 클릭하면 Firestore 데이터를 업데이트하라는 메서드를 실행합니다.
Firestore에서 값이 변경되면 다시 StreamBuilder
를 통해 변경된 내용을 받아 UI를 업데이트 합니다.
Hot Reload
를 통해 변경된 내용을 확인해봅니다.
타일을 누르면 투표스가 잘 증가됨을 확인할 수 있습니다.
지금은 단말기 한대로 테스트를 해서 큰 문제가 없습니다.
만약 여러 디바이스에서 동시에 투표를 한다면 어떻게 될까요? 투표 결과는 한개만 반영이 될껍니다.
그러면 이 문제를 어떻게 해결해야 할까요?
바로 transaction
을 사용해서 경쟁 상태(race condition)을 회피하도록 해야 합니다.
onTap: () => Firestore.instance.runTransaction((transaction) async {
final freshSnapshot = await transaction.get(record.reference);
final fresh = Record.fromSnapshot(freshSnapshot);
await transaction
.update(record.reference, {'votes': fresh.votes + 1});
}),
이렇게하면 투표 적용 시간은 조금 늘어나지만 경쟁 상태에 빠지지 않아 원하는 결과가 나옵니다.
최종적인 소스 코드입니다.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
// 메인 진입점
void main() => runApp(MyApp());
// 껍데기는 상태가 변하지 않는 위젯
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) {
// StreamBuilder 위젯은 데이터베이스에 대한 업데이트를 수신하고 데이터가 변경될 때마다 목록을 새로 고칩니다.
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("baby").snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
//return _buildList(context, dummySnapshot); <- old
}
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, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
print("record ${record.name} ${record.votes}");
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: () => Firestore.instance.runTransaction((transaction) async {
final freshSnapshot = await transaction.get(record.reference);
final fresh = Record.fromSnapshot(freshSnapshot);
await transaction
.update(record.reference, {'votes': fresh.votes + 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['votes'] != null),
name = map['name'],
votes = map['votes'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
@override
String toString() => "Record<$name:$votes>";
}
'Flutter' 카테고리의 다른 글
Flutter - TextField를 이용하여 로그인 페이지 구현하기(2) (0) | 2019.04.28 |
---|---|
Flutter - TextField를 이용하여 로그인 페이지 구현하기(1) (0) | 2019.04.28 |
Flutter - Firestore 라이브러리 사용하기 (2) (0) | 2019.04.26 |
Flutter - Firestore 라이브러리 사용하기 (1) (0) | 2019.04.26 |
Flutter - 채팅방 구현하기 (0) | 2019.04.23 |