Firebase_친구_기능_(2)_ _친구_검색 - boostcampwm-2024/and04-Nature-Album GitHub Wiki
-
UI 설계: 현재 이메일(
email
) 기반 검색으로 구현되어 있음. -
displayName
사용 여부: 중복 가능성이 있으므로,email
로 검색하는 것이 더 적합하다고 판단됨. - 검색 방식 고민: 사용자가 이메일을 입력할 때마다 검색 결과를 실시간으로 필터링해야 하는지, 아니면 전체 입력 후 검색 버튼을 눌러야 하는지 논의 필요.
-
친구 검색 기능과 관련하여 먼저 기존에 구현된 라벨 검색 로직을 참고하였다. 라벨 검색 로직이 친구 검색에서도 적합하다면, 이를 유사하게 활용하려고 하였다.
-
라벨 검색 로직 먼저 분석해보자
-
ViewModel 초기화 시 데이터 Fetch:
-
init
에서fetchLabels()
호출로 데이터를 가져와StateFlow
에 저장.
-
-
로컬 필터링:
- UI에서 검색어 입력 이벤트를 통해 로컬에 저장된 데이터를 필터링.
- 라벨 만들고 사진 저장:
- 백그라운드 서비스로
insertLabel()
진행
- 백그라운드 서비스로
-
근데 init이 페이지 들어갈 때마다 된다는 것은 viewModel이 재사용되지 않고 있다는 것인데 이거 괜찮은 것인가?
package com.and04.naturealbum.ui.labelsearch @HiltViewModel class LabelSearchViewModel @Inject constructor( private val repository: DataRepository ): ViewModel(){ private val _labels = MutableStateFlow(emptyList<Label>()) val labels: StateFlow<List<Label>> = _labels init { fetchLabels() Log.d("TEST", "ViewModel 생성: ${this.hashCode()}") } private fun fetchLabels(){ viewModelScope.launch { _labels.emit(repository.getLabels()) } } override fun onCleared() { super.onCleared() Log.d("TEST", "ViewModel 소멸: ${this.hashCode()}") } }
-
확인 결과 화면이 사라지면 그때 같이 소멸됨. 즉 재사용은 되지 않고 재생성해서 사용하는 것.
-
⇒ 이거 괜찮은건가? 도윤님께 여쭤보아야겠다. ⇒ 의도한 바라고 함.
-
ViewModel 초기화 시 데이터 Fetch:
현재 MyPage에서 FriendViewModel
을 사용하여 데이터를 관리하고 있다.
따라서 우선적으로 addSnapshotListener
를 활용하여 Firebase 데이터가 변경될 때 자동으로 UI를 업데이트하도록 구현할 예정이다.
그다음에는 전체 email 데이터를 가져와 저장한 뒤, 이를 기반으로 필터링하여 검색 결과를 표시하는 방식으로 구현할 계획이다.
이는 라벨 검색에서 사용된 방식과 유사하다.
Q. 모든 USER 데이터를 가져오게 되는 것인가?
A. YES. 모든 데이터를 가져오는 방식이 된다.
사용자가 입력한 email로만 검색 버튼을 눌렀을 때 Firestore 쿼리를 날려 검색 결과를 가져오는 방법도 있다.
하지만, 일반적인 검색 UX를 고려하면, 사용자가 입력한 내용에 따라 바로 검색 결과가 표시되는 것이 더 편리하다.
즉, 사용자가 입력한 email로 시작하는 내용이 아래에 바로 표시되는 것이 사용자 경험 측면에서 유리하다.
-
실시간 쿼리 요청 방식:
- 사용자가 입력할 때마다 쿼리를 서버로 전송하여 검색 결과를 가져옴.
- 장점: 서버에서 필터링하므로 클라이언트 리소스 부담이 적음.
- 단점: 네트워크 요청 빈도가 증가하며 성능 저하 가능.
-
로컬 필터링 방식 (라벨 검색 방식):
- 한 번에 데이터를 모두 가져온 후, 입력된 검색어를 기반으로 필터링.
- 장점: 네트워크 요청 횟수가 줄어들며 실시간 UI 업데이트가 가능.
- 단점: 데이터 크기가 클 경우 초기 로딩 시간이 길어질 수 있음.
현재 우리 프로젝트는 백엔드 서버 없이 클라이언트에서 필터링을 처리해야 하는 구조다.
또한, 예상되는 회원 수가 많지 않다는 점을 고려하면, 로컬 필터링 방식이 더 적합하다.
즉, 모든 email 데이터를 한 번에 가져온 뒤 클라이언트에서 필터링을 수행하는 것이 효율적일 것으로 판단된다.
그러나 우선은 Firebase 실시간 데이터 가져오는 것 확인해보고 더 생각해보아야겠다.
탭 각각에 addSnapshotListener
를 적용하여 실시간 데이터를 감지하는 동시에, 네트워크 연결 상태에 따라 적절한 UI를 표시하도록 구현할 예정이다.
네트워크 연결이 끊긴 경우에는 "네트워크 연결 없음" 메시지를 표시하는 Composable을 보여줄 계획이다. 그러므로 네트워크 연결이 끊길 경우 어떻게 데이터를 보여줄지는 고려하지 않도록 한다.
- 네트워크 연결 상태 감지
-
탭 UI 변경
- 네트워크 연결 상태를 감지하여, 연결이 끊긴 경우 해당 탭에서 데이터를 불러오지 않고 "네트워크 연결 없음" 메시지를 표시하는 Composable로 대체한다.
- 탭별 처리
- 친구 목록
-
addSnapshotListener
를 사용하여 실시간으로 친구 데이터를 감지하고 UI에 표시.
-
- 친구 검색
- 사용 가능한 사용자 목록을
addSnapshotListener
로 감지하고, 검색 필터링된 데이터를 표시. ⇒ 로직 결정 필요
- 사용 가능한 사용자 목록을
- 친구 요청 확인 메세지함
-
addSnapshotListener
를 사용하여 친구 요청 데이터를 실시간으로 감지하고 UI에 표시
-
- 친구 목록
addSnapshotListener
는 Firebase Firestore에서 제공하는 메서드로, Firestore의 문서(Document)나 컬렉션(Collection)에 대한 실시간 변경 사항을 감지하는 데 사용된다. 데이터 변경 시 클라이언트는 즉시 변경된 데이터를 받아 UI나 로직에 반영할 수 있다.
-
실시간 데이터 감지
Firestore 데이터베이스에서 문서나 컬렉션의 추가, 수정, 삭제를 실시간으로 감지할 수 있다.
-
비동기 콜백 처리
데이터가 변경될 때마다 콜백 함수가 실행되며, 비동기적으로 데이터를 받아올 수 있다.
-
오프라인 지원
네트워크 연결이 끊겨도 로컬 캐시(Local Cache)를 통해 마지막으로 동기화된 데이터를 제공하며, 연결이 복원되면 자동으로 동기화를 재개한다.
-
실시간 동기화
데이터 변경 사항을 즉시 감지하여 최신 데이터를 유지할 수 있다.
-
오프라인 지원
네트워크가 끊겼을 때도 마지막으로 동기화된 데이터를 제공하며, 연결 복구 후 자동으로 동기화된다.
-
반응형 UI
데이터를 실시간으로 업데이트함으로써 사용자 경험을 개선할 수 있다.
-
리소스 누수 방지
-
addSnapshotListener
는 ViewModel이나 Activity의 생명주기와 연결되지 않으므로, 더 이상 필요하지 않을 경우 구독을 명시적으로 해제해야 한다. - Compose에서는
DisposableEffect
또는ViewModel
의onCleared
를 활용하여 리소스를 관리한다.
-
-
쿼리 최적화
- 구독 중인 데이터가 많아질수록 네트워크와 클라이언트 리소스가 증가하므로, 필요한 데이터만 가져오도록 쿼리를 최적화해야 한다.
-
오류 처리
-
addSnapshotListener
의error
매개변수를 활용하여 네트워크 문제, 권한 문제 등을 처리해야 한다.
-
특성 | addSnapshotListener | get() |
---|---|---|
실시간 업데이트 | 데이터 변경 시마다 자동으로 호출 | 호출 시점에만 데이터 가져옴 |
오프라인 지원 | 네트워크 없이도 로컬 캐시 데이터 제공 | 로컬 캐시 데이터 제공 가능 |
사용 사례 | 실시간 UI 업데이트가 필요한 경우 | 초기 데이터 로드나 비실시간 데이터 처리에 적합 |
리소스 소비 | 네트워크 연결 유지 필요 | 필요한 순간에만 네트워크 요청 |
-
ViewModel 초기화 시 서버에서 데이터를 가져옴
-
fetchLabels()
호출로 서버에서 라벨 데이터를 가져옴. -
StateFlow
에 저장.
-
-
UI에서 입력 이벤트를 통해 필터링
- 사용자가 검색어를 입력할 때마다 리스트를 필터링.
- 검색어와 일치하는 라벨만 필터링하여 UI에 표시.
-
데이터 크기 고려
- 라벨 검색은 일반적으로 사용자의 개인 데이터(라벨)만을 대상으로 하므로 데이터 크기가 작다.
- 친구 검색은 모든 사용자 데이터를 대상으로 할 수 있으므로 데이터 크기가 훨씬 클 수 있다.
- 데이터를 처음에 모두 가져오면 성능 저하가 발생할 수 있다. (PR에서 리뷰 받은 우려되는 사항)
-
검색 데이터 업데이트 빈도
- 라벨 데이터는 일반적으로 자주 변경되지 않으므로 처음 가져온 데이터를 필터링해도 문제가 없다.
- 친구 검색 데이터는 새로운 사용자가 추가되거나 친구 상태가 변경될 가능성이 높아 실시간 업데이트가 필요할 수 있다.
-
네트워크 요청 비용
- 처음에 모든 사용자 데이터를 가져오는 방식은 네트워크 요청 비용이 높을 수 있다.
- 특히 사용자가 많을 경우 초기 데이터 로드에 시간이 오래 걸릴 수 있다.
- 라벨 검색 방식과 동일하게 처리
-
addSnapshotListener
으로 state로 받아와두고 거기에서 라벨 필터링한 것처럼 필터링 - 초기에 데이터 가져오는 부분만 좀 시간이 걸림
- 데이터 전체를 가져오면 비효율적이지 않은지에 대한 의문을 제시해준 팀원이 있었으나 우리 앱의 사용자 규모로는 괜찮지 않은가 생각이 드는 상태
-
- 서버 필터링으로 처리
- 서버에 사용자 입력에 따라 쿼리 요청 계속 보내서 받아온 것을 UI에 계속 표시
- 네트워크 통신이 더 많이 요구됨
- 해당 부분에 대해서는 추가 조사 해서 통신을 줄일 수 있는 방향 고려
- 검색 버튼을 눌렀을 때 서버에서 필터링
- 2번이 사용자 입력이 진행됨에 따라 계속 쿼리 요청해서 UI를 보여주는 것이라면, 이건 검색 버튼 누를 때만 요청하는 것
- 사용자 입장에서 내가 검색하는 것으로 시작하는 것이 안 보이면 불편할 것 같음
- 아무래도 팀원들과 논의해보는 것이 나을 것 같아서 논의한 결과 서버 필터링 처리가 선정되었다.
-
라벨 검색 방식과 동일하게 처리
-
방법:
-
addSnapshotListener
를 사용하여 전체 데이터를 로컬에 저장하고, UI에서 필터링.
-
-
장점:
- 네트워크 요청 횟수를 최소화하여 효율적.
- 클라이언트에서 데이터를 처리하므로 빠른 검색 결과 제공 가능.
-
단점:
- 데이터가 많을 경우 클라이언트의 메모리 사용량 증가!
- 초기 데이터 로딩 시 시간이 걸릴 수 있음!
-
방법:
-
서버 필터링으로 처리
-
방법:
- 사용자가 입력할 때마다 서버에 쿼리 요청을 보내, 검색 결과를 가져와 UI에 표시.
-
장점:
- 클라이언트 부담이 줄어들며, 서버에서 필터링을 처리하므로 메모리 사용량 감소.
- 실시간으로 검색 결과 반영 가능.
-
단점:
- 네트워크 요청이 잦아져 서버 부담이 증가할 수 있음.
- 대규모 트래픽이 발생할 경우 서버 최적화 필요.
-
방법:
-
검색 버튼을 눌렀을 때 서버에서 필터링
-
방법:
- 사용자가 검색 버튼을 누르면 서버에 쿼리 요청을 보내고, 결과를 가져와 UI에 표시.
-
장점:
- 네트워크 요청 횟수를 최소화하여 서버 부담 감소.
-
단점:
- 실시간 검색 경험 제공이 어려워 사용자 경험이 떨어질 수 있음.
-
방법:
-
결정 이유:
- 전체 데이터를 클라이언트에서 가져와 처리할 경우, 사용자가 많아질수록 클라이언트의 메모리 사용량이 급격히 증가함.
- 반면, 사용자가 입력할 때마다 서버에 요청을 보내는 방식은 네트워크 요청이 잦아져 서버 부담이 증가할 수 있지만, 클라이언트 리소스를 절약할 수 있음.
- 따라서, 클라이언트와 서버 간의 적절한 리소스 분배를 위해 서버 필터링 방식이 가장 적합하다고 판단됨.
dev → feature/friend-feature → feature/friend-search
- 친구 검색 서버 필터링으로 구현
- 작업이 끝나면
feature/friend-feature
브랜치로 머지
git checkout feature/friend-feature
git merge feature/friend-search
- 친구 목록, 친구 요청 목록 실시간 데이터로 가져와 UI 자동 업데이트
- 네트워크 없을 때의 UI 표시
- Debounce를 사용하면 사용자가 빠르게 입력할 때 불필요한 필터링 로직 실행을 줄이고 성능을 최적화할 수 있다.
- 입력을 멈춘 후 짧은 시간 동안 기다렸다가 필터링을 수행하므로 서버 요청이나 복잡한 로직에서도 성능 저하를 방지할 수 있다.
-
친구 검색 기능 구현:
-
FriendViewModel
에서searchQuery
를 받아debounce
를 적용하여 사용자가 검색어를 입력할 때마다 서버로 쿼리 요청을 보내는 방식으로 구현했습니다. - 검색 결과는
FirestoreUserWithStatus
객체로 받아 상태에 따라 친구 요청 상태 또는 친구 관계 정보를 제공합니다.
-
-
서버에서 사용자 검색:
-
searchUsers
메서드에서는Firestore
의whereGreaterThanOrEqualTo
와whereLessThanOrEqualTo
를 사용해 이메일을 기준으로 사용자들을 검색합니다. 이 방식은 검색어에 맞는 이메일을 찾는 데 사용됩니다. - 검색 결과를
StateFlow
에 반영하여 UI에서 바로 갱신될 수 있도록 설정되었습니다.
-
UI에서 검색 시 키보드 처리
- 현재 검색을 누르면 UI가 키보드보다 위로 올라가야 하는데, 이 부분이 아직 해결되지 않았습니다. 키보드가 올라올 때
RequestedList
와SearchBar
의 위치를 적절히 조정할 수 있도록 개선이 필요합니다. -
windowInsets
나imePadding()
을 활용하여 UI가 적절히 이동하도록 시도했으나, 아직 해결되지 않았습니다. 이후 추가적으로 이 문제를 해결할 필요가 있습니다.