- permission, dependency 설정
- api key 와 .gitignore
- Rectofit (Service Creator)
- Entity
- Service interface
- Service 사용
- 중요 과제
이번 시간에는 서버와 통신을 하기 위해 Retrofit2 라는 라이브러리를 이용합니다. 서버와 HTTP 통신하기 위해 사용되는 대표적인 라이브러리는 Volley 와 Retrofit2 이 있습니다. Volley 가 진입장벽은 낮지만 Retrofit2 가 성능이 더 좋고 확장성에도 좋기 때문에 대부분 Retrofit2 을 이용합니다.
보통 JSON을 가장 많이 이용하기 때문에 JSON 포맷을 사용하도록 하겠습니다. 옛날 API 나 공공데이터 API 의 경우 XML 을 사용하는 경우도 많이 있습니다. JSON 은 JavaScript Object 이지만 js 가 아니더라도 HTTP 통신할 때 흔히 쓰입니다.
Retrofit 은 위와 같은 의존 관계를 가집니다. OOP 에 경험이 있다면 친숙한 모델입니다. 사실 MainActivity 도 나중에 ViewModel 로 분리할 수 있습니다. (키워드 : MVVM 패턴) 위와같이 객체지향 설계를 따라가기 때문에 우리는 Service 객체만 이용하면 됩니다. 이 구조의 장점은 Service 객체의 사용 방법만 알면 서버와 통신하는 다른 코드가 어떻게 동작하는 지 알 필요가 없습니다.
시작하기에 앞서 3가지 설정을 해 줘야 합니다.
- permission
- dependency
- HTTP 사용 가능하게 설정 (서버가 https 라면 생략)
먼저 dependency 입니다. Module 단위의 build.gradle 파일의 dependencies 에 다음 내용을 추가해 줍니다. 만약 아래 버전이 최신 버전이 아닐 경우 (JetBrains IDE 에서는)Alt + Enter 를 누르면 최신버전으로 업데이트할 수 있습니다.
// retrofit2
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
manifests/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/> // <manifest> 이 위치에 삽입하면 됩니다. </manifest>
android:usesCleartextTraffic="true" // application 의 속성으로 넣을 수 있습니다. http 관련 설정입니다.
코드가 들어가는 폴더에 새로운 폴더(패키지) model 을 추가합니다. 그리고 네 개의 파일을 만듭니다.
Configs : 전역변수처럼 이용하기 위한 kotlin class 파일, class 대신 object 키워드를 사용하여 싱글턴으로 이용
PapagoEntity : 서버로부터 전송받을 데이터의 형식을 정의한 Entity 클래스
PapagoServie : 여기서 정의한 메소드들로 서버와 통신이 가능하다. 인터페이스이다.
PapagoServiceCreator : Service 객체를 만들어주는 클래스, 다른 방식으로 구현하는 사람도 있다.
예상 했겠지만 파파고 API 를 이용할 예정입니다. 파파고 API 는 네이버 개발자센터에 가입 후 API 이용 신청할 수 있습니다. 가입하는 방법, API 이용 신청하는 방법은 생략하겠습니다. API 이용 신청이 완료되면 다음과 같은 화면을 볼 수 있습니다.
Configs 파일을 먼저 살펴보겠습니다.
object Configs {
val clientID = "buhGiXpppTfUsDU7KEiY"
val apiKey = "Client Secret"
}
네이버로부터 발급받은 ID 와 token(api key, 여기서는 Client Secret) 을 Configs 에 넣어줍니다. 이 파일은 외부로 유출되면 안 되는 token 을 포함하고 있습니다. 따라서 이대로 github 에 push 하면 github 에서 보안 관련 이메일이 날아옵니다. github 에 push 되지 않도록 관리해야합니다. .gitignore 파일을 이용하면 git 으로 추적하지 않을 파일을 추가할 수 있습니다. .gitignore 파일은 Android 로는 보이지 않기 때문에 잠시 Project 로 바꿔줘야 합니다.
프로젝트 최상위 디렉터리에 있는 .gitignore 파일 기준으로 다음과 같은 코드를 삽입해주면 됩니다. 폴더 이름은 사람마다 다르기 때문에 아래 코드를 참고하여 추가하시면 됩니다.
<module name>/src/main/java/<your pakage>/model/Configs.kt
이 내용을 추가하기 전과 후의 git status 명령어 결과가 다르게 나오는 것을 확인할 수 있습니다. git status 는 터미널에서 입력할 수 있습니다.
앞에서 언급했다시피 MainActivity 에서는 Service 객체를 통해 간접적으로 서버와 통신할 수 있습니다.
// PapagoServiceCreator.kt
class PapagoServiceCreator {
val BASE_URL = "https://openapi.naver.com/v1/papago/" // JSON 출력
fun create() : PapagoService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PapagoService::class.java)
}
}
이 클래스의 역할은 retrofit 의 빌더를 이용해 우리가 사용한 Service 객체를 생성해주는 역할을 합니다. builder 패턴을 이용하는 경우 메서드 단위로 개행하는 게 일반적인 코드 컨벤션입니다. 이 클래스에서 서버의 url 을 설정합니다. 서버의 url 은 API 문서에서 확인할 수 있습니다.
API 문서를 통해 JSON 에 포함되는 필드들을 알아낼 수 있습니다. 여기서 필요한 필드의 이름과 똑같은 변수 이름을 사용하여 Entity class 를 구현합니다. 우리에게는 translatedText 만 필요합니다. 이 경로에 해당하는 객체나 배열들을 모두 정의해야 합니다. 전체 > message > result > translatedText 구조입니다. 아래 코드로 위 translatedText 를 받을 수 있습니다.
// PapagoEntity
class PapagoEntity {
var message: ResultMessage? = null
inner class ResultMessage {
var result: Result? = null
inner class Result {
var translatedText: String? = null
}
}
}
마지막으로 Service interface 를 정의하면 서버와의 통신 준비는 끝납니다.
// PapagoService.kt
interface PapagoService {
@FormUrlEncoded
@POST("n2mt")
fun requestTranslation(@Header("X-Naver-Client-Id")clientID: String = Configs.clientID,
@Header("X-Naver-Client-Secret")apiKey: String = Configs.apiKey,
@Field("source")fromLang: String = "en",
@Field("target")toLang: String = "ko",
@Field("text")text: String? = "this is android") : Call<PapagoEntity>
}
네이버에서 발급받은 id 와 secret 을 request header 에 넣어주고 나머지 옵션들은 body 에 넣어줍니다. http 통신은 header 와 body 로 나누고 @Field 애너테이션으로 지정한 내용이 body 에 들어갑니다. POST 방식으로 요청을 보내고 @Field 애너테이션을 사용했기 때문에 @FormUrlEncoded 를 추가합니다. @Header 나 @Field 애너테이션의 (괄호) 안의 문자열은 서버에서 요구하는 이름과 같아야 합니다. API 문서를 참고하시면 됩니다. Header 에 들어갈 인증 정보 이름은 안 적혀있었지만 예제 코드를 통해 확인할 수 있습니다.
이로써 서버와 통신한 준비가 끝났습니다. MainActivity 코드는 RecyclerView 내용과 이어집니다.
class MainActivity : AppCompatActivity() {
private val LOG_TAG = "MainActivity Request"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val papagoService = PapagoServiceCreator().create()
val call = papagoService.requestTranslation()
call.enqueue(object : retrofit2.Callback<PapagoEntity> {
override fun onResponse(call: Call<PapagoEntity>, response: Response<PapagoEntity>) {
if(response.isSuccessful) {
// 성공
Log.d(LOG_TAG, "Successful!")
val result = response.body()
val chattingList = arrayListOf( ChattingAdapter.Message(result?.message?.result?.translatedText!!) )
recyclerView.adapter = ChattingAdapter(chattingList)
Log.e(LOG_TAG, response.raw().toString());
}
else {
// 서버에 연결은 됐으나 결과 받기 실패
Log.e(LOG_TAG, "fail!")
Log.e(LOG_TAG, "error code : " + response.code())
Log.e(LOG_TAG, "error message : " + response.message())
}
}
// 서버 연결 실패
override fun onFailure(call: Call<PapagoEntity>, t: Throwable) {
Log.d(LOG_TAG, "onFailure!")
t.printStackTrace()
}
})
}
}
지난번에는 고정된 문자열을 RecyclerView 에 출력해줬지만 이번에는 파파고에 번역을 요청하고 그 요청 결과를 받아 결과를 RecyclerView 에 출력해줬습니다. papageService.requestTranslation() 에는 인자를 넣어줄 수 있지만 Service 를 정의한 코드에 default 값을 넣어 줬기 때문에 넣어주지 않아도 됩니다.
기본적으로 서버나 데이터베이스에 요청하는 작업은 시간이 오래 걸리는 작업입니다. 사용자는 그 작업으로 인해 지연되면 불편함을 느낍니다. 그래서 이런 작업들은 백그라운드에서 처리해야 합니다. 선택이 아니라 강제입니다. call.endqueue() 부분이 서버 통신을 비동기로 처리하는 부분입니다. 통신을 요청했을 때 호출될 콜백 메소드를 구현하여 enqueue 의 인자로 넘겨줘야 합니다. 성공 혹은 실패하는 경우 어디로 이동하게 되는 지는 주석으로 달았습니다. 결과는 response.body 로 받을 수 있으며 우리가 만든 Entity 클래스의 객체로 나옵니다.
위 코드에서 콜백 메소드에 대한 인자를 넘겨주는 부분을 분리시키면 아래 코드로도 작성할 수 있습니다. 두 코드의 차이를 분석하면 kotlin 문법을 이해하는 데 도움이 됩니다. object 는 싱글턴 객체를 생성한다는 의미이고, retrofit2.Callback<> { ... } 처럼 클래스 이름을 지어주지 않고 상속받을 클래스명만 명시할 수도 있습니다.
class MainActivity : AppCompatActivity() {
private val LOG_TAG = "MainActivity Request"
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerview)
val papagoService = PapagoServiceCreator().create()
val call = papagoService.requestTranslation()
call.enqueue(SimpleClass())
}
inner class SimpleClass : retrofit2.Callback<PapagoEntity> {
override fun onResponse(call: Call<PapagoEntity>, response: Response<PapagoEntity>) {
if(response.isSuccessful) {
// 성공
Log.d(LOG_TAG, "Successful!")
val result = response.body()
val chattingList = arrayListOf( ChattingAdapter.Message(result?.message?.result?.translatedText!!) )
recyclerView.adapter = ChattingAdapter(chattingList)
Log.e(LOG_TAG, response.raw().toString());
}
else {
// 서버에 연결은 됐으나 결과 받기 실패
Log.e(LOG_TAG, "fail!")
Log.e(LOG_TAG, "error code : " + response.code())
Log.e(LOG_TAG, "error message : " + response.message())
}
}
// 서버 연결 실패
override fun onFailure(call: Call<PapagoEntity>, t: Throwable) {
Log.d(LOG_TAG, "onFailure!")
t.printStackTrace()
}
}
}
이번 과제는 중요 과제입니다. 중요 과제 2개를 통과하셔야 프로젝트에 참여하실 수 있습니다. 중요 과제를 하나도 통과하지 못 한다면 앱센터와 이별하게될 수도 있습니다. 과제를 제출하시려면 본인의 깃허브에 과제를 올린 뒤 제게 말씀해주시면 됩니다. 기간은 종강하기 전까지이며 여러 번 피드백 받으셔도 됩니다.
과제 : 서버로부터 여러 개의 데이터를 받아서 결과를 RecyclerView 에 출력하는 안드로이드 앱을 만들어주세요. 어떤 API 나 서버를 이용하든, 어떤 형식으로 출력하든 상관 없습니다. 원하는 UI 로 꾸미고 원하는 API 를 이용하여 만드시면 됩니다.
이번 과제는 처음 하기에 어려울 수 있습니다. 그래서 기간을 넉넉하게 드렸고 미리 과제를 시도해 보시고 방법을 찾아보시기 바랍니다. 스터디에서 배우지 않은 내용을 찾아서 사용하셔도 좋습니다.
과제 예시
- 5일간 각 요일마다 최저, 최고 기온을 알려주는 앱
- 한글로 채팅을 치면 영어로 번역해 주는 번역 앱
'안드로이드' 카테고리의 다른 글
[안드로이드 #6] Fragment, ViewPager2 (0) | 2021.05.27 |
---|---|
[안드로이드 #5] menu, drawer, toolbar (0) | 2021.05.23 |
[안드로이드 #3] RecyclerView (상) (0) | 2021.03.31 |
[안드로이드 #2] 이벤트 (0) | 2021.03.03 |
[안드로이드 #1] 안드로이드 시작 (하) (0) | 2021.03.01 |