mirror of
https://github.com/AdrianKuta/android-challange-adrian-kuta.git
synced 2025-07-01 20:28:00 +02:00
feat: Add Retrofit and Moshi for network operations
This commit introduces Retrofit and Moshi to handle network requests and JSON parsing. Key changes include: - Added Retrofit, Moshi, OkHttp, and OkHttp Logging Interceptor dependencies to `libs.versions.toml` and `model/data/api/build.gradle.kts`. - Created data classes for API responses: `AirportResponse`, `RouteResponse`, and `FlightResponse`. - Defined Retrofit service interfaces: `AirportService`, `RoutesService`, and `FlightService`. - Implemented a Hilt `NetworkModule` to provide Retrofit, Moshi, and OkHttpClient instances. - Added a Detekt configuration file for the `model/data/api` module. - Temporarily commented out `configureFlavors` in `AndroidApplicationConvention.kt` and `ConfigureLibrary.kt`.
This commit is contained in:
@ -57,7 +57,7 @@ class AndroidApplicationConvention : Plugin<Project> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configureFlavors(this)
|
//configureFlavors(this)
|
||||||
configureAndroidLint(this)
|
configureAndroidLint(this)
|
||||||
// configureGradleManagedDevices(this)
|
// configureGradleManagedDevices(this)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ internal fun Project.configureLibrary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configureFlavors(this)
|
//configureFlavors(this)
|
||||||
configureAndroidLint(this)
|
configureAndroidLint(this)
|
||||||
// configureGradleManagedDevices(this)
|
// configureGradleManagedDevices(this)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ androidxLifecycle = "2.9.0"
|
|||||||
androidxNavigation = "2.9.0"
|
androidxNavigation = "2.9.0"
|
||||||
appUpdateKtx = "2.1.0"
|
appUpdateKtx = "2.1.0"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
|
converterMoshi = "2.11.0"
|
||||||
coreTest = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
|
coreTest = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
|
||||||
detekt = "1.23.8" # https://detekt.dev/changelog
|
detekt = "1.23.8" # https://detekt.dev/changelog
|
||||||
detektCompose = "0.4.22" # https://github.com/mrmans0n/compose-rules/releases
|
detektCompose = "0.4.22" # https://github.com/mrmans0n/compose-rules/releases
|
||||||
@ -31,6 +32,8 @@ kotlinxSerializationJson = "1.8.1"
|
|||||||
ksp = "2.1.21-2.0.1" # https://github.com/google/ksp/releases
|
ksp = "2.1.21-2.0.1" # https://github.com/google/ksp/releases
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
mockk = "1.14.2" # https://github.com/mockk/mockk/releases
|
mockk = "1.14.2" # https://github.com/mockk/mockk/releases
|
||||||
|
okhttpBom = "4.12.0"
|
||||||
|
retrofit = "3.0.0"
|
||||||
secrets = "2.0.1"
|
secrets = "2.0.1"
|
||||||
testRules = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
|
testRules = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
|
||||||
testRunner = "1.6.2" # https://developer.android.com/jetpack/androidx/releases/test
|
testRunner = "1.6.2" # https://developer.android.com/jetpack/androidx/releases/test
|
||||||
@ -69,6 +72,7 @@ androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator",
|
|||||||
androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
|
androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
|
||||||
app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "appUpdateKtx" }
|
app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "appUpdateKtx" }
|
||||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "converterMoshi" }
|
||||||
detekt-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" }
|
detekt-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" }
|
||||||
detekt-ktlint = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
detekt-ktlint = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
||||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
@ -83,6 +87,10 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t
|
|||||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
||||||
|
okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
||||||
|
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
|
||||||
|
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
|
||||||
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
|
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
|
||||||
truth = { module = "com.google.truth:truth", version.ref = "truth" }
|
truth = { module = "com.google.truth:truth", version.ref = "truth" }
|
||||||
|
|
||||||
|
@ -10,6 +10,13 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.core.util)
|
implementation(projects.core.util)
|
||||||
|
|
||||||
|
implementation(platform(libs.okhttp.bom))
|
||||||
|
|
||||||
|
implementation(libs.converter.moshi)
|
||||||
|
implementation(libs.okhttp)
|
||||||
|
implementation(libs.okhttp.logging.interceptor)
|
||||||
|
implementation(libs.retrofit)
|
||||||
|
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
}
|
}
|
||||||
|
10
model/data/api/config/detekt/detekt.yml
Normal file
10
model/data/api/config/detekt/detekt.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Deviations from defaults
|
||||||
|
formatting:
|
||||||
|
TrailingCommaOnCallSite:
|
||||||
|
active: true
|
||||||
|
autoCorrect: true
|
||||||
|
useTrailingCommaOnCallSite: true
|
||||||
|
TrailingCommaOnDeclarationSite:
|
||||||
|
active: true
|
||||||
|
autoCorrect: true
|
||||||
|
useTrailingCommaOnDeclarationSite: true
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.adriankuta.model.data.api
|
||||||
|
|
||||||
|
import dev.adriankuta.model.data.api.entities.AirportResponse
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
interface AirportService {
|
||||||
|
|
||||||
|
@GET("/views/locate/5/airports/{language}/active")
|
||||||
|
suspend fun getAirports(@Path("language") languageCode: String): List<AirportResponse>
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package dev.adriankuta.model.data.api
|
||||||
|
|
||||||
|
import dev.adriankuta.model.data.api.entities.FlightResponse
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Headers
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface FlightService {
|
||||||
|
|
||||||
|
// example query: v4/en-gb/Availability?dateout=2025-09-17&origin=WRO&destination=DUB&adt=1&ToUs=AGREED
|
||||||
|
@Headers(
|
||||||
|
"client: android",
|
||||||
|
"client-version: 3.300.0"
|
||||||
|
)
|
||||||
|
@GET("v4/en-gb/Availability")
|
||||||
|
suspend fun getFlights(
|
||||||
|
@Query("dateout") date: String,
|
||||||
|
@Query("origin") origin: String,
|
||||||
|
@Query("destination") destination: String,
|
||||||
|
@Query("adt") adult: Int,
|
||||||
|
@Query("teen") teen: Int,
|
||||||
|
@Query("chd") child: Int,
|
||||||
|
@Query("inf") inf: Int = 0,
|
||||||
|
@Query("ToUs") toUs: String = "AGREED"
|
||||||
|
): FlightResponse
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.adriankuta.model.data.api
|
||||||
|
|
||||||
|
import dev.adriankuta.model.data.api.entities.RouteResponse
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
interface RoutesService {
|
||||||
|
|
||||||
|
@GET("/views/locate/5/routes/{language}")
|
||||||
|
suspend fun getAllRoutes(
|
||||||
|
@Path("language") languageCode: String,
|
||||||
|
): List<RouteResponse>
|
||||||
|
|
||||||
|
@GET("/views/locate/5/routes/{language}/airport/{departure}")
|
||||||
|
suspend fun getRoutes(
|
||||||
|
@Path("language") languageCode: String,
|
||||||
|
@Path("departure") departureAirportCode: String,
|
||||||
|
): List<RouteResponse>
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package dev.adriankuta.model.data.api.di
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dev.adriankuta.model.data.api.AirportService
|
||||||
|
import dev.adriankuta.model.data.api.FlightService
|
||||||
|
import dev.adriankuta.model.data.api.RoutesService
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class NetworkModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideMoshi() = Moshi.Builder().build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideHttpLoggingInterceptor() = HttpLoggingInterceptor().apply {
|
||||||
|
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesOkHttpClient(loggingInterceptor: HttpLoggingInterceptor) =
|
||||||
|
OkHttpClient.Builder().also { builder ->
|
||||||
|
builder.addInterceptor(loggingInterceptor)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit = Retrofit.Builder()
|
||||||
|
// TODO("add URL provided in the task instructions")
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.client(client)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAirportService(retrofit: Retrofit): AirportService =
|
||||||
|
retrofit.create(AirportService::class.java)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideRoutesService(retrofit: Retrofit): RoutesService =
|
||||||
|
retrofit.create(RoutesService::class.java)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideFlightService(retrofit: Retrofit): FlightService =
|
||||||
|
retrofit.create(FlightService::class.java)
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package dev.adriankuta.model.data.api.entities
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class AirportResponse(
|
||||||
|
@Json(name = "code") val code: String?,
|
||||||
|
@Json(name = "name") val name: String?,
|
||||||
|
@Json(name = "seoName") val seoName: String?,
|
||||||
|
@Json(name = "base") val isBase: Boolean?,
|
||||||
|
@Json(name = "timeZone") val timeZone: String?,
|
||||||
|
@Json(name = "city") val city: City?,
|
||||||
|
@Json(name = "macCity") val macCity: MacCity?,
|
||||||
|
@Json(name = "region") val region: Region?,
|
||||||
|
@Json(name = "country") val country: Country?,
|
||||||
|
@Json(name = "coordinates") val coordinates: Coordinates?
|
||||||
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class City(
|
||||||
|
@Json(name = "code") val code: String?,
|
||||||
|
@Json(name = "name") val name: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MacCity(
|
||||||
|
@Json(name = "code") val code: String?,
|
||||||
|
@Json(name = "macCode") val macCode: String?,
|
||||||
|
@Json(name = "name") val name: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Region(
|
||||||
|
@Json(name = "code") val code: String?,
|
||||||
|
@Json(name = "name") val name: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Country(
|
||||||
|
@Json(name = "code") val code: String?,
|
||||||
|
@Json(name = "name") val name: String?,
|
||||||
|
@Json(name = "currency") val currencyCode: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Coordinates(
|
||||||
|
@Json(name = "latitude") val latitude: Double?,
|
||||||
|
@Json(name = "longitude") val longitude: Double?
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package dev.adriankuta.model.data.api.entities
|
||||||
|
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class FlightResponse(
|
||||||
|
@Json(name = "currency") val currency: String?,
|
||||||
|
@Json(name = "currPrecision") val currPrecision: Int?,
|
||||||
|
@Json(name = "trips") val trips: List<Trip>?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Trip(
|
||||||
|
@Json(name = "dates") val dates: List<TripDate>?,
|
||||||
|
@Json(name = "origin") val origin: String?,
|
||||||
|
@Json(name = "destination") val destination: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TripDate(
|
||||||
|
@Json(name = "dateOut") val dateOut: String?,
|
||||||
|
@Json(name = "flights") val flights: List<TripFlight>?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TripFlight(
|
||||||
|
@Json(name = "faresLeft") val faresLeft: Int?,
|
||||||
|
@Json(name = "regularFare") val regularFare: RegularFare?,
|
||||||
|
@Json(name = "flightNumber") val flightNumber: String?,
|
||||||
|
@Json(name = "time") val dateTimes: List<String>?,
|
||||||
|
@Json(name = "duration") val duration: String?,
|
||||||
|
@Json(name = "segments") val segments: List<Segment>?,
|
||||||
|
@Json(name = "operatedBy") val operatedBy: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RegularFare(
|
||||||
|
@Json(name = "fares") val fares: List<TripFare>?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TripFare(
|
||||||
|
@Json(name = "type") val type: String?,
|
||||||
|
@Json(name = "amount") val amount: Double?,
|
||||||
|
@Json(name = "count") val count: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Segment(
|
||||||
|
@Json(name = "origin") val origin: String?,
|
||||||
|
@Json(name = "destination") val destination: String?,
|
||||||
|
@Json(name = "flightNumber") val flightNumber: String?,
|
||||||
|
@Json(name = "time") val dateTimes: List<String>?,
|
||||||
|
@Json(name = "duration") val duration: String?
|
||||||
|
)
|
@ -0,0 +1,50 @@
|
|||||||
|
package dev.adriankuta.model.data.api.entities
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RouteResponse(
|
||||||
|
@Json(name = "departureAirport") val departureAirport: Airport.Departure?,
|
||||||
|
@Json(name = "arrivalAirport") val arrivalAirport: Airport.Arrival?,
|
||||||
|
@Json(name = "connectingAirport") val connectingAirport: Airport.Connecting?
|
||||||
|
) {
|
||||||
|
|
||||||
|
sealed interface Airport {
|
||||||
|
|
||||||
|
@Json(name = "code")
|
||||||
|
val code: String?
|
||||||
|
|
||||||
|
@Json(name = "name")
|
||||||
|
val name: String?
|
||||||
|
|
||||||
|
@Json(name = "macCity")
|
||||||
|
val macCity: MacCity?
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Departure(
|
||||||
|
override val code: String?,
|
||||||
|
override val name: String?,
|
||||||
|
override val macCity: MacCity?,
|
||||||
|
) : Airport
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Arrival(
|
||||||
|
override val code: String?,
|
||||||
|
override val name: String?,
|
||||||
|
override val macCity: MacCity?
|
||||||
|
) : Airport
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Connecting(
|
||||||
|
override val code: String?,
|
||||||
|
override val name: String?,
|
||||||
|
override val macCity: MacCity?
|
||||||
|
) : Airport
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MacCity(
|
||||||
|
@Json(name = "macCode") val macCode: String?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user