FirebaseFirestore 에 대한 기본 개념을 안다면 아래의 CRUD 개념을 이해하는데 도움이 될 것 입니다.
2022.08.12 - [Flutter/06 Basic] - [Flutter] Firestore 구조 알아보기
firebase에 CRUD 하는 기본 샘플코드.
/* 플러그인 정보 */
firebase_core: ^0.7.0
firebase_storage: ^7.0.0
cloud_firestore: ^0.16.0
1. Create - (doc().set or collection().add)
2개의 차이는 doc의 ID를 설정가능 여부이다.
doc().set 는 doc('abc').set 처럼 doc ID 를 설정가능하다, 공백으로하면 collection().add 과 동일하게 자동생성된다.
/* Create */
var doc = FirebaseFirestore.instance.collection('post').doc();
doc.set({
'id': doc.id,
'datetime' : DateTime.now().toString(),
'displayName': 'MrKim',
'photoUrl': photoUrl,
});
/* Create */
var doc = FirebaseFirestore.instance.collection('post');
doc.add({
'id': doc.id,
'datetime' : DateTime.now().toString(),
'displayName': 'MrKim',
'photoUrl': photoUrl,
});
2. Read
/* Read */
// collection 하위의 모든 document 정보 읽고 리스트로 데이터 관리
Future _loadTestResult() async {
var result = await FirebaseFirestore.instance
.collection('post') //
.doc(docId) // chapter_code
.collection('post_sub')
//.where('email', isEqualTo: 'aaa@gmail.com') // where 조건이 필요한 경우.
.get()
.then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
_testResult.add(doc.data()); //모든 document 정보를 리스트에 저장.
})
});
if (result.toString() != null) {
//리스트 관련 후처리 필요한 경우 여기서 처리함.
}
}
//특정 collection에 포함된 documents 갯수 가져오기
void _getSubCnt() {
FirebaseFirestore.instance
.collection('post')
.get()
.then((snapShot) {
qTotal = snapShot.docs.length;
});
}
// tree 구조로 하위 docment 찾으면서 필요한 정보 읽기.
// collection(post) > all document > collection(post_sub) > 모든 document 관리.
Future multiCollection() async {
var result = await FirebaseFirestore.instance
.collection('post')
.get()
.then((QuerySnapshot querySnapshot) => {
//1차 collection의 모든 document 관련 처리
querySnapshot.docs.forEach((doc) async {
var resultSub = await FirebaseFirestore.instance
.collection('post')
.doc(doc.data()['docId']) // forEach에서 가져옴
.collection('post_sub')
.get()
.then((QuerySnapshot querySnapshot1) => {
//2차 collection의 모든 document 관련 처리
});
if(resultSub.toString() != null) {
// 후처리 작업이 필요한 경우.
}
}),
});
if(result.toString() != null) {
첫번째 collection 관련 후처리 필요한 경우
}
}
// doc 에서 필드를 읽고 후처리 하는 예시
void _getUserInfo() {
FirebaseFirestore.instance
.collection('user_info')
.doc(widget.user.uid)
.get()
.then((doc) async {
if (doc.exists) { // 기 사용자 확인
var _email = doc.data();
this._userInfo['userType'] = _email['user_type'];
this._userInfo['userLangType'] = _email['language'];
} else { // 신규 사용자 등록시
// doc.data() will be undefined in this case
var result = await Navigator.push(context,
MaterialPageRoute(builder: (context) => InformPage(widget.user))
);
try{
if (result['complete'] == null) { // 정보입력이 완료되지 않음
FirebaseAuth.instance.signOut();
_googleSignIn.signOut();
}
else {// 정보입력이 완료
this._userInfo['userType'] = result['user_type'];
this._userInfo['userLangType'] = result['language'];
}
} catch (error) {// 정보입력이 완료되지 않음
FirebaseAuth.instance.signOut();
_googleSignIn.signOut();
}
}
});
}
추가 힌트, 알면 쉬운데, 모르면 개고생 ~~ factory 패턴 한번 쓸려고 하다가 밤새 코딩중 ~~
// 위에서 자세히 보면 collection 으로 마무리되면 리스트 타입으로 반환되고
// doc 으로 마무리하면 json/map 형식으로 반환된다.
// 그래서 factory 패턴을 사용하기 위해서는 리스트 형식이 필요하므로
// collection + where 형식으로 처리하면 원하는 결과를 얻을수 있다
LoginUser get curUser => _curUser.value;
Future getUserInfo(String uid) async {
var result = await FirebaseFirestore.instance
.collection('user_info')
.get();
final userList = result.docs.map((user) {
return LoginUser.fromJson(user);
}).where((user) {
return (user.uid == uid);
}).toList();
if (userList.isNotEmpty) {
_curUser(userList.first); // or _curUser(userList[0])
print(userList.length);
print(curUser.photoURL); // _curUser.photoURL.value 이렇게 접근 불가(원인 아직 모름)
}
return userList.length.toInt();
}
DocumentSnapshot, QuerySnapshot, QueryDocumentSnapshot 예시
Future<ItemModel2> getItem(String itemKey) async {
// .doc 의 리턴 타입은 DocumentReference
DocumentReference<Map<String, dynamic>> docRef =
FirebaseFirestore.instance.collection(COL_ITEMS).doc(itemKey);
// DocumentReference.get 의 리턴 타입은 DocumentSnapshot
final DocumentSnapshot<Map<String, dynamic>> documentSnapshot = await docRef.get();
// documentSnapshot.data() 은 nullable 이므로 "!" 필요
ItemModel2 itemModel = ItemModel2.fromJson(documentSnapshot.data()!);
itemModel.reference = documentSnapshot.reference;
return itemModel;
}
// .collection 의 리턴 타입은 CollectionReference
Future<List<ItemModel2>> getItems(String userKey) async {
CollectionReference<Map<String, dynamic>> collectionReference =
FirebaseFirestore.instance.collection(COL_ITEMS);
// collectionReference.get 의 리턴 타입은 QuerySnapshot
QuerySnapshot<Map<String, dynamic>> snapshots = await collectionReference.get();
List<ItemModel2> items = [];
// QuerySnapshot.docs 의 타입은 QueryDocumentSnapshot
for (var snapshot in snapshots.docs) {
// documentSnapshot.data() 은 nullable 아니므로 "!" 필요 없음,
ItemModel2 itemModel = ItemModel2.fromJson(snapshot.data());
itemModel.reference = snapshot.reference;
items.add(itemModel);
}
return items;
}
3. Update
/* Update */
// 특정 document 에 데이터 update
var doc = FirebaseFirestore.instance
.collection('post')
.doc(docId);
doc.update({
'question_cnt': _questionCount,
});
4. Delete
/* Delete */
// collection에 속한 모든 documents 삭제(document에 연관된 사진들도 삭제)
FirebaseFirestore.instance
.collection('post')
.get()
.then((snapshot) async{ //사진 삭제때문에 async 옵션 필요한 경우 있음
for (DocumentSnapshot ds in snapshot.docs) { // 하위 documents 모두 삭제
ds.reference.delete();
//document가 포함한 사진을 찾아서 Storeage에서 삭제.
final ref = FirebaseStorage.instance.refFromURL(ds['photoUrl']); //photoUrl 사진관련 필드 정보
ref.delete();
}
});
//특정 document만 삭제 및 해당 게시물에 포함된 사진 삭제
FirebaseFirestore.instance
.collection('post')
.doc(docId) // 특정 document 아이디 정보
.delete()
.catchError((e) {
print(e);
}).then((onValue) async{ //사진 삭제때문에 async 옵션 필요한 경우 있음
final ref = FirebaseStorage.instance.refFromURL(photoUrl); //photoUrl 사진관련 필드 정보
ref.delete();
});
5. field 가 존재하지 않을때 처리 - 새로운 필드를 추가했는데, 해당 필드를 먼저 읽고 후 처리 필요한 경우.
- 좋아요 클릭시, 사용자 키를 이용하여 중복 방지처리함
- 사용자 키가 중복일 경우 set 으로 중간처리하고 list 로 변환하면 더 좋음.
// ['favorite'] 필드(array 타입)가 없는데, 읽으면 오류가 발생한다.
// 그래서 try { if{} else{} } catch {} 구문으로 처리가 필요함.
// 경우에 따라 try 에서 오류나서 catch 로 넘어가는 경우도 있고,
// null 이 할당되어 try 내부의 else 에서 처리되는 경우 있음.
FirebaseFirestore.instance
.collection(document['teacher_uid']) // post
.doc(document['chapter_code'])
.get()
.then((doc) {
var doc1 = FirebaseFirestore.instance
.collection(document['teacher_uid']) // post
.doc(document['chapter_code']);
List<dynamic> _tmp = [];
Set<dynamic> _set = Set();
try{
// doc.data()['favorite'] 필드가 존재하지 않아서
// try에서 오류나고 catch 로 넘어가는 경우를 예상했으나,
// 아래의 경우는 _tmp 에 null 이 할당되서 else 구문으로 넘어가는 경우임.
// 그래서 else와 catch 내부 처리를 동일하게 하여 모든 경우를 대비함.
_tmp = doc.data()['favorite'];
if(_tmp != null){
_set = _tmp.toSet();
_tmp = _set.toList();
if (_favorite[document['chapter_code']]){
_tmp.add(document['student_uid'].toString());
} else {
_tmp.remove(document['student_uid'].toString());
}
_set = _tmp.toSet();
_tmp = _set.toList();
print('>>>' + _tmp.toString());
doc1.update({
'favorite' : _tmp,
}).then((onValue) {
setState(() {});
});
} else {
print('>>> else: 에서 처리함');
doc1.update({
'favorite' : [document['student_uid'],],
}).then((onValue) {
setState(() {});
});
}
} catch (error) {// 정보입력이 완료되지 않음
print('>>> error: ' + error.toString());
doc1.update({
'favorite' : [document['student_uid'],],
}).then((onValue) {
setState(() {});
});
}
});
runTransaction/transaction.set
- 여러군데에 동시에 create 를 진행가능, 둘중에 하나라도 오류가 발생하면 두군데 모두 롤백 처리됨
Future createNewItem(ItemModel2 itemModel, String itemKey, String userKey) async {
// 신규 작성글을 업로드하기 위한 위치 설정
DocumentReference<Map<String, dynamic>> itemDocRef =
FirebaseFirestore.instance.collection(COL_ITEMS).doc(itemKey);
final DocumentSnapshot documentSnapshot = await itemDocRef.get();
// 신규 작성글을 사용자 정보의 하위 콜랙션에 추가, No SQL 에서는 역정규화 하는것이 좋을때가 있음,
DocumentReference<Map<String, dynamic>> userItemDocRef = FirebaseFirestore
.instance
.collection(COL_USERS)
.doc(userKey)
.collection(COL_USER_ITEMS)
.doc(itemKey);
// 신규 작성글이 없다면 저장,
if (!documentSnapshot.exists) {
// await itemDocRef.set(itemModel.toJson());
await FirebaseFirestore.instance.runTransaction((transaction) async {
// 2개중에 하나라도 오류가 발생하면 모두 롤백한다,
transaction.set(itemDocRef, itemModel.toJson());
transaction.set(userItemDocRef, itemModel.toMinJson());
});
}
}
'Flutter > 00 Legacy' 카테고리의 다른 글
[Flutter] Provider with Flutter sample - ChangeNotifierProvider (0) | 2021.05.04 |
---|---|
[Flutter] Bloc, Stream - setState 을 Bloc, Stream 으로 변경 (0) | 2021.05.03 |
[Flutter] Bloc, Stream - setState 로 구현 (0) | 2021.05.03 |
[Flutter] StreamBuilder with FirebaseFirestore (0) | 2021.02.03 |
[Flutter] Uploading image to FirebaseStorage (async/await) (0) | 2021.02.03 |