반응형

 

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 프로젝트

https://github.com/Daseul727/Mobile-Skill-Up

728x90

+ Recent posts