1. Network 통신설정
Network 통신하여 강아지 이미지를 받아보겠음!
1) Manifest network 권한추가
http 통신을 하기 위하여 Retrofit 이라는 라이브러리를 사용할것이다.
해당 라이브러리를 사용하기에 앞서 아래의 퍼미션을 추가하자
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
//추가
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".NetworkApplication"
...
2) build.gradle 설정 추가
Retrofit 관련 설정을 build.gradle(app)에 추가한다
/* Retrofit */
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
/*lifecycle*/
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
3) 통신관련 정보 static 보관
통신과 관련된 URL 이나 IP정보는 변하지않기때문에 한곳에서 관리하는것이 좋다.
utils 폴더 하나만들어서, Constants 파일을 만들어주자
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())을 자동으로 생성해준다.
2) API Service 생성
package com.example.network_sample.data.remote
import com.example.network_sample.model.DogResponse
import com.example.network_sample.utils.Constants
import retrofit2.Response
import retrofit2.http.GET
interface APIService {
@GET(Constants.RANDOM_URL)
suspend fun getDog(): Response<DogResponse>
}
만약 @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>()
}
2) Repository 생성
위에서 만든 BaseApiResponse를 사용하여 Repository를 만들어보자
package com.example.network_sample.data
import com.example.network_sample.data.remote.RemoteDataSource
import com.example.network_sample.model.BaseApiResponse
import com.example.network_sample.model.DogResponse
import com.example.network_sample.utils.NetworkResult
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
@ActivityRetainedScoped
class Repository @Inject constructor(
private val remoteDataSource: RemoteDataSource
): BaseApiResponse() {
suspend fun getDog(): Flow<NetworkResult<DogResponse>> {
return flow {
emit(safeApiCall { remoteDataSource.getDog() })
}.flowOn(Dispatchers.IO)
}
}
4) Model - Repository 연결
위에서 만든 repository 를 model에서 불러보자
package com.example.network_sample.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.network_sample.data.Repository
import com.example.network_sample.utils.NetworkResult
import kotlinx.coroutines.launch
import javax.inject.Inject
class MainViewModel @Inject constructor(private val repository: Repository) : ViewModel() {
private val _response : MutableLiveData<NetworkResult<DogResponse>> = MutableLiveData()
val response : LiveData<NetworkResult<DogResponse>> = _response
fun fetchDogResponse() = viewModelScope.launch {
repository.getDog().collect { values ->
_response.value = values
}
}
}
만약 viewModelScope가 안써지진다면, 상단 build.gradle 설정에 lifecycle이 제대로 설정되어잇는지 확인해보자
이렇게하면 fetchDogResponse 를 호출하면 결과값이 잘 호출될것이다
github주소
- showDog 프로젝트
'Mobile > Android' 카테고리의 다른 글
<data> 태그 사용법 (0) | 2024.04.02 |
---|---|
Kotlin - Android Hilt 사용하여 앱 개발하기 (3) - 데이터 바인딩 (0) | 2024.03.29 |
Kotlin - Android Hilt 사용하여 앱 개발하기 (1) - Hilt 추가 (0) | 2024.03.28 |
Kotlin - Android 앱 개발하기 (5) - GNB 영역 추가 (0) | 2024.03.07 |
Kotlin - Android 앱 개발하기 (4) - 타이틀 바 추가 (0) | 2024.03.05 |