개발/Flutter

Flutter - 코딩마스터하기|피드페이지도 파이어스토어에 연결하기

ffuny 2021. 11. 19. 00:23
반응형

fetch posts from specific user

해당 유저키를 가지고 Post에 그 유저키를 가지고 해당하는 글만 가져오게 해보자. 우선 post_network_repository 로 가서 Stream<void> getPostsFromSpecificUser(String userKey)의 메소드를 만들어 준다. return Firestore.instance.collection(COLLECTION_POSTS)으로 Post 콜렉션으로 들어가고 .where()를 추가도 달아준다. where는 sql에서 where와 같이 ()안의 조건에 따라 검색하는 것이다. 우리는 팔로우하는 유저키에 따른 포스트를 가져오고 싶으므로 .where(KYE_USERSKEY, isEqualTo: userKey) 로 해주면된다. 그리고 .snapshots()로 가져와 주고 .transform()으로 변환시켜준다. ()안에 toPosts를 넣어주는데 이에 해당하는 메소드를 추가로 만들어 줘야한다. 우선 class에 with Transformers를 추가해주고 해당 dart파일로 이동한다. toUsersExceptMe 메소드를 똑같이 복사해주고 이름을 toPosts로 변경해준다. 그리고 List<UserModel>을 List<PostModel>로 변경 해준다. 또한 List users 부분도 List<PostModel> posts = [];로 변경 해준다. 기존 현재 유저를 찾는 부분은 삭제 해준뒤 snapshot로 forEach 로 가져오는 부분은 snapshot.documents.forEach((documentSnapshot) { posts.add(PostModel.fromSnapshot(documentSnapshot)); });로 변경해주면 된다.


  final toPosts =
      StreamTransformer<QuerySnapshot, List<PostModel>>.fromHandlers(
          //StreamTransformer<도착한 모델, 보낼 모델>
          handleData: (snapshot, sink) async {
    List<PostModel> posts = [];

    snapshot.documents.forEach((documentSnapshot) {
      posts.add(PostModel.fromSnapshot(documentSnapshot));
    });

    sink.add(posts);
  });
  Stream<void> getPostsFromSpecificUser(String userKey){
    //sql의 where 처럼 원하는 부분만 가져옴,
    return Firestore.instance.collection(COLLECTION_POSTS).where(KEY_USERSKEY,isEqualTo: userKey).snapshots().transform(toPosts);
  }

fetch all posts and combine them into one stream

우선 한명의 유저에서 가져오는것을 했는데 오늘은 여러유저꺼를 다 가져와서 합쳐볼 것이다. 우선 rxdart를 사용할 것이다. 우리는 지금까지 stream을 사용했는데 stream을 사용하는 그 보다 더 발전된 라이브러리다. 강의는 24.1이지만 나는 우선 rxdart: ^0.25.0로 사용할 것이다. 우선 Stream<List<PostModel>> fetchPostsFromAllFollowers(List<dynamic> followers)의 메소드를 만들어 팔로우한 전체의 목록을 가져온다. 그리고 final CollectionReference collectionReference = Firestore.instance.collection(COLLECTION_POSTS); 로 Posts의 ref를 가져온다. List<Stream<List<PostModel>>> streams= []; 를 추가 해줘서 Stream의 List들을 저장할 곳을 생성해둔다. 그리고 for(final follower in followers) 를 통해 하나씩 가져온다. 그후 위의 메소드에서 만들었던 것과 똑같이 streams.add(collectionReference.where(KEY_USERSKEY,isEqualTo: follower).snapshots().transform(toPosts));로  post를 가져온다. 그후 return 에 CombineLatestStream.list()를 가져온다. 단 여기서 Combine은 9개까지 밖에 안된다. 더 많은 수를 하려면 다른 방법을 찾아봐야 한다. 우선은 이방법으로 실행해 보자. 다음으로 위의 stream과 같이 가져오는 부분인데 만약 포스트를 가져온다면 최신순으로 정렬되어야 한다. 그래서 transformer에 최신순으로 정렬하는 메소드를 만들어보자. toPosts를 복사해서 latestToTop으로 만들어 주고 (posts, sink){}로 변경해준다. posts.sort((a, b) => b.postTime.compareTo( a.postTime)); 이렇게 작성 해주는데 a.postTime와 b.postTime의 자리가 바뀌면 오래된 순으로 정렬된다. CombineLatestStream.list() 에 받아온 목록을 준다. CombineLatestStream.list(streams)에 .transform(latestToTop); 정렬 해주면 안되는데 그 이유는 List<PostModel> 의 형태가 아닌 List<List<PostModel>>로 오기 때문에 이 형태를 다시 바꿔 줘야 한다. 다시한번 transformers에서 똑같이 복사해서 combineListOfPosts를 만들어 주고 (listOfPosts, sink) async { List<PostModel> posts = []; for (final postList in listOfPosts) { posts.addAll(postList); } sink.add(posts); } 대로 작성 해준다. 한번 리스트에서 꺼내서 하나의 리스트에 집어넣어주는 것이다. 그래서 최종적으로 return CombineLatestStream.list<List<PostModel>>(streams).transform(combineListOfPosts).transform(latestToTop); 과 같이 작성해주면 된다.


  Stream<List<PostModel>> fetchPostsFromAllFollowers(List<dynamic> followers) {
    final CollectionReference collectionReference =
        Firestore.instance.collection(COLLECTION_POSTS);
    List<Stream<List<PostModel>>> streams = [];

    for (final follower in followers) {
      streams.add(collectionReference
          .where(KEY_USERSKEY, isEqualTo: follower)
          .snapshots()
          .transform(toPosts));
    }
    //CombineLatestStream.을 보면 combine9까지 밖에 안됨 9개밖에 합쳐지지가 않음 더 여러개를 하려면 다른 것을 찾아봐야함.
    //밑의 형태는 List<List<PostModel>> 형태로 도착하므로 List<PostModel>로 바꿔줘야함.
    return CombineLatestStream.list<List<PostModel>>(streams)
        .transform(combineListOfPosts)
        .transform(latestToTop);
  }
  final combineListOfPosts =
      StreamTransformer<List<List<PostModel>>, List<PostModel>>.fromHandlers(
          //StreamTransformer<도착한 모델, 보낼 모델>
          handleData: (listOfPosts, sink) async {
    List<PostModel> posts = [];
    for (final postList in listOfPosts) {
      posts.addAll(postList);
    }
    sink.add(posts);
  });
  final latestToTop =
      StreamTransformer<List<PostModel>, List<PostModel>>.fromHandlers(
          //StreamTransformer<도착한 모델, 보낼 모델>
          handleData: (posts, sink) async {
    posts.sort((a, b) => b.postTime.compareTo(
        a.postTime)); //a.postTime.compareTo(b.postTime) 로하면 최신이 밑으로 내려감

    sink.add(posts);
  });

