Skip to content

Commit 567e574

Browse files
committed
reworked example to include a login + list for better usage of patterns
1 parent 6c15c31 commit 567e574

Some content is hidden

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

46 files changed

+686
-217
lines changed

app/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ android {
3636
manifestPlaceholders = [
3737
APP_NAME: "MonstarlabDev"
3838
]
39-
buildConfigField "String", "API_URL", "\"https://jsonplaceholder.typicode.com\""
39+
buildConfigField "String", "API_URL", "\"https://reqres.in/api/\""
4040
}
4141
staging {
4242
dimension "default"
@@ -45,15 +45,15 @@ android {
4545
manifestPlaceholders = [
4646
APP_NAME: "MonstarlabStaging"
4747
]
48-
buildConfigField "String", "API_URL", "\"https://jsonplaceholder.typicode.com\""
48+
buildConfigField "String", "API_URL", "\"https://reqres.in/api/\""
4949
}
5050
production {
5151
dimension "default"
5252
//signingConfig signingConfigs.production
5353
manifestPlaceholders = [
5454
APP_NAME: "Monstarlab",
5555
]
56-
buildConfigField "String", "API_URL", "\"https://jsonplaceholder.typicode.com\""
56+
buildConfigField "String", "API_URL", "\"https://reqres.in/api/\""
5757
}
5858
}
5959

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="com.monstarlab">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
57
<application
68
android:allowBackup="true"
79
android:icon="@mipmap/ic_launcher"

app/src/main/java/com/monstarlab/arch/data/SharedPreferenceDataStore.kt

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
package com.monstarlab.arch.data
22

33
import android.content.SharedPreferences
4-
import kotlinx.serialization.decodeFromString
5-
import kotlinx.serialization.encodeToString
4+
import kotlinx.serialization.KSerializer
5+
import kotlinx.serialization.builtins.ListSerializer
66
import kotlinx.serialization.json.Json
7-
import java.lang.Exception
87

