Sometimes (very rarely, but still) you have to work with a service that sends data to both Json and XML. Here’s how to do it

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.retrofit2:converter-jaxb:2.4.0'

There are coroutines (why not?), Retrofit and two converters: Moshi for Json and Jaxb for XML

Now let’s create two annotations:

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention()
internal annotation class Json
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention()
internal annotation class Xml

Main magic – coverters factory:

class XmlOrJsonConverterFactory : Converter.Factory() {

    override fun responseBodyConverter(type: Type?, annotations: Array<Annotation>?, retrofit: Retrofit?): Converter<ResponseBody, *>? {
        annotations?.forEach { annotation ->
            when (annotation.annotationClass) {
                Xml::class.java -> return JaxbConverterFactory.create().responseBodyConverter(type, annotations, retrofit)
                Json::class.java -> return MoshiConverterFactory.create().responseBodyConverter(type, annotations, retrofit)
            }
        }
        return MoshiConverterFactory.create().responseBodyConverter(type, annotations, retrofit)
    }

    companion object {
        fun create() = XmlOrJsonConverterFactory()
    }
}

Retrofit API:

interface JsonPlaceholderApi {

    @Json
    @GET("/posts")
    fun getPosts(): Deferred<Response<List<Post>>>

    companion object {
        private const val BASE_URL = "https://jsonplaceholder.typicode.com"
        fun getApi(): JsonPlaceholderApi = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(XmlOrJsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
            .create(JsonPlaceholderApi::class.java)
    }
}

And also one small data-class:

data class Post(
    val userId: Long,
    val id: Long,
    val title: String,
    val body: String
): Serializable

And this is all. Activity, launch, log results, applause:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch(Dispatchers.Main) {
            val postsRequest = JsonPlaceholderApi.getApi().getPosts()
            val postsResponse = postsRequest.await()
            postsResponse.body()?.forEach { post -> Log.d("POSTS", post.title) }
        }
    }
}

And one more thing. Do not forget the premission for network access in the manifest:

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

Code on my GitLab.

Leave a Reply