Flutter - 코딩마스터하기|사진 업로드,게시하기3 , storage에 저장한 Post의 데이터를 database에 저장하기
create post model
post도 모델을 생성하기 위해서 models - firestore 에 post_model.dart를 생성해준다. 그리고 user_model을 복사해와서 똑같이 만들어 주고 class 명을 PostModel로 바꿔준다. 그리고 상단에 상수들을 선언해 준다.
final String postKey;
final String userKey;
final String username;
final String postImg;
final List<dynamic> numOfLikes;
final String caption;
final String lastCommentor;
final String lastComment;
final DateTime lastCommentTime;
final int numOfComments;
final DateTime postTime;
final DocumentReference reference;
중간에 fromMap은 다음과 같이 변경 해준다.
PostModel.fromMap(Map<String, dynamic> map, this.postKey,
{this.reference}) //fromMap은 Dart 내장 함수
: userKey = map[KEY_USERSKEY],
username = map[KEY_USERNAME],
postImg = map[KEY_POSTIMG],
numOfLikes = map[KEY_NUMOFLIKES],
caption = map[KEY_CAPTION],
lastCommentor = map[KEY_LASTCOMMENTOR],
lastComment = map[KEY_LASTCOMMENT],
lastCommentTime = map[KEY_LASTCOMMENTTIME]==null? DateTime.now().toUtc():(map[KEY_LASTCOMMENTTIME] as Timestamp).toDate(),
numOfComments = map[KEY_NUMOFCOMMENTS],
postTime = map[KEY_POSTTIME]==null? DateTime.now().toUtc():(map[KEY_POSTTIME] as Timestamp).toDate();
여기서 DateTime 부분에서는 시간을 가져올때 null을 해서 에러가 날 수 있으므로 위와같이 처리해 주면 된다.,
fromSnapshot부분은 똑같이 해주면 된다. 그리고 getMapForCreatePost는 다음과 같이 해주면 된다.
static Map<String, dynamic> getMapForCreatePost({String userKey, String username, String caption}) {
Map<String, dynamic> map = Map();
map[KEY_USERSKEY] = userKey;
map[KEY_USERNAME] = username;
map[KEY_POSTIMG] = "";
map[KEY_NUMOFLIKES] = [];
map[KEY_CAPTION] = caption;
map[KEY_LASTCOMMENTOR] = "";
map[KEY_LASTCOMMENT] = "";
map[KEY_LASTCOMMENTTIME] = DateTime.now().toUtc();
map[KEY_NUMOFCOMMENTS] = 0;
map[KEY_POSTTIME] = DateTime.now().toUtc();
return map;
}
create transaction to upload post data
우선 repo 폴더에 post_network_repository.dart 를 만들어 준다. 그리고 메소드를 만들어 주는데 우선 Futuer<void> createNewPost를 만들어 준다. 그리고 DocumentReference postRef, DocumnetReference userRef를 해주고 포스트와 유저의 레퍼런스를 가져와준다. post는 Firestore.instance.collection(COLLECTION_POSTS).document(postKey);로 해주지만 userRef는 COLLECTION_USERS를 넣어주고 userKey는 postDate에서[KEY_USERKEY]로 알 수 있으므로 이 방식으로 가져와 준다. 그리고 postSnapahot을 가져온다. DocumentSnapshot postSnapshot = await postRef.get(); 으로 레퍼런스를 통해서 가져올 수 있다. 이제 트랜섹션을 해주면 된다. Firestore.instance에서 .runTransaction을 해주면 된다. 그리고 ()안에 (Transaction tx) async {}를 넣어 주낟. 그리고 if 문에 !post.Snapshot.exists를 해줘서 스냅샷에 대한 정보가 없으면 포스트를 만들어 주고 User에서 my_post에 대한 정보를 업데이트 해주면 된다. await tx.set(postRef,postData);로 포스트 정보를 만들어 주고 await tx.update(userRef,{KEY_MYPOSTS: FieldValue.arrayUnion([postKey])})로 업데이트 해줄 수 있다. 이부분은 그냥 외워야 한다. 우리는 하나만 업데이트 하기때문에 이렇게 썼지만 2개이상일때는 다른 방법을 사용한다. postData가 아닌 {KEY_MYPOSTS: FieldValue.arrayUnion([postKey])}으로 원하는 부분을 업데이트를 하는것을 기억해두자. 여기서 if안에 update에서 실패하면 set부분도 안되고 돌아가게 된다. 충돌이 막아진다.
class PostNetworkRepository {
Future<Map<String, dynamic>> createNewPost(
String postKey, Map<String, dynamic> postData) async {
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
final DocumentSnapshot postSnapshot = await postRef.get();
final DocumentReference userRef = Firestore.instance
.collection(COLLECTION_USERS)
.document(postData[KEY_USERSKEY]);
return Firestore.instance.runTransaction((Transaction tx) async {
if (!postSnapshot.exists) {
await tx.set(postRef, postData);
await tx.update(userRef, {
KEY_MYPOSTS: FieldValue.arrayUnion([postKey])
});
}
});
}
}
PostNetworkRepository userNetworkRepository = PostNetworkRepository();
use create new post
이미지를 올릴때 포스트가 올라가고 데이터가 올라가는데 이것을 image_network_repository 에서 하는것보다는 share_post_screen에서 하는게 더 나을꺼 같다는 강의에 따라 await imageNetworkRepository.uploadImage(imageFile,postKey: postKey); 아래에 만들어 주자. 우선 uploadImageNCreateNewPost를 rename으로 uploadImage로 바꿔주고, share_post_screen에서 await postNetworkRepository.createNewPost() 를 생성 해준다. 여기에 postKey와 postData에 따라 usermodel이 필요하므로 우선 그 위에 userKey나 username을 불러 올 수 있도록 UserModel userModel = Provider.of<UserModel>(context,listen: false);로 생성 해준다. 그리고 createNewPost()안에 postKey, PostModel.getMapForCreatePost(userKey: userModel.userKey,username: userModel.username,caption: "") 를 넣어준다. caption은 하단에 caption 작성부분에서 가져와야 되는데 이 부분은 다음시간에 해보자.
onPressed: () async {
showModalBottomSheet(
context: context,
builder: (_) => MyProgressIndicator(),
//원래 빌더에 ()안에 context가 와야하는데 context를 사용 안하므로 _ 로 해준다
isDismissible: false,
//이 창의 바깥 눌렀을때 끝나게 해줄것이냐
enableDrag: false, //드래그를 하게 해줄 것이냐.
);
await imageNetworkRepository.uploadImage(imageFile,postKey: postKey);
UserModel userModel = Provider.of<UserModel>(context,listen: false);
await postNetworkRepository.createNewPost(postKey, PostModel.getMapForCreatePost(userKey: userModel.userKey,username: userModel.username,caption: ""));
Navigator.of(context).pop(); //await 되고 나면 pop으로 꺼짐짐
},
use textfield to get caption
캡션을 넣어줘 보자. 우선 statelessWidget를 statefulWidget로 변경 해주자. 그리고 caption부분이 textField므로 state class 상단에 TextEditingController _textEditingController = TextEditingController();을 생성해준다. 그리고 dispose()도 생성해줘서 창이 꺼질때 dispose 하게 해준다. TextField로 가서 controller: _textEditingController,을 넣어주고 바로 캡션을 쓸수 있도록 autoFocus: true, 로 해준다. 그리고 아까 비워뒀던 caption에 await postNetworkRepository.createNewPost(widget.postKey, PostModel.getMapForCreatePost(userKey: userModel.userKey,username: userModel.username,caption: _textEditingController.text)); 이렇게 추가해주면 된다. 여기서 하단에 pop를 통해 모달을 껐는데 중복으로 사진을 올리는것을 막기 위해 똑같이 Navigator.of(context).pop(); 을 한번더 해준다.
title: TextField(
controller: _textEditingController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Write a caption....', border: InputBorder.none),
),
TextEditingController _textEditingController = TextEditingController();
@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
onPressed: () async {
showModalBottomSheet(
context: context,
builder: (_) => MyProgressIndicator(),
//원래 빌더에 ()안에 context가 와야하는데 context를 사용 안하므로 _ 로 해준다
isDismissible: false,
//이 창의 바깥 눌렀을때 끝나게 해줄것이냐
enableDrag: false, //드래그를 하게 해줄 것이냐.
);
await imageNetworkRepository.uploadImage(widget.imageFile,postKey: widget.postKey);
UserModel userModel = Provider.of<UserModel>(context,listen: false);
await postNetworkRepository.createNewPost(widget.postKey, PostModel.getMapForCreatePost(userKey: userModel.userKey,username: userModel.username,caption: _textEditingController.text));
Navigator.of(context).pop(); //await 되고 나면 모달창이 pop으로 꺼짐
Navigator.of(context).pop(); // sharePost 창이 꺼짐
},
finish share post
이미지를 firestore에 올린 후 그 이미지의 url을 받아서 post 데이터베이스를 업데이트 시켜줘보자. 우선 데이터에 업데이트 해주는 부분을 만들어 주자. post_network_repository에 와서 하단에 Future<void> updatePostImageUrl({String postImg, String postKey})를 만들어 주자. Future이기 때문에 async를 해주고 postRef와 postSnapshot 부분을 그대로 가져오자. 그리고 if(postSnapshot.exists)로 스냅샷이 존재할때 업데이트 시켜주면 된다. await post.updateDate({KEY_POSTIMG: postImg})로 post img 경로가 지정되는 부분만 메소드를 쓰는 곳에서 받아오는 경로로 업데이트 시켜주면 된다. 이제 이 부분을 pop으로 닫히는 부분 위에 String postImgLink = await imageNetworkRepository.getPostImageUrl(widget.postKey); 와 await postNetworkRepository.updatePostImageUrl( postKey: widget.postKey, postImg: postImgLink);와 같이 링크를 받아오고 그 링크와 postKey를 넘겨주면 된다. 실행해보면 정상적으로 다운로드 주소가 올라간것이 보이고, 주소를 실행하면 해당 이미지를 볼 수 있다. 그리고 이 onPressed 부분이 너무 복잡하고 길어서 하단에 메소드로 빼주면 된다.
post_network_repository.dart
Future<void> updatePostImageUrl({String postImg, String postKey}) async {
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
final DocumentSnapshot postSnapshot = await postRef.get();
if (postSnapshot.exists) {
await postRef.updateData({
KEY_POSTIMG: postImg,
});
}
}
share_post_screen.dart
void sharPostProcedure() async {
showModalBottomSheet(
context: context,
builder: (_) => MyProgressIndicator(),
//원래 빌더에 ()안에 context가 와야하는데 context를 사용 안하므로 _ 로 해준다
isDismissible: false,
//이 창의 바깥 눌렀을때 끝나게 해줄것이냐
enableDrag: false, //드래그를 하게 해줄 것이냐.
);
await imageNetworkRepository.uploadImage(widget.imageFile,
postKey: widget.postKey);
UserModel userModel =
Provider.of<UserModelState>(context, listen: false).userModel;
await postNetworkRepository.createNewPost(
widget.postKey,
PostModel.getMapForCreatePost(
userKey: userModel.userKey,
username: userModel.username,
caption: _textEditingController.text));
String postImgLink =
await imageNetworkRepository.getPostImageUrl(widget.postKey);
await postNetworkRepository.updatePostImageUrl(
postKey: widget.postKey, postImg: postImgLink);
Navigator.of(context).pop(); //await 되고 나면 모달창이 pop으로 꺼짐
Navigator.of(context).pop(); // sharePost 창이 꺼짐
}