Kotlin을 활용한 안드로이드 앱을 만드는 프로젝트를 진행 중 REST API 통신이 필요하게 되었고 다음과 같이 코드를 작성했다.
interface PictureApiService {
...
// 오늘의 이미지 리스트를 받아오는 요청
@GET("/picture/today_list/no_user")
suspend fun getTodayImages(): List<PictureResponse>
...
}
// PictureApiService
// retrofit 요청 생성 함수
fun PictureApiService(): PictureApiService {
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(PictureApiService::class.java)
}
val response = PictureApiService().getTodayImages()
Suspend function 'getTodayImages' should be called only from a coroutine or another suspend function
LaunchEffect 활용
LaunchedEffect(Unit) {
val response = PictureApiService().getTodayImages()
}
LaunchedEffect(tag) {
try {
isLoading = true
val response = if (currentUser != null) {
val token = currentUser.getIdToken(false).result?.token.toString()
val uid = currentUser.uid
when (tag) {
"Today's Pick" -> pictureApiService.getUserTodayImages(token, uid)
"Weekly" -> pictureApiService.getUserWeeklyImages(token, uid)
"Monthly" -> pictureApiService.getUserMonthlyImages(token, uid)
else -> pictureApiService.getUserTagImages(token, tag, uid)
}
} else {
when (tag) {
"Today's Pick" -> pictureApiService.getTodayImages()
"Weekly" -> pictureApiService.getNoUserWeeklyImages()
"Monthly" -> pictureApiService.getNoUserMonthlyImages()
else -> pictureApiService.getNoUserTagImages(tag)
}
}
posts = response
} catch (e: Exception) {
e.printStackTrace()
posts = emptyList()
} finally {
isLoading = false
}
}
rememberCoroutineScope 활용
val coroutineScope = rememberCoroutineScope()
...
onClick = {
coroutineScope.launch {
// Coroutine 필요 요청들
PictureApiService().getTodayImages()
}
}
...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Tab(scrollBehavior: TopAppBarScrollBehavior, context: Context, tag: String) {
// 현재 화면 구성에 대한 정보를 가져옴
val configuration = LocalConfiguration.current
// 화면의 너비(DP)를 가져옴
val screenWidth = configuration.screenWidthDp.dp
// 목록 상태를 기억하며 코루틴 스코프를 생성
val listState = rememberLazyGridState()
val coroutineScope = rememberCoroutineScope()
// 로딩 상태를 기억
var isLoading by remember { mutableStateOf(false) }
// 사진 API 서비스 객체를 생성
val pictureApiService = PictureApiService()
// 현재 사용자 정보를 가져옴
val currentUser = FirebaseAuth.getInstance().currentUser
// 게시물 목록 상태를 기억
var posts by remember { mutableStateOf(emptyList<PictureResponse>()) }
// 새로 고침 상태를 기억
var isRefreshing = remember { mutableStateOf(false) }
// 게시물을 가져오는 코루틴 함수
val fetchPosts = suspend {
try {
isLoading = true
val response = if (currentUser != null) {
val token = currentUser.getIdToken(false).result?.token.toString()
val uid = currentUser.uid
// 사용자Images(token, uid)
"Weekly" -> pictureApiService.getUserWeeklyImages(token, uid)
"Monthly" -> pictureApiService.getUserMonthlyImages(token, uid)
else -> pictureApiService.getUserTagImages(token, tag, uid)
}
} else {
when (tag) {
"Today's Pick" -> pictureApiService.getTodayImages()
"Weekly" -> pictureApiService.getNoUserWeeklyImages()
"Monthly" -> pictureApiService.getNoUserMonthlyImages()
else -> pictureApiService.getNoUserTagImages(tag)
}
}
posts = response
} catch (e: Exception) {
e.printStackTrace()
posts = emptyList()
} finally {
isLoading = false
}
}
// 태그 값이 변경될 때마다 게시물 목록을 가져옴
LaunchedEffect(tag) {
coroutineScope.launch {
fetchPosts()
}
}
// 스와이프 새로고침 컴포넌트의 상태를 기억
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing.value)
SwipeRefresh 로그인 여부에 따라 태그에 맞는 이미지 목록을 가져옴
when (tag) {
"Today's Pick" -> pictureApiService.getUser state = swipeRefreshState,
onRefresh = {
// 새로고침 이벤트 발생 시 게시물을 다시 가져옴
coroutineScope.launch {
isRefreshing.value = true
fetchPosts()
isRefreshing.value = false
}
},
indicator = { state, trigger ->
SwipeRefreshIndicator(
state = state,
refreshTriggerDistance = trigger,
contentColor = MaterialTheme.colors.primary
)
}
) {
// 로딩 중일 때 로딩 인디케이터를 표시하고, 아닐 경우 게시물 목록을 표시
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
} else {
LazyVerticalGrid(
modifier = Modifier,
columns = GridCells.Adaptive(minSize = screenWidth / 2 - 4.dp),
state = listState,
contentPadding = PaddingValues(2.dp),
) {
itemsIndexed(posts) { index, post ->
// 각 게시물에 대해 PostListItem 컴포넌트를 생성
PostListItem(post = post, context = context, screenWidth = screenWidth)
}
}
}
}
}
https://stackoverflow.com/questions/66474049/using-remembercoroutinescope-vs-launchedeffect
https://developer.android.com/jetpack/compose/side-effects?hl=ko