Retrofit์ผ๋ก ์๋ฒ์์ ํ ์ผ ๊ฐ์ ธ์ค๊ธฐ
์ง๋ ๋ ๊ธ์์ Room๊ณผ DataStore๋ก ๋ก์ปฌ์ ์ ์ฅํ๋ ๊ฑธ ๋ดค๋ค. ์ด๋ฒ์ ๋ฐ๋๋ก, ์๋ฒ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์๋ณด์. ํ
์คํธ์ฉ ๊ณต๊ฐ API์ธ JSONPlaceholder์ /todos๋ฅผ ์ด๋ค. ๊ณต๊ต๋กญ๊ฒ๋ ์ฐ๋ฆฌ ์ฑ์ด๋ ๋ชจ์์ด ๋๊ฐ์ ํ ์ผ ๋ชฉ๋ก์ ๋๋ ค์ค๋ค. ์ด๊ฑธ ๋ฐ์์์ "์ถ์ฒ ํ ์ผ ๊ฐ์ ธ์ค๊ธฐ" ๋ฒํผ์ผ๋ก ๋ด ๋ชฉ๋ก์ ์ถ๊ฐํด๋ณด์.
์์กด์ฑ ์ถ๊ฐ
// app/build.gradle.kts
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
}์๋ฒ๊ฐ ์ฃผ๋ ๋ฐ์ดํฐ ๋ชจ์ ์ ์ํ๊ธฐ
data class RemoteTodo(
val id: Int,
val title: String,
val completed: Boolean
)์ฃผ์ํ ์ ์ด ์๋ค. ์ด RemoteTodo๋ ์ฐ๋ฆฌ Room์ Todo ์ํฐํฐ๋ ๋น์ทํ๊ฒ ์๊ฒผ์ง๋ง ๊ฐ์ ํด๋์ค๊ฐ ์๋๋ค. ์๋ฒ๊ฐ ์ฃผ๋ JSON ๋ชจ์๊ณผ ์ฐ๋ฆฌ DB ํ
์ด๋ธ ๋ชจ์์ ์ฐ์ฐํ ๋ฎ์์ ๋ฟ, ์๋ฒ ์ชฝ ํ๋๊ฐ ๋ฐ๋์ด๋ ์ฐ๋ฆฌ DB ๊ตฌ์กฐ๋ ์ํฅ๋ฐ์ง ์์์ผ ํ๋ค. ๊ทธ๋์ ๋ฐ๋ก ๋๋ค.
API ์ธํฐํ์ด์ค ์ ์
import retrofit2.http.GET
interface TodoApiService {
@GET("todos")
suspend fun getTodos(): List<RemoteTodo>
}ํจ์ ๋ณธ๋ฌธ์ด ์๋ค. @GET("todos")๋ง ๋ถ์ด๋ฉด, Retrofit์ด ์ด ํจ์๊ฐ ํธ์ถ๋ ๋ ์ค์ HTTP GET ์์ฒญ์ ๋ณด๋ด๊ณ , ๋์์จ JSON์ List<RemoteTodo>๋ก ๋ณํํด์ ๋๋ ค์ฃผ๋ ์ฝ๋๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋ค. suspend๋ก ์ ์ธํ์ผ๋, ์ด ํจ์๋ ์ฝ๋ฃจํด ์์์๋ง ํธ์ถํ ์ ์๊ณ , ๋คํธ์ํฌ ์๋ต์ด ์ค๊ธฐ ์ ๊น์ง ํธ์ถํ ์ชฝ์ ๊ธฐ๋ค๋ฆฌ๊ฒ(๋ธ๋กํน ์์ด) ๋ง๋ ๋ค.
Retrofit ์ธ์คํด์ค ๋ง๋ค๊ธฐ
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val todoApiService = retrofit.create(TodoApiService::class.java)baseUrl์ ๊ณตํต ์ฃผ์๋ฅผ ์ ์ด๋๋ฉด, @GET("todos")๋ ๊ทธ ๋ค์ "todos"๋ง ๋ถ์ฌ์ ์์ฒญํ๋ค. GsonConverterFactory๋ ์๋ฒ๊ฐ ๋ณด๋ธ JSON ๋ฌธ์์ด์ RemoteTodo ๊ฐ์ฒด๋ก(๋๋ ๋ฐ๋๋ก) ๋ฐ๊ฟ์ฃผ๋ ์ญํ ์ด๋ค.
ViewModel์์ ํธ์ถํ๊ธฐ - ๋ก๋ฉ/์๋ฌ ์ํ ์ถ๊ฐ
data class TodoUiState(
val todoList: List<Todo> = emptyList(),
val isLoading: Boolean = false,
val errorMessage: String? = null
)fun importFromServer() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, errorMessage = null)
try {
val remoteTodos = todoApiService.getTodos().take(5)
remoteTodos.forEach { remote ->
dao.insert(Todo(title = remote.title, isDone = remote.completed))
}
_uiState.value = _uiState.value.copy(isLoading = false)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
isLoading = false,
errorMessage = "ํ ์ผ์ ๊ฐ์ ธ์ค์ง ๋ชปํ๋ค: ${e.message}"
)
}
}
}[์ด๋ฏธ์ง: "์ถ์ฒ ํ ์ผ ๊ฐ์ ธ์ค๊ธฐ" ๋ฒํผ์ ๋๋ฅธ ๋ค ๋ก๋ฉ ํ์๊ฐ ๋จ๊ณ , ์ ์ ๋ค ์๋ฒ์์ ๋ฐ์ ํ ์ผ 5๊ฐ๊ฐ ๋ชฉ๋ก์ ์ถ๊ฐ๋ ํ๋ฉด]
๋คํธ์ํฌ ์์ฒญ์ ์คํจํ ์ ์๋ค. ์์ดํ์ด๊ฐ ๋๊ฒผ๊ฑฐ๋, ์๋ฒ๊ฐ ์๋ต์ ์ ํ๊ฑฐ๋, ํ์์์์ด ๋ ์๋ ์๋ค. ๊ทธ๋์ try/catch๋ก ๊ฐ์ธ๊ณ , ์คํจํ๋ฉด errorMessage์ ์๋ฌ๋ฅผ ๋ด์์ UiState์ ๋ฐ์ํ๋ค. isLoading์ ์์ฒญ์ ์์ํ ๋ true๋ก, ์ฑ๊ณตํ๋ ์คํจํ๋ ๋๋๋ฉด false๋ก ๋๋๋ฆฐ๋ค.
ํ๋ฉด์์๋ ์ํ๋ฅผ ๋ณด๊ณ ๋ค๋ฅด๊ฒ ๊ทธ๋ฆฐ๋ค
if (uiState.isLoading) {
CircularProgressIndicator()
}
uiState.errorMessage?.let { message ->
Text(text = message, color = MaterialTheme.colorScheme.error)
}
Button(onClick = { viewModel.importFromServer() }) {
Text("์ถ์ฒ ํ ์ผ ๊ฐ์ ธ์ค๊ธฐ")
}UiState ์์ isLoading, errorMessage๋ฅผ ๋ฃ์ด๋๋ฉด, ํ๋ฉด์ "์ง๊ธ ๋ก๋ฉ ์ค์ธ๊ฐ", "์๋ฌ๊ฐ ์๋"๋ฅผ ๊ทธ๋ฅ ๊ฐ์ผ๋ก ์ฝ๊ณ ๊ทธ์ ๋ง๊ฒ ๊ทธ๋ฆฌ๊ธฐ๋ง ํ๋ฉด ๋๋ค. ๋ก๋ฉ๋ฐ๋ฅผ ์ธ์ ๋์ฐ๊ณ ์ง์ธ์ง๋ฅผ ํ๋ฉด์ด ์ง์ ์ ๊ฒฝ ์ฐ์ง ์๋๋ค.
์ ๋ฆฌ
@GET๊ฐ์ ์ ๋ ธํ ์ด์ ๋ง ๋ถ์ธ ์ธํฐํ์ด์ค๋ฅผ ์ ์ธํ๋ฉด,Retrofit์ด ์ค์ ๋คํธ์ํฌ ํธ์ถ ์ฝ๋๋ฅผ ๋์ ๋ง๋ค์ด์ค๋ค- ์๋ฒ ๋ฐ์ดํฐ ๋ชจ์(
RemoteTodo)๊ณผ ๋ด DB ์ํฐํฐ(Todo)๋ ๋ฎ์์ด๋ ๊ฐ์ ํด๋์ค๋ก ๋์ง ์๋๋ค - ๋คํธ์ํฌ ์์ฒญ์ ๋ก๋ฉ/์คํจ๋
UiState์ ํ๋(isLoading,errorMessage)๋ก ํํํ๋ฉด, ํ๋ฉด์ ๊ทธ ๊ฐ๋ง ๋ณด๊ณ ๊ทธ๋ฆฌ๋ฉด ๋๋ค
๋ค์ ๊ธ์์๋ ์ง๊ธ TodoViewModel ์์ ์ง์ ๋ง๋ค์ด๋ Room, Retrofit, DataStore ์ธ์คํด์ค๋ค์ Hilt๋ก ์ฃผ์
๋ฐ๋ ๊ตฌ์กฐ๋ก ์ ๋ฆฌํด๋ณด๊ฒ ๋ค.
