Для взаимодействия с rest мы используем связку OkHttp и Retrofit. OkHttp — http-клиент для большинства проектов, требующий минимум настроек. В то же время достаточно гибок для редких случаев, связанных с ручной настройкой работы с cookies, dns, proxy и т.д. Retrofit — «обертка» REST API в Java-интерфейсы. Имеет конверторы, работающие с Gson, Moshi, Protobuf, Xml и др. Как мы используем:

1. Инициализация

В первую очередь необходимо сделать обертку над Retroft, в котором производится настройка Retrofit.

GsonConverterFactory — для сериализации/десериализации json в Java-объекты; RxJava2CallAdapterFactory — для возможности получать ответы, обернутые в RxJava 2 reactive objects (Single, Observable). Помимо прочих настроек мы добавляем RestExceptionInterceptor, который представляет из себя единое место обработки всех REST-ошибок

interface RestClient {
    fun getRetrofit(): Retrofit
}

class RestClientImpl(
    application: App,
) : RestClient {
    private companion object {
        const val SOCKET_TIMEOUT_EXCEPTION: Long = 15
        const val DISK_CACHE_SIZE: Long = 50 * 1024 * 1024 // 50MB
        const val ENDPOINT = NETWORK_BASE_URL
    }

    private val file = File(application.cacheDir, "http")
    private val cache = Cache(
        file,
        DISK_CACHE_SIZE
    )
    private val logginInteceptor = HttpLoggingInterceptor()
		private val restExceptionInterceptor = RestExceptionInterceptor()
    private val okHttpClient = OkHttpClient.Builder()
        .connectTimeout(SOCKET_TIMEOUT_EXCEPTION, TimeUnit.SECONDS)
        .readTimeout(SOCKET_TIMEOUT_EXCEPTION, TimeUnit.SECONDS)
        .writeTimeout(SOCKET_TIMEOUT_EXCEPTION, TimeUnit.SECONDS)
        .cache(cache)
        .addInterceptor(logginInteceptor)
        .build()

    private val retrofit = Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(ENDPOINT)
        .addConverterFactory(GsonConverterFactory.create())
				.addInterceptor(restExceptionInterceptor )
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()

    override fun getRetrofit(): Retrofit {
        return retrofit
    }
}

Следующим шагом производится подключение обертки в глобальный граф зависимостей

@Module
class RestModule{
    @Singleton
    @Provides
    fun provideRestClient(projectApplication: ProjectApplication): RestClient {
        return RestClientImpl(projectApplication)
    }
}

2. Обработчик REST-ошибок

class RestExceptionInterceptor(application: Application) : Interceptor {
    private val connectivityManager: ConnectivityManager =
            application.baseContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    override fun intercept(chain: Interceptor.Chain): Response {
        if (!hasInternetConnection()) {
            throw NoInternetConnectionException()
        }
        val response = chain.proceed(chain.request())
        if (Arrays.binarySearch(RestExceptionFactory.ERROR_CODES, response.code()) >= 0) {
            throw RestExceptionFactory.createFromBufferedSource(response)
        }
        if (response.code() in 500..599) {
            throw ServerErrorException()
        }
        return response //200 OK
    }

    private fun hasInternetConnection(): Boolean {
        val netinfo = connectivityManager.activeNetworkInfo
        return netinfo != null && netinfo.isConnectedOrConnecting
    }
}

Все классы ошибок расположены в Business-слое.

3. Настройка интерфейса

interface CinemaService {

    @GET("cinemas")
    fun getCinemas(@Query("city_id") cityid: Long): Single<List<DataCinema>>

    @GET("cinemas/{id}/show-times")
    fun getSchedule(@Path("id") cinemaId: Long, @Query("date") date: String)
            : Single<List<DataCinemaSchedule>>
}

Пример сервиса, который получает кинотеатры по id города и расписание фильмов по id кинотеатра.

4. Создание инстанса REST-сервиса

@Provides
@ApplicationScope
fun provideCinemaCervice(retrofit: Retrofit): CinemaService = retrofit.create(CinemaService::class.java)

Создание и провайд инстанса CinemaService Retrofit'ом

5. Использование

override fun getAllFromRest(cityId: Long): Single<List<DataCinema>> {
    return cinemaService.getCinemas(cityId)
}

override fun getSchedule(cinemaId: Long, date: String): Single<List<DataCinemaSchedule>> {
    return cinemaService.getSchedule(cinemaId, date)
}

Использование CinemaServiceImpl в реализации CinemaRepository.