1. Retrofit의 반환형 Call, Response, Flow 중 선택
이번 프로젝트에서는 Gson을 이용하여 Json 응답을 처리했습니다. 하지만 Retrofit에서는 이 응답을 Call 형태로 반환하고, 이에 맞는 콜백 인터페이스를 통해 응답을 처리하는 방법과, Response 자료형으로 받아 try-catch 문으로 해당 함수를 실행하는 방법이 있습니다. 이와 유사하게, List 형태로 데이터를 가져온 후, flow 스코프에서 try-catch로 처리하여 결과를 Flow 형태로 전달하는 방법도 가능합니다.
Retrofit 결과를 처리하는 방법은 크게 세 가지로 나뉩니다. 이 중 멀티모듈 클린 아키텍처에서 적절한 방법을 찾았는데, Call을 사용하는 경우 데이터 레이어에서 콜백을 처리하면서 도메인이나 UI 레이어로 응답을 전달하기 어려울 수 있습니다. 또한, 콜백을 처리하는 메서드에 프레젠테이션 레이어에서 함수를 전달해야 하는 방식은 콜백 지옥이나 중복된 코드의 위험이 커지며, 멀티모듈 구조에서 적절하지 않다고 생각했습니다.
그래서 당시에는 Response나 Flow 중에서 선택해야 했고, Flow보다는 처리가 쉬운 Response를 사용했습니다. 그 이유는, 나중에 Paging 라이브러리를 사용할 때 Response로 처리하는 방식이 더 구현하기 쉬울 것이라고 판단했기 때문입니다. 하지만 이후 생각해보니, Flow로 처리하는 방법도 충분히 좋은 선택지가 될 수 있다는 결론에 이르렀습니다.
// Retrofit API 정의
interface MyApiService {
@GET("data")
suspend fun getData(@Query("page") page: Int): List<MyItem> // No Response wrapping
}
// PagingSource 구현 - Flow 방식
class MyPagingSource(private val apiService: MyApiService) : PagingSource<Int, MyItem>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyItem> {
return try {
val page = params.key ?: 1
val data = apiService.getData(page)
LoadResult.Page(
data = data,
prevKey = if (page == 1) null else page - 1,
nextKey = if (data.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
// ViewModel - Flow 방식
class MyViewModel(private val apiService: MyApiService) : ViewModel() {
val pagingDataFlow: Flow<PagingData<MyItem>> = Pager(
config = PagingConfig(pageSize = 20)
) {
MyPagingSource(apiService)
}.flow
}
이런 식으로 처리할 수도 있기 때문에 Flow로 처리하는 것도 나쁜지 않은 것 같습니다.
2. Paging + MultiModule + CleanArchitecture 처리
페이징을 구현하는 방법으로는 RecyclerView에서 addOnScrollListener를 통해 스크롤 상태에 따라 데이터를 요청하는 방법이 있습니다. 그러나 더 효율적인 방법으로는 Paging 라이브러리를 사용하는 것입니다.
Paging 라이브러리를 사용하려면 PagingAdapter에서 신호를 보내줘야 하므로, Paging 관련 소스는 data, domain, presentation 레이어 모두에 필요하게 됩니다.
data 레이어에서는 PagingSource에서 페이징할 때마다 데이터를 어떻게 가져올지 정의하고, repository에서는 이 PagingSource를 이용하여 Pager를 만들며, Flow<PagingData<Model>> 형태로 데이터를 반환하여 domain 레이어로 전달합니다.
따라서 domain 레이어에서도 Paging 관련 의존성이 필요하게 됩니다. 하지만 domain 레이어는 Android 라이브러리가 아니라 Kotlin 라이브러리로 구성되므로, Android 의존성을 포함하지 않는 paging-common을 추가해야 합니다.
// alternatively - without Android dependencies for tests
implementation("androidx.paging:paging-common:$paging_version")
이렇게 paging-common을 추가하면 domain 레이어에서도 페이징 처리를 위한 의존성을 충족시킬 수 있습니다.
3. Room 실시간 변경 + Paging 구현
페이징(Paging)은 데이터를 한 번에 모두 가져올 수 없는 경우가 많습니다. 한 번에 가져오더라도 이를 처리하는 데 시간이 걸리고, 메모리 과부하가 발생할 수 있습니다. 또한, 데이터를 모두 가져오더라도 양이 많아 한 화면에 다 표시할 수 없기 때문에 스크롤하거나 페이지를 넘기는 등의 액션으로 다음 페이지를 볼 수 있게 됩니다. 안드로이드에서는 주로 스크롤을 통해 페이징을 하여 데이터를 가져옵니다.
하지만 로컬 데이터를 처리할 때는 "굳이 페이징을 해야 할까?"라는 의문을 가질 수 있습니다. 이미 기기 내에 있는 데이터인데 굳이 페이징을 해야 하는지 말이죠. 하지만 많은 양의 데이터를 처리하는 경우 페이징을 사용하는 것이 좋습니다. 작은 데이터라면 의미가 없겠지만, 데이터 양이 많을 경우 기기 메모리가 과부하될 수 있기 때문입니다. 안드로이드 기기는 여러 작업을 동시에 처리하기 때문에, 과도한 데이터 로딩으로 앱이 중단될 위험도 있습니다. 따라서, 리사이클러뷰와 같이 연속된 데이터를 처리해야 하는 경우 페이징을 사용하는 것이 좋습니다.
이제 페이징을 구현하려고 할 때 직면하는 문제가 있습니다. 안드로이드에서는 로컬 DB를 사용할 때 주로 Room 라이브러리를 사용하여 데이터를 관리합니다. 특히, 로컬 데이터 변경을 감지할 수 있도록 Flow를 활용할 수 있습니다. Room이나 DataStore는 이러한 변경 사항을 실시간으로 알려줄 수 있는 대표적인 예입니다.
페이징과 관련해서는, 변경된 데이터를 처리하는 데 많은 노력이 필요할 것처럼 보이지만, 다행히도 Room에서는 페이징을 쉽게 처리할 수 있도록 PagingSource를 반환하는 방법을 지원합니다. 이를 통해 별도의 PagingSource를 구현하지 않고도 페이징을 쉽게 적용할 수 있게 됩니다.
'안드로이드 > 안드로이드' 카테고리의 다른 글
CNA(Chirag Note App) 프로젝트하면서 생긴 이슈 및 해결 ,기술 정리 (0) | 2024.10.02 |
---|---|
CNA(Chirag Note App) 클론코딩 & 리펙토링 회고 (1) | 2024.10.01 |
CIP(Cat Image Provider) 개발 회고 (0) | 2024.09.26 |
TBA(Ticket-Booking-app) 프로젝트하면서 생긴 이슈 및 해결 ,기술 정리 (3) | 2024.09.22 |
Ticket Booking app - UiLover Android 클론코딩 회고 (4) | 2024.09.21 |