Refactor: Migrate from Retrofit to Ktor #1
							
								
								
									
										1
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,4 +1,3 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> |   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> | ||||||
|   | |||||||
| @@ -43,7 +43,6 @@ fun PixabayNavGraph( | |||||||
|         ) { entry -> |         ) { entry -> | ||||||
|             PhotoDetailRoute( |             PhotoDetailRoute( | ||||||
|                 photoId = entry.arguments?.getInt(PHOTO_ID_ARG)!!, |                 photoId = entry.arguments?.getInt(PHOTO_ID_ARG)!!, | ||||||
|                 onBack = { navController.popBackStack() }, |  | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -23,16 +23,19 @@ android { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation(libs.retrofit) |  | ||||||
|  |  | ||||||
|     implementation(platform(libs.okhttp.bom)) |  | ||||||
|     implementation(libs.okhttp) |  | ||||||
|     implementation(libs.logging.interceptor) |  | ||||||
|  |  | ||||||
|     implementation(libs.androidx.paging.compose) |     implementation(libs.androidx.paging.compose) | ||||||
|  |  | ||||||
|     implementation(libs.kotlinx.serialization.json) |     implementation(libs.kotlinx.serialization.json) | ||||||
|     implementation(libs.retrofit2.kotlinx.serialization.converter) |  | ||||||
|  |     //Ktor | ||||||
|  |     implementation(libs.ktor.client.android) | ||||||
|  |     implementation(libs.ktor.client.resources) | ||||||
|  |     implementation(libs.ktor.client.content.negotiation) | ||||||
|  |     implementation(libs.ktor.client.logging) | ||||||
|  |     implementation(libs.ktor.serialization.kotlinx.json) | ||||||
|  |  | ||||||
|  |     //Logging | ||||||
|  |     implementation(libs.slf4j.android) | ||||||
|  |  | ||||||
|     implementation(libs.androidx.room.runtime) |     implementation(libs.androidx.room.runtime) | ||||||
|     ksp(libs.androidx.room.compiler) |     ksp(libs.androidx.room.compiler) | ||||||
|   | |||||||
| @@ -1,52 +1,60 @@ | |||||||
| package dev.adriankuta.pixabay.data.di | package dev.adriankuta.pixabay.data.di | ||||||
|  |  | ||||||
| import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory | import dagger.Binds | ||||||
| import dagger.Module | import dagger.Module | ||||||
| import dagger.Provides | import dagger.Provides | ||||||
| import dagger.hilt.InstallIn | import dagger.hilt.InstallIn | ||||||
| import dagger.hilt.components.SingletonComponent | import dagger.hilt.components.SingletonComponent | ||||||
| import dev.adriankuta.pixabay.data.BuildConfig | import dev.adriankuta.pixabay.data.BuildConfig | ||||||
|  | import dev.adriankuta.pixabay.data.network.KtorPixabayService | ||||||
| import dev.adriankuta.pixabay.data.network.PixabayService | import dev.adriankuta.pixabay.data.network.PixabayService | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.engine.android.Android | ||||||
|  | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation | ||||||
|  | import io.ktor.client.plugins.defaultRequest | ||||||
|  | import io.ktor.client.plugins.logging.Logging | ||||||
|  | import io.ktor.client.plugins.resources.Resources | ||||||
|  | import io.ktor.http.URLProtocol | ||||||
|  | import io.ktor.serialization.kotlinx.json.json | ||||||
| import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||||
| import okhttp3.MediaType.Companion.toMediaType |  | ||||||
| import okhttp3.OkHttpClient |  | ||||||
| import okhttp3.logging.HttpLoggingInterceptor |  | ||||||
| import retrofit2.Retrofit |  | ||||||
| import javax.inject.Singleton | import javax.inject.Singleton | ||||||
|  |  | ||||||
| @InstallIn(SingletonComponent::class) | @InstallIn(SingletonComponent::class) | ||||||
| @Module | @Module | ||||||
| internal class NetworkModule { | internal abstract class NetworkModule { | ||||||
|     @Singleton |  | ||||||
|     @Provides |  | ||||||
|     fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { |  | ||||||
|         val logging = HttpLoggingInterceptor() |  | ||||||
|         logging.level = |  | ||||||
|             if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE |  | ||||||
|         return logging |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Singleton |     @Singleton | ||||||
|     @Provides |     @Binds | ||||||
|     fun provideOkHttpClient( |     abstract fun provideKtorApi(ktorPixabayService: KtorPixabayService): PixabayService | ||||||
|         httpLoggingInterceptor: HttpLoggingInterceptor |  | ||||||
|     ): OkHttpClient { |     companion object { | ||||||
|         return OkHttpClient.Builder() |         @Singleton | ||||||
|             .addInterceptor(httpLoggingInterceptor) |         @Provides | ||||||
|             .build() |         fun provideKtorClient(): HttpClient { | ||||||
|     } |             val httpClient = HttpClient(Android) { | ||||||
|  |                 install(ContentNegotiation) { | ||||||
|  |                     json( | ||||||
|  |                         Json { | ||||||
|  |                             ignoreUnknownKeys = true | ||||||
|  |                         } | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |                 install(Resources) | ||||||
|  |                 install(Logging) | ||||||
|  |                 defaultRequest { | ||||||
|  |                     url { | ||||||
|  |                         protocol = URLProtocol.HTTP | ||||||
|  |                         host = "pixabay.com" | ||||||
|  |                         parameters.append("key", BuildConfig.PIXABAY_API_KEY) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 engine { | ||||||
|  |                     connectTimeout = 100_000 | ||||||
|  |                     socketTimeout = 100_000 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return httpClient | ||||||
|  |         } | ||||||
|  |  | ||||||
|     @Singleton |  | ||||||
|     @Provides |  | ||||||
|     fun providePixabayApi( |  | ||||||
|         okHttpClient: OkHttpClient |  | ||||||
|     ): PixabayService { |  | ||||||
|         val networkJson = Json { ignoreUnknownKeys = true } |  | ||||||
|         return Retrofit.Builder() |  | ||||||
|             .client(okHttpClient) |  | ||||||
|             .baseUrl("https://pixabay.com/") |  | ||||||
|             .addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType())) |  | ||||||
|             .build() |  | ||||||
|             .create(PixabayService::class.java) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.adriankuta.pixabay.data.dto.request | ||||||
|  |  | ||||||
|  | import io.ktor.resources.Resource | ||||||
|  | import kotlinx.serialization.SerialName | ||||||
|  |  | ||||||
|  | @Resource("/api") | ||||||
|  | internal class PixabayImages( | ||||||
|  |     @SerialName("q") | ||||||
|  |     val query: String, | ||||||
|  |     val page: Int, | ||||||
|  |     val pageSize: Int | ||||||
|  | ) | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.adriankuta.pixabay.data.dto.request | ||||||
|  |  | ||||||
|  | import io.ktor.resources.Resource | ||||||
|  |  | ||||||
|  | @Resource("/api") | ||||||
|  | internal class PixabayImagesById( | ||||||
|  |     val id: String, | ||||||
|  | ) | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| package dev.adriankuta.pixabay.data.dto.response | package dev.adriankuta.pixabay.data.dto.response | ||||||
|  |  | ||||||
|  | import io.ktor.resources.Resource | ||||||
| import kotlinx.serialization.Serializable | import kotlinx.serialization.Serializable | ||||||
|  |  | ||||||
| @Serializable | @Serializable | ||||||
|   | |||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.adriankuta.pixabay.data.network | ||||||
|  |  | ||||||
|  | import dev.adriankuta.pixabay.data.dto.request.PixabayImages | ||||||
|  | import dev.adriankuta.pixabay.data.dto.request.PixabayImagesById | ||||||
|  | import dev.adriankuta.pixabay.data.dto.response.SearchPixabayImagesResponse | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.call.body | ||||||
|  | import io.ktor.client.plugins.resources.get | ||||||
|  | import javax.inject.Inject | ||||||
|  |  | ||||||
|  | internal class KtorPixabayService @Inject constructor( | ||||||
|  |     private val client: HttpClient, | ||||||
|  | ) : PixabayService { | ||||||
|  |  | ||||||
|  |     override suspend fun searchImages( | ||||||
|  |         query: String, | ||||||
|  |         page: Int, | ||||||
|  |         pageSize: Int | ||||||
|  |     ): SearchPixabayImagesResponse { | ||||||
|  |         return client.get(PixabayImages(query, page, pageSize)).body() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override suspend fun searchImageById(id: String): SearchPixabayImagesResponse { | ||||||
|  |         return client.get(PixabayImagesById(id)).body() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,23 +1,16 @@ | |||||||
| package dev.adriankuta.pixabay.data.network | package dev.adriankuta.pixabay.data.network | ||||||
|  |  | ||||||
| import dev.adriankuta.pixabay.data.BuildConfig |  | ||||||
| import dev.adriankuta.pixabay.data.dto.response.SearchPixabayImagesResponse | import dev.adriankuta.pixabay.data.dto.response.SearchPixabayImagesResponse | ||||||
| import retrofit2.http.GET |  | ||||||
| import retrofit2.http.Query |  | ||||||
|  |  | ||||||
| internal interface PixabayService { | internal interface PixabayService { | ||||||
|  |  | ||||||
|     @GET("api") |  | ||||||
|     suspend fun searchImages( |     suspend fun searchImages( | ||||||
|         @Query("q") query: String, |         query: String, | ||||||
|         @Query("page") page: Int, |         page: Int, | ||||||
|         @Query("per_page") pageSize: Int, |         pageSize: Int | ||||||
|         @Query("key") key: String = BuildConfig.PIXABAY_API_KEY |  | ||||||
|     ): SearchPixabayImagesResponse |     ): SearchPixabayImagesResponse | ||||||
|  |  | ||||||
|     @GET("api") |  | ||||||
|     suspend fun searchImageById( |     suspend fun searchImageById( | ||||||
|         @Query("id") query: String, |         id: String, | ||||||
|         @Query("key") key: String = BuildConfig.PIXABAY_API_KEY |  | ||||||
|     ): SearchPixabayImagesResponse |     ): SearchPixabayImagesResponse | ||||||
| } | } | ||||||
| @@ -5,7 +5,6 @@ import androidx.paging.PagingState | |||||||
| import dev.adriankuta.pixabay.data.model.PixabayImage | import dev.adriankuta.pixabay.data.model.PixabayImage | ||||||
| import dev.adriankuta.pixabay.data.network.PixabayService | import dev.adriankuta.pixabay.data.network.PixabayService | ||||||
| import dev.adriankuta.pixabay.data.repository.PixabayImageRepository.Companion.NETWORK_PAGE_SIZE | import dev.adriankuta.pixabay.data.repository.PixabayImageRepository.Companion.NETWORK_PAGE_SIZE | ||||||
| import retrofit2.HttpException |  | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
|  |  | ||||||
| private const val STARTING_PAGE_INDEX = 1 | private const val STARTING_PAGE_INDEX = 1 | ||||||
| @@ -34,8 +33,6 @@ internal class PixabayPagingSource( | |||||||
|             ) |             ) | ||||||
|         } catch (exception: IOException) { |         } catch (exception: IOException) { | ||||||
|             return LoadResult.Error(exception) |             return LoadResult.Error(exception) | ||||||
|         } catch (exception: HttpException) { |  | ||||||
|             return LoadResult.Error(exception) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,12 +7,10 @@ import androidx.paging.LoadType | |||||||
| import androidx.paging.PagingState | import androidx.paging.PagingState | ||||||
| import androidx.paging.RemoteMediator | import androidx.paging.RemoteMediator | ||||||
| import androidx.room.withTransaction | import androidx.room.withTransaction | ||||||
| import dev.adriankuta.pixabay.data.model.PixabayImage |  | ||||||
| import dev.adriankuta.pixabay.data.network.PixabayService | import dev.adriankuta.pixabay.data.network.PixabayService | ||||||
| import dev.adriankuta.pixabay.data.room.AppDatabase | import dev.adriankuta.pixabay.data.room.AppDatabase | ||||||
| import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity | import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity | ||||||
| import dev.adriankuta.pixabay.data.room.entity.RemoteKeys | import dev.adriankuta.pixabay.data.room.entity.RemoteKeys | ||||||
| import retrofit2.HttpException |  | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
|  |  | ||||||
| private const val STARTING_PAGE_INDEX = 1 | private const val STARTING_PAGE_INDEX = 1 | ||||||
| @@ -78,8 +76,6 @@ internal class PixabayRemoteMediator( | |||||||
|             return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) |             return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) | ||||||
|         } catch (exception: IOException) { |         } catch (exception: IOException) { | ||||||
|             return MediatorResult.Error(exception) |             return MediatorResult.Error(exception) | ||||||
|         } catch (exception: HttpException) { |  | ||||||
|             return MediatorResult.Error(exception) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.Flow | |||||||
| import kotlinx.coroutines.flow.map | import kotlinx.coroutines.flow.map | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
|  | import javax.inject.Named | ||||||
|  |  | ||||||
| internal class PixabayImageRepository @Inject constructor( | internal class PixabayImageRepository @Inject constructor( | ||||||
|     private val database: AppDatabase, |     private val database: AppDatabase, | ||||||
| @@ -75,6 +76,6 @@ internal class PixabayImageRepository @Inject constructor( | |||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NETWORK_PAGE_SIZE = 30 |         const val NETWORK_PAGE_SIZE = 30 | ||||||
|         const val USE_CACHE_PAGER = true |         const val USE_CACHE_PAGER = false | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -39,7 +39,6 @@ import dev.adriankuta.pixabay.feature.details.di.PhotoDetailsViewModelFactory | |||||||
| @Composable | @Composable | ||||||
| fun PhotoDetailRoute( | fun PhotoDetailRoute( | ||||||
|     photoId: Int, |     photoId: Int, | ||||||
|     onBack: () -> Unit, |  | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     viewModel: PhotoDetailViewModel = hiltViewModel( |     viewModel: PhotoDetailViewModel = hiltViewModel( | ||||||
|         creationCallback = { factory: PhotoDetailsViewModelFactory -> |         creationCallback = { factory: PhotoDetailsViewModelFactory -> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| [versions] | [versions] | ||||||
| androidxNavigation = "2.7.7" | androidxNavigation = "2.7.7" | ||||||
| androidGradlePlugin = "8.1.4" | androidGradlePlugin = "8.1.4" | ||||||
| agp = "8.6.0-beta02" | agp = "8.6.0-rc01" | ||||||
| coilCompose = "2.6.0" | coilCompose = "2.6.0" | ||||||
| composeCompiler = "1.5.14" | composeCompiler = "1.5.14" | ||||||
| kotlin = "1.9.24" | kotlin = "1.9.24" | ||||||
| @@ -15,13 +15,13 @@ androidxLifecycle = "2.8.3" | |||||||
| activityCompose = "1.9.0" | activityCompose = "1.9.0" | ||||||
| composeBom = "2024.06.00" | composeBom = "2024.06.00" | ||||||
| kotlinxSerializationJson = "1.6.0" | kotlinxSerializationJson = "1.6.0" | ||||||
|  | ktor = "2.3.12" | ||||||
| okhttpBom = "4.12.0" | okhttpBom = "4.12.0" | ||||||
| pagingCompose = "3.3.0" | pagingCompose = "3.3.0" | ||||||
| retrofit2KotlinxSerializationConverter = "1.0.0" |  | ||||||
| room = "2.6.1" | room = "2.6.1" | ||||||
|  | slf4jAndroid = "1.7.36" | ||||||
| timber = "5.0.1" | timber = "5.0.1" | ||||||
| hilt = "2.51.1" | hilt = "2.51.1" | ||||||
| retrofit = "2.11.0" |  | ||||||
| ksp = "1.9.24-1.0.20" | ksp = "1.9.24-1.0.20" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
| @@ -52,14 +52,18 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man | |||||||
| androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } | ||||||
| androidx-material3 = { group = "androidx.compose.material3", name = "material3" } | androidx-material3 = { group = "androidx.compose.material3", name = "material3" } | ||||||
| kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } | ||||||
|  | ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" } | ||||||
|  | ktor-client-resources = { group = "io.ktor", name = "ktor-client-resources", version.ref = "ktor" } | ||||||
|  | ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } | ||||||
|  | ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } | ||||||
|  | ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } | ||||||
| logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } | logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } | ||||||
| okhttp = { module = "com.squareup.okhttp3:okhttp" } | okhttp = { module = "com.squareup.okhttp3:okhttp" } | ||||||
| okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } | okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } | ||||||
| retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } | slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroid" } | ||||||
| timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } | timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } | ||||||
| hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } | hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } | ||||||
| hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } | hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } | ||||||
| retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } |  | ||||||
|  |  | ||||||
| [plugins] | [plugins] | ||||||
| android-application = { id = "com.android.application", version.ref = "agp" } | android-application = { id = "com.android.application", version.ref = "agp" } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user