유지보수 중인 소스의 API 연동 NetworkManager 클래스 관련된 소스들이 오래전에 사용하던 소스코드여서 리팩토링 해보았습니다.

 

RxJava 및 retrofit2 를 이용해서 간단하게 적용했습니다. RxJava 기능이 너무 광범위해서 간단한 기능만 사용해 봅니다.

 

프로젝트에 포함한 라이브러리

 

def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
implementation "io.reactivex.rxjava2:rxandroid:2.0.1"

 

인터페이스 구현 

  프로젝트에서 API 연동하는 부분이 한군대라 간단하게 구현해 놨습니다.

Observable<JsonObject> 로 받는 방법도 있고 Call<ResponseBody> 형태로도 구현해봤습니다.

 

Observable< T > 

Call< T >

T 에 모델 TestModel 를 바로 넣어서 받아도 됩니다.

 

[TestModel.kt]

class TestModel {
    //    {"title":"title","body":"body","userId":"1","id":101}
    var title: String? = null
    var body: String? = null
    var userId: String? = null
    var id: Int = 0
}

 

[API 연동 클래스]

 

구문에 .client(unsafeOkHttpClient.build())  해당 하는 부분은 개발서버 연동할때 SSL 인증서가 없거나 만료되서 SSL 에러 발생으로 SSL 인증서 예외처리하는 클래스 입니다. 

 

import okhttp3.OkHttpClient
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

object TrustOkHttpClientUtil {
    val unsafeOkHttpClient: OkHttpClient
    .Builder
        get() = try {
            val trustAllCerts = arrayOf<TrustManager>(
                object : X509TrustManager {
                    @Throws(CertificateException::class)
                    override fun checkClientTrusted(
                        chain: Array<X509Certificate>,
                        authType: String
                    ) {
                    }

                    @Throws(CertificateException::class)
                    override fun checkServerTrusted(
                        chain: Array<X509Certificate>,
                        authType: String
                    ) {
                    }

                    override fun getAcceptedIssuers(): Array<X509Certificate> {
                        return arrayOf()
                    }
                }
            )

            val sslContext = SSLContext.getInstance("SSL")
            sslContext.init(null, trustAllCerts, SecureRandom())

            val sslSocketFactory = sslContext.socketFactory
            val builder = OkHttpClient.Builder()
            builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
            builder.hostnameVerifier { hostname, session -> 
                // 그냥 true 리턴해도 되지만 취약점 점검시 취약점으로 걸림.
                if (hostname.equals("m.운영도메인.co.kr") ||
                    hostname.equals("m.개발도매인.co.kr")) {
                    true
                } else {
                    false
                }
            }
            builder
        } catch (e: CertificateException) {
            throw RuntimeException(e)
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
}

 

interface API {
    companion object {
        private var instance: API? = null
        val gson = GsonBuilder()
            .setLenient()
            .create()

        fun getInstance(): API {
            return instance ?: synchronized(this) {
                val retrofit = Retrofit.Builder()
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .baseUrl("https://jsonplaceholder.typicode.com")
                    .client(unsafeOkHttpClient.build())     // 개발서버 SSL 인증서 없는 경우 적용
                    .build()
                instance ?: retrofit.create(API::class.java)
            }
        }
    }

    // RxJava 방식 
    @FormUrlEncoded
    @Headers (
        "accept: application/json",
        "content-type: application/x-www-form-urlencoded; charset=utf-8"
    )
    @POST
    fun getPost(@Url suburl: String, @FieldMap param: MutableMap<String, Any>): Observable<JsonObject>

    // Retrofit2 방식 
    @FormUrlEncoded
    @Headers(
        "accept: application/json",
        "content-type: application/x-www-form-urlencoded; charset=utf-8"
    )
    @POST
    fun getPostResponseBody(@Url suburl: String, @FieldMap param: MutableMap<String, Any>): Call<ResponseBody>

