- 페이징 라이브러리를 사용한 것은 아니고, 파라미터로 page: Int를 보내고 response로 hasNext: Boolean을 수신받을 수 있는 형태의 api에서 페이징을 자체 코드로 간단하게 구현한 예시다.
상단 구성
- Scaffold 구조로 상단에 TopAppBar를 가지고 내부 content로 리스트를 가지는 화면에서 페이징을 구성했다.
Scaffold(topBar = {
TopAppBar(modifier = Modifier.padding(start = 12.dp),
title = {
Text()
}, navigationIcon = {
IconButton(modifier = Modifier
.size(40.dp)
.padding(8.dp),
onClick = { finish() }) {}
}, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White)
)
}, content = {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.systemBarsPadding()
) {
MainList(it) //페이징처리가 적용될 Composable
}
})
- MainList에는 4가지의 remember 변수가 선언된다.
val items = remember { mutableStateListOf<Item>() }
var hasNext by remember { mutableStateOf(true) }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
- 리스트의 대상이 되는 객체를 담아둘 items와 마지막 페이지인지 검사하는 hasNext.
- hasNext의 경우 아직 api 호출을 안했으니 일단 무조건 다음 페이지가 있다고 가정한다.
- listState : LazyColumn / LazyRow에서 스크롤 상태를 기억하고 조작할 수 있도록 도와주는 LazyListState 객체를 생성
- 이를 통해 아이템 인덱스 및 스크롤 처리에 대한 접근이 가능하다. 또한 페이징 처리 시 list의 갱신 매개체가 된다.
- scope : Compose 내부에서 비동기 처리를 위해 Coroutine을 선언해 준다. 페이징 처리에 사용될 예정
페이징 처리 함수
- loadMore 함수가 페이징 처리의 역할을 한다. 해당 함수가 호출될 때마다 response로 서버로부터 마지막 페이지인지 여부를 알 수 있는 hasNext를 수신받는데, 이것이 false가 될 경우 더 이상 해당 함수의 로직을 진입하지 않는다.
fun loadMore() {
if (!hasNext) return
scope.launch {
withContext(Dispatchers.IO) {
isLoading = true
val updated = try {
GetApiTask(page).executeSync() ?: return@withContext
} catch (e: Exception) {
return@withContext
}
items.addAll(updated.data)
hasNext = updated.hasNext
isLoading = false
page++
}
}
}
- IO dispatcher에서 API 호출이 진행되고 그 결과에 따라 아이템 리스트 목록이 추가되고, hasNext 및 page 값이 변동된다.
- isLoading의 경우 전역 변수로 선언되어 있는데 해당 변수는 API 호출 진행 중인 상태를 파악하여 중복으로 API가 호출되면서 발생하는 충돌 상황을 방지해 준다.
페이징 활용
LaunchedEffect(Unit) { loadMore() }
- LaunchedEffect는 컴포저블이 Composition 될 때 한 번 실행되는 비동기 블록이다.
- 컴포저블이 재구성(Recomposition)되어도 파라미터 변수 값이 변하지 않으면 재실행되지 않는다. 근데 Unit을 넣었으니 결국 컴포저블 생성 최초 1회만 생성되는 것이다.
- 그리고 그렇게 초기 구성된 아이템 리스트는 LazyColumn을 구성하게 된다.
if (items.isNotEmpty()) {
LazyColumn(
state = listState,
contentPadding = contentPadding,
modifier = Modifier
.fillMaxSize()
.padding(start = 4.dp)
) {
items(items) {
ItemHolder(item = it)
}
}
}
- 이제 listState의 변화를 체크하면서 마지막 리스트 요소에 도달할 때, 자동으로 loadMore() 함수를 호출하도록 구성해줘야 한다.
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull() }.collect { lastVisibleItem ->
if (lastVisibleItem != null && lastVisibleItem.index >= items.size - 3 && !isLoading) loadMore()
}
}
- 마지막 아이템이 유효할 때, 현재 등록된 아이템의 3개 아이템 도달 전에 미리 로딩 상태를 체크하여 페이징 함수를 호출한다.
- snapshotFlow :Compose의 상태(State)가 변경될 때 Flow를 생성하는 기능을 제공한다.
- 이때 조건에 해당하는 flow를 collect 하여 조건 맞게 처리를 진행해 주면 된다.
- 정석적인 페이징 처리는 아니라서.. 오류 상황을 더 검토해 봐야겠지만 이처럼 간단한 구현 방식도 있다.
- 페이징 라이브러리를 사용한 것은 아니고, 파라미터로 page: Int를 보내고 response로 hasNext: Boolean을 수신받을 수 있는 형태의 api에서 페이징을 자체 코드로 간단하게 구현한 예시다.
상단 구성
- Scaffold 구조로 상단에 TopAppBar를 가지고 내부 content로 리스트를 가지는 화면에서 페이징을 구성했다.
Scaffold(topBar = {
TopAppBar(modifier = Modifier.padding(start = 12.dp),
title = {
Text()
}, navigationIcon = {
IconButton(modifier = Modifier
.size(40.dp)
.padding(8.dp),
onClick = { finish() }) {}
}, colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White)
)
}, content = {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.systemBarsPadding()
) {
MainList(it) //페이징처리가 적용될 Composable
}
})
- MainList에는 4가지의 remember 변수가 선언된다.
val items = remember { mutableStateListOf<Item>() }
var hasNext by remember { mutableStateOf(true) }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
- 리스트의 대상이 되는 객체를 담아둘 items와 마지막 페이지인지 검사하는 hasNext.
- hasNext의 경우 아직 api 호출을 안했으니 일단 무조건 다음 페이지가 있다고 가정한다.
- listState : LazyColumn / LazyRow에서 스크롤 상태를 기억하고 조작할 수 있도록 도와주는 LazyListState 객체를 생성
- 이를 통해 아이템 인덱스 및 스크롤 처리에 대한 접근이 가능하다. 또한 페이징 처리 시 list의 갱신 매개체가 된다.
- scope : Compose 내부에서 비동기 처리를 위해 Coroutine을 선언해 준다. 페이징 처리에 사용될 예정
페이징 처리 함수
- loadMore 함수가 페이징 처리의 역할을 한다. 해당 함수가 호출될 때마다 response로 서버로부터 마지막 페이지인지 여부를 알 수 있는 hasNext를 수신받는데, 이것이 false가 될 경우 더 이상 해당 함수의 로직을 진입하지 않는다.
fun loadMore() {
if (!hasNext) return
scope.launch {
withContext(Dispatchers.IO) {
isLoading = true
val updated = try {
GetApiTask(page).executeSync() ?: return@withContext
} catch (e: Exception) {
return@withContext
}
items.addAll(updated.data)
hasNext = updated.hasNext
isLoading = false
page++
}
}
}
- IO dispatcher에서 API 호출이 진행되고 그 결과에 따라 아이템 리스트 목록이 추가되고, hasNext 및 page 값이 변동된다.
- isLoading의 경우 전역 변수로 선언되어 있는데 해당 변수는 API 호출 진행 중인 상태를 파악하여 중복으로 API가 호출되면서 발생하는 충돌 상황을 방지해 준다.
페이징 활용
LaunchedEffect(Unit) { loadMore() }
- LaunchedEffect는 컴포저블이 Composition 될 때 한 번 실행되는 비동기 블록이다.
- 컴포저블이 재구성(Recomposition)되어도 파라미터 변수 값이 변하지 않으면 재실행되지 않는다. 근데 Unit을 넣었으니 결국 컴포저블 생성 최초 1회만 생성되는 것이다.
- 그리고 그렇게 초기 구성된 아이템 리스트는 LazyColumn을 구성하게 된다.
if (items.isNotEmpty()) {
LazyColumn(
state = listState,
contentPadding = contentPadding,
modifier = Modifier
.fillMaxSize()
.padding(start = 4.dp)
) {
items(items) {
ItemHolder(item = it)
}
}
}
- 이제 listState의 변화를 체크하면서 마지막 리스트 요소에 도달할 때, 자동으로 loadMore() 함수를 호출하도록 구성해줘야 한다.
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull() }.collect { lastVisibleItem ->
if (lastVisibleItem != null && lastVisibleItem.index >= items.size - 3 && !isLoading) loadMore()
}
}
- 마지막 아이템이 유효할 때, 현재 등록된 아이템의 3개 아이템 도달 전에 미리 로딩 상태를 체크하여 페이징 함수를 호출한다.
- snapshotFlow :Compose의 상태(State)가 변경될 때 Flow를 생성하는 기능을 제공한다.
- 이때 조건에 해당하는 flow를 collect 하여 조건 맞게 처리를 진행해 주면 된다.
- 정석적인 페이징 처리는 아니라서.. 오류 상황을 더 검토해 봐야겠지만 이처럼 간단한 구현 방식도 있다.