Для взаимодействия с rest мы используем связку OkHttp и Retrofit. OkHttp — http-клиент для большинства проектов, требующий минимум настроек. В то же время достаточно гибок для редких случаев, связанных с ручной настройкой работы с cookies, dns, proxy и т.д. Retrofit — «обертка» REST API в Java-интерфейсы. Имеет конверторы, работающие с Gson, Moshi, Protobuf, Xml и др. Как мы используем:
В первую очередь необходимо сделать обертку над 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)
}
}
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-слое.
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 кинотеатра.
@Provides
@ApplicationScope
fun provideCinemaCervice(retrofit: Retrofit): CinemaService = retrofit.create(CinemaService::class.java)
Создание и провайд инстанса CinemaService Retrofit'ом
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.