개발/Flutter

Flutter - 코딩마스터하기|사진 업로드,게시하기3 , storage에 저장한 Post의 데이터를 database에 저장하기

ffuny 2021. 11. 16. 21:10
반응형

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 창이 꺼짐
  }

 

반응형