24.02.29 Firebase로 검색기능 구현하기 (Algolia)

KSang·2024년 2월 28일
0

TIL

목록 보기
75/101

파이어 베이스에 저장된 데이터를 검색해보자.

검색 기능을 구현하고 있는데, 파이어베이스에선 라이크 쿼리를 지원해주지 않는다,,

키워드 검색으로 얼추 만들어 볼 순있다.

    override suspend fun searchQuery(query: String) = runCatching {
        firebaseFirestore.collection(DataBaseType.POST.title)
            .orderBy("title")
            .startAt(query)
            .endAt(query + "\uf8ff")
            .get().await().documents.mapNotNull {
                it.toObject(PostEntity::class.java)
            }
    }

이렇게 구현하면 query가 포함된 문장으로 시작되는 title을 가진 데이터들을 가지고 올 수 가 있다.

하지만 한계점이 존재하는데, 시작부분에 존재 하지 않으면 검색이 안된다.

예를 들어

"안녕하세요 저희 프로젝트에서 새로운 팀원을 모집합니다" 라는 게시글이 있을때

"안녕하세요"를 검색하게된다면 검색이 되지만 "모집"을 검색하면 검색이 안된다.

그래서 이걸 해결하기 위해 제목의 모든 단어를 쪼개서 키워드로 저장할수도있다.

하지만 이렇게 되면 제목이 길어질수록 데이터의 크기가 커지게 되고 관리도 힘들어져 한계점이 존재한다.

그럼 firebase론 검색기능을 구현할 수 없는걸까??

extension

파이어 베이스에선 extension으로 외부 api를 지원해준다.

검색기능 또한 이를 사용해서 구현이 가능한데, 주로 algolia를 이용한다.

algolia는 강력한 검색기능을 제공하는데, 빠른 검색 성능 뿐만 아니라, 오타 검수, 검색 우선 순위 설정, 원하는 부분만 검색등 다양한 검색 기능을 제공한다.

우선 이 방법을 사용하려면 무료 플랜인 Spark가 아닌 Blaze로 요금제를 업그레이드 해야한다.
요금제 한도 설정이 가능한데, 한달에 100원정도도 가능하지 부담되지 않았다.

우선 알고리아가 어떻게 작동하는지 알아보자

client - >firestore -> algolia

우선 클라이언트가 파이어 스토어에 데이터를 저장하면 알고리아에선 이를 감지하고 알고리아에 그 데이터를 저장한다.

파이어 스토어에 저장한 데이터 만큼 알고리아에도 저장되는것

파이어 베이스와 알고리아 설정하는건
포스트를 참고하면 된다.

둘 연동이 되었으면 안드로이드 스튜디오에서 의존성을 추가해준다.

gradle.kts

    //algolia
    implementation ("com.algolia:algoliasearch-android:3.+")
    implementation ("com.algolia:algoliasearch-client-kotlin:2.1.9")

이제 레포지토리에 연결해서 사용하면되는데,

현제 Hilt를 사용하고 있으니 module을 추가해준다.

@Module
@InstallIn(SingletonComponent::class)
object AlgoliaModule {

    @Provides
    @Singleton
    fun provideAlgoliaClient(): Client {
        return Client(BuildConfig.ALGOLIA_APP_ID, BuildConfig.ALGOLIA_API_KEY)
    }

    @Provides
    fun provideAlgoliaIndex(client: Client): Index {
        return client.getIndex(DataBaseType.POST.title)
    }
}

여기서 algolia의 Client를 설정해주고

Index (firestore로 치면 collection 같은 녀석)을 설정해준다.

DataBaseType은 enum으로 DataBaseType.POST.title = "posts"라는 String이다.

기존에 게시글을 관리하던 레포지토리의 구현부를 설정해주자

class PostRepositoryImpl @Inject constructor(
    private val firebaseFirestore: FirebaseFirestore,
    private val algolia: Index,
) : PostRepository {
override suspend fun searchQuery(query: String) = suspendCancellableCoroutine { continuation ->
        val algoliaQuery = Query(query)
        algolia.searchAsync(algoliaQuery) { jsonArray, exception ->
            if (exception != null) {
                continuation.resumeWithException(exception)
            } else {
                jsonArray?.getJSONArray("hits")?.let { hits ->
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            val posts = fetchAllPosts(hits)
                            continuation.resume(posts)
                        } catch (e: Exception) {
                            continuation.resumeWithException(e)
                        }
                    }
                } ?: continuation.resumeWithException(RuntimeException("No hits found"))
            }
        }
    }

    private suspend fun fetchAllPosts(hits: JSONArray): List<PostEntity> = withContext(Dispatchers.IO) {
        val hitSequence = (0 until hits.length()).asSequence().map { hits.getJSONObject(it) }
        hitSequence.mapNotNull { hit ->
            hit.optString("objectID", null)?.let { key ->
                Log.d("SearchDebug", "Processing post with key: $key")
                async { fetchPostEntity(key) }
            }
        }.toList().awaitAll().filterNotNull()
    }

    private suspend fun fetchPostEntity(key: String): PostEntity? = getPost(key).getOrNull()

뷰모델에서검색어를 입력받으면 알고리아의 Query를 사용해서 검색어를 넣어주고 searchAsync를 이용해 병렬로 검색한다.

그러면 json형태로 데이터가 들어오게 되는데,여기서 hits를 이용해 검색결과를 구별해준다.

fetchAllPosts는 jsonArray타입의 데이터에서 key를 찾아준다.

키를 얻은 다음 fireStore에서 key를 기준으로 검색된 게시글의 자세한 데이터를 가져온다.

파이어 스토어와 알고리아가 잘 동기화가 되질 않아서

추가, 수정 될때마다 직접 알고리아에 데이터를 넣어주고 있다.

왜 그런 슬픈일이 자꾸 나한테 발생하는걸까...

0개의 댓글