package com.maxi.dogapi.utils
class Constants {
companion object {
const val BASE_URL = "https://dog.ceo/"
const val RANDOM_URL = "api/breeds/image/random"
}
}
companion object를 쓰면 Constants 인스턴스를 생성하지않고도 BASE_URL 에 접근할수있다
2. 통신연결
위에서 설정한 RANDOM_URL과 get방식 통신을 할건데, 결과값을 받기위한 Response 클래스를 만들어주자
1) Response class 생성
package com.example.network_sample.model
import com.google.gson.annotations.SerializedName
data class DogResponse(
@SerializedName("message")
val message: String,
@SerializedName("status")
val status: String
)
data class 를 사용하면 클래스의 기본적인 메소드들(toString(), equals(), hashCode(), copy())을 자동으로 생성해준다.
만약 @GET import 안되면 retrofit build.gradle 설정을 확인해보자.
위에서 RANDOM_URL 을 만든이유는, 저런식으로 상수화 시켜서 사용하기 위함이었다.
실제로 통신할때는 해당 api를 바로 DI 시켜서 쓰지않는다. 별도 ServiceImpl 같이 하나 만들어서 주입해주자
3) RemoteDataSource 생성
class RemoteDataSource @Inject constructor(private val dogService: APIService) {
suspend fun getDog() =
dogService.getDog()
}
@Inject?
이전 포스팅에서는 @Inject 로 DI 를 할수있다고 했는데 @Inject 방법에는 두가지가 있다.
방법1. 필드주입
이전 포스팅과 같이 class 내부에 @Inject 하는방법이며 코드가 간결하게보일 수 있지만 외부 의존성에 대한 테스트가 어려울수 있다고 한다
방법2. 생성자주입
클래스 의존성을 외부로부터 완벽히 분리하고 의존성주입을 명시적으로 만든다.
왠만하면 생상자주입이 권장되는편인가보다.
constructor ?
클래스 생성자 선언하는 방법중에 하나인데, 파라미터 선언없이도 바로 생성이 가능하게 만들어준다
// 클래스의 주 생성자를 선언하는 방법
class MyClass constructor(param1: Int, param2: String) {
// 클래스 본문
}
// 클래스의 주 생성자를 선언하지 않고 속성과 함께 선언하는 방법
class MyClass(param1: Int, param2: String) {
// 주 생성자 파라미터로 선언된 속성들
val property1: Int = param1
val property2: String = param2
}
suspend?
코틀린에서 코루틴(coroutine)을 사용하는 함수를 정의할 때 사용되는 키워드 네트워크 요청이나 파일 I/O 등의 비동기적인 작업을 수행할 수 있고 위 코드를 보면 getDog() 함수는 일시 중단되어 네트워크 응답을 기다린 후 결과를 반환한다.
코루틴은 Android에서 비동기적인 작업을 수행할 때 매우 유용하며, UI 스레드를 차단하지 않고 작업을 수행할 수 있도록 해준다고 한다.
-> 결과적으론 RemoteDataSource 에서 API 를 비동기연결하였다.
3. Model 과 API 연결
이제 실제로 MainViewModel 에서 직접 연결할 repository 를 만들어보자.
model ---- RemoteDataSource 를 연결하는 공간이라고 보면 된다.
model에게 api 통신결과를 보내줄건데, success/fail 에 따라 어떻게 보내줄건지도 정의해야할것이다.
repository 를 생성하기에 앞서, BaseApiResponse를 만들어보자
1) BaseApiResponse 생성
package com.maxi.dogapi.model
import com.maxi.dogapi.utils.NetworkResult
import retrofit2.Response
abstract class BaseApiResponse {
suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): NetworkResult<T> {
try {
val response = apiCall()
if (response.isSuccessful) {
val body = response.body()
body?.let {
return NetworkResult.Success(body)
}
}
return error("${response.code()} ${response.message()}")
} catch (e: Exception) {
return error(e.message ?: e.toString())
}
}
private fun <T> error(errorMessage: String): NetworkResult<T> =
NetworkResult.Error("Api call failed $errorMessage")
}
이런식으로 api 통신결과를 어떻게 처리할지 간단히 정의해하였고 result 를 보내기위해 NetworkResult 도 아래와 같이 만들어주었다
package com.example.network_sample.utils
sealed class NetworkResult<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T) : NetworkResult<T>(data)
class Error<T>(message: String, data: T? = null) : NetworkResult<T>(data, message)
class Loading<T> : NetworkResult<T>()
}
empty activity 를 추가해주자. 파일명은 EntryActivity 이며 activity_entry.xml 은 건들지말고 EntryActivity.kt 파일만 수정한다
class EntryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
goPageIntro()
}
fun goPageIntro() {
Handler(Looper.getMainLooper()).postDelayed({
val intent = Intent(this, IntroActivity::class.java)
startActivity(intent)
finish()
},1500) //여기숫자로 splash 화면 초 설정 가능
}
}
해당 액티비티에 들어오면바로 IntroActivity 로 이동하게 하는 로직을 추가했다.
5. AndroidManifest 수정
이렇게 만든 EntryActivity 가 앱 첫실행 시 로드되도록 AndroidManifest를 수정해보자