mirror of
https://github.com/AdrianKuta/android-challange-adrian-kuta.git
synced 2025-07-02 05:37:59 +02:00
feat: Implement airport data fetching and display
This commit introduces the functionality to fetch airport data from the Ryanair API and display it in the UI. Key changes: - Added `AirportInfoModel` interface and its implementation `AirportInfoModelImpl` to represent airport data in the data layer. - Created `AirportInfoModelMapper` to map `AirportResponse` to `AirportInfoModel`. - Set the base URL for Retrofit in `NetworkModule` to `https://services-api.ryanair.com`. - Added new Gradle modules: `model:data:shared`, `model:datasource:airports`, `model:datasource:shared`, and `domain:search`. - Implemented `CacheImpl` in `model:data:shared` for generic caching. - Defined `ObserveAirportsUseCase` interface in `domain:search` to observe airport data. - Added Detekt configuration for the `domain:search` and `model:datasource:shared` modules. - Created `AirportsDatasource` interface and its implementation `AirportsDatasourceImpl` to manage airport data. - Implemented `AirportInfoDomainMapper` to map `AirportInfoModel` to the domain `AirportInfo`. - Updated `HomeScreen` to display a list of airports using `LazyColumn`. - Updated `HomeScreenViewModel` to fetch and expose airport data via `ObserveAirportsUseCase`. - Added `ObserveAirportsUseCaseModule` to provide the `ObserveAirportsUseCase` implementation. - Implemented `ObserveAirportsUseCaseImpl` in the repository layer to fetch data from the API and update the datasource. - Added `kotlinx-datetime` dependency. - Created `AirportsDatasourceModule` to provide `AirportsDatasource` and its implementation. - Added `CacheObservers.kt` with a utility function `loadData` to handle cache observation and data loading logic. - Updated dependencies in `model:data:simple`, `model:repository`, and `ui:home` build files. - Updated `settings.gradle.kts` to include new modules. - Defined `Cache` interface in `model:datasource:shared`.
This commit is contained in:
@ -8,7 +8,12 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.timber)
|
||||
implementation(projects.domain.search)
|
||||
implementation(projects.model.data.api)
|
||||
implementation(projects.model.datasource.airports)
|
||||
|
||||
testImplementation("io.mockk:mockk:1.13.8")
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
|
||||
testImplementation(libs.mockk.android)
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package dev.adriankuta.flights.model.repository.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.adriankuta.flights.domain.search.ObserveAirportsUseCase
|
||||
import dev.adriankuta.flights.model.repository.usecases.ObserveAirportsUseCaseImpl
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Suppress("UnnecessaryAbstractClass")
|
||||
internal abstract class ObserveAirportsUseCaseModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bind(
|
||||
observeAirportsUseCaseImpl: ObserveAirportsUseCaseImpl
|
||||
): ObserveAirportsUseCase
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package dev.adriankuta.flights.model.repository.mappers
|
||||
|
||||
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||
import dev.adriankuta.flights.domain.types.City
|
||||
import dev.adriankuta.flights.domain.types.Coordinates
|
||||
import dev.adriankuta.flights.domain.types.Country
|
||||
import dev.adriankuta.flights.domain.types.MacCity
|
||||
import dev.adriankuta.flights.domain.types.Region
|
||||
import dev.adriankuta.flights.model.datasource.airports.entities.AirportInfoModel
|
||||
|
||||
internal fun AirportInfoModel.toDomain(): AirportInfo {
|
||||
return AirportInfo(
|
||||
code = code,
|
||||
name = name,
|
||||
seoName = seoName,
|
||||
isBase = isBase,
|
||||
timeZone = timeZone,
|
||||
city = City(
|
||||
code = cityCode,
|
||||
name = cityName
|
||||
),
|
||||
macCity = MacCity(
|
||||
macCode = macCode
|
||||
),
|
||||
region = Region(
|
||||
code = regionCode,
|
||||
name = regionName
|
||||
),
|
||||
country = Country(
|
||||
code = countryCode,
|
||||
name = countryName,
|
||||
currencyCode = countryCurrencyCode
|
||||
),
|
||||
coordinates = Coordinates(
|
||||
latitude = latitude,
|
||||
longitude = longitude
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dev.adriankuta.flights.model.repository.usecases
|
||||
|
||||
import dev.adriankuta.flights.domain.search.ObserveAirportsUseCase
|
||||
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||
import dev.adriankuta.flights.model.datasource.airports.AirportsDatasource
|
||||
import dev.adriankuta.flights.model.datasource.airports.entities.AirportInfoModel
|
||||
import dev.adriankuta.flights.model.repository.mappers.toDomain
|
||||
import dev.adriankuta.flights.model.repository.utilities.loadData
|
||||
import dev.adriankuta.model.data.api.AirportService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ObserveAirportsUseCaseImpl @Inject constructor(
|
||||
private val airportService: AirportService,
|
||||
private val airportsDatasource: AirportsDatasource,
|
||||
) : ObserveAirportsUseCase {
|
||||
override fun invoke(): Flow<List<AirportInfo>?> = loadData(
|
||||
onCacheInvalidated = {
|
||||
val response = airportService.getAirports("PL")
|
||||
airportsDatasource.setAirportsInfo(response, "PL")
|
||||
},
|
||||
observeCache = {
|
||||
airportsDatasource.airports
|
||||
},
|
||||
mapToDomain = {
|
||||
it.orEmpty().map(AirportInfoModel::toDomain)
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package dev.adriankuta.flights.model.repository.utilities
|
||||
|
||||
import dev.adriankuta.flights.model.datasource.shared.Cache
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
internal fun <T, R> loadData(
|
||||
onCacheInvalidated: suspend (cacheKey: String) -> Unit,
|
||||
observeCache: () -> Flow<Cache<T>>,
|
||||
mapToDomain: (T?) -> R?,
|
||||
): Flow<R?> {
|
||||
return observeCache().distinctUntilChanged().map {
|
||||
if (it.cacheKey == null) {
|
||||
val refreshTime = Clock.System.now()
|
||||
onCacheInvalidated(it.cacheKey ?: refreshTime.toEpochMilliseconds().toString())
|
||||
}
|
||||
mapToDomain(it.data)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user