From afdffae39bd2cebdf2a0a0b219c166a162ca0b64 Mon Sep 17 00:00:00 2001 From: morz Date: Wed, 27 Aug 2025 11:30:43 +0330 Subject: [PATCH 1/2] refactor: restructure network module using DI and separate interfaces per entity API --- .../core/network/api/NewsResourceApi.kt | 38 +++++++++++ .../nowinandroid/core/network/api/TopicApi.kt | 38 +++++++++++ .../nowinandroid/core/network/di/ApiModule.kt | 49 +++++++++++++++ .../core/network/di/NetworkModule.kt | 23 +++++++ .../network/retrofit/RetrofitNiaNetwork.kt | 63 +++---------------- 5 files changed, 158 insertions(+), 53 deletions(-) create mode 100644 core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/NewsResourceApi.kt create mode 100644 core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/TopicApi.kt create mode 100644 core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/ApiModule.kt diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/NewsResourceApi.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/NewsResourceApi.kt new file mode 100644 index 000000000..9fca7ec24 --- /dev/null +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/NewsResourceApi.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.network.api + +import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList +import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource +import com.google.samples.apps.nowinandroid.core.network.retrofit.NetworkResponse +import retrofit2.http.GET +import retrofit2.http.Query + +/** + * Retrofit API declaration for News Resource API + */ +internal interface NewsResourceApi { + @GET(value = "newsresources") + suspend fun getNewsResources( + @Query("id") ids: List?, + ): NetworkResponse> + + @GET(value = "changelists/newsresources") + suspend fun getNewsResourcesChangeList( + @Query("after") after: Int?, + ): List +} diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/TopicApi.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/TopicApi.kt new file mode 100644 index 000000000..39cf4a2b8 --- /dev/null +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/api/TopicApi.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.network.api + +import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList +import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic +import com.google.samples.apps.nowinandroid.core.network.retrofit.NetworkResponse +import retrofit2.http.GET +import retrofit2.http.Query + +/** + * Retrofit API declaration for Topic API + */ +internal interface TopicApi { + @GET(value = "topics") + suspend fun getTopics( + @Query("id") ids: List?, + ): NetworkResponse> + + @GET(value = "changelists/topics") + suspend fun getTopicChangeList( + @Query("after") after: Int?, + ): List +} diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/ApiModule.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/ApiModule.kt new file mode 100644 index 000000000..9b3d7598e --- /dev/null +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/ApiModule.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.core.network.di + +import androidx.tracing.trace +import com.google.samples.apps.nowinandroid.core.network.api.NewsResourceApi +import com.google.samples.apps.nowinandroid.core.network.api.TopicApi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import javax.inject.Singleton +import kotlin.jvm.java + +@Module +@InstallIn(SingletonComponent::class) +internal object ApiModule { + + @Provides + @Singleton + internal fun providesTopicApi(retrofit: Retrofit): TopicApi { + trace("RetrofitNiaNetwork") { + return retrofit.create(TopicApi::class.java) + } + } + + @Provides + @Singleton + internal fun providesNewsResourceApi(retrofit: Retrofit): NewsResourceApi { + trace("RetrofitNiaNetwork") { + return retrofit.create(NewsResourceApi::class.java) + } + } +} diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt index a97540f2b..736ed21df 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt @@ -23,6 +23,7 @@ import coil.decode.SvgDecoder import coil.util.DebugLogger import com.google.samples.apps.nowinandroid.core.network.BuildConfig import com.google.samples.apps.nowinandroid.core.network.demo.DemoAssetManager +import com.google.samples.apps.nowinandroid.core.network.retrofit.NIA_BASE_URL import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -30,8 +31,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json import okhttp3.Call +import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory import javax.inject.Singleton @Module @@ -92,4 +96,23 @@ internal object NetworkModule { } .build() } + + @Provides + @Singleton + fun provideRetrofit( + networkJson: Json, + okhttpCallFactory: dagger.Lazy, + ): Retrofit { + trace("RetrofitNiaNetwork") { + return Retrofit.Builder() + .baseUrl(NIA_BASE_URL) + // We use callFactory lambda here with dagger.Lazy + // to prevent initializing OkHttp on the main thread. + .callFactory { okhttpCallFactory.get().newCall(it) } + .addConverterFactory( + networkJson.asConverterFactory("application/json".toMediaType()), + ) + .build() + } + } } diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt index bdd852f8b..3d60ffc49 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt @@ -16,55 +16,25 @@ package com.google.samples.apps.nowinandroid.core.network.retrofit -import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.network.BuildConfig import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource +import com.google.samples.apps.nowinandroid.core.network.api.NewsResourceApi +import com.google.samples.apps.nowinandroid.core.network.api.TopicApi import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import okhttp3.Call -import okhttp3.MediaType.Companion.toMediaType import retrofit2.Retrofit -import retrofit2.converter.kotlinx.serialization.asConverterFactory -import retrofit2.http.GET -import retrofit2.http.Query import javax.inject.Inject import javax.inject.Singleton -/** - * Retrofit API declaration for NIA Network API - */ -private interface RetrofitNiaNetworkApi { - @GET(value = "topics") - suspend fun getTopics( - @Query("id") ids: List?, - ): NetworkResponse> - - @GET(value = "newsresources") - suspend fun getNewsResources( - @Query("id") ids: List?, - ): NetworkResponse> - - @GET(value = "changelists/topics") - suspend fun getTopicChangeList( - @Query("after") after: Int?, - ): List - - @GET(value = "changelists/newsresources") - suspend fun getNewsResourcesChangeList( - @Query("after") after: Int?, - ): List -} - -private const val NIA_BASE_URL = BuildConfig.BACKEND_URL +internal const val NIA_BASE_URL = BuildConfig.BACKEND_URL /** * Wrapper for data provided from the [NIA_BASE_URL] */ @Serializable -private data class NetworkResponse( +internal data class NetworkResponse( val data: T, ) @@ -73,32 +43,19 @@ private data class NetworkResponse( */ @Singleton internal class RetrofitNiaNetwork @Inject constructor( - networkJson: Json, - okhttpCallFactory: dagger.Lazy, + val newsResourceApi: NewsResourceApi, + val topicApi: TopicApi, ) : NiaNetworkDataSource { - private val networkApi = trace("RetrofitNiaNetwork") { - Retrofit.Builder() - .baseUrl(NIA_BASE_URL) - // We use callFactory lambda here with dagger.Lazy - // to prevent initializing OkHttp on the main thread. - .callFactory { okhttpCallFactory.get().newCall(it) } - .addConverterFactory( - networkJson.asConverterFactory("application/json".toMediaType()), - ) - .build() - .create(RetrofitNiaNetworkApi::class.java) - } - override suspend fun getTopics(ids: List?): List = - networkApi.getTopics(ids = ids).data + topicApi.getTopics(ids = ids).data override suspend fun getNewsResources(ids: List?): List = - networkApi.getNewsResources(ids = ids).data + newsResourceApi.getNewsResources(ids = ids).data override suspend fun getTopicChangeList(after: Int?): List = - networkApi.getTopicChangeList(after = after) + topicApi.getTopicChangeList(after = after) override suspend fun getNewsResourceChangeList(after: Int?): List = - networkApi.getNewsResourcesChangeList(after = after) + newsResourceApi.getNewsResourcesChangeList(after = after) }