Переглянути джерело

Refactor project to replace Dagger Hilt with Koin for dependency injection

- Removed Dagger Hilt dependencies and annotations across multiple modules.
- Integrated Koin for dependency injection, updating ViewModels and repositories accordingly.
- Updated ProGuard rules to accommodate changes in dependency management.
- Incremented version code and name in build.gradle.kts for the upcoming release.
- Enhanced various Gradle files to ensure compatibility with new dependencies.
Carles Sentis 2 днів тому
батько
коміт
0c155b8d2e
41 змінених файлів з 488 додано та 409 видалено
  1. 7 12
      app/build.gradle.kts
  2. 72 3
      app/proguard-rules.pro
  3. 29 3
      app/src/main/java/com/codeskraps/weather/WeatherApp.kt
  4. 10 19
      app/src/main/java/com/codeskraps/weather/ui/MainActivity.kt
  5. 1 2
      build.gradle.kts
  6. 4 6
      core/local/build.gradle.kts
  7. 26 0
      core/local/src/main/java/com/codeskraps/core/local/data/dao/GeoLocationDao.kt
  8. 21 2
      core/local/src/main/java/com/codeskraps/core/local/data/db/GeocodingDB.kt
  9. 7 9
      core/local/src/main/java/com/codeskraps/core/local/data/mappers/GeoLocationMappers.kt
  10. 6 5
      core/local/src/main/java/com/codeskraps/core/local/data/repository/LocalGeocodingRepositoryImpl.kt
  11. 21 23
      core/local/src/main/java/com/codeskraps/core/local/data/repository/LocalResourceRepositoryImpl.kt
  12. 21 20
      core/local/src/main/java/com/codeskraps/core/local/di/LocalModule.kt
  13. 19 20
      core/local/src/main/java/com/codeskraps/core/local/di/LocalRepositoryModule.kt
  14. 9 10
      core/local/src/main/java/com/codeskraps/core/local/domain/repository/LocalResourceRepository.kt
  15. 4 5
      core/location/build.gradle.kts
  16. 1 2
      core/location/src/main/java/com/codeskraps/core/location/data/DefaultLocationTracker.kt
  17. 10 18
      core/location/src/main/java/com/codeskraps/core/location/di/CoreLocationModule.kt
  18. 2 4
      core/umami/build.gradle.kts
  19. 12 3
      core/umami/src/main/java/com/codeskraps/umami/data/repository/AnalyticsRepositoryImpl.kt
  20. 5 4
      core/umami/src/main/java/com/codeskraps/umami/data/repository/DeviceIdRepositoryImpl.kt
  21. 5 37
      core/umami/src/main/java/com/codeskraps/umami/di/UmamiModule.kt
  22. 18 13
      feature/common/build.gradle.kts
  23. 8 21
      feature/common/src/main/java/com/codeskraps/feature/common/di/FeatureModule.kt
  24. 1 1
      feature/common/src/test/java/com/codeskraps/feature/common/dispatcher/TestDispatcherProvider.kt
  25. 4 10
      feature/geocoding/build.gradle.kts
  26. 14 1
      feature/geocoding/proguard-rules.pro
  27. 1 2
      feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/data/repository/GeocodingRepositoryImpl.kt
  28. 20 19
      feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/di/FeatureModule.kt
  29. 0 20
      feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/di/RepositoryModule.kt
  30. 1 4
      feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/presentation/GeocodingViewModel.kt
  31. 10 6
      feature/maps/build.gradle.kts
  32. 17 0
      feature/maps/src/main/java/com/codeskraps/maps/di/FeatureModule.kt
  33. 1 4
      feature/maps/src/main/java/com/codeskraps/maps/presentation/MapViewModel.kt
  34. 12 22
      feature/weather/build.gradle.kts
  35. 17 1
      feature/weather/proguard-rules.pro
  36. 1 2
      feature/weather/src/main/java/com/codeskraps/feature/weather/data/repository/WeatherRepositoryImpl.kt
  37. 34 18
      feature/weather/src/main/java/com/codeskraps/feature/weather/di/FeatureModule.kt
  38. 0 18
      feature/weather/src/main/java/com/codeskraps/feature/weather/di/RepositoryModule.kt
  39. 1 4
      feature/weather/src/main/java/com/codeskraps/feature/weather/presentation/WeatherViewModel.kt
  40. 29 29
      feature/weather/src/main/java/com/codeskraps/feature/weather/presentation/components/WeatherScreen.kt
  41. 7 7
      gradle/libs.versions.toml

+ 7 - 12
app/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.application)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
     alias(libs.plugins.compose.compiler)
 }
 
@@ -16,8 +15,8 @@ android {
         applicationId = "com.arklan.weather"
         minSdk = ConfigData.minSdk
         targetSdk = ConfigData.targetSdk
-        versionCode = 17
-        versionName = "2.6.1"
+        versionCode = 18
+        versionName = "2.6.2"
         setProperty("archivesBaseName", "weekly-weather-v$versionName.$versionCode")
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -26,8 +25,6 @@ android {
         }
     }
 
