Android 인터넷에서 데이터 가져오기 Codelab 정리

바키찬·2022년 9월 10일
0

Android

목록 보기
1/1

이 글은 Google Codelab 인터넷에서 데이터 가져오기
학습하고 정리한 글 입니다.

최근 레트로핏을 사용한지 오래 됐고 문법이 기억나지 않아서
Google Codelab에서 레트로핏을 공부했다. 해당 코드랩

이 글은 공부한 내용을 정리한 글이다.

예제 앱 보기

이 앱은 화성 표면의 이미지르 받아와서 보여주는 앱니다.!

이 앱은 이 프로젝트의 starter 브랜치를 학습하면 된다.

Retrofit 의존성 추가하기

외부 라이브러리를 프로젝트에 추가하기 위해서는 라이브러리가 호스팅 되는,
즉 라이브러리를 받아오는 저장소를 추가해줘야 한다.

build.gradle(Project)에 repositories에 다음과 같은 코드를 추가해준다.

repositories {
   google()
   jcenter()
}

각각 google과 JCenter라는 커뮤니티에서 라이브러리를 받아오는 것이다.

라이브러리를 가져오기 위해서 build.gradle(app)에 dependencies 섹션에 다음과 같은 코드를 추가해준다.

// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"

Sync Now를 눌러준뒤 적용한다.

Retrofit2와 같이 타사의 라이브러리는 Java8 기능을 사용한다.
Android Gradle에는 Java8 기능이 내장되어 있고 이 기능을 사용하기 위해서는 build.gradle(Project)에 다음 코드가 있어야 한다.

android {
  ...

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

  kotlinOptions {
    jvmTarget = '1.8'
  }
}

Retrofit 서비스 만들기

우선 기본적으로 API를 요청할 주소를 상수로 저장해둔다.

private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com"

이 주소를 변수로 저장하지 않고 하드코딩해서 사용해도 된다.
하드코딩을 한다면 보안상으로 좋지 않을뿐 아니라 나중에 주소가 변경된다면 하나씩 수정해야한다는 불편함이 있다.

그 다음 Retrofit의 기능이 담겨져있는 객체를 만들어 준다.

private val retrofit = Retrofit.Builder()

네트워크에서 받아온 값을 String으로 변경해주는 팩토리가 있어야 한다.
팩토리는 네트워크에서 얻은 데이터로 해야할 일을 알려주는 역할을 한다.
String으로 변경하기 위햐서는 ScalarsConverterFactory를 사용해준다.

addConverteFactory()함수를 사용해서 팩토리를 가져와준다.

private val retrofit = Retrofit.Builder()
	.addConverterFactory(ScalarsConverterFactory.create())

이 레드토핏 서비스가 어떤 웹 서비스에 접근해서 데이터를 가져올 것인지를 명시하기 위해서
baseUrl() 함수를 사용해서 기본 URL을 추가해준다.
아까 상수로 저장한 BASE_URL을 사용한다.

private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()

Retrofit이 웹 서버와 통신하는 방법을 정의해줘야 한다.
이말은 BASE_URL의 어떤 경로에 접속할지를 정의하는 것이다.

인터페이스를 만들고 함수를 만들어 준다.

interface MarsApiService {

}

이 인터페이스에 함수를 만들어주고 해당 함수가
어떤 방법으로 어떤 경로에 접속하는 것인지를 정의한다.

네트워크 통신을 하면 시간이 딜레이 되기 때문에 suspend 함수로 만들어준다.

interface MarsApiService {
	@GET(name = "photos")
    suspend fun getPhotos() : String
}

getPhotos() 함수를 GET방식의 photos으로 지정했다.

base url이 https://android-kotlin-fun-mars-server.appspot.com이니
getPhotos() 함수를 호출하면 GET 방식으로 https://android-kotlin-fun-mars-server.appspot.com/photos 경로로 요청을 보내게 된다.

반환값으로 String을 넣어 줬는데 위에서 ScalarsConverterFactory를 사용해서
네트워크 요청 결과를 String으로 만들어 줬기 때문에 이 결과가 반환된다.

Retrofit 객체 만들기

싱글톤 패턴으로 객체 하나를 만들어주기 위해서 object를 만들어 준다.

object MarsApi {
    val retrofitService : MarsApiService by lazy {
       retrofit.create(MarsApiService::class.java)
    }
}

이 object에는 retrofitService 변수가 있고 이 변수를 호출하면 늦은 초기화로
위에서 만들어준 retrofit 객체에 create() 함수를 사용해서 만들어준다.
이때 통신하는 법을 정의해준 인터페이스를 같이 넣어주게 된다.

