본문 바로가기

개발일기

아이고 다 실패했다! : Retrofit

아이고 아이고. 일단 울고 시작한다.

 

WPF 개발하던 코드가 이식된 것과 같은 프로젝트이기도 하고, 또 나부터가 깊이 있는 배움 없이 바로 프로젝트에 투입되어 '무엇이 안드로이드다운 코드인가?' 에 대한 베이스가 부족했기에, 이번에 일단 배포는 시켰으니 브랜치를 꺾어 안드로이드스러운 개발을 하고자 했다.

 

우선 네트워크 통신 쪽을 손 보려 했다. 기존의 방식은

open class ActivityBase : AppCompatActivity() {
    fun setNetworkData(data: String, responseCode: Int) {
    }
}

와 같이 기초 액티비티를 생성, 다른 액태비티가 해당 액티비티를 extends 하여 오버라이딩, 내가 apiCall 별로 임의로 설정해준 responseCode에 맞춰 JsonObject(data) 식으로 가져왔다.

 

물론 기능상의 문제가 있는 것은 아니지만 연속된 apiCall이나 그 응답인 JsonObject를 모델로 치환할 때 NullPointerException 방지를 위해서는 결국 SetNetWorkData에서 응답을 받은 후 다른 메소드를 실행해야 했다. 비동기 통신이 가능한데 시스템은 동기식으로 움직일 수밖에 없던 것.

 

Coroutine과 Retrofit를 함께 활용하면 두 가지 apiCall을 함께 쏘더라도 하나가 응답이 올 때까지 suspend 시킬 수 있다고 이해하여 적용을 해보려고 했다. 일단 가져와보고 그 다음 돌아가는 것을 보며 이해하는 것이 지금까지의 배워온 방식이었기 때문.

 

하지만 난관에 부딪혔다.

JsonObject를 인자로 사용할 때 Http Response는 정상적이지만 내가 Json으로 넣은 정보가 누락되어 들어간다.

 

방식은 이러했다.

https://yang-droid.tistory.com/21 를 참고하여 일단 apiBuilder와 실제 api 인터페이스를 만들었다.

ApiBuilder.kt

object LoginApiBuilder {
    init {
        val retrofit = Retrofit.Builder()
                        .baseUrl(myUrl)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()

        api = retrofit.create(LoginApi::class.java)
    }
}

LogInApi.kt

interface LoginApi {
    // @FormUrlEncoded : 이걸 쓰면 IllegalArgumentException에 걸린다. @Field가 없어서.
    @POST("/Login")
    fun logIn(
        @Body logInData: JSONObject
    ): Call<LoginApiResult>
}

ActivityLogIn의 로그인 버튼을 클릭했을 때 쏘도록 리스너 안에 달아준 코드

ActivityLogIn.kt

var result = ApiResultForJsonObj(-99, null, null, null)
LoginApiBuilder.api.logIn(logInData).enqueue(object: CallbacK<LoginApiResult> {
    override fun onResponse(call: Call<LoginApiResult>) {
        result = response.body()!!
        // TODO
    }

    override fun onFailure(call: Call<LoginApiResult>, t: Throwable) {
            Log.d("Login Falied", t.toString())
            // 실제로는 알러트를 띄운다.
    }
})

 

response의 데이터가 JSONObject인 경우와 JSONArray인 경우로 나뉘는데, 일단 나는 처음 해보는 것이라 잘 모르므로,, 각각에 대한 데이터 클래스를 생성해주기로 했다. 향후 보완하면 되니까.

logIn()에 대한 response는 JSONObject로 들어오므로 그것부터 생성.

ApiResultForJsonObj.kt

data class ApiResultForJsonObj(
    @SerializedName("ResponseCode") var resultCode: Int,
      @SerializedName("ResultMessage") var resultMessage: String?,
      @SerializedName("Data") var data: JSONObject?
)

Retrofit을 액티비티 내에서 생성해주는 방법도 있긴 했는데 이게 매번 새로운 객체를 생성하는 것보다 빌더(Repository와 비슷한 개념인 건가 그러면?)로 관리해주는 게 좋다고. 버튼 클릭시마다 생성할 수도 있기는 있더라.

ActivityLogIn.kt

val retrofit = Retrofit.Builder()
    .baseUrl(StaticValues.getServerUrl())
    .addConverterFactory(GsonConverterFactory.create())
    .build()
val logInRetroFitApi = retrofit.create(LoginApi::class.java)
var result = ApiResultForJsonObj(-99, null, null, null)
val call = logInRetroFitApi .logIn(jsonObj)
call.enqueue(object: Callback<ApiResultForJsonObj> {
    override fun onResponse(call: Call<ApiResultForJsonObj>, response: Response<ApiResultForJsonObj>) {
    // 실패할 경우 resonse가 null로 들어와서 try catch 구문을 사용했다.
    // coroutine을 사용하면 exception handling을 이렇게 내부에 구현하지 않아도 된다고 하는데,
    // 일단 retrofit과 MVVM에 성공하면 그때 시도하려고 한다.
        try {
            result = response.body()!!
        } catch (e: NullPointerException) {
            setDialog("로그인 실패", "로그인 프로세스 실패")
        } catch (e: IOException) {
            setDialog("로그인 실패", "로그인 프로세스 실패")
        }
    }

    override fun onFailure(call: Call<ApiResultForJsonObj>, t: Throwable) {
        setDialog("로그인 실패", "로그인 프로세스 실패")
    }
})

 

처음에는 code=500으로 bad response가 들어왔었는데, 이는 JsonObject를 문자열로 치환한 후 전송하면서 생긴 문제로, JsonObject로 전달하니 해당 문제는 발생하지 않았다. 원하는 결과를 얻지 못한 게 문제지...

DB에 다이렉트로 쿼리를 때려박을 수도 없는 노릇이고, 난감하다.

 

다음주에 좀 더 찾아보고 다시 시도해보기로 했다. 이번에는 이해하고 시도해야 하려나 싶네.