
Jetpack Navigation은 내부적으로 FragmentManager나 Compose의 NavHostController를 이용해 자체적으로 백스택을 관리합니다. 각 목적지(Destination)는 NavGraph에 등록되며, navigate() 호출 시 새로운 목적지를 스택에 push하고, popBackStack() 호출 시 pop 합니다.
FragmentManager의 백스택과 별도로 Navigation 전용 백스택을 유지합니다.NavHostController가 백스택을 BackStackEntry 목록으로 보존합니다.NavController 혹은 NavHostController를 사용해 다음과 같이 백스택을 조작할 수 있습니다:
| 메서드 | 설명 |
|---|---|
navigate(destinationId) | 새로운 목적지로 이동하며, 백스택에 추가됨 |
popBackStack() | 현재 목적지를 백스택에서 제거 (뒤로가기 효과) |
popBackStack(destinationId, inclusive) | 특정 목적지까지 백스택 제거 가능(inclusive=true면 해당 destination도 제거됨) |
navigate(route) { popUpTo(...) { inclusive = ... } } | Compose에서는 popUpTo로 백스택 조절 가능 |
clearBackStack() (직접 지원은 없음) | navigate(start) { popUpTo(graph.startDestinationId) { inclusive=true } }로 대체 가능 |
kotlin
복사편집
navController.navigate("home") {
popUpTo("login") {
inclusive = true
}
}
→ login을 포함해서 그 아래 백스택을 제거하고 home으로 이동
Safe Args는 Jetpack Navigation의 공식 Gradle Plugin으로, 목적지 간에 데이터를 전달할 때 타입 안전성을 보장하는 도구입니다. arguments를 사용하는 전통적인 방법은 Bundle 기반이므로 타입 오류가 런타임에 발생할 수 있지만, Safe Args는 컴파일 타임에 타입을 체크합니다.
nav_graph.xml에서 <argument>로 목적지에 전달할 값을 정의하면Directions 클래스와 Args 클래스를 생성합니다FragmentADirections.actionToFragmentB(param1, param2) 형태로 호출FragmentBArgs.fromBundle(arguments)로 수신Int, String, Boolean, Parcelable, Serializable 등 타입을 명시하면, 컴파일 시 잘못된 타입 전달 시 에러 발생xml
복사편집
<fragmentandroid:id="@+id/fragmentB"
android:name="com.example.FragmentB">
<argumentandroid:name="userId"
app:argType="string"
android:defaultValue="guest" />
</fragment>
kotlin
복사편집
val action = FragmentADirections.actionToFragmentB("user123")
findNavController().navigate(action)
kotlin
복사편집
val args: FragmentBArgs by navArgs()
val userId = args.userId
| 항목 | 요약 |
|---|---|
| 백스택 처리 | Jetpack Navigation은 NavHost 기반으로 자체 백스택 유지. navigate, popBackStack, popUpTo로 조작 |
| Safe Args | 목적지 간 안전한 데이터 전달 방식. 컴파일 시 타입 검사를 통해 오류 방지 |
Paging 3 라이브러리는 세 가지 로딩 상태(load type)에서 각각 오류가 발생할 수 있습니다:
| LoadType | 설명 |
|---|---|
REFRESH | 첫 페이지 로딩 시 |
APPEND | 다음 페이지 로딩 시 |
PREPEND | 이전 페이지 로딩 시 (대부분 사용 안 함) |
LoadStatePagingDataAdapter는 LoadStateListener를 통해 각 로딩 상태의 결과를 알려줍니다:
kotlin
복사편집
adapter.addLoadStateListener { loadStates ->
val refreshState = loadStates.refresh
val appendState = loadStates.append
if (refreshState is LoadState.Error) {
// 초기 로딩 오류
}
if (appendState is LoadState.Error) {
// 스크롤 시 오류 (네트워크 등)
}
}
adapter.retry()LoadState.Error 상태가 감지되면, 재시도 버튼을 보여주고 adapter.retry() 호출로 실패한 요청을 다시 시도할 수 있습니다.kotlin
복사편집
retryButton.setOnClickListener {
adapter.retry()
}
PagingData는 Flow<PagingData<T>>로 전달되므로, Flow에서 catch 연산자를 사용해 오류를 잡을 수도 있습니다.
kotlin
복사편집
val pagingFlow = repository.getPagingData()
.catch { e ->
// 전체 스트림 수준의 오류 처리
}
.cachedIn(viewModelScope)
다만 이 catch는 PagingSource 내부에서 throw된 예외가 아니라, Flow 전체에서 발생하는 예외를 잡는 용도입니다. 대부분의 오류 처리는 LoadState 기반으로 UI에서 하는 것이 바람직합니다.
PagingSource의 load() 함수에서 예외가 발생하면 Paging은 자동으로 LoadState.Error로 전파합니다.
kotlin
복사편집
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
return try {
val data = api.getItems(page = params.key ?: 1)
LoadResult.Page(
data = data,
prevKey = null,
nextKey = if (data.isEmpty()) null else params.key!! + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
| 목적 | 전략 |
|---|---|
| 오류 UI 노출 | adapter.loadStateFlow 또는 addLoadStateListener로 상태 감지 후, 오류 메시지 + 재시도 버튼 노출 |
| 재시도 처리 | adapter.retry() 호출 (자동 실패 요청 재시도) |
| 네트워크 요청 내 예외 | PagingSource.load() 내에서 try-catch로 감싸고 LoadResult.Error(e) 반환 |
| 전체 스트림 오류 | Flow.catch {} 사용 (예: repository → ViewModel 연결 시) |
kotlin
복사편집
val loadState = lazyPagingItems.loadState
when (val refreshState = loadState.refresh) {
is LoadState.Loading -> CircularProgressIndicator()
is LoadState.Error -> RetrySection { lazyPagingItems.retry() }
}
Jetpack Paging은 오류 처리를 LoadState 기반의 UI 처리 + retry() 호출 기반의 자동 재시도 메커니즘으로 분리하고 있으며, PagingSource와 ViewModel, UI 각 계층에서 역할에 맞는 처리를 하는 것이 중요합니다.
필요하시면 Compose 기반 RetrySection 컴포저블, 더 복잡한 ErrorView 구성도 예시로 드릴 수 있습니다.