using stream with stream provider

이젠 만든것을 써봐야 하니 feed_screen에 가져와보자. Scaffold를 감싸주는데 이번에는 StreamProvider를 사용해보자. create에 지난 시간에 만든 피드를 모두 가져오는 부분인 create: (BuildContext context) => postNetworkRepository.fetchPostsFromAllFollowers(followers),를 넣어준다. 그리고 다시 한번 Consumer로 감싸준다.(Stream으로 감싸준뒤 변경시켜주면 된다). builder에는 (BuildContext context, List<PostModel> posts, Widget child) 으로 넣어주고 post가 비어있을때는 로딩 바를 보여주고 아닐때는 기존 Scaffold를 보여주게 한다. 그리고 여기서 posts를 사용할 수 있으니 하단 body에 itemCount는 posts.length로 해준다. 또 하단 위젯도 int index를 사용하는게 아닌 PostModel postModel을 받게 해주고 return Post(postModel)로 변경해준다. 그리고 body에 itemBuilder은 (context, index) => feedListBuilder(context, posts[index]) 로 변경해주면 된다. 이제 해당 유저의 전체 팔로워를 받아오는 부분은 다음 시간에 해보자.


class FeedScreen extends StatelessWidget {
  final List<dynamic> followers;

  const FeedScreen(this.followers, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamProvider<List<PostModel>>(
      create: (BuildContext context) =>
          postNetworkRepository.fetchPostsFromAllFollowers(followers),
      child: Consumer<List<PostModel>>(
          builder: (BuildContext context, List<PostModel> posts, Widget child) {
        if (posts == null || posts.isEmpty) {
          return MyProgressIndicator();
        } else {
          return Scaffold(
            appBar: CupertinoNavigationBar(
              leading: IconButton(
                onPressed: () {},
                icon: const Icon(
                  CupertinoIcons.photo_camera_solid,
                  color: Colors.black87,
                ),
              ),
              middle: const Text(
                'instagram',
                style:
                    TextStyle(fontFamily: 'VeganStyle', color: Colors.black87),
              ),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    onPressed: () {},
                    icon: const ImageIcon(
                      AssetImage('assets/images/actionbar_camera.png'),
                    ),
                  ),
                  IconButton(
                    onPressed: () {},
                    icon: const ImageIcon(
                      AssetImage('assets/images/direct_message.png'),
                    ),
                  ),
                ],
              ),
            ),
            body: ListView.builder(
              itemBuilder: (context, index) => feedListBuilder(context, posts[index]),
              itemCount: posts.length,
            ),
          );
        }
      }),
    );
  }

  Widget feedListBuilder(BuildContext context, PostModel postModel) {
    // return Container(
    //   color: Colors.accents[index % Colors.accents.length],
    //   //Colors.accents 이게 리스트로 되어있음
    //   height: 100,
    // );
    return Post(postModel);
  }
}

 

반응형