Skip to content

Commit c27266e

Browse files
committed
- Add a workaround for mislaunched CancellationExceptions silently killing Coroutines {
https://betterprogramming.pub/the-silent-killer-thats-crashing-your-coroutines-9171d1e8f79b Kotlin/kotlinx.coroutines#3658 (comment) https://kotlinlang.org/docs/exception-handling.html } - Create the "Neutral" state for when CancellationExceptions are wrongfully launched (Refer to the above links to know when and how that happens) - Add an Architecture that uses both By Layer and By Feature separation of concerns (https://www.youtube.com/watch?v=16SwTvzDO0A) - Refactor dependencies between modules - Remove datasource dependency from midfield (a.k.a domain) - Create specific feature modules inside each base layer (ui, midfield (domain) and datasource (data/model)) - Bump dependency versions to newer ones - Turn common dependencies into bundles - Replace multiple dependency references with Bundles - Refactor code - Remove all occurences of suspend functions being executed using runBlocking from the project - Remove runBlocking from the project - Remove fundamental features (Pagination, Upper views management etc...) (Temporarily until I implement them back, but in Compose) - Create specific modules for both JVM and Integrated/UI tests (The old way, JVM tests had access to Integrated/UI test dependencies and vice-versa, now that's sorted out) - Split Remote datasource models from Local datasource models (Then, there would be only one class having both kotlin.serialization and Room annotations) - Create mapping from Remote datasource model to Local datasource model (Entity) - Create mapping from Local datasource model to clean model (No external dependencies referenced) (Plain Old Kotlin Object) KNOWN ISSUES: 1- There's no pagination anymore (It will be re-implemented in the future using Paging Compose) 2- There may be UI inconsistencies when CancellationExceptions are launched intentionally or not (Will be addressed in the future) FEATURES: No new features have been introduced
1 parent 17f1c47 commit c27266e

File tree

131 files changed

+819
-521
lines changed

Some content is hidden

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

131 files changed

+819
-521
lines changed

app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ 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') because 'Datasource Module'
74-
implementation project(':modules:common:ui') because 'UI Module'
73+
implementation project(':modules:common:datasource:datasource') because 'Datasource Module'
74+
implementation project(':modules:common:ui:ui') because 'UI Module'
7575
implementation project(':modules:features:profile') because 'Profile Module'
7676

7777
debugImplementation dep.leakCanary

app/src/main/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
android:allowBackup="true"
88
android:icon="@mipmap/ic_launcher"
99
android:label="@string/app_name"
10+
android:localeConfig="@xml/locales_config" tools:targetApi="tiramisu"
1011
android:roundIcon="@mipmap/ic_launcher_round"
1112
android:supportsRtl="true"
1213
android:theme="@style/AppTheme"

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ 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.global
9-
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.model.di.userProfileViewModel
8+
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.di.datasource
9+
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.di.profileModule
1010
import org.koin.android.ext.koin.androidContext
1111
import org.koin.android.ext.koin.androidLogger
1212
import org.koin.core.context.startKoin
@@ -19,7 +19,7 @@ class App : Application(), ImageLoaderFactory {
1919
startKoin {
2020
androidContext(this@App)
2121
androidLogger(Level.DEBUG)
22-
modules(global, userProfileViewModel)
22+
modules(datasource, profileModule)
2323
}
2424
}
2525

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version ="1.0" encoding="utf-8"?>
2+
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
3+
<locale android:name="en" />
4+
<locale android:name="pt-BR" />
5+
</locale-config>

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ allprojects {
2424

2525
tasks.register("clean", Delete) {
2626
delete layout.buildDirectory
27-
}
27+
}

gradle/libs.versions.toml

+54-40
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ targetSdk = '34'
99
versionCode = '1'
1010
versionName = '1.0'
1111

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

1414
chucker = '4.0.0'
1515

@@ -18,15 +18,15 @@ coil = '2.6.0'
1818
#Compose
1919
activity = '1.9.0'
2020
compiler = '1.5.11'
21-
composeCore = '1.7.0-alpha07'
22-
material3 = '1.3.0-alpha05'
21+
composeCore = '1.7.0-alpha08'
22+
material3 = '1.3.0-alpha06'
2323
runtimeTracingCompose = '1.0.0-beta01'
2424

2525
coroutines = '1.8.0'
2626

27-
dataStore = '1.1.0'
27+
dataStore = '1.1.1'
2828

29-
gradle = '8.3.2'
29+
gradle = '8.4.0'
3030

3131
koin = '3.5.6'
3232

@@ -36,11 +36,14 @@ ksp = '1.9.23-1.0.19'
3636

3737
ktor = '2.3.10'
3838

39-
ktxCore = '1.13.0'
39+
ktxCore = '1.13.1'
4040

41-
lifecycle = '2.8.0-beta01'
41+
lifecycle = '2.8.0-rc01'
4242

43-
materialDesign = '1.11.0'
43+
materialDesign = '1.12.0'
44+
45+
#Paging
46+
paging = '3.3.0-rc01'
4447

4548
#Quality/Performance
4649
detekt = '1.23.6'
@@ -55,16 +58,16 @@ room = '2.6.1'
5558
constraintLayout = '2.1.4'
5659
recyclerView = '1.3.2'
5760

58-
# Test dependency versions #
61+
# Test Dependencies #
5962

6063
archTest = '2.2.0'
61-
testCore = '1.6.0-alpha05'
6264
jacoco = '0.8.12'
63-
junit = '1.2.0-alpha03'
64-
kotest = '5.8.1'
65+
junit = '1.2.0-alpha04'
66+
junit5 = '5.11.0-M1'
6567
mockkVersion = '1.13.10'
6668
robolectric = '4.11.1'
67-
testRunner = '1.6.0-alpha06'
69+
testCore = '1.6.0-alpha06'
70+
testRunner = '1.6.0-alpha07'
6871

6972
##### Libraries #####
7073

@@ -115,6 +118,10 @@ lifecycleViewmodelKtx = { group = "androidx.lifecycle", name = "lifecycle-viewmo
115118

116119
materialDesign = { group = "com.google.android.material", name = "material", version.ref = "materialDesign" }
117120

121+
#Paging
122+
pagingCompose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
123+
pagingRuntimeKtx = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" }
124+
118125
#Room
119126
room = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
120127
roomCompiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
@@ -128,23 +135,19 @@ recyclerView = { group = "androidx.recyclerview", name = "recyclerview", version
128135

129136
##### Testing Section #####
130137

138+
# Common #
139+
131140
coroutinesTest = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
141+
jacoco = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" }
132142

133143
# JVM #
134144

135-
jacoco = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" }
145+
#JUnit5
146+
junit5Api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit5" }
147+
junit5Engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit5" }
136148

137149
koinTest = { group = "io.insert-koin", name = "koin-android-test", version.ref = "koin" }
138-
139-
#Kotest
140-
kotestCore = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" }
141-
kotestProperty = { group = "io.kotest", name = "kotest-property", version.ref = "kotest" }
142-
kotestRunner = { group = "io.kotest", name = "kotest-runner-junit5", version.ref = "kotest" }
143-
144-
#MockK
145-
mockkAgent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockkVersion" }
146-
mockkAndroid = { group = "io.mockk", name = "mockk-android", version.ref = "mockkVersion" }
147-
150+
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockkVersion" }
148151
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
149152

150153
# Instrumented/UI #
@@ -154,9 +157,13 @@ composeTestJUnit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", ve
154157
composeTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "composeCore" }
155158

156159
#JUnit
157-
junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
160+
junitAndroid = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
158161
junitKtx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junit" }
159162

163+
#MockK
164+
mockkAgent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockkVersion" }
165+
mockkAndroid = { group = "io.mockk", name = "mockk-android", version.ref = "mockkVersion" }
166+
160167
#TestCore
161168
archTest = { group = "androidx.arch.core", name = "core-testing", version.ref = "archTest" }
162169
testCore = { group = "androidx.test", name = "core-ktx", version.ref = "testCore" }
@@ -207,6 +214,11 @@ lifecycle = [
207214
"lifecycleViewmodelKtx"
208215
]
209216

217+
paging = [
218+
"pagingCompose",
219+
"pagingRuntimeKtx"
220+
]
221+
210222
room = [
211223
"room",
212224
"roomKtx"
@@ -219,24 +231,26 @@ views = [
219231

220232
# Test Bundles #
221233

222-
junit = [
223-
"junit",
224-
"junitKtx"
225-
]
226-
227-
kotest = [
228-
"kotestCore",
229-
"kotestProperty",
230-
"kotestRunner"
234+
common = [
235+
"coroutinesTest",
236+
"jacoco"
231237
]
232238

233-
mockk = [
234-
"mockkAgent",
235-
"mockkAndroid"
236-
]
237-
238-
testCore = [
239+
instrumented = [
239240
"archTest",
241+
"composeTestJUnit4",
242+
"composeTestManifest",
243+
"junitAndroid",
244+
"junitKtx",
245+
"mockkAgent",
246+
"mockkAndroid",
240247
"testCore",
241248
"testRunner"
249+
]
250+
251+
jvm = [
252+
"junit5Engine",
253+
"koinTest",
254+
"mockk",
255+
"robolectric"
242256
]

modules/common/core/build.gradle

+2-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
33
plugins {
44
id 'com.android.library'
55
id 'kotlin-android'
6-
id 'kotlinx-serialization'
76
}
87

98
apply from: "$project.rootDir/flavors/flavors.gradle"
@@ -13,7 +12,6 @@ android {
1312
defaultConfig {
1413
minSdkVersion dep.versions.minSdk.get()
1514
targetSdkVersion dep.versions.targetSdk.get()
16-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1715
}
1816

1917
compileOptions {
@@ -49,9 +47,6 @@ android {
4947

5048
dependencies {
5149
implementation fileTree(dir: 'libs', include: ['*.jar'])
52-
androidTestImplementation project(':modules:common:testing') because 'Android Testing Module'
53-
testImplementation project(':modules:common:testing') because 'JVM Testing Module'
54-
55-
api dep.ktxCore
56-
api dep.bundles.lifecycle
50+
testImplementation project(':modules:common:testing:jvm') because 'JVM Testing Module'
51+
api dep.koin
5752
}

modules/common/core/src/main/java/githubprofilesearcher/caiodev/com/br/githubprofilesearcher/core/extensions/ViewModel.kt

-13
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ package githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core
22

33
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core.cast.ValueCasting
44
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core.types.number.defaultInteger
5-
import io.kotest.core.spec.style.FunSpec
6-
import io.kotest.matchers.shouldBe
5+
import org.junit.jupiter.api.Assertions.assertEquals
6+
import org.junit.jupiter.api.Test
77

8-
internal class ValueCastingTest : FunSpec({
9-
test("castTo should return a valid value") {
8+
internal class ValueCastingTest {
9+
@Test
10+
fun `castToNullable should return a valid value`() {
1011
ValueCasting.castTo<List<Int>>(arrayListOf(defaultInteger()))
1112
?.first()
12-
.shouldBe(defaultInteger())
13+
?.apply { assertEquals(defaultInteger(), this) }
1314
}
1415

15-
test("castToNonNullable should return a valid value") {
16+
@Test
17+
fun `castToNonNullable should return a valid value`() {
1618
ValueCasting.castToNonNullable<List<Int>>(arrayListOf(defaultInteger()))
1719
.first()
18-
.shouldBe(defaultInteger())
20+
.apply { assertEquals(defaultInteger(), this) }
1921
}
20-
})
22+
}

modules/common/datasource/build.gradle renamed to modules/common/datasource/datasource/build.gradle

+3-4
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,10 @@ android {
6969
dependencies {
7070
implementation fileTree(dir: 'libs', include: ['*.jar'])
7171
implementation project(':modules:common:core') because 'Core Module'
72-
androidTestImplementation project(':modules:common:testing') because 'Android Testing Module'
73-
testImplementation project(':modules:common:testing') because 'JVM Testing Module'
72+
androidTestImplementation project(':modules:common:testing:instrumented') because 'Android Testing Module'
73+
testImplementation project(':modules:common:testing:jvm') because 'JVM Testing Module'
7474

7575
api dep.bundles.apiConnection
76-
api dep.koin
7776

7877
//Logging
7978
debugImplementation dep.debugChucker
@@ -82,6 +81,6 @@ dependencies {
8281
api dep.preferencesDataStore
8382

8483
//Room
85-
implementation dep.bundles.room
84+
api dep.bundles.room
8685
ksp dep.roomCompiler
8786
}
+3-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fet
77
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fetchers.remote.RemoteFetcher
88
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fetchers.remote.api.newInstance
99
import kotlinx.coroutines.Dispatchers
10+
import okhttp3.Interceptor
1011
import org.koin.android.ext.koin.androidContext
1112
import org.koin.dsl.module
1213

13-
val global =
14+
val datasource =
1415
module {
1516
single<Database> {
1617
Room.databaseBuilder(
@@ -19,7 +20,7 @@ val global =
1920
AppDatabase.DATABASE_NAME,
2021
).build()
2122
}
22-
single { ChuckerInterceptor(androidContext()) }
23+
single<Interceptor> { ChuckerInterceptor(androidContext()) }
2324
single { newInstance(interceptor = get()) }
2425
single { RemoteFetcher() }
2526
single { Dispatchers.IO }
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
package githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.cell
1+
package githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.fetchers.handler
22

33
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core.cast.ValueCasting
4-
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.cell.contracts.ICell
54
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.states.ClientSide
65
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.states.Connect
76
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.states.Error
@@ -16,26 +15,25 @@ import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.sta
1615
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.datasource.states.UnknownHost
1716
import githubprofilesearcher.caiodev.com.br.githubprofilesearcher.core.R as Core
1817

19-
inline fun <reified T> ICell.handleResult(
20-
value: State<*>,
21-
onSuccess: (success: Success<*>) -> T,
18+
inline fun <reified S, T> State<*>.handleResult(
19+
onSuccess: (success: S?) -> T,
2220
crossinline onFailure: (error: Error) -> T,
2321
): T =
24-
if (value is Success<*>) {
25-
onSuccess(value)
22+
if (this is Success<*>) {
23+
onSuccess(ValueCasting.castTo<S>(this.data))
2624
} else {
27-
onFailure(handleError(ValueCasting.castTo(value)))
25+
onFailure(handleError(ValueCasting.castTo(this)))
2826
}
2927

3028
@PublishedApi
3129
internal fun handleError(errorState: ErrorState?): Error {
3230
when (errorState) {
33-
UnknownHost, SocketTimeout, Connect -> Core.string.unknown_host_and_socket_timeout
34-
SSLHandshake -> Core.string.ssl_handshake
3531
ClientSide -> Core.string.client_side
36-
ServerSide -> Core.string.server_side
37-
SearchQuotaReached -> Core.string.query_limit
32+
Connect, UnknownHost, SocketTimeout -> Core.string.unknown_host_and_socket_timeout
3833
ResultLimitReached -> Core.string.limit_of_profile_results
34+
SearchQuotaReached -> Core.string.query_limit
35+
ServerSide -> Core.string.server_side
36+
SSLHandshake -> Core.string.ssl_handshake
3937
else -> Core.string.generic
4038
}.apply { return Error(error = this) }
4139
}

0 commit comments

Comments
 (0)