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"> | ||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||
|   <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 -> | ||||
|             PhotoDetailRoute( | ||||
|                 photoId = entry.arguments?.getInt(PHOTO_ID_ARG)!!, | ||||
|                 onBack = { navController.popBackStack() }, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -23,16 +23,19 @@ android { | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(libs.retrofit) | ||||
|  | ||||
|     implementation(platform(libs.okhttp.bom)) | ||||
|     implementation(libs.okhttp) | ||||
|     implementation(libs.logging.interceptor) | ||||
|  | ||||
|     implementation(libs.androidx.paging.compose) | ||||
|  | ||||
|     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) | ||||
|     ksp(libs.androidx.room.compiler) | ||||
|   | ||||
| @@ -1,52 +1,60 @@ | ||||
| package dev.adriankuta.pixabay.data.di | ||||
|  | ||||
| import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory | ||||
| import dagger.Binds | ||||
| import dagger.Module | ||||
| import dagger.Provides | ||||
| import dagger.hilt.InstallIn | ||||
| import dagger.hilt.components.SingletonComponent | ||||
| import dev.adriankuta.pixabay.data.BuildConfig | ||||
| import dev.adriankuta.pixabay.data.network.KtorPixabayService | ||||
| 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 okhttp3.MediaType.Companion.toMediaType | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||
| import retrofit2.Retrofit | ||||
| import javax.inject.Singleton | ||||
|  | ||||
| @InstallIn(SingletonComponent::class) | ||||
| @Module | ||||
| internal class NetworkModule { | ||||
|     @Singleton | ||||
|     @Provides | ||||
|     fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { | ||||
|         val logging = HttpLoggingInterceptor() | ||||
|         logging.level = | ||||
|             if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE | ||||
|         return logging | ||||
|     } | ||||
| internal abstract class NetworkModule { | ||||
|  | ||||
|     @Singleton | ||||
|     @Provides | ||||
|     fun provideOkHttpClient( | ||||
|         httpLoggingInterceptor: HttpLoggingInterceptor | ||||
|     ): OkHttpClient { | ||||
|         return OkHttpClient.Builder() | ||||
|             .addInterceptor(httpLoggingInterceptor) | ||||
|             .build() | ||||
|     } | ||||
|     @Binds | ||||
|     abstract fun provideKtorApi(ktorPixabayService: KtorPixabayService): PixabayService | ||||
|  | ||||
|     companion object { | ||||
|         @Singleton | ||||
|         @Provides | ||||
|         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 | ||||
|  | ||||
| import io.ktor.resources.Resource | ||||
| import kotlinx.serialization.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 | ||||
|  | ||||
| import dev.adriankuta.pixabay.data.BuildConfig | ||||
| import dev.adriankuta.pixabay.data.dto.response.SearchPixabayImagesResponse | ||||
| import retrofit2.http.GET | ||||
| import retrofit2.http.Query | ||||
|  | ||||
| internal interface PixabayService { | ||||
|  | ||||
|     @GET("api") | ||||
|     suspend fun searchImages( | ||||
|         @Query("q") query: String, | ||||
|         @Query("page") page: Int, | ||||
|         @Query("per_page") pageSize: Int, | ||||
|         @Query("key") key: String = BuildConfig.PIXABAY_API_KEY | ||||
|         query: String, | ||||
|         page: Int, | ||||
|         pageSize: Int | ||||
|     ): SearchPixabayImagesResponse | ||||
|  | ||||
|     @GET("api") | ||||
|     suspend fun searchImageById( | ||||
|         @Query("id") query: String, | ||||
|         @Query("key") key: String = BuildConfig.PIXABAY_API_KEY | ||||
|         id: String, | ||||
|     ): SearchPixabayImagesResponse | ||||
| } | ||||
| @@ -5,7 +5,6 @@ import androidx.paging.PagingState | ||||
| import dev.adriankuta.pixabay.data.model.PixabayImage | ||||
| import dev.adriankuta.pixabay.data.network.PixabayService | ||||
| import dev.adriankuta.pixabay.data.repository.PixabayImageRepository.Companion.NETWORK_PAGE_SIZE | ||||
| import retrofit2.HttpException | ||||
| import java.io.IOException | ||||
|  | ||||
| private const val STARTING_PAGE_INDEX = 1 | ||||
| @@ -34,8 +33,6 @@ internal class PixabayPagingSource( | ||||
|             ) | ||||
|         } catch (exception: IOException) { | ||||
|             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.RemoteMediator | ||||
| import androidx.room.withTransaction | ||||
| import dev.adriankuta.pixabay.data.model.PixabayImage | ||||
| import dev.adriankuta.pixabay.data.network.PixabayService | ||||
| import dev.adriankuta.pixabay.data.room.AppDatabase | ||||
| import dev.adriankuta.pixabay.data.room.entity.PixabayImageEntity | ||||
| import dev.adriankuta.pixabay.data.room.entity.RemoteKeys | ||||
| import retrofit2.HttpException | ||||
| import java.io.IOException | ||||
|  | ||||
| private const val STARTING_PAGE_INDEX = 1 | ||||
| @@ -78,8 +76,6 @@ internal class PixabayRemoteMediator( | ||||
|             return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) | ||||
|         } catch (exception: IOException) { | ||||
|             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 timber.log.Timber | ||||
| import javax.inject.Inject | ||||
| import javax.inject.Named | ||||
|  | ||||
| internal class PixabayImageRepository @Inject constructor( | ||||
|     private val database: AppDatabase, | ||||
| @@ -75,6 +76,6 @@ internal class PixabayImageRepository @Inject constructor( | ||||
|  | ||||
|     companion object { | ||||
|         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 | ||||
| fun PhotoDetailRoute( | ||||
|     photoId: Int, | ||||
|     onBack: () -> Unit, | ||||
|     modifier: Modifier = Modifier, | ||||
|     viewModel: PhotoDetailViewModel = hiltViewModel( | ||||
|         creationCallback = { factory: PhotoDetailsViewModelFactory -> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| [versions] | ||||
| androidxNavigation = "2.7.7" | ||||
| androidGradlePlugin = "8.1.4" | ||||
| agp = "8.6.0-beta02" | ||||
| agp = "8.6.0-rc01" | ||||
| coilCompose = "2.6.0" | ||||
| composeCompiler = "1.5.14" | ||||
| kotlin = "1.9.24" | ||||
| @@ -15,13 +15,13 @@ androidxLifecycle = "2.8.3" | ||||
| activityCompose = "1.9.0" | ||||
| composeBom = "2024.06.00" | ||||
| kotlinxSerializationJson = "1.6.0" | ||||
| ktor = "2.3.12" | ||||
| okhttpBom = "4.12.0" | ||||
| pagingCompose = "3.3.0" | ||||
| retrofit2KotlinxSerializationConverter = "1.0.0" | ||||
| room = "2.6.1" | ||||
| slf4jAndroid = "1.7.36" | ||||
| timber = "5.0.1" | ||||
| hilt = "2.51.1" | ||||
| retrofit = "2.11.0" | ||||
| ksp = "1.9.24-1.0.20" | ||||
|  | ||||
| [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-material3 = { group = "androidx.compose.material3", name = "material3" } | ||||
| 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" } | ||||
| okhttp = { module = "com.squareup.okhttp3:okhttp" } | ||||
| 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" } | ||||
| hilt-android = { group = "com.google.dagger", name = "hilt-android", 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] | ||||
| android-application = { id = "com.android.application", version.ref = "agp" } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user