티스토리 뷰

 

1. 지난 시간에 설정한 '내 위치'와 '동네 범위'를 이용해 FireStore db에 저장된 게시글을 필터링한다.
2.  필터링된 게시글을 Stream+ListView이용해 모두 가져온다

내 위치를 기반으로 필터링 하기 

들어가기 앞서,
필터링 하기 쉽게 하기 위해 userLocation을 String→List<String>으로 바꾸었다.
EX. '경기도 성남시 수정구 위례동' → ['경기도', '성남시 수정구, '위례동']

 

지난 시간에 내 위치와 동네 범위를 설정하였다. 이번에는 이 설정한 위치와 동네 범위를 이용해 Firestore db에 저장된 게시글을 필터링하고자 한다. 

 

Query<Map<String, dynamic>> postFilterLocation = FirebaseFirestore.instance
        .collection("Post")
        .where("UserLocation",
            arrayContains: _userProvider.locations[_userProvider.scope]);

 

여기서 주의할 점은  array-contains 절을 최대 하나만 사용할 수 있다.

 

Cloud Firestore에서 단순 및 복합 쿼리 수행  |  Firebase

Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기 이 페이지는 Cloud Translation API를 통해 번역되었습니

firebase.google.com

 

arrayContains을 and절하려고 아래와 같이 했는데

_firestore.collection("Post").where("userLocation", arrayContains:_userProvider.locations[0]).
where("userLocation", arrayContains:_userProvider.locations[0]).where("userLocation", arrayContains:_userProvider.locations[0])

 

그러면 아래와 같은 오류가 뜬다.

Querry cannot contain more than one ARRAY_CONTAINS

 

따라서, 원래는 userLocation의 인덱스를 설정 범위까지 모두 조사해서 필터링하려고 했는데, 해당 설정 범위 인덱스의 위치가 같은지만 비교하려고 한다.

예를 들어 현재 사용자의 Locationlist [경기도, 성남시 수정구, 위례동]이고, 범위가 1(구까지)이면, 게시글 작성한 작성자의 위치 Locationlist가 [경기도, 성남시 분당구, 야탑2동]이라면  Locationlist[0], Locationlist[1]을 모두 비교하지 않고, 범위 인덱스인 Locationlist[1]만 비교한다는 뜻이다. 


필터링 된 모든 게시글의 목록 보여주기

return StreamBuilder(
      stream: postFilterLocation.snapshots(),
      builder: (context,
          AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }
        final postDocs = snapshot.data!.docs;

        return ListView.builder(
          itemCount: postDocs.length,
          itemBuilder: (context, index) {
            return GestureDetector(
              onTap: () {
                Post post = Post.fromQuerySnapshot(postDocs[index]);
                print(post.content);
                Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) => GroupBuyingDetailPage(post)));
              },
              child: PostList(
                postDocs[index]['Content'],
                postDocs[index]['Title'],
                postDocs[index]['Date'],
                postDocs[index]['Time'],
                postDocs[index]['WriterName'],
                postDocs[index]['UpperCategory'],
                postDocs[index]['LowerCategory'],
                postDocs[index]['maxParticipants'],
                postDocs[index]['curParticipants'],
                postDocs[index]['Place'],
              ),
            );
          },
        );
      },
    );

1. StreamBuilder

stream뒤에는 아까 필터링 한 Query의 snapshots()을 입력한다.

 

2. ListView.builder

itemCount는 전체 item의 개수로, length를 이용한다.

itemBuilder에 위젯으로 감싼 각 post에 대한 간단한 정보인 PostList를 return하게 한다.

 

PostList

import 'package:flutter/material.dart';
import 'package:home_alone_recipe/config/palette.dart';

class PostList extends StatelessWidget {
  const PostList(
      this.content,
      this.title,
      this.date,
      this.time,
      this.writerName,
      this.upperCategory,
      this.lowerCategory,
      this.maxParticipants,
      this.curParticipants,
      this.place,
      {Key? key})
      : super(key: key);

  final String content;
  final String title;
  final String date;
  final String time;
  final String writerName;
  final String upperCategory;
  final String lowerCategory;
  final int maxParticipants;
  final int curParticipants;
  final String place;

  String curState() {
    if (maxParticipants > curParticipants) {
      return "모집중";
    } else {
      return "모집완료";
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.only(left: 20, right: 15),
          child: RichText(
            text: TextSpan(
              children: [
                TextSpan(
                    text: "${curState()}  ",
                    style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 18,
                        color: curState() == "모집중"
                            ? Palette.blue
                            : Colors.redAccent)),
                TextSpan(
                    text: "$title\n",
                    style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                        color: Colors.black)),
                WidgetSpan(
                  child: Icon(
                    Icons.people,
                  ),
                ),
                TextSpan(
                    text: "  $curParticipants/$maxParticipants명 참여\n",
                    style: TextStyle(
                      fontSize: 13,
                      color: Colors.black,
                    )),
                WidgetSpan(
                  child: Icon(
                    Icons.calendar_month,
                  ),
                ),
                TextSpan(
                  text: "  $date $time",
                  style: TextStyle(
                    fontSize: 13,
                    color: Colors.black,
                  ),
                ),
              ],
            ),
          ),
        ),
        Container(
          padding: EdgeInsets.fromLTRB(18, 10, 10, 7),
          height: MediaQuery.of(context).size.width * 0.23,
          width: MediaQuery.of(context).size.width * 0.98,
          child: DecoratedBox(
            decoration: BoxDecoration(
                color: Palette.lightgrey,
                borderRadius: BorderRadius.circular(7.0)),
            child: Center(
              child: Padding(
                padding: EdgeInsets.only(left: 10, right: 10),
                child: Text(
                  content,
                  style: TextStyle(
                    fontSize: 14,
                  ),
                  overflow: TextOverflow.ellipsis,
                  maxLines: 3,
                ),
              ),
            ),
          ),
        ),
        Padding(
          padding: EdgeInsets.only(left: 22, right: 5, top: 5),
          child: Text(
            "작성자: $writerName",
            style: TextStyle(fontSize: 13, color: Colors.black, height: 1.0),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(bottom: 15, top: 10),
          child: Container(
            decoration: BoxDecoration(
              color: Colors.grey,
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(1),
                  spreadRadius: 0,
                  blurRadius: 2,
                  offset: Offset(0, 2), // changes position of shadow
                ),
              ],
            ),
            height: 1.0,
            width: 500.0,
          ),
        ),
      ],
    );
  }
}

 

최종 구현 화면