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 
}
  1. 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
  1. We use a retrofit instance to create an object that will make HTTP requests according to the MarvelApi interface definition.
  2. We create observable ready to send call to API.
  3. 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 
   } 
}

results matching ""

    No results matching ""