mirror of
				https://github.com/AdrianKuta/android-challange-adrian-kuta.git
				synced 2025-10-31 05:43:40 +01: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