View
[Flutter] Firebase에서 닉네임 문자열 필드에 대한 검색(인 척 하는) Query 기능 구현
sm_amoled 2024. 5. 22. 14:17이번에는 닉네임 검색 기능이 필요해, 아래같은 검색 기능을 구현하고자 했다.
ㅤ
그런데, 처음에 생각했던 것보다 조금 막막했다…! 😭😭😭😭
ㅤ
기능 구현에 들어가기 전에는 파이어베이스에 날릴 쿼리를 단순하게 아래처럼 생각했다.
“User 컬렉션의 문서들에 대해 문서 내 name 필드에 해당 문자열을 포함하고 있는 문서를 요청해야지”
ㅤ
그러나, Firebase에 조건을 설정하는 where 함수에는 해당 방식이 포함되어있지 않았다. 기껏 비슷한건 arrayContains 같은거였는데, 이는 객체나 값이 array 타입 필드에 포함되어있는지를 확인하는 조건문이다. 아마 요거를 보고 문자열 패턴 쿼리가 있다고 생각했던 것 같다.
ㅤ
ㅤ
ㅤ
이걸 처리할 수 있는 쿼리를 지원하지 않는다면, 정녕 모든 사용자 데이터를 다 긁어온 다음에 디바이스 내에서 필터링을 해서 보여줘야 한다는건가?? 사용자의 수가 많아지면 절대로 불가능한 방식일텐데, 어떻게 해야하지? 라고 고민을 방방 하다가 운좋게 스택오버플로우의 글을 하나 발견했다! 여기에서 알려준 방법을 적용하면 별도의 검색 알고리즘을 사용하거나 파이어베이스의 Cloud Function(유료) 등의 기능을 사용할 수 있다. 그러나 “패턴 매칭”의 검색은 불가능하고, “첫글자부터 일치”하는 문자열을 가진 문서를 찾아낼 수는 있다.
ㅤ
방법은 아래와 같다.
컬렉션의 특정 필드에 대해 where 옵션 중 isGreaterThanOrEqualTo 를 사용하면 해당 문자열보다 크거나 같은 데이터를 찾아낼 수 있다.
// 검색하고자 하는 데이터가 String nickname 이라고 할 때
firestore
.collection(FirebaseCollectionName.users)
.where(
FirebaseUserFieldName.displayName,
isGreaterThanOrEqualTo: nickname,
)
.get()
ㅤ
위 코드를 실행하면 아래와 같은 결과를 얻을 수 있다.
문서 내 이름 목록이 [ a aa b ba bac bb c d ] 라고 하면
검색어로 ba를 전달했을 때
결과는 [ ba bac bb c d ] 가 된다.
ㅤ
결과로 받은 데이터 중에서 [ ba bac ] 까지는 만족스러웠는데, 이후 bb c d 는 전혀 상관없는 데이터이기 때문에 제거해줄 필요가 있었고, 처음에는 where 옵션의 isLessThanOrEqualTo: bb 를 사용해 필터링을 할 생각을 하고있었다. 그런데, 알파벳이면 간단하겠지만, 한글같은 영역으로 넘어가면 오히려 더 복잡해질 것 같아서 다른 방법을 고민해봤다.
ㅤ
그리고 이를 위해 stringValue.startsWith(pattern) 이라는 함수를 사용해주었다. 가져온 데이터에 대해 앞에서부터 일치하는 데이터만 보여준다면 ba, bac 데이터만 남고 나머지 bb, c, d 같은 적절하지 않은 데이터는 제거될 것이라고 생각했다!
ㅤ
// 검색하고자 하는 데이터가 String nickname 이라고 할 때
firestore
.collection(FirebaseCollectionName.users)
.where(
FirebaseUserFieldName.displayName,
isGreaterThanOrEqualTo: nickname,
)
.get()
.then((users) {
users.docs.map((doc) {
UserDataEntity userDataEntity = // doc 을 데이터 모델로 parse 해주기
// ********************
// 이렇게 데이터의 시작 지점을 검사하기
if ((userDataEntity.userName ?? '').startsWith(nickname)) {
// 유효한 데이터로 반환
return userDataEntity
} else {
// 불필요한 데이터는 제거
return null
}
})
// 반환된 lement 중에서 유효한 값만 남기고 List로 만들어주기
.where((element) => element != null)
.toList();
ㅤ
startsWith 함수로 불필요한 데이터를 제거해주고 나면, 아래처럼 검색어 뒤에 나오는 불필요한 데이터들을 제거해줄 수 있다.
그런데, 그러면 100만개의 데이터가 있다고 할 때 중간정도 되는 녀석을 검색했다고 하면, 결국 50만개의 데이터를 받아와야 하는거 아닌가? 라는 생각이 들어서 이걸 해결할 방법을 찾아나섰다. 혹시 인스타그램은 이걸 어떻게 다루고 있는지 확인해보려고 들어가봤는데, 생각보다 답은 간단했다. ㅎㅎ 페이지네이션으로 중간중간에 끊어서 데이터를 받아와주면 된다.
ㅤ
ㅤ
아래처럼 한정된 개수를 limit 함수를 활용해 불러와주고, 사용자가 데이터를 더 원하는 경우에는 startsAfter 함수를 통해 이어서 데이터를 가져오는 방식으로 구현해줄 수 있다.
final lastElement;
firestore
.collection(FirebaseCollectionName.users)
.where(
FirebaseUserFieldName.displayName,
isGreaterThanOrEqualTo: nickname,
)
.startsAfter(lastElement)
.limit(10)
.get()
...
ㅤ
이를 적용하면 아래처럼 나쁘지 않은 닉네임 검색 기능을 구현할 수 있다. 꽤나 쉽게 구현한 것 같은데?
샤라웃
https://mingeesuh.tistory.com/entry/Firebase-파이어스토어-페이지네이션-무한-스크롤-구현하기
'Develop > Flutter 개발' 카테고리의 다른 글
[Flutter] Visibility 위젯의 maintainState 프로퍼티, Offstage 위젯 (0) | 2024.06.05 |
---|---|
[Flutter] 자식 위젯에서 부모 위젯 setState 호출하기 (0) | 2024.05.23 |
[Flutter] map 함수는 단순히 리스트를 순회하는 함수가 아니다 (0) | 2024.05.18 |
[Flutter] 자연스러운 비동기 UI를 위해 FutureBuilder 사용하기 (0) | 2024.05.11 |
[Flutter] 이미지 에셋을 로딩할 때는 FutureBuilder 보다 Image의 loadingBuilder를 쓰자 (0) | 2024.03.03 |