안드로이드 개발환경에서 RestApi의 get방식으로 데이터를받아야할때가 있습니다. 이러한경우 대부분 Rerofit을 이용하여 웹서버에 데이터를 받아옵니다. 이제 여기서 개발자는 선택을 할수있습니다. Call 클래스를 이용하여 콜백으로 처리할것인지 Response클래스를 이용하여 데이터를 처리할것인지 Rx의 Observable를 이용하여 처리할것인지, 아니면 코루틴을 사용할것인지등 현재프로젝트의 규모나 개발환경에따라 여러방식으로 구현할수 있습니다. 그렇다면 각 구현방법의 장점과 단전을 알아보며 어떨때 해당방법을 처리해야될 알아가보도록 합시다.
interface ApiService {
@GET("users/{username}")
fun getUser(@Path("username") username: String): Call<User>
}
data class User(
val username: String,
val email: String
)
object RetrofitClient {
private const val BASE_URL = "your_base_url"
val apiService: ApiService by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(ApiService::class.java)
}
}
RetrofitClient.apiService.getUser(userName)
.enqueue(object : Callback<Void>{
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful){
//...
} else {
//...
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
Log.e(TAG, t.toString())
}
})
Call을 enqueue메서드를 이용하여 비동기적으로 실행하고 Callback을 집어넣어 결과에따른 처리를 수행할수 있음
data class User(val id: Int, val username: String, val email: String) // 예시 사용자 모델
interface ApiService {
@GET("users/{username}")
suspend fun getUser(@Path("username") username: String): Response<User>
}
object RetrofitClient {
private const val BASE_URL = "your_base_url"
val apiService: ApiService by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(ApiService::class.java)
}
}
suspend fun fetchUser(username: String): Response<User> {
return try {
RetrofitClient.apiService.getUser(username)
} catch (e: Exception) {
// 예외 처리
Response.error("Unknown error", null)
}
}
withContext(Dispatchers.IO) {
val response = fetchUser(username)
if (response.isSuccessful) {
val user = response.body()
// 받아온 사용자 데이터 처리
} else {
// 서버로부터 오류 응답을 받았을 때 처리
// response.errorBody()를 이용하여 오류 응답의 내용을 확인할 수 있습니다.
}
}
Response클래스의 경우 중단함수를 이용합니다. 중단함수를 이용한다는것은 코루틴을 사용한다는 말이기도 합니다. suspend를 사용하기에 콜백처리를 하지않고도 해당 결과만 가져올수있어 간편하다는 장점이 있습니다.
이러한 단점들은 sealed 클래스를 이용하여 에러를 관리하거나 비동기처리의 경우 데이터를 바로 받아 사용하는것이 아닌 LiveData나 Flow같은 관찰가능한 객채를 이용하여 단점을 보완할수 있습니다.
data class User(val id: Int, val username: String, val email: String) // 예시 사용자 모델
interface ApiService {
@GET("users/{username}")
fun getUser(@Path("username") username: String): Observable<User>
}
object RetrofitClient {
private const val BASE_URL = "your_base_url"
val apiService: ApiService by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(ApiService::class.java)
}
}
fun fetchUser(username: String): Disposable {
return RetrofitClient.apiService.getUser(username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user ->
// 성공적으로 데이터를 받아왔을 때 처리
// user 객체를 사용하여 UI 업데이트 등을 수행
}, { error ->
// 오류가 발생했을 때 처리
// error 객체를 사용하여 오류 메시지를 표시하거나 기타 작업 수행
})
}
class MainActivity : AppCompatActivity() {
private var disposable: Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val username = "example_username"
disposable = fetchUser(username)
}
override fun onDestroy() {
super.onDestroy()
disposable?.dispose() // 메모리 누수를 방지하기 위해 Disposable을 해제합니다.
}
}
Observable을 사용하여 Retrofit을 통해 데이터를 가져오고, 이를 비동기적으로 처리할 수 있습니다. 코드를 통해 각각의 스레드에서 작업이 수행되고, 마지막에는 메인 스레드로 결과가 전달되어 UI를 업데이트할 수 있음
data class User(val id: Int, val username: String, val email: String) // 예시 사용자 모델
interface ApiService {
@GET("users/{username}")
suspend fun getUser(@Path("username") username: String): User
}
object RetrofitClient {
private const val BASE_URL = "your_base_url"
val apiService: ApiService by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(ApiService::class.java)
}
}
suspend fun fetchUser(username: String): User? {
return withContext(Dispatchers.IO) {
try {
RetrofitClient.apiService.getUser(username)
} catch (e: IOException) {
// 네트워크 오류 처리
null
}
}
}
withContext(Dispatchers.IO) {
val user = fetchUser(username)
if (user != null) {
// 사용자 데이터를 성공적으로 받아왔을 때 처리
// user 객체를 사용하여 UI 업데이트 등을 수행
} else {
// 네트워크 오류 등으로 사용자 데이터를 받아오지 못했을 때 처리
}
}
suspend Response와 비슷한 방법 Response로 감싸지지 않았기 때문에 좀더 편히 사용할수 있지만 Response의 경우 상태코드를 확인할수있는등의 기능은 실패할경우 null로밖에 못받기 때문에 단점이라할수 있음
작은 프로젝트에서는 Call 클래스나 Response 클래스를 사용하여 간단하게 처리할 수 있고, 대규모 프로젝트에서는 RxJava나 코루틴을 사용하여 복잡한 비동기 작업을 효과적으로 처리할 수 있습니다.
reference
https://velog.io/@cksgodl/AndroidKotlin-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B2%B0%EA%B3%BC%EA%B0%92%EC%9D%84-Flow%EB%B3%80%ED%99%98-%EB%B0%8F-Sealed-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0
https://onlyfor-me-blog.tistory.com/478
https://algosketch.tistory.com/145
https://algosketch.tistory.com/176