Skip to content

Commit d85f247

Browse files
committed
- Migrate to Kotlin 2.0
- Migrate to KSP2 - Add .kotlin folder to .gitignore - Create a Network security config xml file - Change the direct Datasource module call from App to the first feature loaded - Implement Koin's Lazy Modules throughout the project so modules are instantiated in the background not afecting Initial Time To Display metric (ITTD) - Replace Koin's single {} with singleOf() where possible to improve code conciseness and readability - Replace StateFlow with SharedFlow so everytime a new value is emitted, it gets collected by the View through its internal caching (Replay) system - Remove mutable lists from Cells so now they are confined to method scopes - Turn Room into the Single Source Of Truth of the app (It is not finished since Paging is still not present but it will once I migrate the current UI to Compose so I can migrate to Paging Compose instead of implementing it now with XML and then having to migrate to Paging Compose) - Finish migration to Mixed modularization (By Feature + By Layer: https://www.youtube.com/watch?v=16SwTvzDO0A) (I probably will adopt the "By Feature/Feature Module" only approach since the current approach does not adhere to Clean Architecture and I'm sensing it will get progressively bureaucratic/harder to maintain once/if this project scales and it might me valid to other projects as well) - Refactor the way viewModelScope is told to check on its coroutines status - Create a mapper to map ProfileState into ProfileUIState since there are fields in ProfileUIState that should not be accessible/visible by layers outside UI - Create mutableSharedFlowOf extension function so there is no need to add the replay parameter everytime a MutableSharedFLow is needed - Re-add inline and crossinline to runTaskOnBackground extension function so there is no memory waste - Refactor ResultHandler1s handleResult() so State<*> is accessible through context - Bump dependency version to newer ones - Refactor code - Implement the new way to reference the Compose Compiler - Remove blank spaces from profile strings by using .trim() on them before being sent to the Github API
1 parent c27266e commit d85f247

File tree

89 files changed

+395
-326
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+395
-326
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
/config
88
/.gradle
99
/.idea
10+
/.kotlin
1011
/local.properties

app/build.gradle

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
22

33
plugins {
4-
id 'com.android.application'
5-
id 'kotlin-android'
6-
id 'kotlinx-serialization'
4+
alias dep.plugins.androidApplication
5+
alias dep.plugins.kotlinPlugin
6+
alias dep.plugins.serialization
77
}
88

99
apply from: "$project.rootDir/flavors/flavors.gradle"
@@ -70,7 +70,6 @@ android {
7070
dependencies {
7171
implementation fileTree(dir: 'libs', include: ['*.jar'])
7272
implementation project(':modules:common:core') because 'Core Module'
73-
implementation project(':modules:common:datasource:datasource') because 'Datasource Module'
7473
implementation project(':modules:common:ui:ui') because 'UI Module'
7574
implementation project(':modules:features:profile') because 'Profile Module'
7675

app/src/main/AndroidManifest.xml

+18-15
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
3+
xmlns:tools="http://schemas.android.com/tools">
44

55
<application
6-
android:name=".App"
7-
android:allowBackup="true"
8-
android:icon="@mipmap/ic_launcher"
9-
android:label="@string/app_name"
10-
android:localeConfig="@xml/locales_config" tools:targetApi="tiramisu"
11-
android:roundIcon="@mipmap/ic_launcher_round"
12-
android:supportsRtl="true"
13-
android:theme="@style/AppTheme"
14-
tools:ignore="AllowBackup">
6+
android:name=".App"
7+
android:allowBackup="true"
8+
android:icon="@mipmap/ic_launcher"
9+
android:label="@string/app_name"
10+
android:localeConfig="@xml/locales_config"
11+
android:networkSecurityConfig="@xml/network_security_config"
12+
android:roundIcon="@mipmap/ic_launcher_round"
13+
android:supportsRtl="true"
14+
android:theme="@style/AppTheme"
15+
tools:ignore="AllowBackup"
16+
tools:targetApi="tiramisu">
1517

16-
<profileable tools:targetApi="q" android:shell="true" />
18+
<profileable
19+
android:shell="true"
20+
tools:targetApi="q" />
1721

1822
<activity
19-
android:name=".view.activity.ProfileListingActivity"
20-
android:configChanges="uiMode"
21-
android:exported="true">
23+
android:name=".ui.profile.activity.ProfileListingActivity"
24+
android:configChanges="uiMode"
25+
android:exported="true">
2226
<intent-filter>
2327
<action android:name="android.intent.action.MAIN" />
24-
2528
<category android:name="android.intent.category.LAUNCHER" />
2629
</intent-filter>
2730
</activity>

app/src/main/kotlin/githubprofilesearcher/caiodev/com/br/githubprofilesearcher/App.kt

+4-5
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@ import coil.ImageLoader
55
import coil.ImageLoaderFactory
66
import coil.disk.DiskCache
77
import coil.memory.MemoryCache
8-
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.di.datasource
98
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.di.profileModule
109
import org.koin.android.ext.koin.androidContext
1110
import org.koin.android.ext.koin.androidLogger
1211
import org.koin.core.context.startKoin
12+
import org.koin.core.lazyModules
1313
import org.koin.core.logger.Level
14-
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.ui.R as UI
1514

1615
class App : Application(), ImageLoaderFactory {
1716
override fun onCreate() {
1817
super.onCreate()
1918
startKoin {
2019
androidContext(this@App)
2120
androidLogger(Level.DEBUG)
22-
modules(datasource, profileModule)
21+
lazyModules(profileModule)
2322
}
2423
}
2524

@@ -37,8 +36,8 @@ class App : Application(), ImageLoaderFactory {
3736
.maxSizePercent(MEMORY_CACHE_CAP)
3837
.build()
3938
}
40-
.placeholder(UI.mipmap.ic_launcher)
41-
.error(UI.mipmap.ic_launcher)
39+
.placeholder(R.mipmap.ic_launcher)
40+
.error(R.mipmap.ic_launcher)
4241
.build()
4342
}
4443

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<base-config cleartextTrafficPermitted="false" />
4+
</network-security-config>

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
alias dep.plugins.androidApplication apply false
33
alias dep.plugins.androidLibrary apply false
4+
alias dep.plugins.composeCompiler apply false
45
alias dep.plugins.detekt apply false
56
alias dep.plugins.kotlinPlugin apply false
67
alias dep.plugins.ksp apply false

gradle/libs.versions.toml

+29-19
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,45 @@ targetSdk = '34'
99
versionCode = '1'
1010
versionName = '1.0'
1111

12-
appCompat = '1.7.0-beta01'
12+
appCompat = '1.7.0-rc01'
1313

1414
chucker = '4.0.0'
1515

1616
coil = '2.6.0'
1717

1818
#Compose
1919
activity = '1.9.0'
20-
compiler = '1.5.11'
21-
composeCore = '1.7.0-alpha08'
22-
material3 = '1.3.0-alpha06'
20+
composeCore = '1.7.0-beta01'
21+
material3 = '1.3.0-beta01'
2322
runtimeTracingCompose = '1.0.0-beta01'
2423

2524
coroutines = '1.8.0'
2625

2726
dataStore = '1.1.1'
2827

29-
gradle = '8.4.0'
28+
gradle = '8.4.1'
3029

31-
koin = '3.5.6'
30+
koin = '3.6.0-Beta4'
3231

33-
kotlin = '1.9.23'
32+
kotlin = '2.0.0'
3433

35-
ksp = '1.9.23-1.0.19'
34+
ksp = '2.0.0-1.0.21'
3635

3736
ktor = '2.3.10'
3837

3938
ktxCore = '1.13.1'
4039

41-
lifecycle = '2.8.0-rc01'
40+
lifecycle = '2.8.0'
4241

4342
materialDesign = '1.12.0'
4443

4544
#Paging
46-
paging = '3.3.0-rc01'
45+
paging = '3.3.0'
4746

4847
#Quality/Performance
4948
detekt = '1.23.6'
5049
ktlint = '1.2.1'
51-
ktlintPlugin = '12.1.0'
50+
ktlintPlugin = '12.1.1'
5251
leakCanary = '2.14'
5352
runtimeTracing = '1.3.0-alpha02'
5453

@@ -62,12 +61,12 @@ recyclerView = '1.3.2'
6261

6362
archTest = '2.2.0'
6463
jacoco = '0.8.12'
65-
junit = '1.2.0-alpha04'
64+
junit = '1.2.0-beta01'
6665
junit5 = '5.11.0-M1'
67-
mockkVersion = '1.13.10'
66+
mockk = '1.13.10'
6867
robolectric = '4.11.1'
69-
testCore = '1.6.0-alpha06'
70-
testRunner = '1.6.0-alpha07'
68+
testCore = '1.6.0-beta01'
69+
testRunner = '1.6.0-beta01'
7170

7271
##### Libraries #####
7372

@@ -108,7 +107,10 @@ ktxCore = { group = "androidx.core", name = "core-ktx", version.ref = "ktxCore"
108107

109108
preferencesDataStore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "dataStore" }
110109

111-
koin = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
110+
koinAndroid = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
111+
koinCompose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" }
112+
koinCore = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }
113+
koinCoroutines = { group = "io.insert-koin", name = "koin-core-coroutines", version.ref = "koin" }
112114

113115
leakCanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakCanary" }
114116

@@ -147,7 +149,7 @@ junit5Api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.r
147149
junit5Engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit5" }
148150

149151
koinTest = { group = "io.insert-koin", name = "koin-android-test", version.ref = "koin" }
150-
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockkVersion" }
152+
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
151153
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
152154

153155
# Instrumented/UI #
@@ -161,8 +163,8 @@ junitAndroid = { group = "androidx.test.ext", name = "junit", version.ref = "jun
161163
junitKtx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junit" }
162164

163165
#MockK
164-
mockkAgent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockkVersion" }
165-
mockkAndroid = { group = "io.mockk", name = "mockk-android", version.ref = "mockkVersion" }
166+
mockkAgent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockk" }
167+
mockkAndroid = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
166168

167169
#TestCore
168170
archTest = { group = "androidx.arch.core", name = "core-testing", version.ref = "archTest" }
@@ -174,6 +176,7 @@ testRunner = { group = "androidx.test", name = "runner", version.ref = "testRunn
174176
[plugins]
175177
androidApplication = { id = "com.android.application", version.ref = "gradle" }
176178
androidLibrary = { id = "com.android.library", version.ref = "gradle" }
179+
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
177180
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
178181
kotlinPlugin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
179182
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
@@ -209,6 +212,13 @@ compose = [
209212
"uiToolingPreviewCompose"
210213
]
211214

215+
koin = [
216+
"koinAndroid",
217+
"koinCompose",
218+
"koinCore",
219+
"koinCoroutines"
220+
]
221+
212222
lifecycle = [
213223
"lifecycleRuntimeKtx",
214224
"lifecycleViewmodelKtx"

modules/common/core/build.gradle

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
22

33
plugins {
4-
id 'com.android.library'
5-
id 'kotlin-android'
4+
alias dep.plugins.androidLibrary
5+
alias dep.plugins.kotlinPlugin
66
}
77

88
apply from: "$project.rootDir/flavors/flavors.gradle"
@@ -48,5 +48,5 @@ android {
4848
dependencies {
4949
implementation fileTree(dir: 'libs', include: ['*.jar'])
5050
testImplementation project(':modules:common:testing:jvm') because 'JVM Testing Module'
51-
api dep.koin
51+
api dep.bundles.koin
5252
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core.types.number
22

33
private const val DEFAULT_INTEGER = 0
4+
private const val ONE = 1
45
private const val DEFAULT_LONG = 0L
56

67
fun defaultInteger(): Int = DEFAULT_INTEGER
78

9+
fun one(): Int = ONE
10+
811
fun defaultLong(): Long = DEFAULT_LONG

modules/common/datasource/datasource/build.gradle

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
22

33
plugins {
4-
id 'com.android.library'
5-
id 'com.google.devtools.ksp'
6-
id 'kotlin-android'
7-
id 'kotlinx-serialization'
4+
alias dep.plugins.androidLibrary
5+
alias dep.plugins.kotlinPlugin
6+
alias dep.plugins.ksp
7+
alias dep.plugins.serialization
88
}
99

1010
apply from: "$project.rootDir/flavors/flavors.gradle"

modules/common/datasource/datasource/src/main/kotlin/githubprofilesearcher/caiodev/com/br/githubprofilesearcher/datasource/di/DatasourceDI.kt

+10-7
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fet
99
import kotlinx.coroutines.Dispatchers
1010
import okhttp3.Interceptor
1111
import org.koin.android.ext.koin.androidContext
12-
import org.koin.dsl.module
12+
import org.koin.core.module.dsl.singleOf
13+
import org.koin.dsl.bind
14+
import org.koin.dsl.lazyModule
15+
import kotlin.coroutines.CoroutineContext
1316

1417
val datasource =
15-
module {
16-
single<Database> {
18+
lazyModule {
19+
single {
1720
Room.databaseBuilder(
1821
androidContext(),
1922
AppDatabase::class.java,
2023
AppDatabase.DATABASE_NAME,
2124
).build()
22-
}
23-
single<Interceptor> { ChuckerInterceptor(androidContext()) }
25+
} bind Database::class
26+
singleOf(::ChuckerInterceptor) bind Interceptor::class
2427
single { newInstance(interceptor = get()) }
25-
single { RemoteFetcher() }
26-
single { Dispatchers.IO }
28+
singleOf(::RemoteFetcher)
29+
single { Dispatchers.IO } bind CoroutineContext::class
2730
}

modules/common/datasource/datasource/src/main/kotlin/githubprofilesearcher/caiodev/com/br/githubprofilesearcher/datasource/fetchers/handler/ResultHandler.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ inline fun <reified S, T> State<*>.handleResult(
2020
crossinline onFailure: (error: Error) -> T,
2121
): T =
2222
if (this is Success<*>) {
23-
onSuccess(ValueCasting.castTo<S>(this.data))
23+
onSuccess(ValueCasting.castTo<S>(data))
2424
} else {
2525
onFailure(handleError(ValueCasting.castTo(this)))
2626
}
@@ -29,7 +29,7 @@ inline fun <reified S, T> State<*>.handleResult(
2929
internal fun handleError(errorState: ErrorState?): Error {
3030
when (errorState) {
3131
ClientSide -> Core.string.client_side
32-
Connect, UnknownHost, SocketTimeout -> Core.string.unknown_host_and_socket_timeout
32+
Connect, SocketTimeout, UnknownHost -> Core.string.unknown_host_and_socket_timeout
3333
ResultLimitReached -> Core.string.limit_of_profile_results
3434
SearchQuotaReached -> Core.string.query_limit
3535
ServerSide -> Core.string.server_side

modules/common/datasource/datasource/src/main/kotlin/githubprofilesearcher/caiodev/com/br/githubprofilesearcher/datasource/fetchers/local/database/features/profile/dao/User.kt

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fetchers.local.database.features.profile.dao
22

33
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.OnConflictStrategy
46
import androidx.room.Query
57
import androidx.room.Upsert
68
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fetchers.local.database.features.profile.entity.UserEntity
@@ -10,6 +12,9 @@ interface User {
1012
@Query("SELECT * " + "FROM User LIMIT 20")
1113
suspend fun get(): List<UserEntity>?
1214

15+
@Insert(onConflict = OnConflictStrategy.REPLACE)
16+
suspend fun insert(profileList: List<UserEntity>)
17+
1318
@Upsert
1419
suspend fun upsert(profileList: List<UserEntity>)
1520

modules/common/datasource/features/profile/build.gradle

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
22

33
plugins {
4-
id 'com.android.library'
5-
id 'com.google.devtools.ksp'
6-
id 'kotlin-android'
7-
id 'kotlinx-serialization'
4+
alias dep.plugins.androidLibrary
5+
alias dep.plugins.kotlinPlugin
6+
alias dep.plugins.ksp
7+
alias dep.plugins.serialization
88
}
99

1010
apply from: "$project.rootDir/flavors/flavors.gradle"

0 commit comments

Comments
 (0)