만들어준 Retrofit을 ViewModel에서 호출하기

지금까지는 Retrofit을 사용하기 위해서 사전 세팅을 해줬다면 이제는
ViewModel에서 Retrofit을 호출해서 사용해준다.


viewModel에 데이터를 불러오는 함수를 만들고 ViewModelScope를 열어준다.

private fun getMarsPhotos() {
    viewModelScope.launch {
    
    }
}

이 ViewModelScope안에서 Retrofit 서비스의 getPhotos() 함수를 사용해준다.

private fun getMarsPhotos() {
    viewModelScope.launch {
    	val listResult = MarsApi.retrofitService.getPhotos()
    }
}

이 코드의 결과로 listResult가 이 링크로 요청한 결과가 String으로 들어오게 된다.

에러및 예외 처리

앱을 실행하면 다음과 같은 에러가 발생한다.

    --------- beginning of crash
22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.example.android.marsphotos, PID: 22803
    java.lang.SecurityException: Permission denied (missing INTERNET permission?)

이 에러는 앱에 인터넷 권한을 허용하지 않아서 발생하는 에러이다.

manifest에서 다음 코드를 추가해줘서 인터넷 권한을 추가해준다.

<uses-permission android:name="android.permission.INTERNET"/>

이 권한을 추가하고 다시 빌드하면 다음과 같이 결과가 잘 나온다.


하지만 인터넷이 꺼져 있는 상황에서 앱을 실행하면 다음과 같은 에러가 발생해서 앱이 꺼진다.

3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.android.marsphotos, PID: 3302
    java.net.SocketTimeoutException: timeout

이런 경우를 위해서 예외처리를 사용한다.
ViewModel에서 try-catch 문을 사용해서 네트워크 요청을 보낸다.

viewModelScope.launch {
   try {
       val listResult = MarsApi.retrofitService.getPhotos()
       _status.value = listResult
   } catch (e: Exception) {
		_status.value = "Failure: ${e.message}"
   }

인터넷이 꺼져 있는 등 네트워크 통신을 할수 없는 상황이라면 앱이 거지는게 아니라
다음과 같은 결과가 나온다.

네트워크 요청 결과를 객체로 만들기

현재 코드의 문제점은 요청 결과 Json을 String으로 변환해서 사용하는 것이다.

String으로 변환하면 데이터를 사용할 수 없으니
Moshi를 사용해서 Json으로 객체로 만들어서 사용한다.


build.gradle(app)에 다음 코드를 추가한다.

// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'

위에서 추가한 레드로핏을 Moshi와 호환되는 라이브러리로 변경해준다.

// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"

이 코드를 삭제하고

// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

이 코드로 변경한다.


현재 Json은 다음과 같은 형식을 가지고 있다.

[
  {
    "id": "424905",
    "img_src": "https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000MR0044631300503690E01_DXXX.jpg"
  },
  {
    "id": "424906",
    "img_src": "https://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
  },
]

Json객체가 id와 img_src의 키로 규칙적으로 반복되고
이 객체들이 Json List에 담겨있다.

Json객체를 Kotlin의 data class로 만들어 준다.
그리고 Moshi를 사용해서 자동으로 변환해준다.

data class MarsPhoto(
   val id: String,
   val img_src: String
)

Json의 id와 img_src의 값이 클래스 맴버 변수의 id와 img_src에 들어가서 객체가 만들어진다.

Kotlin애서는 변수명을 카멜 케이스로 작성한다.
Json 객체의 key값은 img_src이지만 맴버 변수명을 imgSrc로
사용하고 싶다면 Json 어노테이션을 사용한다.

data class MarsPhoto(
   val id: String,
   @Json(name = "img_src")
   val imgSrcUrl: String
)

현재는 retrofit을 만들 때 팩토리로 ScalarsConverterFactory를 사용하지만 Moshi를 사용하도록 변경해준다.

우선 moshi 객체를 만들어 준다.

private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

retforit을 만들때 addConverterFactory 함수를 변경해준다.

private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()

retrofit의 결과가 변경됐으니까 MarsApiService 인터페이스의 getPhotos() 함수의 반환값도 변경해준다.

interface MarsApiService {
    @GET("photos")
    suspend fun getPhotos() : List<MarsPhoto>
}

이제 viewModel에서 호출한 결과로 String이 아닌 데이터 리스트가 들어가게 된다.

val listResult : List<MarsPhoto> = MarsApi.retrofitService.getPhotos()
profile
천재 개발자가 되고 싶어요

0개의 댓글