-
-
     buildTypes {
         release {
             isMinifyEnabled = ConfigData.isMinifyRelease
@@ -35,6 +32,7 @@ android {
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
             )
+            isShrinkResources = true
         }
         debug {
             isMinifyEnabled = ConfigData.isMinifyDebug
@@ -60,9 +58,6 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
-    hilt {
-        enableAggregatingTask = true
-    }
 }
 
 dependencies {
@@ -87,10 +82,10 @@ dependencies {
     implementation(libs.androidx.compose.graphics)
     implementation(libs.androidx.compose.tooling.preview)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    implementation(libs.hilt.navigation.compose)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
+    implementation(libs.koin.androidx.compose)
+    implementation(libs.koin.androidx.compose.navigation)
 
     implementation(libs.coroutines.test)
     testImplementation(libs.junit.junit)

+ 72 - 3
app/proguard-rules.pro

@@ -35,9 +35,81 @@
 -keep class com.codeskraps.maps.** { *; }
 -keep class com.codeskraps.umami.** { *; }
 
+# Additional dontwarn rules from missing_rules.txt
+-dontwarn com.codeskraps.feature.geocoding.di.FeatureModuleKt
+-dontwarn com.codeskraps.feature.weather.di.FeatureModuleKt
+
+# Koin
+-keepnames class android.arch.lifecycle.ViewModel
+-keepclassmembers public class * extends android.arch.lifecycle.ViewModel { public <init>(...); }
+-keepclassmembers class com.codeskraps.** { public <init>(...); }
+-keepclassmembers class * { @org.koin.core.annotation.KoinInternalApi *; }
+-keep class org.koin.** { *; }
+-keep class com.codeskraps.**.di.** { *; }
+-keep class * extends org.koin.core.module.Module { *; }
+
+# Keep Koin Modules
+-keep class com.codeskraps.feature.geocoding.di.FeatureModuleKt { *; }
+-keep class com.codeskraps.feature.maps.di.FeatureModuleKt { *; }
+-keep class com.codeskraps.feature.weather.di.FeatureModuleKt { *; }
+
 # Java language APIs
 -dontwarn java.lang.invoke.StringConcatFactory
 
+# Hilt ViewModel classes
+-keep class * extends androidx.lifecycle.ViewModel { *; }
+-keep class * extends dagger.hilt.android.lifecycle.HiltViewModel { *; }
+-keep,allowobfuscation @dagger.hilt.android.lifecycle.HiltViewModel class *
+-keepclasseswithmembers class * {
+    @javax.inject.Inject <init>(...);
+}
+
+# Explicitly keep Hilt generated ViewModel classes
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$* { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$* { *; }
+-keep class com.codeskraps.maps.presentation.MapViewModel_HiltModules$* { *; }
+
+# Keep all Hilt related classes
+-keep class com.codeskraps.weather.WeatherApp_HiltComponents$* { *; }
+-keep class dagger.hilt.** { *; }
+-keep class javax.inject.** { *; }
+-keep class * extends dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper { *; }
+
+# Keep Dagger components
+-keep,allowobfuscation,allowshrinking @dagger.** class *
+-keep,allowobfuscation,allowshrinking class dagger.** { *; }
+-keep,allowobfuscation,allowshrinking class javax.inject.** { *; }
+-keep,allowobfuscation,allowshrinking class * extends dagger.internal.Factory { *; }
+-keepclassmembers,allowobfuscation class * {
+    @javax.inject.* *;
+    @dagger.* *;
+    <init>();
+}
+
+# Keep all generated Hilt code
+-keep class **_HiltModules$* { *; }
+-keep class **_Impl { *; }
+-keep class **_Factory { *; }
+-keep class **_MembersInjector { *; }
+
+# Keep specific Hilt ViewModel components
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$BindsModule { *; }
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$KeyModule { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$BindsModule { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$KeyModule { *; }
+-keep class com.codeskraps.maps.presentation.MapViewModel_HiltModules$BindsModule { *; }
+-keep class com.codeskraps.maps.presentation.MapViewModel_HiltModules$KeyModule { *; }
+
+# Keep Hilt ViewModel factories
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_Factory { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_Factory { *; }
+-keep class com.codeskraps.maps.presentation.MapViewModel_Factory { *; }
+
+# Keep Hilt ViewModel members injectors
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_MembersInjector { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_MembersInjector { *; }
+-keep class com.codeskraps.maps.presentation.MapViewModel_MembersInjector { *; }
+
 # Additionally, adding specific rules from the missing_rules.txt
 -dontwarn com.codeskraps.core.local.data.repository.LocalGeocodingRepositoryImpl
 -dontwarn com.codeskraps.core.local.data.repository.LocalResourceRepositoryImpl
@@ -57,7 +129,6 @@
 -dontwarn com.codeskraps.feature.geocoding.data.repository.GeocodingRepositoryImpl
 -dontwarn com.codeskraps.feature.geocoding.di.FeatureModule_ProvidesGeocodingApiFactory
 -dontwarn com.codeskraps.feature.geocoding.presentation.GeocodingViewModel
--dontwarn com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$KeyModule
 -dontwarn com.codeskraps.feature.geocoding.presentation.components.GeocodingScreenKt
 -dontwarn com.codeskraps.feature.geocoding.presentation.mvi.GeoEvent
 -dontwarn com.codeskraps.feature.geocoding.presentation.mvi.GeoState
@@ -67,12 +138,10 @@
 -dontwarn com.codeskraps.feature.weather.di.FeatureModule_ProvidesWeatherApiFactory
 -dontwarn com.codeskraps.feature.weather.domain.repository.WeatherRepository
 -dontwarn com.codeskraps.feature.weather.presentation.WeatherViewModel
--dontwarn com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$KeyModule
 -dontwarn com.codeskraps.feature.weather.presentation.components.WeatherScreenKt
 -dontwarn com.codeskraps.feature.weather.presentation.mvi.WeatherEvent
 -dontwarn com.codeskraps.feature.weather.presentation.mvi.WeatherState
 -dontwarn com.codeskraps.maps.presentation.MapViewModel
--dontwarn com.codeskraps.maps.presentation.MapViewModel_HiltModules$KeyModule
 -dontwarn com.codeskraps.maps.presentation.components.MapScreenKt
 -dontwarn com.codeskraps.maps.presentation.mvi.MapEvent
 -dontwarn com.codeskraps.maps.presentation.mvi.MapState

+ 29 - 3
app/src/main/java/com/codeskraps/weather/WeatherApp.kt

@@ -1,7 +1,33 @@
 package com.codeskraps.weather
 
 import android.app.Application
-import dagger.hilt.android.HiltAndroidApp
+import com.codeskraps.core.local.di.localModule
+import com.codeskraps.core.location.di.locationModule
+import com.codeskraps.feature.common.di.commonModule
+import com.codeskraps.feature.geocoding.di.geocodingModule
+import com.codeskraps.feature.weather.di.weatherModule
+import com.codeskraps.maps.di.mapsFeatureModule
+import com.codeskraps.umami.di.umamiModule
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
 
-@HiltAndroidApp
-class WeatherApp : Application()
+class WeatherApp : Application() {
+    override fun onCreate() {
+        super.onCreate()
+
+        startKoin {
+            androidLogger()
+            androidContext(this@WeatherApp)
+            modules(
+                commonModule,
+                localModule,
+                locationModule,
+                geocodingModule,
+                weatherModule,
+                mapsFeatureModule,
+                umamiModule
+            )
+        }
+    }
+}

+ 10 - 19
app/src/main/java/com/codeskraps/weather/ui/MainActivity.kt

@@ -9,7 +9,6 @@ import androidx.compose.animation.AnimatedContentTransitionScope
 import androidx.compose.animation.core.tween
 import androidx.compose.runtime.getValue
 import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.navigation.NavType
@@ -27,15 +26,13 @@ import com.codeskraps.maps.presentation.components.MapScreen
 import com.codeskraps.umami.domain.AnalyticsRepository
 import com.codeskraps.weather.ui.theme.ScreenTransitions
 import com.codeskraps.weather.ui.theme.WeatherTheme
-import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.launch
-import javax.inject.Inject
+import org.koin.android.ext.android.inject
+import org.koin.androidx.compose.koinViewModel
 
-@AndroidEntryPoint
 class MainActivity : ComponentActivity() {
 
-    @Inject
-    lateinit var analyticsRepository: AnalyticsRepository
+    private val analyticsRepository: AnalyticsRepository by inject()
 
     private var isAnalyticsInitialized = false
 
@@ -94,7 +91,7 @@ class MainActivity : ComponentActivity() {
                         popEnterTransition = { ScreenTransitions.slideRightIntoContainer(this) },
                         popExitTransition = { ScreenTransitions.slideLeftOutOfContainer(this) }
                     ) {
-                        val viewModel = hiltViewModel<WeatherViewModel>()
+                        val viewModel = koinViewModel<WeatherViewModel>()
                         val state by viewModel.state.collectAsStateWithLifecycle()
 
                         WeatherScreen(
@@ -112,7 +109,7 @@ class MainActivity : ComponentActivity() {
                         popEnterTransition = { ScreenTransitions.slideLeftIntoContainer(this) },
                         popExitTransition = { ScreenTransitions.slideRightOutOfContainer(this) }
                     ) {
-                        val viewModel = hiltViewModel<GeocodingViewModel>()
+                        val viewModel = koinViewModel<GeocodingViewModel>()
                         val state by viewModel.state.collectAsStateWithLifecycle()
 
                         GeocodingScreen(
@@ -142,23 +139,17 @@ class MainActivity : ComponentActivity() {
                         }
                     ) {
                         Log.d("Navigation", "Attempting to navigate to Map screen")
-                        val viewModel = hiltViewModel<MapViewModel>()
+                        val viewModel = koinViewModel<MapViewModel>()
                         val state by viewModel.state.collectAsStateWithLifecycle()
                         MapScreen(
                             state = state,
                             handleEvent = viewModel.state::handleEvent,
                             action = viewModel.action,
                             navRoute = { route ->
-                                when (route) {
-                                    "nav_up" -> {
-                                        Log.d("Navigation", "Map screen navigation up")
-                                        navController.navigateUp()
-                                    }
-
-                                    else -> {
-                                        Log.d("Navigation", "Map screen navigation to $route")
-                                        navController.navigate(route)
-                                    }
+                                if (route == "nav_up") {
+                                    navController.navigateUp()
+                                } else {
+                                    navController.navigate(route)
                                 }
                             }
                         )

+ 1 - 2
build.gradle.kts

@@ -3,9 +3,8 @@ plugins {
     alias(libs.plugins.android.library) apply false
     alias(libs.plugins.org.jetbrains.kotlin.android) apply false
     alias(libs.plugins.com.google.devtools.ksp) apply false
-    alias(libs.plugins.dagger.hilt) apply false
-    alias(libs.plugins.secrets.gradle.plugin) apply false
     alias(libs.plugins.compose.compiler) apply false
+    alias(libs.plugins.secrets.gradle.plugin) apply false
 }
 
 tasks.register("clean", Delete::class) {

+ 4 - 6
core/local/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
 }
 
 android {
@@ -46,15 +45,14 @@ dependencies {
     implementation(libs.appcompat)
     implementation(libs.material)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
-
     // Room
     implementation(libs.room.runtime)
-    annotationProcessor(libs.room.compiler)
+    implementation(libs.androidx.room.ktx)
     ksp(libs.room.compiler)
 
+    // Koin
+    implementation(libs.koin.android)
+
     testImplementation(libs.junit.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.espresso.core)

+ 26 - 0
core/local/src/main/java/com/codeskraps/core/local/data/dao/GeoLocationDao.kt

@@ -0,0 +1,26 @@
+package com.codeskraps.core.local.data.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.codeskraps.core.local.data.model.GeoLocationEntity
+
+@Dao
+interface GeoLocationDao {
+    @Query("SELECT * FROM GeoLocationEntity")
+    suspend fun getAll(): List<GeoLocationEntity>
+
+    @Query("SELECT * FROM GeoLocationEntity WHERE latitude = :latitude AND longitude = :longitude")
+    suspend fun getByLocation(latitude: Double, longitude: Double): List<GeoLocationEntity>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insert(geoLocation: GeoLocationEntity)
+
+    @Delete
+    suspend fun delete(geoLocation: GeoLocationEntity)
+
+    @Query("DELETE FROM GeoLocationEntity WHERE latitude = :latitude AND longitude = :longitude")
+    suspend fun delete(latitude: Double, longitude: Double)
+} 

+ 21 - 2
core/local/src/main/java/com/codeskraps/core/local/data/db/GeocodingDB.kt

@@ -1,7 +1,10 @@
 package com.codeskraps.core.local.data.db
 
+import android.content.Context
 import androidx.room.Database
+import androidx.room.Room
 import androidx.room.RoomDatabase
+import com.codeskraps.core.local.data.dao.GeoLocationDao
 import com.codeskraps.core.local.data.model.GeoLocationEntity
 
 @Database(
@@ -9,7 +12,23 @@ import com.codeskraps.core.local.data.model.GeoLocationEntity
     version = 1,
     exportSchema = false
 )
-abstract class GeocodingDB: RoomDatabase() {
+abstract class GeocodingDB : RoomDatabase() {
+    abstract fun geocodingDao(): GeoLocationDao
 
-    abstract fun geocodingDao(): GeocodingDao
+    companion object {
+        @Volatile
+        private var INSTANCE: GeocodingDB? = null
+
+        fun getInstance(context: Context): GeocodingDB {
+            return INSTANCE ?: synchronized(this) {
+                val instance = Room.databaseBuilder(
+                    context.applicationContext,
+                    GeocodingDB::class.java,
+                    "geocoding_database"
+                ).build()
+                INSTANCE = instance
+                instance
+            }
+        }
+    }
 }

+ 7 - 9
core/local/src/main/java/com/codeskraps/core/local/data/mappers/GeocodingMapper.kt → core/local/src/main/java/com/codeskraps/core/local/data/mappers/GeoLocationMappers.kt

@@ -2,26 +2,24 @@ package com.codeskraps.core.local.data.mappers
 
 import com.codeskraps.core.local.data.model.GeoLocationEntity
 import com.codeskraps.core.local.domain.model.GeoLocation
-import kotlin.random.Random
 
-fun GeoLocationEntity.toGeocoding(): GeoLocation {
-    return GeoLocation(
+fun GeoLocation.toGeoLocationEntity(): GeoLocationEntity {
+    return GeoLocationEntity(
+        uid = 0, // Room will auto-generate this
         name = name,
         latitude = latitude,
         longitude = longitude,
         country = country,
-        admin1 = admin1,
-        cached = true
+        admin1 = admin1
     )
 }
 
-fun GeoLocation.toGeoLocationEntity(): GeoLocationEntity {
-    return GeoLocationEntity(
-        uid = Random.nextInt(),
+fun GeoLocationEntity.toGeoLocation(): GeoLocation {
+    return GeoLocation(
         name = name,
         latitude = latitude,
         longitude = longitude,
         country = country,
         admin1 = admin1
     )
-}
+} 

+ 6 - 5
core/local/src/main/java/com/codeskraps/core/local/data/repository/LocalGeocodingRepositoryImpl.kt

@@ -1,14 +1,15 @@
 package com.codeskraps.core.local.data.repository
 
+import com.codeskraps.core.local.data.mappers.toGeoLocation
+import com.codeskraps.core.local.data.mappers.toGeoLocationEntity
 import com.codeskraps.core.local.domain.model.GeoLocation
 import com.codeskraps.core.local.domain.repository.LocalGeocodingRepository
-import com.codeskraps.core.local.data.mappers.toGeoLocationEntity
-import com.codeskraps.core.local.data.mappers.toGeocoding
 import com.codeskraps.core.local.domain.repository.LocalResourceRepository
 import com.codeskraps.feature.common.util.Resource
-import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
-class LocalGeocodingRepositoryImpl @Inject constructor(
+class LocalGeocodingRepositoryImpl(
     private val geocodingDB: com.codeskraps.core.local.data.db.GeocodingDB,
     private val localResource: LocalResourceRepository
 ) : LocalGeocodingRepository {
@@ -16,7 +17,7 @@ class LocalGeocodingRepositoryImpl @Inject constructor(
     override suspend fun getCachedGeoLocation(): Resource<List<GeoLocation>> {
         return try {
             Resource.Success(
-                data = geocodingDB.geocodingDao().getAll().map { it.toGeocoding() }
+                data = geocodingDB.geocodingDao().getAll().map { it.toGeoLocation() }
             )
         } catch (e: Exception) {
             e.printStackTrace()

+ 21 - 23
core/local/src/main/java/com/codeskraps/core/local/data/repository/LocalResourceRepositoryImpl.kt

@@ -1,46 +1,44 @@
 package com.codeskraps.core.local.data.repository
 
-import android.content.res.Resources
-import com.codeskraps.feature.common.R
+import android.content.Context
 import com.codeskraps.core.local.domain.repository.LocalResourceRepository
-import javax.inject.Inject
 
-class LocalResourceRepositoryImpl @Inject constructor(
-    private val resources: Resources
+class LocalResourceRepositoryImpl(
+    private val context: Context
 ) : LocalResourceRepository {
-    override suspend fun getUnknownErrorString(): String {
-        return resources.getString(R.string.unknown_error)
+    override fun getCurrentLocationString(): String {
+        return "Current Location"
     }
 
-    override suspend fun getCurrentLocationString(): String {
-        return resources.getString(R.string.current_location)
+    override fun getLocationCanNotBeBlankString(): String {
+        return "Location name cannot be blank"
     }
 
-    override suspend fun getCheckInternetString(): String {
-        return resources.getString(R.string.check_internet_connection)
+    override fun getNoResultString(): String {
+        return "No results found"
     }
 
-    override suspend fun getCheckGPSString(): String {
-        return resources.getString(R.string.check_gps)
+    override fun getIssueLoadingCache(): String {
+        return "Issue loading cache"
     }
 
-    override suspend fun getLocationCanNotBeBlankString(): String {
-        return resources.getString(R.string.location_can_not_be_blank)
+    override fun getIssueSaving(): String {
+        return "Issue saving location"
     }
 
-    override suspend fun getNoResultString(): String {
-        return resources.getString(R.string.no_results)
+    override fun getIssueDeleting(): String {
+        return "Issue deleting location"
     }
 
-    override suspend fun getIssueLoadingCache(): String {
-        return resources.getString(R.string.issue_loading_cache)
+    override fun getUnknownErrorString(): String {
+        return "An unknown error occurred"
     }
 
-    override suspend fun getIssueSaving(): String {
-        return resources.getString(R.string.issue_saving)
+    override fun getCheckInternetString(): String {
+        return "Please check your internet connection"
     }
 
-    override suspend fun getIssueDeleting(): String {
-        return resources.getString(R.string.issue_deleting)
+    override fun getCheckGPSString(): String {
+        return "Please enable GPS"
     }
 }

+ 21 - 20
core/local/src/main/java/com/codeskraps/core/local/di/LocalModule.kt

@@ -1,26 +1,27 @@
 package com.codeskraps.core.local.di
 
-import android.app.Application
-import androidx.room.Room
+import android.content.Context
 import com.codeskraps.core.local.data.db.GeocodingDB
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
+import com.codeskraps.core.local.data.repository.LocalGeocodingRepositoryImpl
+import com.codeskraps.core.local.data.repository.LocalResourceRepositoryImpl
+import com.codeskraps.core.local.domain.repository.LocalGeocodingRepository
+import com.codeskraps.core.local.domain.repository.LocalResourceRepository
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
 
-@Module
-@InstallIn(SingletonComponent::class)
-object LocalModule {
-
-    @Provides
-    @Singleton
-    fun providesGeocodingDB(
-        application: Application
-    ): GeocodingDB {
-        return Room.databaseBuilder(
-            application,
-            GeocodingDB::class.java, "database-name"
-        ).build()
+val localModule = module {
+    single<LocalResourceRepository> { 
+        LocalResourceRepositoryImpl(androidContext())
+    }
+    
+    single { 
+        GeocodingDB.getInstance(androidContext())
+    }
+    
+    single<LocalGeocodingRepository> { 
+        LocalGeocodingRepositoryImpl(
+            geocodingDB = get(),
+            localResource = get()
+        )
     }
 }

+ 19 - 20
core/local/src/main/java/com/codeskraps/core/local/di/LocalRepositoryModule.kt

@@ -1,28 +1,27 @@
 package com.codeskraps.core.local.di
 
+import android.content.Context
+import com.codeskraps.core.local.data.db.GeocodingDB
 import com.codeskraps.core.local.data.repository.LocalGeocodingRepositoryImpl
 import com.codeskraps.core.local.data.repository.LocalResourceRepositoryImpl
 import com.codeskraps.core.local.domain.repository.LocalGeocodingRepository
 import com.codeskraps.core.local.domain.repository.LocalResourceRepository
-import dagger.Binds
-import dagger.Module
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
 
-@Module
-@InstallIn(SingletonComponent::class)
-abstract class LocalRepositoryModule {
-
-    @Binds
-    @Singleton
-    abstract fun bindsGeocodingRepository(
-        localGeocodingRepositoryImpl: LocalGeocodingRepositoryImpl
-    ): LocalGeocodingRepository
-
-    @Binds
-    @Singleton
-    abstract fun bindsLocalResourceRepository(
-        localResourceRepositoryImpl: LocalResourceRepositoryImpl
-    ): LocalResourceRepository
+val localRepositoryModule = module {
+    single<LocalResourceRepository> { 
+        LocalResourceRepositoryImpl(androidContext())
+    }
+    
+    single<LocalGeocodingRepository> { 
+        LocalGeocodingRepositoryImpl(
+            geocodingDB = get(),
+            localResource = get()
+        )
+    }
+    
+    single { 
+        GeocodingDB.getInstance(androidContext())
+    }
 }

+ 9 - 10
core/local/src/main/java/com/codeskraps/core/local/domain/repository/LocalResourceRepository.kt

@@ -1,14 +1,13 @@
 package com.codeskraps.core.local.domain.repository
 
 interface LocalResourceRepository {
-
-    suspend fun getUnknownErrorString(): String
-    suspend fun getCurrentLocationString(): String
-    suspend fun getCheckInternetString(): String
-    suspend fun getCheckGPSString(): String
-    suspend fun getLocationCanNotBeBlankString(): String
-    suspend fun getNoResultString(): String
-    suspend fun getIssueLoadingCache(): String
-    suspend fun getIssueSaving(): String
-    suspend fun getIssueDeleting(): String
+    fun getCurrentLocationString(): String
+    fun getLocationCanNotBeBlankString(): String
+    fun getNoResultString(): String
+    fun getIssueLoadingCache(): String
+    fun getIssueSaving(): String
+    fun getIssueDeleting(): String
+    fun getUnknownErrorString(): String
+    fun getCheckInternetString(): String
+    fun getCheckGPSString(): String
 }

+ 4 - 5
core/location/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
 }
 
 android {
@@ -40,18 +39,18 @@ android {
 }
 
 dependencies {
+    implementation(project(mapOf("path" to ":feature:common")))
 
     implementation(libs.androidx.core.ktx)
     implementation(libs.appcompat)
     implementation(libs.material)
 
+    // Koin
+    implementation(libs.koin.android)
+
     // Location Services
     implementation(libs.android.play.services.location)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
-
     testImplementation(libs.junit.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.espresso.core)

+ 1 - 2
core/location/src/main/java/com/codeskraps/core/location/data/DefaultLocationTracker.kt

@@ -10,10 +10,9 @@ import androidx.core.content.ContextCompat
 import com.codeskraps.core.location.domain.LocationTracker
 import com.google.android.gms.location.FusedLocationProviderClient
 import kotlinx.coroutines.suspendCancellableCoroutine
-import javax.inject.Inject
 import kotlin.coroutines.resume
 
-class DefaultLocationTracker @Inject constructor(
+class DefaultLocationTracker(
     private val locationClient: FusedLocationProviderClient,
     private val application: Application
 ) : LocationTracker {

+ 10 - 18
core/location/src/main/java/com/codeskraps/core/location/di/CoreLocationModule.kt

@@ -4,24 +4,16 @@ import android.app.Application
 import com.codeskraps.core.location.data.DefaultLocationTracker
 import com.codeskraps.core.location.domain.LocationTracker
 import com.google.android.gms.location.LocationServices
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
+import org.koin.android.ext.koin.androidApplication
+import org.koin.dsl.module
 
-@Module
-@InstallIn(SingletonComponent::class)
-object CoreLocationModule {
+val locationModule = module {
+    single<LocationTracker> { provideLocationTracker(androidApplication()) }
+}
 
-    @Provides
-    @Singleton
-    fun providesLocationTracker(
-        app: Application
-    ): LocationTracker {
-        return DefaultLocationTracker(
-            locationClient = LocationServices.getFusedLocationProviderClient(app),
-            application = app
-        )
-    }
+private fun provideLocationTracker(app: Application): LocationTracker {
+    return DefaultLocationTracker(
+        locationClient = LocationServices.getFusedLocationProviderClient(app),
+        application = app
+    )
 }

+ 2 - 4
core/umami/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
 }
 
 android {
@@ -50,9 +49,8 @@ dependencies {
     // WebView
     implementation(libs.androidx.webkit)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
 
     // Testing
     testImplementation(libs.junit.junit)

+ 12 - 3
core/umami/src/main/java/com/codeskraps/umami/data/repository/AnalyticsRepositoryImpl.kt

@@ -1,14 +1,23 @@
 package com.codeskraps.umami.data.repository
 
+import android.app.Application
 import com.codeskraps.umami.data.remote.UmamiAnalyticsDataSource
+import com.codeskraps.umami.data.remote.UmamiConfig
 import com.codeskraps.umami.domain.AnalyticsRepository
 import com.codeskraps.umami.domain.DeviceIdRepository
-import javax.inject.Inject
 
-internal class AnalyticsRepositoryImpl @Inject constructor(
-    private val analyticsDataSource: UmamiAnalyticsDataSource,
+internal class AnalyticsRepositoryImpl(
+    private val application: Application,
     private val deviceIdRepository: DeviceIdRepository
 ) : AnalyticsRepository {
+    private val analyticsDataSource = UmamiAnalyticsDataSource(
+        context = application,
+        config = UmamiConfig(
+            scriptUrl = "https://umami.codeskraps.com/script.js",
+            websiteId = "047c76b3-6f42-45f0-ba9b-772b056ccdc1",
+            baseUrl = "https://umami.codeskraps.com"
+        )
+    )
 
     override suspend fun initialize() {
         analyticsDataSource.initialize()

+ 5 - 4
core/umami/src/main/java/com/codeskraps/umami/data/repository/DeviceIdRepositoryImpl.kt

@@ -1,16 +1,17 @@
 package com.codeskraps.umami.data.repository
 
+import android.app.Application
 import android.content.Context
 import android.content.SharedPreferences
+import android.provider.Settings
 import com.codeskraps.umami.domain.DeviceIdRepository
 import java.util.UUID
-import javax.inject.Inject
 
-class DeviceIdRepositoryImpl @Inject constructor(
-    context: Context
+class DeviceIdRepositoryImpl(
+    private val application: Application
 ) : DeviceIdRepository {
 
-    private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+    private val prefs: SharedPreferences = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
 
     override suspend fun getOrCreateDeviceId(): String {
         return prefs.getString(KEY_DEVICE_ID, null) ?: generateAndSaveDeviceId()

+ 5 - 37
core/umami/src/main/java/com/codeskraps/umami/di/UmamiModule.kt

@@ -1,46 +1,14 @@
 package com.codeskraps.umami.di
 
 import android.app.Application
-import com.codeskraps.umami.data.remote.UmamiAnalyticsDataSource
-import com.codeskraps.umami.data.remote.UmamiConfig
 import com.codeskraps.umami.data.repository.AnalyticsRepositoryImpl
 import com.codeskraps.umami.data.repository.DeviceIdRepositoryImpl
 import com.codeskraps.umami.domain.AnalyticsRepository
 import com.codeskraps.umami.domain.DeviceIdRepository
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
+import org.koin.android.ext.koin.androidApplication
+import org.koin.dsl.module
 
-@Module
-@InstallIn(SingletonComponent::class)
-object CoreUmamiModule {
-
-    @Provides
-    @Singleton
-    fun providesDeviceIdRepository(
-        app: Application
-    ): DeviceIdRepository {
-        return DeviceIdRepositoryImpl(app)
-    }
-
-    @Provides
-    @Singleton
-    fun providesAnalyticsRepository(
-        app: Application,
-        deviceIdRepository: DeviceIdRepository
-    ): AnalyticsRepository {
-        return AnalyticsRepositoryImpl(
-            UmamiAnalyticsDataSource(
-                context = app,
-                config = UmamiConfig(
-                    scriptUrl = "https://umami.codeskraps.com/script.js",
-                    websiteId = "047c76b3-6f42-45f0-ba9b-772b056ccdc1",
-                    baseUrl = "https://umami.codeskraps.com"
-                )
-            ),
-            deviceIdRepository = deviceIdRepository
-        )
-    }
+val umamiModule = module {
+    single<DeviceIdRepository> { DeviceIdRepositoryImpl(androidApplication()) }
+    single<AnalyticsRepository> { AnalyticsRepositoryImpl(androidApplication(), get()) }
 }

+ 18 - 13
feature/common/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
     alias(libs.plugins.compose.compiler)
 }
 
@@ -16,6 +15,7 @@ android {
         minSdk = ConfigData.minSdk
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles("consumer-rules.pro")
     }
 
     buildTypes {
@@ -48,26 +48,31 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
-    hilt {
-        enableAggregatingTask = true
-    }
 }
 
 dependencies {
-
     implementation(libs.androidx.core.ktx)
-    implementation(libs.appcompat)
-    implementation(libs.material)
-
+    implementation(libs.androidx.lifecycle.runtime.ktx)
     implementation(libs.androidx.activity.compose)
+    implementation(libs.androidx.navigation.compose)
+    implementation(libs.androidx.lifecycle.runtime.compose)
+    implementation(libs.android.compose.material3)
+    val composeBom = platform(libs.androidx.compose.bom)
+    implementation(composeBom)
+    implementation(libs.androidx.compose.ui)
+    implementation(libs.androidx.compose.graphics)
+    implementation(libs.androidx.compose.tooling.preview)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
+    implementation(libs.koin.androidx.compose)
+
+    // Coroutines
+    implementation(libs.kotlinx.coroutines.core)
+    implementation(libs.kotlinx.coroutines.android)
 
     testImplementation(libs.junit.junit)
+    testImplementation(libs.coroutines.test)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.espresso.core)
-
-    implementation(libs.coroutines.test)
 }

+ 8 - 21
feature/common/src/main/java/com/codeskraps/feature/common/di/FeatureModule.kt

@@ -4,27 +4,14 @@ import android.app.Application
 import android.content.res.Resources
 import com.codeskraps.feature.common.dispatcher.DispatcherProvider
 import com.codeskraps.feature.common.dispatcher.StandardDispatcherProvider
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
+import org.koin.android.ext.koin.androidApplication
+import org.koin.dsl.module
 
-@Module
-@InstallIn(SingletonComponent::class)
-object FeatureModule {
-
-    @Provides
-    @Singleton
-    fun providesDispatcherProvider(): DispatcherProvider {
-        return StandardDispatcherProvider()
-    }
+val commonModule = module {
+    single<DispatcherProvider> { StandardDispatcherProvider() }
+    single { provideResources(androidApplication()) }
+}
 
-    @Provides
-    @Singleton
-    fun providesResources(
-        application: Application
-    ): Resources {
-        return application.resources
-    }
+private fun provideResources(application: Application): Resources {
+    return application.resources
 }

+ 1 - 1
feature/common/src/main/java/com/codeskraps/feature/common/dispatcher/TestDispatcherProvider.kt → feature/common/src/test/java/com/codeskraps/feature/common/dispatcher/TestDispatcherProvider.kt

@@ -11,4 +11,4 @@ class TestDispatcherProvider(
     override val ui: CoroutineDispatcher get() = StandardTestDispatcher(scheduler)
     override val io: CoroutineDispatcher get() = StandardTestDispatcher(scheduler)
     override val default: CoroutineDispatcher get() = StandardTestDispatcher(scheduler)
-}
+} 

+ 4 - 10
feature/geocoding/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
     alias(libs.plugins.compose.compiler)
 }
 
@@ -16,9 +15,7 @@ android {
         minSdk = ConfigData.minSdk
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        vectorDrawables {
-            useSupportLibrary = true
-        }
+        consumerProguardFiles("consumer-rules.pro")
     }
 
     buildTypes {
@@ -51,9 +48,6 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
-    hilt {
-        enableAggregatingTask = true
-    }
 }
 
 dependencies {
@@ -73,9 +67,9 @@ dependencies {
     implementation(libs.androidx.compose.graphics)
     implementation(libs.androidx.compose.tooling.preview)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
+    implementation(libs.koin.androidx.compose)
 
     // Retrofit & Moshi
     implementation(libs.retrofit.retrofit)

+ 14 - 1
feature/geocoding/proguard-rules.pro

@@ -21,4 +21,17 @@
 #-renamesourcefileattribute SourceFile
 
 # Added to fix R8 warning for missing StringConcatFactory
--dontwarn java.lang.invoke.StringConcatFactory
+-dontwarn java.lang.invoke.StringConcatFactory
+
+# Keep Hilt generated classes
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$* { *; }
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_Factory { *; }
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_MembersInjector { *; }
+
+# Keep specific Hilt ViewModel components
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$BindsModule { *; }
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_HiltModules$KeyModule { *; }
+
+# Keep Hilt ViewModel factory and members injector
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_Factory { *; }
+-keep class com.codeskraps.feature.geocoding.presentation.GeocodingViewModel_MembersInjector { *; }

+ 1 - 2
feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/data/repository/GeocodingRepositoryImpl.kt

@@ -5,9 +5,8 @@ import com.codeskraps.feature.common.util.Resource
 import com.codeskraps.feature.geocoding.data.mappers.toGeocoding
 import com.codeskraps.feature.geocoding.data.remote.GeocodingApi
 import com.codeskraps.feature.geocoding.repository.GeocodingRepository
-import javax.inject.Inject
 
-class GeocodingRepositoryImpl @Inject constructor(
+class GeocodingRepositoryImpl(
     private val api: GeocodingApi,
 ) : GeocodingRepository {
 

+ 20 - 19
feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/di/FeatureModule.kt

@@ -1,30 +1,31 @@
 package com.codeskraps.feature.geocoding.di
 
 import com.codeskraps.feature.geocoding.data.remote.GeocodingApi
+import com.codeskraps.feature.geocoding.data.repository.GeocodingRepositoryImpl
+import com.codeskraps.feature.geocoding.presentation.GeocodingViewModel
+import com.codeskraps.feature.geocoding.repository.GeocodingRepository
 import com.squareup.moshi.Moshi
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
 import retrofit2.Retrofit
 import retrofit2.converter.moshi.MoshiConverterFactory
 import retrofit2.create
-import javax.inject.Singleton
 
-@Module
-@InstallIn(SingletonComponent::class)
-object FeatureModule {
+val geocodingModule = module {
+    single { provideGeocodingApi() }
+    single<GeocodingRepository> { GeocodingRepositoryImpl(get()) }
+    viewModel { GeocodingViewModel(get(), get(), get(), get(), get()) }
+}
 
-    @Provides
-    @Singleton
-    fun providesGeocodingApi(): GeocodingApi {
-        val moshi = Moshi.Builder()
-            .build()
+private fun provideGeocodingApi(): GeocodingApi {
+    val moshi = Moshi.Builder()
+        .add(KotlinJsonAdapterFactory())
+        .build()
 
-        return Retrofit.Builder()
-            .baseUrl("https://geocoding-api.open-meteo.com/")
-            .addConverterFactory(MoshiConverterFactory.create(moshi))
-            .build()
-            .create()
-    }
+    return Retrofit.Builder()
+        .baseUrl("https://geocoding-api.open-meteo.com/")
+        .addConverterFactory(MoshiConverterFactory.create(moshi))
+        .build()
+        .create()
 }

+ 0 - 20
feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/di/RepositoryModule.kt

@@ -1,20 +0,0 @@
-package com.codeskraps.feature.geocoding.di
-
-import com.codeskraps.feature.geocoding.data.repository.GeocodingRepositoryImpl
-import com.codeskraps.feature.geocoding.repository.GeocodingRepository
-import dagger.Binds
-import dagger.Module
-import dagger.hilt.InstallIn
-import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
-
-@Module
-@InstallIn(SingletonComponent::class)
-abstract class RepositoryModule {
-
-    @Binds
-    @Singleton
-    abstract fun bindsGeocodingRepository(
-        geocodingRepositoryImpl: GeocodingRepositoryImpl
-    ): GeocodingRepository
-}

+ 1 - 4
feature/geocoding/src/main/java/com/codeskraps/feature/geocoding/presentation/GeocodingViewModel.kt

@@ -12,14 +12,11 @@ import com.codeskraps.feature.geocoding.presentation.mvi.GeoAction
 import com.codeskraps.feature.geocoding.presentation.mvi.GeoEvent
 import com.codeskraps.feature.geocoding.presentation.mvi.GeoState
 import com.codeskraps.umami.domain.AnalyticsRepository
-import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-@HiltViewModel
-class GeocodingViewModel @Inject constructor(
+class GeocodingViewModel(
     private val localGeocodingRepository: LocalGeocodingRepository,
     private val geocodingRepository: GeocodingRepository,
     private val localResources: LocalResourceRepository,

+ 10 - 6
feature/maps/build.gradle.kts

@@ -4,9 +4,8 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
-    alias(libs.plugins.secrets.gradle.plugin)
     alias(libs.plugins.compose.compiler)
+    alias(libs.plugins.secrets.gradle.plugin)
 }
 
 android {
@@ -41,11 +40,15 @@ android {
     }
     buildFeatures {
         compose = true
-        buildConfig = true
     }
     composeOptions {
         kotlinCompilerExtensionVersion = ConfigData.kotlinCompiler
     }
+    packaging {
+        resources {
+            excludes += "/META-INF/{AL2.0,LGPL2.1}"
+        }
+    }
 }
 
 secrets {
@@ -65,6 +68,7 @@ secrets {
 
 dependencies {
     implementation(project(mapOf("path" to ":feature:common")))
+    implementation(project(mapOf("path" to ":feature:weather")))
     implementation(project(mapOf("path" to ":core:location")))
     implementation(project(mapOf("path" to ":core:local")))
     implementation(project(mapOf("path" to ":core:umami")))
@@ -81,9 +85,9 @@ dependencies {
     implementation(libs.androidx.compose.graphics)
     implementation(libs.androidx.compose.tooling.preview)
 
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
+    implementation(libs.koin.androidx.compose)
 
     implementation(libs.play.services.maps)
     implementation(libs.maps.compose)

+ 17 - 0
feature/maps/src/main/java/com/codeskraps/maps/di/FeatureModule.kt

@@ -0,0 +1,17 @@
+package com.codeskraps.maps.di
+
+import com.codeskraps.maps.presentation.MapViewModel
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+
+val mapsFeatureModule = module {
+    viewModel {
+        MapViewModel(
+            locationTracker = get(),
+            localGeocodingRepository = get(),
+            localResources = get(),
+            dispatcherProvider = get(),
+            analytics = get()
+        )
+    }
+} 

+ 1 - 4
feature/maps/src/main/java/com/codeskraps/maps/presentation/MapViewModel.kt

@@ -12,12 +12,9 @@ import com.codeskraps.maps.presentation.mvi.MapEvent
 import com.codeskraps.maps.presentation.mvi.MapState
 import com.codeskraps.umami.domain.AnalyticsRepository
 import com.google.android.gms.maps.model.LatLng
-import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-@HiltViewModel
-class MapViewModel @Inject constructor(
+class MapViewModel(
     private val locationTracker: LocationTracker,
     private val localGeocodingRepository: LocalGeocodingRepository,
     private val localResources: LocalResourceRepository,

+ 12 - 22
feature/weather/build.gradle.kts

@@ -4,7 +4,6 @@ plugins {
     alias(libs.plugins.android.library)
     alias(libs.plugins.org.jetbrains.kotlin.android)
     alias(libs.plugins.com.google.devtools.ksp)
-    alias(libs.plugins.dagger.hilt)
     alias(libs.plugins.compose.compiler)
 }
 
@@ -16,9 +15,7 @@ android {
         minSdk = ConfigData.minSdk
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        vectorDrawables {
-            useSupportLibrary = true
-        }
+        consumerProguardFiles("consumer-rules.pro")
     }
 
     buildTypes {
@@ -51,15 +48,12 @@ android {
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
         }
     }
-    hilt {
-        enableAggregatingTask = true
-    }
 }
 
 dependencies {
     implementation(project(mapOf("path" to ":feature:common")))
-    implementation(project(mapOf("path" to ":core:location")))
     implementation(project(mapOf("path" to ":core:local")))
+    implementation(project(mapOf("path" to ":core:location")))
     implementation(project(mapOf("path" to ":core:umami")))
 
     implementation(libs.androidx.core.ktx)
@@ -67,33 +61,29 @@ dependencies {
     implementation(libs.androidx.activity.compose)
     implementation(libs.androidx.navigation.compose)
     implementation(libs.androidx.lifecycle.runtime.compose)
-    implementation(libs.android.compose.material)
     implementation(libs.android.compose.material3)
-    implementation(libs.androidx.animation.graphics.android)
-
     val composeBom = platform(libs.androidx.compose.bom)
     implementation(composeBom)
     implementation(libs.androidx.compose.ui)
     implementation(libs.androidx.compose.graphics)
     implementation(libs.androidx.compose.tooling.preview)
+    implementation(libs.androidx.animation.graphics.android)
+    implementation(libs.android.compose.material)
 
-    implementation(libs.kotlinx.collections.immutable)
-
-    //Dagger - Hilt
-    implementation(libs.hilt.android)
-    ksp(libs.hilt.android.compiler)
+    // Koin
+    implementation(libs.koin.android)
+    implementation(libs.koin.androidx.compose)
 
-    // Retrofit
+    // Retrofit & Moshi
     implementation(libs.retrofit.retrofit)
     implementation(libs.retrofit.converter.moshi)
     implementation(libs.moshi.kotlin)
+    ksp(libs.moshi.kotlin.codegen)
+
+    // Immutable Collections
+    implementation(libs.kotlinx.collections.immutable)
 
-    implementation(libs.coroutines.test)
     testImplementation(libs.junit.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.espresso.core)
-    androidTestImplementation(composeBom)
-    androidTestImplementation(libs.androidx.compose.test.junit4)
-    debugImplementation(libs.androidx.compose.tooling)
-    debugImplementation(libs.androidx.compose.test.manifest)
 }

+ 17 - 1
feature/weather/proguard-rules.pro

@@ -18,4 +18,20 @@
 
 # If you keep the line number information, uncomment this to
 # hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile
+
+# Added to fix R8 warning for missing StringConcatFactory
+-dontwarn java.lang.invoke.StringConcatFactory
+
+# Keep Hilt generated classes
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$* { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_Factory { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_MembersInjector { *; }
+
+# Keep specific Hilt ViewModel components
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$BindsModule { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_HiltModules$KeyModule { *; }
+
+# Keep Hilt ViewModel factory and members injector
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_Factory { *; }
+-keep class com.codeskraps.feature.weather.presentation.WeatherViewModel_MembersInjector { *; }

+ 1 - 2
feature/weather/src/main/java/com/codeskraps/feature/weather/data/repository/WeatherRepositoryImpl.kt

@@ -7,9 +7,8 @@ import com.codeskraps.feature.weather.data.remote.WeatherApi
 import com.codeskraps.feature.weather.data.util.retryWithExponentialBackoff
 import com.codeskraps.feature.weather.domain.model.WeatherInfo
 import com.codeskraps.feature.weather.domain.repository.WeatherRepository
-import javax.inject.Inject
 
-class WeatherRepositoryImpl @Inject constructor(
+class WeatherRepositoryImpl(
     private val api: WeatherApi,
     private val localResource: LocalResourceRepository
 ) : WeatherRepository {

+ 34 - 18
feature/weather/src/main/java/com/codeskraps/feature/weather/di/FeatureModule.kt

@@ -1,30 +1,46 @@
 package com.codeskraps.feature.weather.di
 
 import com.codeskraps.feature.weather.data.remote.WeatherApi
+import com.codeskraps.feature.weather.data.repository.WeatherRepositoryImpl
+import com.codeskraps.feature.weather.domain.repository.WeatherRepository
+import com.codeskraps.feature.weather.presentation.WeatherViewModel
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ViewModelComponent
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
 import retrofit2.Retrofit
 import retrofit2.converter.moshi.MoshiConverterFactory
 import retrofit2.create
 
-@Module
-@InstallIn(ViewModelComponent::class)
-object FeatureModule {
+val weatherModule = module {
+    single { provideWeatherApi() }
+    single<WeatherRepository> { 
+        WeatherRepositoryImpl(
+            api = get(),
+            localResource = get()
+        ) 
+    }
+    viewModel { 
+        WeatherViewModel(
+            localGeocodingRepository = get(),
+            weatherRepository = get(),
+            locationTracker = get(),
+            localResource = get(),
+            dispatcherProvider = get(),
+            savedStateHandle = get(),
+            analyticsRepository = get()
+        ) 
+    }
+}
 
-    @Provides
-    fun providesWeatherApi(): WeatherApi {
-        val moshi = Moshi.Builder()
-            .add(KotlinJsonAdapterFactory())
-            .build()
+private fun provideWeatherApi(): WeatherApi {
+    val moshi = Moshi.Builder()
+        .add(KotlinJsonAdapterFactory())
+        .build()
 
-        return Retrofit.Builder()
-            .baseUrl("https://api.open-meteo.com/")
-            .addConverterFactory(MoshiConverterFactory.create(moshi))
-            .build()
-            .create()
-    }
+    return Retrofit.Builder()
+        .baseUrl("https://api.open-meteo.com/")
+        .addConverterFactory(MoshiConverterFactory.create(moshi))
+        .build()
+        .create()
 }

+ 0 - 18
feature/weather/src/main/java/com/codeskraps/feature/weather/di/RepositoryModule.kt

@@ -1,18 +0,0 @@
-package com.codeskraps.feature.weather.di
-
-import com.codeskraps.feature.weather.data.repository.WeatherRepositoryImpl
-import com.codeskraps.feature.weather.domain.repository.WeatherRepository
-import dagger.Binds
-import dagger.Module
-import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ViewModelComponent
-
-@Module
-@InstallIn(ViewModelComponent::class)
-abstract class RepositoryModule {
-
-    @Binds
-    abstract fun bindsWeatherRepository(
-        weatherRepositoryIml: WeatherRepositoryImpl
-    ): WeatherRepository
-}

+ 1 - 4
feature/weather/src/main/java/com/codeskraps/feature/weather/presentation/WeatherViewModel.kt

@@ -17,12 +17,9 @@ import com.codeskraps.feature.weather.presentation.mvi.WeatherEvent
 import com.codeskraps.feature.weather.presentation.mvi.WeatherState
 import com.codeskraps.core.location.domain.LocationTracker
 import com.codeskraps.umami.domain.AnalyticsRepository
-import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-@HiltViewModel
-class WeatherViewModel @Inject constructor(
+class WeatherViewModel(
     private val localGeocodingRepository: LocalGeocodingRepository,
     private val weatherRepository: WeatherRepository,
     private val locationTracker: LocationTracker,

+ 29 - 29
feature/weather/src/main/java/com/codeskraps/feature/weather/presentation/components/WeatherScreen.kt

@@ -63,7 +63,6 @@ fun WeatherScreen(
     action: Flow<WeatherAction>,
     navRoute: (String) -> Unit
 ) {
-
     var showDialog by remember {
         mutableStateOf(false)
     }
@@ -91,10 +90,10 @@ fun WeatherScreen(
     }
 
     val isLoading = state.isLoading
-    val pullRefreshState = rememberPullRefreshState(isLoading, {
-        handleEvent(WeatherEvent.Refresh)
-    })
-
+    val pullRefreshState = rememberPullRefreshState(
+        refreshing = isLoading,
+        onRefresh = { handleEvent(WeatherEvent.Refresh) }
+    )
 
     LaunchedEffect(key1 = Unit) {
         permissionLauncher.launch(
@@ -178,7 +177,6 @@ fun WeatherScreen(
         ) {
             if (state.isLoading) {
                 Text(text = "")
-
                 CircularProgressIndicator(
                     modifier = Modifier.align(Alignment.Center)
                 )
@@ -190,12 +188,14 @@ fun WeatherScreen(
                     modifier = Modifier.align(Alignment.Center)
                 )
             } else {
-                Column(
+                Box(
                     modifier = Modifier
                         .fillMaxSize()
+                        .pullRefresh(pullRefreshState)
                 ) {
-                    Box(Modifier.pullRefresh(pullRefreshState)) {
-
+                    Column(
+                        modifier = Modifier.fillMaxSize()
+                    ) {
                         state.weatherInfo.currentWeatherData?.let {
                             LazyColumn {
                                 item {
@@ -207,30 +207,30 @@ fun WeatherScreen(
                             }
                         }
 
-                        PullRefreshIndicator(
-                            refreshing = false,
-                            state = pullRefreshState,
-                            Modifier.align(Alignment.TopCenter)
-                        )
-                    }
-
-                    LazyColumn(
-                        modifier = Modifier
-                            .fillMaxSize()
-                            .background(MaterialTheme.colorScheme.background)
-                    ) {
-                        item {
-                            state.weatherInfo.weatherDataPerDay.forEach { perDay ->
+                        LazyColumn(
+                            modifier = Modifier
+                                .fillMaxSize()
+                                .background(MaterialTheme.colorScheme.background)
+                        ) {
+                            item {
+                                state.weatherInfo.weatherDataPerDay.forEach { perDay ->
+                                    Spacer(modifier = Modifier.height(16.dp))
+                                    WeatherForecast(
+                                        state.weatherInfo,
+                                        perDay.value,
+                                        handleEvent
+                                    )
+                                }
                                 Spacer(modifier = Modifier.height(16.dp))
-                                WeatherForecast(
-                                    state.weatherInfo,
-                                    perDay.value,
-                                    handleEvent
-                                )
                             }
-                            Spacer(modifier = Modifier.height(16.dp))
                         }
                     }
+
+                    PullRefreshIndicator(
+                        refreshing = isLoading,
+                        state = pullRefreshState,
+                        modifier = Modifier.align(Alignment.TopCenter)
+                    )
                 }
             }
 

+ 7 - 7
gradle/libs.versions.toml

@@ -7,8 +7,8 @@ ktx = "1.15.0"
 compose = "1.10.1"
 immutable = "0.3.7"
 location = "21.3.0"
-hilt = "2.52"
-hilt-navigation = "1.2.0"
+koin = "3.5.3"
+koin-compose = "3.5.3"
 moshiKotlin = "1.15.0"
 moshiKotlinCodegen = "1.15.0"
 navigationCompose = "2.8.9"
@@ -54,10 +54,10 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
 kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesAndroid" }
 android-play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "location" }
 
-# Hilt
-hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
-hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation" }
+# Koin
+koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
+koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin-compose" }
+koin-androidx-compose-navigation = { group = "io.insert-koin", name = "koin-androidx-compose-navigation", version.ref = "koin-compose" }
 
 # Retrofit
 moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshiKotlin" }
@@ -68,6 +68,7 @@ retrofit-converter-moshi = { group = "com.squareup.retrofit2", name = "converter
 # Room
 room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
 room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
+androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
 
 play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "play-services-maps" }
 maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "maps-compose" }
@@ -89,5 +90,4 @@ android-library = { id = "com.android.library", version.ref = "androidGradlePlug
 org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
 compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "org-jetbrains-kotlin-android" }
 com-google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
-dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
 secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }