지난 포스팅까지 Cloud Firestore를 통해서 CRUD하는 방법에 대해서 알아보았습니다. 이번 포스팅에서는 평범한 데이터가 아닌 쿼리를 통한 Read에 대해서 알아보겠습니다.
쿼리란 질의입니다. 데이터베이스에서 Read요청을 하면 테이블에 존재하는 모든 데이터를 가져옵니다. 이건 일반적인 Read입니다. 하지만, 앱의 기능에 따라서 특정 데이터만을 읽어올 필요가 있습니다. 이런 경우 쿼리를 통해서 문제를 해결할 수 있습니다. 그렇다면 Cloud Firestore에서는 어떻게 쿼리를 사용할까요? 이전 포스팅에서 데이터를 Read하기 위해서 Stream을 작성할때, 쿼리를 적용시킬 수 있습니다.
지난 포스팅에서 작성한 코드를 기반으로 임의의 데이터를 미리 데이터베이스에 생성했습니다.
이 데이터들은 todo, isDone, time필드로 구성되어 있습니다. 그리고 저번 포스팅에서 미리 눈치채셨을 수도 있지만, 데이터의 순서가 생성한 순서가 아니라 난잡하다는 것을 알 수 있습니다. 그 이유는 데이터가 문서의 id를 기준으로 정렬되었기 때문입니다. 문서의 id는 Create 메소드에서 doc()라고 정함으로써 무작위의 id가 부여됩니다. 이런 데이터들을 쿼리를 통해서 원하는 대로, 정렬, 필터링, 복잡한 쿼리 등이 가능한 것이죠 !
where를 통해서 Read하는 데이터들을 필터링시킬 수 있습니다. 임의의 데이터중 아무거나 isDone을 true로 바꾸어보겠습니다. 그리고 기존의 Icon은 isDone의 값에 따라서 색상을 구분할 수 있도록 하겠습니다.
...
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('Todos').snapshots(),
builder: (context, snapshot) {
return (snapshot.connectionState == ConnectionState.waiting)
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
final data = snapshot.data!.docs[index];
final id = data.id;
final todo = data['todo'].toString();
final isDone = data['isDone'];
return ListTile(
onTap: () {
_updateTodos(id, isDone);
},
title: Text(todo),
leading: (isDone)
? const Icon( //true면 초록색
Icons.done,
color: Colors.green,
)
: const Icon( //false면 빨간색
Icons.close,
color: Colors.red,
),
trailing: IconButton(
onPressed: () {
_deleteTodos(id);
},
icon: const Icon(Icons.delete_forever),
),
);
});
}),
);
}
...
이전에 작성한 코드에서 leading에 Icon 색상만 변화시킨겁니다.
이제 본격적으로 필터링을 수행해볼까요? isDone이 true인 값만 불러오도록 쿼리를 적용시켜보겠습니다.
...
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Todos')
.where('isDone', isEqualTo: true)
.snapshots(),
builder: (context, snapshot) {
어떤가요? 기존의 isDone이 false인 데이터들은 모두 필터링을 통해서 걸러지고, true인 데이터들만 불러왔습니다. 아래는 필터링 조건을 명시할 수 있는 비교연산자들입니다.
Object? isEqualTo, ---> (==)
Object? isNotEqualTo, ---> (!=)
Object? isLessThan, ---> (<)
Object? isLessThanOrEqualTo, ---> (<=)
Object? isGreaterThan, ---> (>)
Object? isGreaterThanOrEqualTo, ---> (>=)
Object? arrayContains, ---> 배열 필드가 값을 포함하는지
Iterable<Object?>? arrayContainsAny, --->
배열 필드가 값들을 포함하는지 확인, arrayContains의 복수형, 10개까지만 가능
Iterable<Object?>? whereIn,
isEqualTo의 복수형, 최대 10개까지 가능
Iterable<Object?>? whereNotIn,
isNotEqualTo의 복수형, 최대 10개까지 가능
필터링은 필드값을 통해서 데이터를 불러오는 쿼리였습니다. 제한은 limit을 통해서 맨처음 문서부터 정해진 갯수의 데이터를 불러오는 쿼리입니다.
다시 데이터를 기본으로 돌렸습니다. 과제하기부터 씻기까지만 데이터를 불러오고 싶습니다. 총 4개의 데이터를 불러오면 되겠군요?
...
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Todos')
.limit(4)
.snapshots(),
builder: (context, snapshot) {
limit은 주어진 정수데이터에 따라서 정해진 갯수만 데이터를 가져오는 쿼리입니다.
저 데이터는 문서의 id에 따라서 오름차순정렬이 되어있습니다. 진짭니다. 콘솔가서 확인하면 오름차순인것을 알 수 있습니다. 아무래도 NoSQL은 인덱스가 없다보니 인덱스가 있으면 데이터를 조금 더 자연스럽게 정렬시킬 수 있을겁니다. 그것을 위해서 time필드에 Cloud Firestore가 제공하는 Timestamp를 이용했습니다. 인덱스를 위해서 Timestamp기능이 제공되는 겁니다. time필드를 통해서 인덱스처럼 사용할 수 있겠군요? 그것을 위해 orderBy를 통한 정렬이 필요합니다.
...
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Todos')
.orderBy('time')
.snapshots(),
builder: (context, snapshot) {
이제 데이터가 문서 id가 아니라 time필드를 기준으로 정렬되고 있습니다. orderBy는 기본적으로 descending, 내림차순이 false로 지정되어있습니다. 기본은 오름차순이라는 뜻이겠죠? descending을 true로 지정하면 나중에 생성된 데이터가 가장 상단에 위치하게 됩니다.
...
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Todos')
.orderBy('time', descending: true)
.snapshots(),
builder: (context, snapshot) {
그리고 정렬과 함께 제한 중 limitToLast를 사용할 수 있는데요. limitToLast는 limit의 반대 개념입니다. 원래 상단 데이터부터 4개까지만 읽어왔다면, limitToLast는 하단데이터부터 4개만 읽어오는겁니다.
Widget _buildBody() {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Todos')
.orderBy('time', descending: true)
.limitToLast(4)
.snapshots(),
builder: (context, snapshot) {