Web service 와 JSON
우리는 web server 에 저장된 데이터를 어플리케이션으로 가져오고 싶을 때가 있다.
web server에 접근하기 하여 데이터를 받아오는 행위 = "web service".
따라서 web의 component와 프로토콜을 사용하는 REST 아키텍쳐를 이용하여 Web service를 요청해보도록 하자.
보통 web service를 요청하는 방식은 URIs를 경유하는 기본적인 방식이 있다. ( URIs 내에 URL 방식이 있는듯?)
이렇게 web serivce를 요청하면 일반적으로 web은 JSON을 사용하여 포맷한 데이터를 넘겨준다.
*JSON이란? 구조화된 데이터를 표현하기 위한 교환 형식으로 보통 dictionary, hash map, associatiate array 등으로 불려지는 Key-Value Pair 타입의 Collection을 말한다.
web service를 요청하면 "JSON Array"를 응답으로 받는다.
Dependency & Support Language
web service를 요청하기 위해서 retrofit 라이브러리를 사용한다. retrofit 라이브러를 사용하기 위한 dependency는 다음과 같다.
//Retrofit라이브러리와 컨버터, version_retrofit 2.9.0
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
//Moshi 라이브러리 version_moshi = "1.8.0"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
retrofit은 converter를 필요로 한다. scalars 컨버터와 moshi컨버터 현재까지 두 종류의 컨버터를 배웠고 더 있을수도 있다.
scalars컨버터는 약간 JSON을 단순 string만으로 전환하는 컨버터 같고
moshi컨버터는 JSON을 사용자가 지정한 data class구조 (key = 식별자, value = 값) 로 전환할 수 있는 컨버터 .
(moshi 컨버터를 사용하기 위해서는 moshi 라이브러리 추가.)
둘 중 하나만 써도 된다.
retrofit 사용하는 타사 라이브러리는 보통 Java 8을 사용한다. (사실 이해를 잘못했다)
따라서 다음과 같이 Java 8 언어에 대한 support를 추가해줘야 한다.
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
Retrofit 라이브러리를 이용하여 Web service와 연결하기.
Retrofit이란? App에서 Web Serivce를 요청하고 Web 컨텐츠를 받을 수 있도록 (응답) 네트워크 API를 생성하는 라이브러리이다.
네트워크 API는 web server로 부터 data를 Web Service를 통해 가져오고 (Fetch)
실제 App에서 해당 데이터를 사용할 수 있도록 Object형태로 전환할 수 있다. (Decode)
또한 궁극적으로 Background Thread 에서 web service 요청과 응답 활동을 수행할 수 있도록 하는 것과 같은 많은 세부사항을 구현할 수 있는 네트워크 계층을 생산한다.
- package, network 를 만들고 MarsApiService파일과 MarsProperty를 만든다.
MarsApiService.kt 파일은 App의 네트워크 계층을 보유. 즉, Retrofit라이브러리 및 Converter를 사용하여 Web Service를 요청할 수 있는 클래스 및 인터페이스를 구현한다.
- Retrofit 라이브러리를 이용하여 Network API 만들기.
Retrofit이 Network API를 만들기 위해서는 2가지 재료가 필요하다. 1.converter와 2.baseUrl
- ConverterFactory
Web Service 요청으로 부터 받아오는 JSON응답을 App에서 사용할 수 있는 형태(타입)으로 전환하는 컨버터가 필요합니다. 예를 들어 scalarsConverterFactory 또는 moshiConverterFactory 가 있다. - baseUrl
Web Service를 요청하는 가장 기본적인 방식은 Url을 사용한다. 따라서 Url을 이용하는 경우 접속할 인터넷 서버의 Url이 필요하다. Url String을 상수로 만든다.
0) Retrofit.Builder() 를 호출.
1) addConverterFactory() 를 호출. -> 인자로 ConverterFactory를 받는다.
2) baseUrl(BASE_URL) 을 호출. -> 인자로 URL을 담고있는 문자열을 받는다.
ScalarsConverter는 JSON 응답을 String을 포함한 원시타입으로 전환하는 컨버터.private val retrofit = Retrofit.Builder() .addConverterFactory(ScalarsConverterFactory.create()) .baseUrl(BASE_URL) .build()
또는
MoshiConverter는 JSON 응답을 사용자가 지정한 Kotlin Data Object class 타입으로 전환하는 컨버터
private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() private val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) .baseUrl(BASE_URL) .build()
- retrofit (=Network API) 을 만든 뒤, 이제 해당 API와 웹 서버가 통신하는 방법을 구현하는 "인터페이스"를 정의한다.
우리는 Web Server에 저장된 데이터를 요청하고 응답받은 JSON을 APP에서 사용할 수 있는 타입으로 받고자 한다.
이외에도 우리는 Web Server에 데이터를 보낼 수도 있고, 또 Web Server에 존재하는 모든 데이터가 아닌 특정한 데이터만을 받길 원할수도 있다.
따라서 인터페이스 내에 "요청 메소드"를 구현한다.
(Room DAO가 Room Database에 유사하게 쿼리를 날리는 것처럼)
interface MarsApiService {
@GET("realestate")
fun getProperties(): Call<String>
}
MarsApiService 인터페이스 내에
@GET() 이라는 어노테이션을 통해 해당 요청 메서드의 기능을 구체화할 수 있다.
@GET 은 간단하게 말하자면 주어진 URI에 들어가서 데이터를 조회하는 요청 메소드이며, 인자로 주어진 문자열을 주소 맨 뒤에 붙여서 해당 web server로 찾아들어간다.
예를 들어 "https://android-kotlin-fun-mars-server.appspot.com/" 이 BASE URL로 설정이 되어있고,
@GET("realestate") 어노테이션을 통해 요청메소드를 구현한다면
https://android-kotlin-fun-mars-server.appspot.com/realestate -> 로 찾아 들어 간다는 뜻.
또한 getProperites() 메서드는 Call<> Object를 반환하는데, Call 타입은 Web Service에 요청을 시작하기 위해 사용.
네트워크 API (val retrofit)에 "요청 메소드"가 구현된 인터페이스를 넘겨준다면 "Web Service 요청이 가능한 네트워크 API" 완성.
(이게 맞는말인지 좀 더 공부해야 할듯)
- 네트워크API 싱글톤 + lazy 초기화
Web Service 요청이 가능한 네트워크 API 를 만들 때 싱글톤으로 정의하여 메모리에 정적 멤버로 남기고 하나의 retrofit 객체만 사용되도록 만든다. (Database 처럼) 또한 API 변수는 리소스를 많이 소비하기 때문에 lazy 초기화를 이용하여 변수가 사용되는 시점에서 초기화 될 수 있도록 하자.
object MarsApi{
val retrofitService : MarsApiService by lazy{
retrofit.create(MarsApiService::class.java)
}
}
이로써 Retrofit 라이브러리를 이용하여 기능이 구현된 네트워크 API를 만드는 최종 코드는 다음과 같다.
private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com/"
private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
interface MarsApiService {
@GET("realestate")
fun getProperties(): Call<String>
}
object MarsApi{
val retrofitService : MarsApiService by lazy{
retrofit.create(MarsApiService::class.java)
}
}
Internet Permission 설정
인터넷에 연결하면 항상 보안문제가 발생한다. 따라서 기본적으로 앱은 보안 설정을 해주지 않으면 인터넷에 연결되지 않는다. 명시적으로 안드로이드 앱에 인터넷과의 접근이 허용된다는 코드를 구현해야 하낟.
In AndroidManifrests.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.marsrealestate">
<uses-permission android:name="android.permission.INTERNET"/>
<application
...>