From 1b8d0e836c24ec41c9a9197135d923aadd1ed2f0 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 21:10:35 +0300 Subject: [PATCH] Separate Hilt and Koin into their own product flavours --- app/build.gradle | 37 ++++++- app/src/hilt/AndroidManifest.xml | 22 +++++ .../test/showcase/TestShowcaseApplication.kt | 13 +++ .../org/fnives/test/showcase/di/AppModule.kt | 53 ++++++++++ .../test/showcase/ui/IntentCoordinator.kt | 14 +++ .../test/showcase/ui/ViewModelDelegate.kt | 12 +++ .../test/showcase/ui/auth/HiltAuthActivity.kt | 12 +++ .../test/showcase/ui/home/HiltMainActivity.kt | 12 +++ .../showcase/ui/splash/HiltSplashActivity.kt | 6 ++ app/src/koin/AndroidManifest.xml | 21 ++++ .../test/showcase/TestShowcaseApplication.kt | 0 .../test/showcase/di/createAppModules.kt | 2 +- .../test/showcase/ui/IntentCoordinator.kt | 14 +++ .../test/showcase/ui/ViewModelDelegate.kt | 8 ++ app/src/main/AndroidManifest.xml | 15 +-- .../session/SessionExpirationListenerImpl.kt | 11 ++- .../storage/SharedPreferencesManagerImpl.kt | 2 +- .../FavouriteContentLocalStorageImpl.kt | 3 +- .../test/showcase/ui/auth/AuthActivity.kt | 10 +- .../test/showcase/ui/auth/AuthViewModel.kt | 5 +- .../test/showcase/ui/home/MainActivity.kt | 11 ++- .../test/showcase/ui/home/MainViewModel.kt | 5 +- .../test/showcase/ui/splash/SplashActivity.kt | 13 ++- .../showcase/ui/splash/SplashViewModel.kt | 5 +- build.gradle | 13 ++- core/build.gradle | 11 +++ .../content/AddContentToFavouriteUseCase.kt | 3 +- .../core/content/ContentRepository.kt | 3 +- .../core/content/FetchContentUseCase.kt | 4 +- .../core/content/GetAllContentUseCase.kt | 3 +- .../RemoveContentFromFavouritesUseCase.kt | 3 +- .../test/showcase/core/di/hilt/CoreModule.kt | 34 +++++++ .../core/di/hilt/LoggedInModuleInject.kt | 8 ++ .../core/di/{ => koin}/createCoreModule.kt | 6 +- .../core/login/IsUserLoggedInUseCase.kt | 5 +- .../test/showcase/core/login/LoginUseCase.kt | 3 +- .../test/showcase/core/login/LogoutUseCase.kt | 15 ++- .../core/session/SessionExpirationAdapter.kt | 6 +- .../NetworkSessionLocalStorageAdapter.kt | 3 +- .../showcase/core/login/LogoutUseCaseTest.kt | 2 +- gradlescripts/versions.gradle | 2 + network/build.gradle | 2 + .../network/auth/LoginErrorConverter.kt | 3 +- .../network/auth/LoginRemoteSourceImpl.kt | 3 +- .../content/ContentRemoteSourceImpl.kt | 3 +- .../network/di/hilt/HiltNetworkModule.kt | 96 +++++++++++++++++++ .../network/di/hilt/SessionLessQualifier.kt | 6 ++ .../network/di/hilt/SessionQualifier.kt | 6 ++ .../di/{ => koin}/createNetworkmodules.kt | 3 +- .../session/AuthenticationHeaderUtils.kt | 3 +- .../network/session/SessionAuthenticator.kt | 3 +- .../LoginRemoteSourceRefreshActionImplTest.kt | 2 +- .../network/auth/LoginRemoteSourceTest.kt | 2 +- .../content/CodeKataSessionExpirationTest.kt | 2 +- .../content/ContentRemoteSourceImplTest.kt | 2 +- .../network/content/SessionExpirationTest.kt | 2 +- 56 files changed, 496 insertions(+), 72 deletions(-) create mode 100644 app/src/hilt/AndroidManifest.xml create mode 100644 app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt create mode 100644 app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt create mode 100644 app/src/koin/AndroidManifest.xml rename app/src/{main => koin}/java/org/fnives/test/showcase/TestShowcaseApplication.kt (100%) rename app/src/{main => koin}/java/org/fnives/test/showcase/di/createAppModules.kt (96%) create mode 100644 app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt create mode 100644 app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt create mode 100644 core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt create mode 100644 core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt rename core/src/main/java/org/fnives/test/showcase/core/di/{ => koin}/createCoreModule.kt (94%) create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt create mode 100644 network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt rename network/src/main/java/org/fnives/test/showcase/network/di/{ => koin}/createNetworkmodules.kt (97%) diff --git a/app/build.gradle b/app/build.gradle index a390928..7371495 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,8 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + // hilt specific + id 'dagger.hilt.android.plugin' } android { @@ -25,6 +27,17 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + flavorDimensions 'di' + productFlavors { + hilt { + dimension 'di' + applicationId "org.fnives.test.showcase.hilt" + } + koin { + dimension 'di' + applicationId "org.fnives.test.showcase.koin" + } + } buildFeatures { viewBinding true @@ -49,10 +62,17 @@ android { } } +hilt { + enableAggregatingTask = true + enableExperimentalClasspathAggregation = true +} + afterEvaluate { // making sure the :mockserver is assembled after :clean when running tests - testDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') - testReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testKoinDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testKoinReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testHiltDebugUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') + testHiltReleaseUnitTest.dependsOn tasks.getByPath(':mockserver:assemble') } dependencies { @@ -66,7 +86,14 @@ dependencies { implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version" // Koin - implementation "io.insert-koin:koin-android:$koin_version" + koinImplementation "io.insert-koin:koin-android:$koin_version" + + // Hilt +// hiltImplementation "com.google.dagger:hilt-android:$hilt_version" + implementation "com.google.dagger:hilt-android:$hilt_version" +// implementation "com.google.dagger:hilt-core:$hilt_version" + kaptHilt "com.google.dagger:hilt-compiler:$hilt_version" + hiltImplementation "androidx.activity:activity-ktx:$activity_ktx_version" implementation "androidx.room:room-runtime:$androidx_room_version" kapt "androidx.room:room-compiler:$androidx_room_version" @@ -98,6 +125,8 @@ dependencies { testImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" + testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptTest "com.google.dagger:hilt-compiler:$hilt_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" androidTestImplementation "io.insert-koin:koin-test-junit4:$koin_version" @@ -111,4 +140,6 @@ dependencies { androidTestImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" + androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" } \ No newline at end of file diff --git a/app/src/hilt/AndroidManifest.xml b/app/src/hilt/AndroidManifest.xml new file mode 100644 index 0000000..d65911d --- /dev/null +++ b/app/src/hilt/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt new file mode 100644 index 0000000..a3b36fe --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/TestShowcaseApplication.kt @@ -0,0 +1,13 @@ +package org.fnives.test.showcase + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class TestShowcaseApplication : Application() { + + override fun onCreate() { + super.onCreate() + + } +} diff --git a/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt new file mode 100644 index 0000000..57fa755 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/di/AppModule.kt @@ -0,0 +1,53 @@ +package org.fnives.test.showcase.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.fnives.test.showcase.core.session.SessionExpirationListener +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage +import org.fnives.test.showcase.session.SessionExpirationListenerImpl +import org.fnives.test.showcase.storage.SharedPreferencesManagerImpl +import org.fnives.test.showcase.storage.database.DatabaseInitialization +import org.fnives.test.showcase.storage.favourite.FavouriteContentLocalStorageImpl +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object AppModule { + + @Provides + fun provideBaseUrl(): String = BaseUrlProvider.get().baseUrl + + @Provides + fun enableLogging(): Boolean = true + + @Singleton + @Provides + fun provideFavouriteDao(@ApplicationContext context: Context) = + DatabaseInitialization.create(context).favouriteDao + + @Provides + fun provideSharedPreferencesManagerImpl(@ApplicationContext context: Context) = + SharedPreferencesManagerImpl.create(context) + + @Singleton + @Provides + fun provideUserDataLocalStorage( + sharedPreferencesManagerImpl: SharedPreferencesManagerImpl + ): UserDataLocalStorage = sharedPreferencesManagerImpl + + @Provides + fun provideFavouriteContentLocalStorage( + favouriteContentLocalStorageImpl: FavouriteContentLocalStorageImpl + ): FavouriteContentLocalStorage = favouriteContentLocalStorageImpl + + @Provides + internal fun bindSessionExpirationListener( + sessionExpirationListenerImpl: SessionExpirationListenerImpl + ) : SessionExpirationListener = sessionExpirationListenerImpl + +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt new file mode 100644 index 0000000..a7d3f6e --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import android.content.Context +import org.fnives.test.showcase.ui.auth.HiltAuthActivity +import org.fnives.test.showcase.ui.home.HiltMainActivity + +object IntentCoordinator { + + fun mainActivitygetStartIntent(context: Context) = + HiltMainActivity.getStartIntent(context) + + fun authActivitygetStartIntent(context: Context) = + HiltAuthActivity.getStartIntent(context) +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt new file mode 100644 index 0000000..2138fa6 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui + +import androidx.activity.ComponentActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import androidx.activity.viewModels as androidxViewModel + +inline fun ViewModelStoreOwner.viewModels(): Lazy = + when (this) { + is ComponentActivity -> androidxViewModel() + else -> throw IllegalStateException("Only supports activity viewModel for now") + } \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt new file mode 100644 index 0000000..e6df5b0 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/auth/HiltAuthActivity.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui.auth + +import android.content.Context +import android.content.Intent +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltAuthActivity : AuthActivity() { + companion object { + fun getStartIntent(context: Context): Intent = Intent(context, HiltAuthActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt new file mode 100644 index 0000000..f6a7cdc --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/home/HiltMainActivity.kt @@ -0,0 +1,12 @@ +package org.fnives.test.showcase.ui.home + +import android.content.Context +import android.content.Intent +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltMainActivity : MainActivity() { + companion object { + fun getStartIntent(context: Context): Intent = Intent(context, HiltMainActivity::class.java) + } +} \ No newline at end of file diff --git a/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt new file mode 100644 index 0000000..19ba159 --- /dev/null +++ b/app/src/hilt/java/org/fnives/test/showcase/ui/splash/HiltSplashActivity.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.ui.splash + +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class HiltSplashActivity : SplashActivity() \ No newline at end of file diff --git a/app/src/koin/AndroidManifest.xml b/app/src/koin/AndroidManifest.xml new file mode 100644 index 0000000..72cf478 --- /dev/null +++ b/app/src/koin/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/TestShowcaseApplication.kt b/app/src/koin/java/org/fnives/test/showcase/TestShowcaseApplication.kt similarity index 100% rename from app/src/main/java/org/fnives/test/showcase/TestShowcaseApplication.kt rename to app/src/koin/java/org/fnives/test/showcase/TestShowcaseApplication.kt diff --git a/app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt b/app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt similarity index 96% rename from app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt rename to app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt index 8d05715..4c20221 100644 --- a/app/src/main/java/org/fnives/test/showcase/di/createAppModules.kt +++ b/app/src/koin/java/org/fnives/test/showcase/di/createAppModules.kt @@ -1,6 +1,6 @@ package org.fnives.test.showcase.di -import org.fnives.test.showcase.core.di.createCoreModule +import org.fnives.test.showcase.core.di.koin.createCoreModule import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.session.SessionExpirationListenerImpl import org.fnives.test.showcase.storage.LocalDatabase diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt new file mode 100644 index 0000000..4a5170a --- /dev/null +++ b/app/src/koin/java/org/fnives/test/showcase/ui/IntentCoordinator.kt @@ -0,0 +1,14 @@ +package org.fnives.test.showcase.ui + +import android.content.Context +import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.home.MainActivity + +object IntentCoordinator { + + fun mainActivitygetStartIntent(context: Context) = + MainActivity.getStartIntent(context) + + fun authActivitygetStartIntent(context: Context) = + AuthActivity.getStartIntent(context) +} \ No newline at end of file diff --git a/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt new file mode 100644 index 0000000..1cca4d6 --- /dev/null +++ b/app/src/koin/java/org/fnives/test/showcase/ui/ViewModelDelegate.kt @@ -0,0 +1,8 @@ +package org.fnives.test.showcase.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import org.koin.androidx.viewmodel.ext.android.viewModel + +inline fun ViewModelStoreOwner.viewModels(): Lazy = + viewModel() \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 09b554f..f778c8e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,24 +6,13 @@ - - - - - - - - - - + tools:ignore="AllowBackup"/> \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt b/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt index 230a521..b0fb00d 100644 --- a/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/session/SessionExpirationListenerImpl.kt @@ -4,15 +4,20 @@ import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper +import dagger.hilt.android.qualifiers.ApplicationContext import org.fnives.test.showcase.core.session.SessionExpirationListener -import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.IntentCoordinator +import javax.inject.Inject -class SessionExpirationListenerImpl(private val context: Context) : SessionExpirationListener { +class SessionExpirationListenerImpl @Inject constructor( + @ApplicationContext + private val context: Context +) : SessionExpirationListener { override fun onSessionExpired() { Handler(Looper.getMainLooper()).post { context.startActivity( - AuthActivity.getStartIntent(context) + IntentCoordinator.authActivitygetStartIntent(context) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) diff --git a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt index a62af66..aa4265b 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/SharedPreferencesManagerImpl.kt @@ -7,7 +7,7 @@ import org.fnives.test.showcase.model.session.Session import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class SharedPreferencesManagerImpl(private val sharedPreferences: SharedPreferences) : UserDataLocalStorage { +class SharedPreferencesManagerImpl constructor(private val sharedPreferences: SharedPreferences) : UserDataLocalStorage { override var session: Session? by SessionDelegate(SESSION_KEY) diff --git a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt index ac0f9d9..4797f32 100644 --- a/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt +++ b/app/src/main/java/org/fnives/test/showcase/storage/favourite/FavouriteContentLocalStorageImpl.kt @@ -4,8 +4,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class FavouriteContentLocalStorageImpl(private val favouriteDao: FavouriteDao) : FavouriteContentLocalStorage { +class FavouriteContentLocalStorageImpl @Inject constructor(private val favouriteDao: FavouriteDao) : FavouriteContentLocalStorage { override fun observeFavourites(): Flow> = favouriteDao.get().map { it.map(FavouriteEntity::contentId).map(::ContentId) } diff --git a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt index 3e4157c..9e035bc 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthActivity.kt @@ -9,12 +9,12 @@ import androidx.core.widget.doAfterTextChanged import com.google.android.material.snackbar.Snackbar import org.fnives.test.showcase.R import org.fnives.test.showcase.databinding.ActivityAuthenticationBinding -import org.fnives.test.showcase.ui.home.MainActivity -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.IntentCoordinator +import org.fnives.test.showcase.ui.viewModels -class AuthActivity : AppCompatActivity() { +open class AuthActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -35,7 +35,7 @@ class AuthActivity : AppCompatActivity() { } viewModel.navigateToHome.observe(this) { it.consume() ?: return@observe - startActivity(MainActivity.getStartIntent(this)) + startActivity(IntentCoordinator.mainActivitygetStartIntent(this)) finishAffinity() } setContentView(binding.root) diff --git a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt index 0221f14..b5fa26f 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/auth/AuthViewModel.kt @@ -4,14 +4,17 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import org.fnives.test.showcase.core.login.LoginUseCase import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.auth.LoginStatus import org.fnives.test.showcase.model.shared.Answer import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class AuthViewModel(private val loginUseCase: LoginUseCase) : ViewModel() { +@HiltViewModel +class AuthViewModel @Inject constructor(private val loginUseCase: LoginUseCase) : ViewModel() { private val _username = MutableLiveData() val username: LiveData = _username diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt index 187304a..a9bea69 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt @@ -6,17 +6,18 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import org.fnives.test.showcase.R import org.fnives.test.showcase.databinding.ActivityMainBinding import org.fnives.test.showcase.model.content.ContentId -import org.fnives.test.showcase.ui.auth.AuthActivity +import org.fnives.test.showcase.ui.IntentCoordinator import org.fnives.test.showcase.ui.shared.VerticalSpaceItemDecoration import org.fnives.test.showcase.ui.shared.getThemePrimaryColor -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.viewModels -class MainActivity : AppCompatActivity() { +open class MainActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -45,7 +46,7 @@ class MainActivity : AppCompatActivity() { } viewModel.navigateToAuth.observe(this) { it.consume() ?: return@observe - startActivity(AuthActivity.getStartIntent(this)) + startActivity(IntentCoordinator.authActivitygetStartIntent(this)) finishAffinity() } viewModel.loading.observe(this) { diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt index 326ac01..502d543 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase @@ -16,8 +17,10 @@ import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.FavouriteContent import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class MainViewModel( +@HiltViewModel +class MainViewModel @Inject constructor( private val getAllContentUseCase: GetAllContentUseCase, private val logoutUseCase: LogoutUseCase, private val fetchContentUseCase: FetchContentUseCase, diff --git a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt index ab7ee68..48e1ce1 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashActivity.kt @@ -3,21 +3,20 @@ package org.fnives.test.showcase.ui.splash import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.fnives.test.showcase.R -import org.fnives.test.showcase.ui.auth.AuthActivity -import org.fnives.test.showcase.ui.home.MainActivity -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.fnives.test.showcase.ui.IntentCoordinator +import org.fnives.test.showcase.ui.viewModels -class SplashActivity : AppCompatActivity() { +open class SplashActivity : AppCompatActivity() { - private val viewModel by viewModel() + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) viewModel.navigateTo.observe(this) { val intent = when (it.consume()) { - SplashViewModel.NavigateTo.HOME -> MainActivity.getStartIntent(this) - SplashViewModel.NavigateTo.AUTHENTICATION -> AuthActivity.getStartIntent(this) + SplashViewModel.NavigateTo.HOME -> IntentCoordinator.mainActivitygetStartIntent(this) + SplashViewModel.NavigateTo.AUTHENTICATION -> IntentCoordinator.authActivitygetStartIntent(this) null -> return@observe } startActivity(intent) diff --git a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt index d9bbd43..13a1887 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/splash/SplashViewModel.kt @@ -4,12 +4,15 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase import org.fnives.test.showcase.ui.shared.Event +import javax.inject.Inject -class SplashViewModel(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() { +@HiltViewModel +class SplashViewModel @Inject constructor(isUserLoggedInUseCase: IsUserLoggedInUseCase) : ViewModel() { private val _navigateTo = MutableLiveData>() val navigateTo: LiveData> = _navigateTo diff --git a/build.gradle b/build.gradle index c09cd78..c4df286 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,14 @@ buildscript { ext.kotlin_version = "1.5.30" ext.detekt_version = "1.18.1" + ext.hilt_version = "2.38.1" repositories { mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } } dependencies { + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0" @@ -22,6 +24,15 @@ allprojects { repositories { mavenCentral() google() + maven { + url "https://maven.pkg.github.com/fknives/ReloadableHiltModule" + credentials { + username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME") + password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN") + } + // how to get token + // https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token + } } } @@ -29,7 +40,7 @@ task clean(type: Delete) { delete rootProject.buildDir } -task unitTests(dependsOn: ["app:testDebugUnitTest", "core:test", "network:test"]){ +task unitTests(dependsOn: ["app:testKoinDebugUnitTest", "app:testHiltDebugUnitTest", "core:test", "network:test"]){ group = 'Tests' description = 'Run all unit tests' } diff --git a/core/build.gradle b/core/build.gradle index 745175d..a66b8fc 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java-library' id 'kotlin' + id 'kotlin-kapt' } java { @@ -14,12 +15,22 @@ compileKotlin { } } +kapt { + correctErrorTypes = true +} + dependencies { api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" api project(":model") implementation project(":network") + // hilt + implementation "com.google.dagger:hilt-core:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" + implementation "org.fnives.library.reloadable.module:annotation:$reloadable_module_version" + kapt "org.fnives.library.reloadable.module:annotation-processor:$reloadable_module_version" + testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt index c5ea010..2483d1c 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCase.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.content import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class AddContentToFavouriteUseCase internal constructor( +class AddContentToFavouriteUseCase @Inject internal constructor( private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt index b243db5..143d2c8 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import org.fnives.test.showcase.core.di.hilt.LoggedInModuleInject import org.fnives.test.showcase.core.shared.Optional import org.fnives.test.showcase.core.shared.mapIntoResource import org.fnives.test.showcase.core.shared.wrapIntoAnswer @@ -14,7 +15,7 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.content.ContentRemoteSource -internal class ContentRepository(private val contentRemoteSource: ContentRemoteSource) { +internal class ContentRepository @LoggedInModuleInject constructor(private val contentRemoteSource: ContentRemoteSource) { private val mutableContentFlow = MutableStateFlow(Optional>(null)) private val requestFlow: Flow>> = flow { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt index 90c7a5b..16944f5 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/FetchContentUseCase.kt @@ -1,6 +1,8 @@ package org.fnives.test.showcase.core.content -class FetchContentUseCase internal constructor(private val contentRepository: ContentRepository) { +import javax.inject.Inject + +class FetchContentUseCase @Inject internal constructor(private val contentRepository: ContentRepository) { fun invoke() = contentRepository.fetch() } diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt index 24a4b63..56b7c72 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.FavouriteContent import org.fnives.test.showcase.model.shared.Resource +import javax.inject.Inject -class GetAllContentUseCase internal constructor( +class GetAllContentUseCase @Inject internal constructor( private val contentRepository: ContentRepository, private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt index 494af3e..4c03f90 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCase.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.content import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId +import javax.inject.Inject -class RemoveContentFromFavouritesUseCase internal constructor( +class RemoveContentFromFavouritesUseCase @Inject internal constructor( private val favouriteContentLocalStorage: FavouriteContentLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt new file mode 100644 index 0000000..6ca5bdc --- /dev/null +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/CoreModule.kt @@ -0,0 +1,34 @@ +package org.fnives.test.showcase.core.di.hilt + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.fnives.test.showcase.core.login.LogoutUseCase +import org.fnives.test.showcase.core.session.SessionExpirationAdapter +import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter +import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule + +@InstallIn(SingletonComponent::class) +@Module +object CoreModule { + + @Provides + internal fun bindNetworkSessionLocalStorageAdapter( + networkSessionLocalStorageAdapter: NetworkSessionLocalStorageAdapter + ): NetworkSessionLocalStorage = networkSessionLocalStorageAdapter + + @Provides + internal fun bindNetworkSessionExpirationListener( + sessionExpirationAdapter: SessionExpirationAdapter + ): NetworkSessionExpirationListener = sessionExpirationAdapter + + @Provides + fun provideLogoutUseCase( + storage: UserDataLocalStorage, + reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule + ) : LogoutUseCase = LogoutUseCase(storage, reloadLoggedInModuleInjectModule) +} \ No newline at end of file diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt new file mode 100644 index 0000000..3649804 --- /dev/null +++ b/core/src/main/java/org/fnives/test/showcase/core/di/hilt/LoggedInModuleInject.kt @@ -0,0 +1,8 @@ +package org.fnives.test.showcase.core.di.hilt + +import org.fnives.library.reloadable.module.annotation.ReloadableModule + +@ReloadableModule +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +annotation class LoggedInModuleInject \ No newline at end of file diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt similarity index 94% rename from core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt rename to core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt index d3b6bc5..5f66810 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/di/koin/createCoreModule.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.core.di +package org.fnives.test.showcase.core.di.koin import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase import org.fnives.test.showcase.core.content.ContentRepository @@ -14,7 +14,7 @@ import org.fnives.test.showcase.core.storage.NetworkSessionLocalStorageAdapter import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.koin.core.module.Module import org.koin.core.scope.Scope import org.koin.dsl.module @@ -42,7 +42,7 @@ fun repositoryModule() = module { fun useCaseModule() = module { factory { LoginUseCase(get(), get()) } - factory { LogoutUseCase(get()) } + factory { LogoutUseCase(get(), null) } factory { GetAllContentUseCase(get(), get()) } factory { AddContentToFavouriteUseCase(get()) } factory { RemoveContentFromFavouritesUseCase(get()) } diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt index fd0539a..1e21dd1 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCase.kt @@ -1,8 +1,11 @@ package org.fnives.test.showcase.core.login import org.fnives.test.showcase.core.storage.UserDataLocalStorage +import javax.inject.Inject -class IsUserLoggedInUseCase(private val userDataLocalStorage: UserDataLocalStorage) { +class IsUserLoggedInUseCase @Inject constructor( + private val userDataLocalStorage: UserDataLocalStorage +) { fun invoke(): Boolean = userDataLocalStorage.session != null } diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt index 2a91780..cf03c79 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/LoginUseCase.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.model.auth.LoginStatus import org.fnives.test.showcase.model.shared.Answer import org.fnives.test.showcase.network.auth.LoginRemoteSource import org.fnives.test.showcase.network.auth.model.LoginStatusResponses +import javax.inject.Inject -class LoginUseCase internal constructor( +class LoginUseCase @Inject internal constructor( private val loginRemoteSource: LoginRemoteSource, private val userDataLocalStorage: UserDataLocalStorage ) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt index 158e7e0..7e36a78 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/login/LogoutUseCase.kt @@ -1,13 +1,22 @@ package org.fnives.test.showcase.core.login -import org.fnives.test.showcase.core.di.repositoryModule +import org.fnives.test.showcase.core.di.koin.repositoryModule import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.koin.core.context.loadKoinModules +import org.koin.mp.KoinPlatformTools +import org.fnives.test.showcase.core.di.hilt.ReloadLoggedInModuleInjectModule -class LogoutUseCase(private val storage: UserDataLocalStorage) { +class LogoutUseCase( + private val storage: UserDataLocalStorage, + private val reloadLoggedInModuleInjectModule: ReloadLoggedInModuleInjectModule? +) { suspend fun invoke() { - loadKoinModules(repositoryModule()) + if (KoinPlatformTools.defaultContext().getOrNull() == null) { + reloadLoggedInModuleInjectModule?.reload() + } else { + loadKoinModules(repositoryModule()) + } storage.session = null } } diff --git a/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt b/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt index 1947635..3571971 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/session/SessionExpirationAdapter.kt @@ -1,11 +1,11 @@ package org.fnives.test.showcase.core.session import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener +import javax.inject.Inject -internal class SessionExpirationAdapter( +internal class SessionExpirationAdapter @Inject constructor( private val sessionExpirationListener: SessionExpirationListener -) : - NetworkSessionExpirationListener { +) : NetworkSessionExpirationListener { override fun onSessionExpired() = sessionExpirationListener.onSessionExpired() } diff --git a/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt b/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt index a9d5d77..0d157f6 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapter.kt @@ -2,8 +2,9 @@ package org.fnives.test.showcase.core.storage import org.fnives.test.showcase.model.session.Session import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage +import javax.inject.Inject -internal class NetworkSessionLocalStorageAdapter( +internal class NetworkSessionLocalStorageAdapter @Inject constructor( private val userDataLocalStorage: UserDataLocalStorage ) : NetworkSessionLocalStorage { diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt index ffb1900..776f1ab 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/LogoutUseCaseTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.core.login import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.content.ContentRepository -import org.fnives.test.showcase.core.di.createCoreModule +import org.fnives.test.showcase.core.di.koin.createCoreModule import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.model.network.BaseUrl import org.junit.jupiter.api.AfterEach diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index 4f8ed25..0d5a246 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -6,6 +6,7 @@ project.ext { androidx_livedata_version = "2.3.1" androidx_swiperefreshlayout_version = "1.1.0" androidx_room_version = "2.3.0" + activity_ktx_version = "1.3.1" coroutines_version = "1.4.3" koin_version = "3.1.2" @@ -13,6 +14,7 @@ project.ext { retrofit_version = "2.9.0" okhttp_version = "4.9.1" moshi_version = "1.12.0" + reloadable_module_version = "0.1.0" testing_androidx_code_version = "1.4.0" testing_androidx_junit_version = "1.1.3" diff --git a/network/build.gradle b/network/build.gradle index fc17ae7..7449ab7 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -18,6 +18,8 @@ dependencies { kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" api "io.insert-koin:koin-core:$koin_version" + implementation "com.google.dagger:hilt-core:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" api project(":model") diff --git a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt index 49a588b..057064e 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginErrorConverter.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.network.shared.ExceptionWrapper import org.fnives.test.showcase.network.shared.exceptions.ParsingException import retrofit2.HttpException import retrofit2.Response +import javax.inject.Inject -internal class LoginErrorConverter { +internal class LoginErrorConverter @Inject constructor() { @Throws(ParsingException::class) suspend fun invoke(request: suspend () -> Response): LoginStatusResponses = diff --git a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt index 248ffde..0a5bf85 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceImpl.kt @@ -7,8 +7,9 @@ import org.fnives.test.showcase.network.auth.model.LoginStatusResponses import org.fnives.test.showcase.network.shared.ExceptionWrapper import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException +import javax.inject.Inject -internal class LoginRemoteSourceImpl constructor( +internal class LoginRemoteSourceImpl @Inject constructor( private val loginService: LoginService, private val loginErrorConverter: LoginErrorConverter ) : LoginRemoteSource { diff --git a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt index d1daac8..4c0da1a 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImpl.kt @@ -4,8 +4,9 @@ import org.fnives.test.showcase.model.content.Content import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.ImageUrl import org.fnives.test.showcase.network.shared.ExceptionWrapper +import javax.inject.Inject -internal class ContentRemoteSourceImpl(private val contentService: ContentService) : ContentRemoteSource { +internal class ContentRemoteSourceImpl @Inject constructor(private val contentService: ContentService) : ContentRemoteSource { override suspend fun get(): List = ExceptionWrapper.wrap { diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt new file mode 100644 index 0000000..a5a6898 --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/HiltNetworkModule.kt @@ -0,0 +1,96 @@ +package org.fnives.test.showcase.network.di.hilt + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import org.fnives.test.showcase.network.auth.LoginRemoteSource +import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl +import org.fnives.test.showcase.network.auth.LoginService +import org.fnives.test.showcase.network.content.ContentRemoteSource +import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl +import org.fnives.test.showcase.network.content.ContentService +import org.fnives.test.showcase.network.di.setupLogging +import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor +import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils +import org.fnives.test.showcase.network.session.SessionAuthenticator +import org.fnives.test.showcase.network.shared.PlatformInterceptor +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object HiltNetworkModule { + + @Provides + @Singleton + fun provideConverterFactory(): Converter.Factory = MoshiConverterFactory.create() + + @Provides + @Singleton + @SessionLessQualifier + fun provideSessionLessOkHttpClient(enableLogging: Boolean) = + OkHttpClient.Builder() + .addInterceptor(PlatformInterceptor()) + .setupLogging(enableLogging) + .build() + + @Provides + @Singleton + @SessionLessQualifier + fun provideSessionLessRetrofit( + baseUrl: String, + converterFactory: Converter.Factory, + @SessionLessQualifier okHttpClient: OkHttpClient + ) = Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(converterFactory) + .client(okHttpClient) + .build() + + @Provides + @Singleton + @SessionQualifier + internal fun provideSessionOkHttpClient( + @SessionLessQualifier okHttpClient: OkHttpClient, + sessionAuthenticator: SessionAuthenticator, + authenticationHeaderUtils: AuthenticationHeaderUtils + ) = + okHttpClient + .newBuilder() + .authenticator(sessionAuthenticator) + .addInterceptor(AuthenticationHeaderInterceptor(authenticationHeaderUtils)) + .build() + + @Provides + @Singleton + @SessionQualifier + fun provideSessionRetrofit( + @SessionLessQualifier retrofit: Retrofit, + @SessionQualifier okHttpClient: OkHttpClient + ) = retrofit.newBuilder() + .client(okHttpClient) + .build() + + @Provides + internal fun bindContentRemoteSource( + contentRemoteSourceImpl: ContentRemoteSourceImpl + ): ContentRemoteSource = contentRemoteSourceImpl + + @Provides + internal fun bindLoginRemoteSource( + loginRemoteSource: LoginRemoteSourceImpl + ): LoginRemoteSource = loginRemoteSource + + @Provides + internal fun provideLoginService(@SessionLessQualifier retrofit: Retrofit): LoginService = + retrofit.create(LoginService::class.java) + + @Provides + internal fun provideContentService(@SessionQualifier retrofit: Retrofit): ContentService = + retrofit.create(ContentService::class.java) + +} \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt new file mode 100644 index 0000000..46a259d --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionLessQualifier.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.network.di.hilt + +import javax.inject.Qualifier + +@Qualifier +annotation class SessionLessQualifier \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt new file mode 100644 index 0000000..30ad41b --- /dev/null +++ b/network/src/main/java/org/fnives/test/showcase/network/di/hilt/SessionQualifier.kt @@ -0,0 +1,6 @@ +package org.fnives.test.showcase.network.di.hilt + +import javax.inject.Qualifier + +@Qualifier +annotation class SessionQualifier \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt b/network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt similarity index 97% rename from network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt rename to network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt index b4b0e8b..2c84e77 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/koin/createNetworkmodules.kt @@ -1,4 +1,4 @@ -package org.fnives.test.showcase.network.di +package org.fnives.test.showcase.network.di.koin import okhttp3.OkHttpClient import org.fnives.test.showcase.model.network.BaseUrl @@ -9,6 +9,7 @@ import org.fnives.test.showcase.network.auth.LoginService import org.fnives.test.showcase.network.content.ContentRemoteSource import org.fnives.test.showcase.network.content.ContentRemoteSourceImpl import org.fnives.test.showcase.network.content.ContentService +import org.fnives.test.showcase.network.di.setupLogging import org.fnives.test.showcase.network.session.AuthenticationHeaderInterceptor import org.fnives.test.showcase.network.session.AuthenticationHeaderUtils import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt index cfb35c6..ae228d5 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/AuthenticationHeaderUtils.kt @@ -1,8 +1,9 @@ package org.fnives.test.showcase.network.session import okhttp3.Request +import javax.inject.Inject -internal class AuthenticationHeaderUtils(private val networkSessionLocalStorage: NetworkSessionLocalStorage) { +internal class AuthenticationHeaderUtils @Inject constructor(private val networkSessionLocalStorage: NetworkSessionLocalStorage) { fun hasToken(okhttpRequest: Request): Boolean = okhttpRequest.header(KEY) == networkSessionLocalStorage.session?.accessToken diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt index df1c411..544574c 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt @@ -6,8 +6,9 @@ import okhttp3.Request import okhttp3.Response import okhttp3.Route import org.fnives.test.showcase.network.auth.LoginRemoteSourceImpl +import javax.inject.Inject -internal class SessionAuthenticator( +internal class SessionAuthenticator @Inject constructor( private val networkSessionLocalStorage: NetworkSessionLocalStorage, private val loginRemoteSource: LoginRemoteSourceImpl, private val authenticationHeaderUtils: AuthenticationHeaderUtils, diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt index fc71d92..418d667 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceRefreshActionImplTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.network.auth import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt index cd586bd..6d74777 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.auth.LoginCredentials import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.network.auth.model.LoginStatusResponses -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.ContentData.createExpectedLoginRequestJson import org.fnives.test.showcase.network.mockserver.scenario.auth.AuthScenario diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt index eb2f310..625b995 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt @@ -3,7 +3,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import okhttp3.mockwebserver.MockWebServer import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.junit.jupiter.api.AfterEach diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt index f1996d2..cff87d8 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/ContentRemoteSourceImplTest.kt @@ -2,7 +2,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt index 0f80d3e..3450b85 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt @@ -3,7 +3,7 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking import org.fnives.test.showcase.model.network.BaseUrl import org.fnives.test.showcase.model.session.Session -import org.fnives.test.showcase.network.di.createNetworkModules +import org.fnives.test.showcase.network.di.koin.createNetworkModules import org.fnives.test.showcase.network.mockserver.ContentData import org.fnives.test.showcase.network.mockserver.scenario.content.ContentScenario import org.fnives.test.showcase.network.mockserver.scenario.refresh.RefreshTokenScenario