ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter - 코딩마스터하기|사진 업로드,게시하기3 , storage에 저장한 Post의 데이터를 database에 저장하기
    개발/Flutter 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 창이 꺼짐
      }

     

    반응형

    댓글

Designed by Tistory.