Retrofit
setup
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
// Retrofit
implementation(["com.squareup.retrofit2:retrofit:$retrofit_version",
"com.squareup.retrofit2:adapter-
rxjava2:$retrofit_version",
"com.squareup.retrofit2:converter-
gson:$retrofit_version",
"com.squareup.okhttp3:okhttp:$okhttp_version",
"com.squareup.okhttp3:logging-
interceptor:$okhttp_version"])
build.gradle
ext.kotlin_version = '1.1.3-2'
ext.android_support_version = "26.0.0"
ext.glide_version = "3.8.0"
ext.retrofit_version = '2.2.0'
ext.okhttp_version = '3.6.0'
ext.rxjava_version = "2.1.2"
ext.rxandroid_version = '2.0.1'
add permission to mainfest
<uses-permission android:name="android.permission.INTERNET" />
simple use case
val retrofit by lazy { makeRetrofit() } // 1
private fun makeRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("<Base Url>") // 2
.build()
with json and RxJava
// Retrofit.kt
package com.sample.marvelgallery.data.network.provider
import com.google.gson.Gson
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
val retrofit by lazy { makeRetrofit() }
private fun makeRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("http://gateway.marvel.com/v1/public/")
.client(makeHttpClient())
.addConverterFactory(GsonConverterFactory.create(Gson())) // 1
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 2
.build()
private fun makeHttpClient() = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) // 3
.readTimeout(60, TimeUnit.SECONDS) // 4
.addInterceptor(makeHeadersInterceptor()) // 5
.addInterceptor(makeAddSecurityQueryInterceptor()) // 6
.addInterceptor(makeLoggingInterceptor()) // 7
.build()
// HeadersInterceptor.kt
package com.sample.marvelgallery.data.network.provider
import okhttp3.Interceptor
fun makeHeadersInterceptor() = Interceptor { chain -> // 1
chain.proceed(chain.request().newBuilder()
.addHeader("Accept", "application/json")
.addHeader("Accept-Language", "en")
.addHeader("Content-Type", "application/json")
.build())
}
// LoggingInterceptor.kt
package com.sample.marvelgallery.data.network.provider
import com.sample.marvelgallery.BuildConfig
import okhttp3.logging.HttpLoggingInterceptor
fun makeLoggingInterceptor() = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
else HttpLoggingInterceptor.Level.NONE
}
// QueryInterceptor.kt
package com.sample.marvelgallery.data.network.provider
import com.sample.marvelgallery.BuildConfig
import okhttp3.Interceptor
fun makeAddSecurityQueryInterceptor() = Interceptor { chain ->
val originalRequest = chain.request()
val timeStamp = System.currentTimeMillis()
// Url customization: add query parameters
val url = originalRequest.url().newBuilder()
.addQueryParameter("apikey", BuildConfig.PUBLIC_KEY) // 1
.addQueryParameter("ts", "$timeStamp") // 1
.addQueryParameter("hash", calculatedMd5(timeStamp.toString() + BuildConfig.PRIVATE_KEY + BuildConfig.PUBLIC_KEY)) // 1
.build()
// Request customization: set custom url
val request = originalRequest
.newBuilder()
.url(url)
.build()
chain.proceed(request)
}
// MD5.kt
package com.sample.marvelgallery.data.network.provider
import java.math.BigInteger
import java.security.MessageDigest
/**
* Calculate MD5 hash for text
* @param timeStamp Current timeStamp
* @return MD5 hash string
*/
fun calculatedMd5(text: String): String {
val messageDigest = getMd5Digest(text)
val md5 = BigInteger(1, messageDigest).toString(16)
return "0" * (32 - md5.length) + md5 // 1
}
private fun getMd5Digest(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
private operator fun String.times(i: Int) = (1..i).fold("") { acc, _ -> acc + this }
//We are using the times extension operator to fill the hash with zeros if it is shorter than 32.
gradle.build
defaultConfig {
applicationId "com.sample.marvelgallery"
minSdkVersion 16
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
buildConfigField("String", "PUBLIC_KEY", "\"${marvelPublicKey}\"")
buildConfigField("String", "PRIVATE_KEY", "\"${marvelPrivateKey}\"")
}
gradle.properties
org.gradle.jvmargs=-Xmx1536m
marvelPublicKey=REPLEACE_WITH_YOUR_PUBLIC_MARVEL_KEY
marvelPrivateKey=REPLEACE_WITH_YOUR_PRIVATE_MARVEL_KEY
Data Transfer Objects (DTOs)
class DataContainer<T> {
var results: T? = null
}
class DataWrapper<T> {
var data: DataContainer<T>? = null
}
class ImageDto {
lateinit var path: String // 1
lateinit var extension: String // 1
val completeImagePath: String
get() = "$path.$extension"
}
class CharacterMarvelDto {
lateinit var name: String // 1
lateinit var thumbnail: ImageDto // 1
val imageUrl: String
get() = thumbnail.completeImagePath
}
- For values that might not be provided, we should set a default value. Values that are mandatory might be prefixed with lateinit instead.
Convert to an object
class MarvelCharacter(
val name: String,
val imageUrl: String
) {
constructor(dto: CharacterMarvelDto) : this(
name = dto.name,
imageUrl = dto.imageUrl
)
}
API
Retrofit is using reflection to create an HTTP request basing of interface definition. This is how we can implement an interface that is defining an HTTP request:
interface MarvelApi {
@GET("characters")
fun getCharacters(
@Query("offset") offset: Int?,
@Query("limit") limit: Int?
): Single<DataWrapper<List<CharacterMarvelDto>>>
}
retrofit.create(MarvelApi::class.java) // 1
.getCharacters(0, 100) // 2
.subscribe({ /* code */ }) // 3
- We use a retrofit instance to create an object that will make HTTP requests according to the MarvelApi interface definition.
- We create observable ready to send call to API.
- By subscribe, we send an HTTP request and we start listening for a response. The first argument is the callback that is invoked when we successfully receive a response.
Repository
interface MarvelRepository {
fun getAllCharacters(): Single<List<MarvelCharacter>>
}
class MarvelRepositoryImpl : MarvelRepository {
val api = retrofit.create(MarvelApi::class.java)
override fun getAllCharacters(): Single<List<MarvelCharacter>> = api.getCharacters(
offset = 0,
limit = elementsOnListLimit
).map {
it.data?.results.orEmpty().map(::MarvelCharacter) // 1
}
companion object {
const val elementsOnListLimit = 50
}
}