98
abstract class SharedPreferenceDataStore<T> constructor(
10-
private val sharedPreferences: SharedPreferences
9+
private val sharedPreferences: SharedPreferences,
10+
private val serializer: KSerializer<T>
1111
): DataSource<T> {
1212

1313
private val key = this.javaClass.simpleName
1414

1515
override fun getAll(): List<T> {
16-
return try {
17-
val json = sharedPreferences.getString(key, "") ?: ""
18-
val entries = Json.decodeFromString<List<T>>(json)
19-
entries
20-
} catch (e: Exception) {
21-
emptyList()
22-
}
16+
val json = sharedPreferences.getString(key, "") ?: ""
17+
return Json.decodeFromString(ListSerializer(serializer), json)
2318
}
2419

2520
override fun add(item: T) {
@@ -29,12 +24,8 @@ abstract class SharedPreferenceDataStore<T> constructor(
2924
}
3025

3126
override fun addAll(items: List<T>) {
32-
try {
33-
val json = Json.encodeToString(items)
34-
sharedPreferences.edit().putString(key, json).apply()
35-
} catch (e: Exception) {
36-
37-
}
27+
val json = Json.encodeToString(ListSerializer(serializer), items)
28+
sharedPreferences.edit().putString(key, json).apply()
3829
}
3930

4031
override fun clear() {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.monstarlab.arch.data
2+
3+
interface SingleDataSource<T> {
4+
fun get(): T?
5+
fun add(item: T)
6+
fun clear()
7+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.monstarlab.arch.data
2+
3+
import android.content.SharedPreferences
4+
import kotlinx.serialization.*
5+
import kotlinx.serialization.json.Json
6+
import java.lang.Exception
7+
import kotlin.reflect.KClass
8+
9+
abstract class SingleSharedPreferenceDataStore<T> constructor(
10+
private val sharedPreferences: SharedPreferences,
11+
private val serializer: KSerializer<T>
12+
): SingleDataSource<T> {
13+
14+
private val key = this.javaClass.simpleName
15+
16+
override fun get(): T? {
17+
return try {
18+
val json = sharedPreferences.getString(key, "") ?: ""
19+
val entries = Json.decodeFromString(serializer, json)
20+
entries
21+
} catch (e: Exception) {
22+
null
23+
}
24+
}
25+
26+
27+
override fun add(item: T) {
28+
try {
29+
val json = Json.encodeToString(serializer, item)
30+
sharedPreferences.edit().putString(key, json).apply()
31+
} catch (e: Exception) {
32+
33+
}
34+
}
35+
36+
override fun clear() {
37+
sharedPreferences.edit().remove(key).apply()
38+
}
39+
}

app/src/main/java/com/monstarlab/arch/extensions/FlowExtensions.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.flow.Flow
55
import kotlinx.coroutines.flow.collect
66
import kotlinx.coroutines.flow.combine
7+
import kotlinx.coroutines.flow.flow
78
import kotlinx.coroutines.launch
89

910
fun <T1, T2> CoroutineScope.combineFlows(flow1: Flow<T1>, flow2: Flow<T2>, collectBlock: (suspend (T1, T2) -> Unit)) {
@@ -34,4 +35,17 @@ fun <T1, T2, T3, T4> CoroutineScope.combineFlows(flow1: Flow<T1>, flow2: Flow<T2
3435
// Empty collect block to trigger ^
3536
}
3637
}
38+
}
39+
40+
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
41+
var lastEmissionTime = 0L
42+
collect { upstream ->
43+
val currentTime = System.currentTimeMillis()
44+
val mayEmit = currentTime - lastEmissionTime > windowDuration
45+
if (mayEmit)
46+
{
47+
lastEmissionTime = currentTime
48+
emit(upstream)
49+
}
50+
}
3751
}

app/src/main/java/com/monstarlab/arch/extensions/RepositoryExtensions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ fun <T, R> Response<out T>.toResultAndMap(transform: (T) -> R): RepositoryResult
3939

4040
sealed class RepositoryResult<out T> {
4141
data class Success<T>(val value: T): RepositoryResult<T>()
42-
data class Error(val error: ErrorModel.Http): RepositoryResult<Nothing>()
42+
data class Error(val error: ErrorModel): RepositoryResult<Nothing>()
4343
}
4444

4545
inline fun <T> RepositoryResult<T>.onSuccess(block: (T) -> Unit): RepositoryResult<T> {
4646
if(this is RepositoryResult.Success) block.invoke(value)
4747
return this
4848
}
4949

50-
inline fun <T> RepositoryResult<T>.onError(block: (ErrorModel.Http) -> Unit): RepositoryResult<T> {
50+
inline fun <T> RepositoryResult<T>.onError(block: (ErrorModel) -> Unit): RepositoryResult<T> {
5151
if(this is RepositoryResult.Error) block.invoke(error)
5252
return this
5353
}
@@ -56,7 +56,7 @@ fun <T> RepositoryResult<T>.isError(): Boolean {
5656
return this is RepositoryResult.Error
5757
}
5858

59-
val <T> RepositoryResult<T>.errorOrNull: ErrorModel.Http?
59+
val <T> RepositoryResult<T>.errorOrNull: ErrorModel?
6060
get() {
6161
return if(this is RepositoryResult.Error) error else null
6262
}

app/src/main/java/com/monstarlab/arch/extensions/UseCaseExtensions.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ inline fun <T> safeFlow(
2727
crossinline block: suspend () -> RepositoryResult<T>
2828
): Flow<UseCaseResult<T>> = flow {
2929
try {
30-
when (val repoResult = block()) {
30+
val res = when (val repoResult = block()) {
3131
is RepositoryResult.Success -> UseCaseResult.Success(repoResult.value)
3232
is RepositoryResult.Error -> UseCaseResult.Error(repoResult.error)
3333
}
34+
emit(res)
3435
} catch (e: Exception) {
35-
UseCaseResult.Error(e.toError())
36+
emit(UseCaseResult.Error(e.toError()))
3637
}
3738
}
3839

app/src/main/java/com/monstarlab/arch/extensions/ViewExtensions.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.monstarlab.arch.extensions
22

3+
import android.view.View
34
import androidx.fragment.app.Fragment
45
import androidx.lifecycle.DefaultLifecycleObserver
56
import androidx.lifecycle.LifecycleOwner
67
import androidx.lifecycle.lifecycleScope
7-
import kotlinx.coroutines.flow.Flow
8-
import kotlinx.coroutines.flow.collect
9-
import kotlinx.coroutines.flow.combine
10-
import kotlinx.coroutines.flow.zip
8+
import kotlinx.coroutines.channels.awaitClose
9+
import kotlinx.coroutines.flow.*
1110

1211
fun <T> Fragment.collectFlow(targetFlow: Flow<T>, collectBlock: ((T) -> Unit)) {
1312
safeViewCollect {
@@ -77,4 +76,12 @@ fun <T1, T2> Fragment.zipFlows(flow1: Flow<T1>, flow2: Flow<T2>, collectBlock:
7776
}
7877
}
7978
}
80-
}
79+
}
80+
81+
82+
fun View.clicks(throttleTime: Long = 400): Flow<Unit> = callbackFlow {
83+
this@clicks.setOnClickListener {
84+
offer(Unit)
85+
}
86+
awaitClose { this@clicks.setOnClickListener(null) }
87+
}.throttleFirst(throttleTime)
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
package com.monstarlab.core.data.network
22

33
import com.monstarlab.core.data.network.dtos.PostDto
4+
import com.monstarlab.core.data.network.responses.ResourcesResponse
5+
import com.monstarlab.core.data.network.responses.TokenResponse
6+
import com.monstarlab.core.data.network.responses.UserResponse
47
import retrofit2.Response
5-
import retrofit2.http.GET
8+
import retrofit2.http.*
69

710
interface Api {
811
@GET("posts")
912
suspend fun getPosts(): Response<List<PostDto>>
13+
14+
@FormUrlEncoded
15+
@POST("login")
16+
suspend fun postLogin(@Field("email") email: String, @Field("password") password: String): Response<TokenResponse>
17+
18+
@GET("users/2")
19+
suspend fun getUser(): Response<UserResponse>
20+
21+
@GET("unknown")
22+
suspend fun getResources(@Query("page") page: Int = 1): Response<ResourcesResponse>
1023
}

0 commit comments

Comments
 (0)