- HTTP
- 4XX
- 5XX
- ๋คํธ์ํฌ ์ค๋ฅ
- ๋ถ์ ์ ํ ์์ธ์ฒ๋ฆฌ
- ์๋ฒ ์๋ต ์ฝ๋๋ฅผ ViewModel์ ์ ํ
- ๊ด์ฌ์ฌ ๋ถ๋ฆฌ
- ๋น์ฆ๋์ค ๋ก์ง์ด ViewModel๋ก ์ ํ
- Result ๊ฐ์ฒด ํ์ฉ
- Result ๊ฐ์ฒด?
- ์ธ๋ถํ๋ Result ๊ฐ์ฒด
- Repository์์ ์๋ฌ ์ฒ๋ฆฌ
- UI๋ก ์ ํ
- ์ด๋ฐ ๊ณผ์ ์ด ํ์ํ ์ด์ ๋?
- ๋ถ๋ฆฌ๋ ์๋ฌ ์ฒ๋ฆฌ
- ํ ์คํธ ์ฉ์ด์ฑ
- ์ฌ์ฉ์ ๊ฒฝํ ํฅ์
- Sandwich?
๐ก๋คํธ์ํฌ ์์ฒญ ์ค์ ๋ฐ์ํ ์ ์๋ ์๋ฌ์ ๋ํด ์์๋ณด๊ณ , ํด๊ฒฐ ๋ฐฉ์์ ๊ธฐ๋กํ์์ต๋๋ค.
HTTP
- Android์์ HTTP ์๋ฌ๋ ์ฃผ๋ก ๋คํธ์ํฌ ์์ฒญ ์ค์ ๋ฐ์ํ๋ฉฐ, ์ด๋ ์๋ฒ ๊ฐ์ ํต์ ์ด ์คํจํ๊ฑฐ๋ ์๋ฒ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ์ง ๋ชปํ ๋ ๋ํ๋ ๋๋ค.
- ์ด๋ HTTP ์ํ ์ฝ๋, ๋คํธ์ํฌ ์ฐ๊ฒฐ ๋ฌธ์ , ์๋ฒ์ ์๋ต ์ฒ๋ฆฌ ์ค๋ฅ ๋ฑ์ ์ด์ ๊ฐ ์์ ์ ์์ต๋๋ค.
- ์๋์ ๊ฐ์ ์ผ๋ฐ์ ์ธ ์ค๋ฅ๋ก ๋ถ๋ฅํ ์ ์๊ณ , JSON ํ์ฑ์ด๋ SSL ์ธ์ฆ์ ์ค๋ฅ๋ ๋ฐ์ํ ์ ์์ต๋๋ค.
4XX
- HTTP ์ํ ์ฝ๋ 4XX๋ฅผ ๊ฐ์ง๋ฉด ํด๋ผ์ด์ธํธ ์ค๋ฅ๋ก ๋ถ๋ฅํฉ๋๋ค.
- 400 : Bad Resquest
- 401 : Unauthorized
- 403 : Forbidden
- 404 : Not Found
5XX
- HTTP ์ํ ์ฝ๋ 5XX๋ฅผ ๊ฐ์ง๋ฉด ์๋ฒ ์ค๋ฅ๋ก ๋ถ๋ฅํฉ๋๋ค.
- 500 : Internal Server Error
- 502 : Bad Getway
- 503 : Service Unabilable
- 504 : Getway Timeout
๋คํธ์ํฌ ์ค๋ฅ
- ๋คํธ์ํฌ ์ฐ๊ฒฐ์ด ๋ถ์์ ํ๊ฑฐ๋, Wi-Fi ๋๋ ๋ชจ๋ฐ์ผ ๋ฐ์ดํฐ๊ฐ ๋๊ฒผ์ ๋ ๋คํธ์ํฌ ์ฐ๊ฒฐ ์คํจ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
- ์๋ฒ๊ฐ ์ผ์ ์๊ฐ ๋ด์ ์๋ตํ์ง ์์ผ๋ฉด ํ์ ์์์ด ๋ฐ์ํฉ๋๋ค.
๋ถ์ ์ ํ ์์ธ์ฒ๋ฆฌ
- ์๋๋ก์ด๋์์๋ HTTP ์์ฒญ์ ํ ๋ ์ฃผ๋ก Retrofit์ด๋ OKHttp์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด HTTP ์์ฒญ ๋ฐ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- view์ ๋ฐ์ดํฐ๋ฅผ ์ฐ๊ฒฐํ๋ ViewModel์์ ์๋์ ๊ฐ์ด ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์งํํ ์ ์์ต๋๋ค.
// Retrofit API Interface
interface ApiService {
@GET("example/data")
suspend fun getData(): Response<Data>
}
// ViewModel
class MyViewModel(private val apiService: ApiService) : ViewModel() {
fun fetchData() {
viewModelScope.launch {
try {
val response = apiService.getData()
if (response.isSuccessful) {
// ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๊ฒฝ์ฐ
val data = response.body()
// ์ฒ๋ฆฌ ๋ก์ง
} else {
// HTTP ์๋ฌ ์ฒ๋ฆฌ
handleHttpError(response.code())
}
} catch (e: Exception) {
// ๋คํธ์ํฌ ์ค๋ฅ ๋๋ ๊ธฐํ ์์ธ ์ฒ๋ฆฌ
handleNetworkError(e)
}
}
}
private fun handleHttpError(code: Int) {
when (code) {
400 -> Log.e("Error", "Bad Request")
401 -> Log.e("Error", "Unauthorized")
404 -> Log.e("Error", "Not Found")
500 -> Log.e("Error", "Internal Server Error")
else -> Log.e("Error", "Unknown HTTP Error: $code")
}
}
private fun handleNetworkError(e: Exception) {
if (e is SocketTimeoutException) {
Log.e("Error", "Request Timeout")
} else if (e is UnknownHostException) {
Log.e("Error", "No Internet Connection")
} else {
Log.e("Error", "Unknown Network Error: ${e.message}")
}
}
}
- ์ด๋ ์ํ ์ฝ๋์ ๋คํธ์ํฌ, ํ์์์์ ๋ํ ์๋ฌ๋ฅผ ๋ถ๊ธฐ์ฒ๋ฆฌํ ๋ชจ์ต์ด์ง๋ง, ์๋์ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์๋ฒ ์๋ต ์ฝ๋๋ฅผ ViewModel์ ์ ํ
์ ์ฝ๋๋ ์๋ฒ ์๋ต ์ฝ๋๋ฅผ ViewModel์์ ์๊ณ ์์ต๋๋ค. ViewModel์ ์ํคํ ์ฒ์์ UI์ ๋ฐ์ดํฐ ์ฌ์ด์ ์ค์ฌ์ ์ญํ ์ ์ํํฉ๋๋ค. ๋ฐ๋ผ์ ViewModel์ด ์๋ฒ ์๋ต ์ฝ๋๋ ๋ฐ์ดํฐ ์ถ์ฒ์ ๋ํด ์๋ ๊ฒ์ ๋ฐ๋์งํ์ง ์์ผ๋ฉฐ, ViewModel์ ๋ฐ์ดํฐ๊ฐ ์ด๋์ ์ค๋์ง ์๊ด์์ด UI๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ญํ ์ ์ง์คํด์ผ ํฉ๋๋ค.
๊ด์ฌ์ฌ ๋ถ๋ฆฌ
- ViewModel์ UI์ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ๋ฐ์ดํฐ๊ฐ UI์ ์ด๋ป๊ฒ ํ์๋ ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ญํ ์ ํด์ผ ํฉ๋๋ค.
- ์๋ฒ, ๋ก์ปฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฑ์ ๋ฐ์ดํฐ ์ถ์ฒ์ ์๋ต์ฝ๋๋ Repository๋ DataSource ๊ณ์ธต์์ ๊ด๋ฆฌํด์ผ ํ๋ฉฐ, ViewModel์ ๋ฐ์ดํฐ์ ์ถ์ฒ์ ๋ฌด๊ดํ๊ฒ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณต๋ฐ์์ผ ํฉ๋๋ค.
๋น์ฆ๋์ค ๋ก์ง์ด ViewModel๋ก ์ ํ
๋ง์ฝ ViewModel์ด ์๋ฒ ์๋ต ์ฝ๋๋ฅผ ์๊ฒ ๋๋ค๋ฉด, ์ด๋ ๋น์ฆ๋์ค ๋ก์ง์ด ViewModel์ ํฌํจ๋์ด ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ๊ฐ ๋ฌด๋์ง ์ ์์ต๋๋ค. ํน์ ๋ฐ์ดํฐ ์ถ์ฒ์ ์ข ์์ ์ด ๋์ด, ํ ์คํธํ๊ธฐ ์ด๋ ต๊ณ ์ฝ๋๊ฐ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
Result ๊ฐ์ฒด ํ์ฉ
Result ๊ฐ์ฒด?
- Result ๊ฐ์ฒด๋ ๋ฐ์ดํฐ์ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ฑ๊ณต๊ณผ ์คํจ๋ก ๋๋์ด ํํํ๋ ํ์คํ๋ ๋ฐ์ดํฐ ๋ํผ์ ๋๋ค.
- ์ด๋ฅผ ํ์ฉํด์ ํจ์๋ ๋ฉ์๋๊ฐ ์ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ์ ์คํจ ์ํฉ์ ๋ช ํํ๊ฒ ๊ตฌ๋ถํ๊ณ , ์ด๋ฅผ ํ๋์ ๋ฐํ ๊ฐ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
- Kotlin์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก Kotlin.Result๋ฅผ ์ ๊ณตํ์ง๋ง, ์ด๋ฅผ ์ปค์คํ ํ์ฌ ๋์ฑ ์ ์ฐํ๊ฒ ํ์ฉํ ์ ์์ต๋๋ค.
์ธ๋ถํ๋ Result ๊ฐ์ฒด
- Result ๊ฐ์ฒด๋ฅผ ์๋ฌ ์ฒ๋ฆฌ์ ์ ํฉํ๊ฒ ํ์ฉํ๊ธฐ ์ํด์ ์ธ๋ถํํ ์ ์์ต๋๋ค.
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class HttpError(val statusCode: Int) : Result<Nothing>() // HTTP ์ํ ์ฝ๋ ์๋ฌ
data class NetworkError(val exception: Throwable) : Result<Nothing>() // ๋คํธ์ํฌ ์๋ฌ (ํ์์์, ์ฐ๊ฒฐ ์คํจ ๋ฑ)
}
- ์ด๋ฅผ ํ์ฉํ๋ฉด HTTP ์ํ ์ฝ๋, ๋คํธ์ํฌ ์ค๋ฅ, ํ์์์์ ๋ง๊ฒ ์ธ๋ถํํ ์ ์์ผ๋ฉฐ, ๊ตฌ์ฒด์ ์ธ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
Repository์์ ์๋ฌ ์ฒ๋ฆฌ
- Repository์์ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ์งํํ์ฌ ๋น์ฆ๋์ค ๋ก์ง์ด ViewModel๋ก ์ ํ๋๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
- ViewModel๋ก Result๋ก ๊ฐ์ธ์ง ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ์ฌ ViewModel์์๋ ์๋ฌ ์ฝ๋๋ฅผ ์ ์ ์๊ฒ ํ ์ ์์ต๋๋ค.
class DataRepository(private val apiService: ApiService) {
suspend fun getData(): Result<List<String>> {
return try {
val response = apiService.getData() // ์๋ฒ์ ๋ฐ์ดํฐ ์์ฒญ
if (response.isSuccessful) {
Result.Success(response.body() ?: emptyList())
} else {
Result.HttpError(response.code()) // HTTP ์ํ ์ฝ๋ ์๋ฌ ์ฒ๋ฆฌ
}
} catch (e: IOException) {
// ๋คํธ์ํฌ ์์ธ ์ฒ๋ฆฌ (๋คํธ์ํฌ ์ฐ๊ฒฐ ์คํจ, ํ์์์ ๋ฑ)
Result.NetworkError(e)
}
}
}
UI๋ก ์ ํ
- ๊ฐ์ธ์ง Result๊ฐ์ฒด๋ฅผ ๋ถ์ํ์ฌ ์ฌ์ฉ์์๊ฒ ๋ค๋ฅธ UI๋ฅผ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
// ViewModel์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ Result์ ๋ฐ๋ผ ๋ถ๊ธฐ ์ฒ๋ฆฌ
class MyViewModel(private val repository: DataRepository) : ViewModel() {
private val _uiState = MutableLiveData<Result<List<String>>>()
val uiState: LiveData<Result<List<String>>> get() = _uiState
fun loadData() {
viewModelScope.launch {
_uiState.value = Result.Loading
val result = repository.getData()
_uiState.value = result // ๋คํธ์ํฌ ์๋ฌ, ์ํ ์ฝ๋ ๋ฑ์ ์ฒ๋ฆฌํ Result ๋ฐํ
}
}
}
// UI์์ Result ๊ฐ์ฒด์ ์ํ์ ๋ฐ๋ผ UI ๊ฐฑ์
@Composable
fun MyScreen(viewModel: MyViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.observeAsState(Result.Loading)
when (uiState) {
is Result.Success -> ShowData((uiState as Result.Success).data) // ์ฑ๊ณต ์ ๋ฐ์ดํฐ ๋ ๋๋ง
is Result.HttpError -> ShowError("HTTP Error: ${(uiState as Result.HttpError).statusCode}") // HTTP ์ํ ์ฝ๋ ์ฒ๋ฆฌ
is Result.NetworkError -> ShowError("Network Error: ${(uiState as Result.NetworkError).exception.message}") // ๋คํธ์ํฌ ์๋ฌ ์ฒ๋ฆฌ
}
}
์ด๋ฐ ๊ณผ์ ์ด ํ์ํ ์ด์ ๋?
๋ถ๋ฆฌ๋ ์๋ฌ ์ฒ๋ฆฌ
- ViewModel์ ๋จ์ํ UI ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ธ๋ถ์ ์ธ ๋คํธ์ํฌ ๋ฐ HTTP ์ํ ์ฝ๋์ ๋ํ ์ฒ๋ฆฌ๋ Repository๋ก ๋ถ๋ฆฌ๋ฉ๋๋ค.
- ์ด๋ ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ํ๊ณ , ์ฝ๋์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๋ฅผ ์ ์งํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
ํ ์คํธ ์ฉ์ด์ฑ
- ๊ฐ ์ํ๋ฅผ ๋ถ๋ฆฌํจ์ผ๋ก์จ ๊ฐ๋ณ์ ์ธ ํ ์คํธ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ์ฑ๊ณต ์๋ต, HTTP ์ํ ์๋ฌ, ๋คํธ์ํฌ ์๋ฌ๋ฅผ ๊ฐ๊ฐ ํ ์คํธํ ์ ์์ต๋๋ค.
์ฌ์ฉ์ ๊ฒฝํ ํฅ์
- ๋ค์ํ ์๋ฌ ์ํฉ์ ๋ฐ๋ผ ์ ์ ํ ๋ฉ์์ง๋ UI ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
- ์ด๋ ์ฌ์ฉ์๊ฐ ์์์น ๋ชปํ ์ํฉ์ ๋ฐฉ์งํ๋ฉฐ, ์น์ ํ ์์ธ ์ ๋ฌ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค.
Sandwich?
- ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ํคํ ์ฒ์์ API ์ฒ๋ฆฌ๋ฅผ ์งํํ ๋, ๋งค๋ฒ ์์ธ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋ง์ ์๊ฐ์ ์๋ชจํ ์ ์์ต๋๋ค.
- Sandwich๋ Multi-Layred ์ํคํ ์ฒ์์ ๋ฐ์ดํฐ ํ๋ฆ๊ณผ Retrofit ์๋ต ๋ฐ ์๋ฌ๋ฅผ ๊ฐ๊ฒฐํ๊ณ ๋ช ์์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.