Refactor: Migrate network layer from Retrofit to Ktor
This commit migrates the network layer from Retrofit to Ktor. Specific changes include: - Replaced Retrofit with Ktor for network requests. - Updated dependencies to include Ktor libraries. - Refactored network service and data classes to use Ktor's API. - Removed Retrofit-specific code and dependencies. - Adjusted network module to provide Ktor client and services. - Updated Pixabay ImageRepository to use the new Ktor-based PixabayService. - Introduced Result sealed class to handle network responses. - Updated PhotoDetailViewModel to handle Result.Error state.
This commit is contained in:
		| @@ -4,6 +4,6 @@ | |||||||
|     android:viewportWidth="960" |     android:viewportWidth="960" | ||||||
|     android:viewportHeight="960"> |     android:viewportHeight="960"> | ||||||
|     <path |     <path | ||||||
|       android:pathData="M280,560h400q17,0 28.5,-11.5T720,520q0,-17 -11.5,-28.5T680,480L280,480q-17,0 -28.5,11.5T240,520q0,17 11.5,28.5T280,560ZM280,440h400q17,0 28.5,-11.5T720,400q0,-17 -11.5,-28.5T680,360L280,360q-17,0 -28.5,11.5T240,400q0,17 11.5,28.5T280,440ZM280,320h400q17,0 28.5,-11.5T720,280q0,-17 -11.5,-28.5T680,240L280,240q-17,0 -28.5,11.5T240,280q0,17 11.5,28.5T280,320ZM160,720q-33,0 -56.5,-23.5T80,640v-480q0,-33 23.5,-56.5T160,80h640q33,0 56.5,23.5T880,160v623q0,27 -24.5,37.5T812,812l-92,-92L160,720ZM754,640 L800,685v-525L160,160v480h594ZM160,640v-480,480Z" |         android:fillColor="#5f6368" | ||||||
|       android:fillColor="#5f6368"/> |         android:pathData="M280,560h400q17,0 28.5,-11.5T720,520q0,-17 -11.5,-28.5T680,480L280,480q-17,0 -28.5,11.5T240,520q0,17 11.5,28.5T280,560ZM280,440h400q17,0 28.5,-11.5T720,400q0,-17 -11.5,-28.5T680,360L280,360q-17,0 -28.5,11.5T240,400q0,17 11.5,28.5T280,440ZM280,320h400q17,0 28.5,-11.5T720,280q0,-17 -11.5,-28.5T680,240L280,240q-17,0 -28.5,11.5T240,280q0,17 11.5,28.5T280,320ZM160,720q-33,0 -56.5,-23.5T80,640v-480q0,-33 23.5,-56.5T160,80h640q33,0 56.5,23.5T880,160v623q0,27 -24.5,37.5T812,812l-92,-92L160,720ZM754,640 L800,685v-525L160,160v480h594ZM160,640v-480,480Z" /> | ||||||
| </vector> | </vector> | ||||||
|   | |||||||
| @@ -4,6 +4,6 @@ | |||||||
|     android:viewportWidth="960" |     android:viewportWidth="960" | ||||||
|     android:viewportHeight="960"> |     android:viewportHeight="960"> | ||||||
|     <path |     <path | ||||||
|       android:pathData="M480,623q-8,0 -15,-2.5t-13,-8.5L308,468q-12,-12 -11.5,-28t11.5,-28q12,-12 28.5,-12.5T365,411l75,75v-286q0,-17 11.5,-28.5T480,160q17,0 28.5,11.5T520,200v286l75,-75q12,-12 28.5,-11.5T652,412q11,12 11.5,28T652,468L508,612q-6,6 -13,8.5t-15,2.5ZM240,800q-33,0 -56.5,-23.5T160,720v-80q0,-17 11.5,-28.5T200,600q17,0 28.5,11.5T240,640v80h480v-80q0,-17 11.5,-28.5T760,600q17,0 28.5,11.5T800,640v80q0,33 -23.5,56.5T720,800L240,800Z" |         android:fillColor="#5f6368" | ||||||
|       android:fillColor="#5f6368"/> |         android:pathData="M480,623q-8,0 -15,-2.5t-13,-8.5L308,468q-12,-12 -11.5,-28t11.5,-28q12,-12 28.5,-12.5T365,411l75,75v-286q0,-17 11.5,-28.5T480,160q17,0 28.5,11.5T520,200v286l75,-75q12,-12 28.5,-11.5T652,412q11,12 11.5,28T652,468L508,612q-6,6 -13,8.5t-15,2.5ZM240,800q-33,0 -56.5,-23.5T160,720v-80q0,-17 11.5,-28.5T200,600q17,0 28.5,11.5T240,640v80h480v-80q0,-17 11.5,-28.5T760,600q17,0 28.5,11.5T800,640v80q0,33 -23.5,56.5T720,800L240,800Z" /> | ||||||
| </vector> | </vector> | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ android { | |||||||
| dependencies { | dependencies { | ||||||
|     implementation(libs.androidx.paging.compose) |     implementation(libs.androidx.paging.compose) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     //Kotlin Serialization | ||||||
|     implementation(libs.kotlinx.serialization.json) |     implementation(libs.kotlinx.serialization.json) | ||||||
|  |  | ||||||
|     //Ktor |     //Ktor | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.adriankuta.pixabay.data | ||||||
|  |  | ||||||
|  | fun <T> Result<T>.isSuccess() = this is Result.Success<T> | ||||||
|  |  | ||||||
|  | sealed interface Result<out T> { | ||||||
|  |     data class Success<out T>(val data: T) : Result<T> | ||||||
|  |     data class Error(val exception: Throwable) : Result<Nothing> | ||||||
|  | } | ||||||
| @@ -31,7 +31,7 @@ internal abstract class NetworkModule { | |||||||
|         @Singleton |         @Singleton | ||||||
|         @Provides |         @Provides | ||||||
|         fun provideKtorClient(): HttpClient { |         fun provideKtorClient(): HttpClient { | ||||||
|             val httpClient = HttpClient(Android) { |             return HttpClient(Android) { | ||||||
|                 install(ContentNegotiation) { |                 install(ContentNegotiation) { | ||||||
|                     json( |                     json( | ||||||
|                         Json { |                         Json { | ||||||
| @@ -53,8 +53,6 @@ internal abstract class NetworkModule { | |||||||
|                     socketTimeout = 100_000 |                     socketTimeout = 100_000 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             return httpClient |         } | ||||||
|         } |  | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -8,7 +8,6 @@ import dagger.hilt.InstallIn | |||||||
| import dagger.hilt.android.qualifiers.ApplicationContext | import dagger.hilt.android.qualifiers.ApplicationContext | ||||||
| import dagger.hilt.components.SingletonComponent | import dagger.hilt.components.SingletonComponent | ||||||
| import dev.adriankuta.pixabay.data.room.AppDatabase | import dev.adriankuta.pixabay.data.room.AppDatabase | ||||||
| import dev.adriankuta.pixabay.data.room.dao.PixabayImagesDao |  | ||||||
| import javax.inject.Singleton | import javax.inject.Singleton | ||||||
|  |  | ||||||
| @InstallIn(SingletonComponent::class) | @InstallIn(SingletonComponent::class) | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| 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 | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| package dev.adriankuta.pixabay.data.repository | package dev.adriankuta.pixabay.data.repository | ||||||
|  |  | ||||||
| import androidx.paging.PagingData | import androidx.paging.PagingData | ||||||
|  | import dev.adriankuta.pixabay.data.Result | ||||||
| import dev.adriankuta.pixabay.data.model.PixabayImage | import dev.adriankuta.pixabay.data.model.PixabayImage | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  |  | ||||||
| interface ImageRepository { | interface ImageRepository { | ||||||
|  |  | ||||||
|     suspend fun searchImageById(id: Int): PixabayImage |     suspend fun searchImageById(id: Int): Result<PixabayImage> | ||||||
|  |  | ||||||
|     suspend fun searchImages(query: String, page: Int, pageSize: Int): List<PixabayImage> |     suspend fun searchImages(query: String, page: Int, pageSize: Int): Result<List<PixabayImage>> | ||||||
|  |  | ||||||
|     fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>> |     fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>> | ||||||
| } | } | ||||||
| @@ -7,6 +7,7 @@ import androidx.paging.Pager | |||||||
| import androidx.paging.PagingConfig | import androidx.paging.PagingConfig | ||||||
| import androidx.paging.PagingData | import androidx.paging.PagingData | ||||||
| import androidx.paging.map | import androidx.paging.map | ||||||
|  | import dev.adriankuta.pixabay.data.Result | ||||||
| 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.paging.PixabayPagingSource | import dev.adriankuta.pixabay.data.paging.PixabayPagingSource | ||||||
| @@ -16,31 +17,38 @@ 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, | ||||||
|     private val pixabayService: PixabayService |     private val pixabayService: PixabayService | ||||||
| ) : ImageRepository { | ) : ImageRepository { | ||||||
|  |  | ||||||
|     override suspend fun searchImageById(id: Int): PixabayImage { |     override suspend fun searchImageById(id: Int): Result<PixabayImage> { | ||||||
|         return runCatching { |         return runCatching { | ||||||
|             pixabayService.searchImageById(id.toString()) |             pixabayService.searchImageById(id.toString()) | ||||||
|         }.mapCatching { response -> |         }.mapCatching { response -> | ||||||
|             PixabayImage(response.hits.first()) |             Result.Success(PixabayImage(response.hits.first())) | ||||||
|         }.onFailure { e -> |         }.onFailure { e -> | ||||||
|             Timber.e(e) |             Timber.e(e) | ||||||
|         }.getOrThrow() |         }.getOrElse { | ||||||
|  |             Result.Error(it) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override suspend fun searchImages(query: String, page: Int, pageSize: Int): List<PixabayImage> { |     override suspend fun searchImages( | ||||||
|  |         query: String, | ||||||
|  |         page: Int, | ||||||
|  |         pageSize: Int | ||||||
|  |     ): Result<List<PixabayImage>> { | ||||||
|         return runCatching { |         return runCatching { | ||||||
|             pixabayService.searchImages(query, page, pageSize) |             pixabayService.searchImages(query, page, pageSize) | ||||||
|         }.mapCatching { response -> |         }.mapCatching { response -> | ||||||
|             response.hits.map { PixabayImage(it) } |             Result.Success(response.hits.map { PixabayImage(it) }) | ||||||
|         }.onFailure { e -> |         }.onFailure { e -> | ||||||
|             Timber.e(e) |             Timber.e(e) | ||||||
|         }.getOrThrow() |         }.getOrElse { | ||||||
|  |             Result.Error(it) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>> { |     override fun getSearchResultStream(query: String): Flow<PagingData<PixabayImage>> { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope | |||||||
| import dagger.assisted.Assisted | import dagger.assisted.Assisted | ||||||
| import dagger.assisted.AssistedInject | import dagger.assisted.AssistedInject | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import dev.adriankuta.pixabay.data.Result | ||||||
| import dev.adriankuta.pixabay.data.repository.ImageRepository | import dev.adriankuta.pixabay.data.repository.ImageRepository | ||||||
| import dev.adriankuta.pixabay.feature.details.di.PhotoDetailsViewModelFactory | import dev.adriankuta.pixabay.feature.details.di.PhotoDetailsViewModelFactory | ||||||
| import kotlinx.coroutines.flow.SharingStarted | import kotlinx.coroutines.flow.SharingStarted | ||||||
| @@ -23,7 +24,12 @@ class PhotoDetailViewModel @AssistedInject constructor( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     val uiState = loadedData |     val uiState = loadedData | ||||||
|         .map { PhotoDetailUiState.Success(it) } |         .map { | ||||||
|  |             when (it) { | ||||||
|  |                 is Result.Success -> PhotoDetailUiState.Success(it.data) | ||||||
|  |                 is Result.Error -> PhotoDetailUiState.Error | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         .stateIn( |         .stateIn( | ||||||
|             scope = viewModelScope, |             scope = viewModelScope, | ||||||
|             started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000), |             started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000), | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier | |||||||
| import androidx.compose.ui.layout.ContentScale | import androidx.compose.ui.layout.ContentScale | ||||||
| import androidx.compose.ui.platform.testTag | import androidx.compose.ui.platform.testTag | ||||||
| import androidx.compose.ui.res.stringResource | import androidx.compose.ui.res.stringResource | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel | import androidx.hilt.navigation.compose.hiltViewModel | ||||||
| import androidx.paging.LoadState | import androidx.paging.LoadState | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| package dev.adriankuta.pixabay.feature.search | package dev.adriankuta.pixabay.feature.search | ||||||
|  |  | ||||||
| import dev.adriankuta.pixabay.data.model.PixabayImage |  | ||||||
|  |  | ||||||
|  |  | ||||||
| data class SearchUiState( | data class SearchUiState( | ||||||
|     val query: String = "" |     val query: String = "" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user