    /////////////////////////////////////////////////////////////////////////////////
    // TestModel 테스트 함수
    // RxJava 방식
    @FormUrlEncoded
    @POST
    fun getTestModelPostObservable(@Url suburl: String, @FieldMap param: MutableMap<String, Any>): Observable<TestModel>

    // Retrofit2 방식
    @FormUrlEncoded
    @POST
    fun getTestModelPostCall(@Url suburl: String, @FieldMap param: MutableMap<String, Any>): Call<TestModel>

}

 

[사용 방법]

총 4가지 방식으로 API 받아서 처리하는 방법을 소개하겠습니다. 간단한 예제니 한번씩 테스트 해보세요.

1. Call<ResponseBody>

2. Call<TestModel>

3. Observable<JsonObject>

4. Observable<TestModel>

 

 

1. Call<ResponseBody> 사용 방법

val params: MutableMap<String, Any> = HashMap<String, Any>()
params["userId"] = 1
params["title"] = "title"
params["body"] = "body"

API.getInstance().getPostResponseBody("/posts", params).enqueue(object : Callback<ResponseBody> {
    override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
        if(response.isSuccessful) {
            val bodyStringt = response.body()?.string()
            bodyStringt?.let { result ->
                val jsonObject = JSONObject(result)

                DLog.d(Exception(), "[API]RESULT - ${jsonObject.toString()}")
            }
        } else {

            DLog.e(Exception(), "response fail!!!!!")
        }
    }

    override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
        DLog.e(Exception(), t.stackTraceToString())
    }
})

/* 결과
    [API]RESULT : {"title":"title","body":"body","userId":"1","id":101}
*/

 

2. Call Model Class 사용 방법

TestModel 클래스를 다이렉트로 받아서 저리하는 방식

val params: MutableMap<String, Any> = HashMap<String, Any>()
params["userId"] = 1
params["title"] = "title"
params["body"] = "body"

API.getInstance().getTestModelPostCall("/posts", params).enqueue(object : Callback<TestModel> {
    override fun onResponse(call: Call<TestModel>, response: Response<TestModel>) {
        if(response.isSuccessful) {
            val testModel = response.body()
            testModel?.let { it ->
                DLog.d(Exception(), "[API]RESULT - title : ${it.title} / body : ${it.body} / userId : ${it.userId} / id : ${it.id}")
            }
        } else {
            DLog.e(Exception(), "response fail!!!!!")
        }
    }

    override fun onFailure(call: Call<TestModel>, t: Throwable) {
        DLog.e(Exception(), t.stackTraceToString())
    }
})

/* 결과
	[API]RESULT - title : title / body : body / userId : 1 / id : 101
*/

 

 

3. Observable<JsonObject>  사용 방법

val params: MutableMap<String, Any> = HashMap<String, Any>()
params["userId"] = 1
params["title"] = "title"
params["body"] = "body"

var disposable: Disposable? = null

disposable = API.getInstance()
    .getPost("/posts", params)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        { result ->
            DLog.d(Exception(), "[API]RESULT : " + result.toString())
        },
        { error ->
            DLog.e(Exception(), "[API]ERROR : " + error.message)
        }
    )
    
/* 결과
	[API]RESULT : {"title":"title","body":"body","userId":"1","id":101}
*/

 

4. Observable<TestModel>  Class Model  사용 방법

val params: MutableMap<String, Any> = HashMap<String, Any>()
params["userId"] = 1
params["title"] = "title"
params["body"] = "body"

var disposable: Disposable? = null

disposable = API.getInstance().getTestModelPostObservable("/posts", params)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe ({ result ->
        val testModel = result
        testModel?.let { it ->
            DLog.d(Exception(), "[API]RESULT - title : ${it.title} / body : ${it.body} / userId : ${it.userId} / id : ${it.id}")
        }
    } , { error ->
        DLog.e(Exception(), error.stackTraceToString())
    })
    
/* 결과
	[API]RESULT - title : title / body : body / userId : 1 / id : 101
*/

+ Recent posts