Skip to content

Commit 83e96c6

Browse files
Implement UseCase#2 with RxJava
1 parent 356cee6 commit 83e96c6

16 files changed

+210
-13
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ Unit Tests exist for most use cases.
4545

4646
### 1. Perform single network request
4747

48-
Performs a single network request to get the latest Android Versions.
48+
This use case performs a single network request to get the latest Android Versions and displays them on the screen.
4949

5050
[[code](app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase1/PerformSingleNetworkRequestViewModel.kt)]
5151

5252
### 2. Perform two sequential network requests
5353

54-
Performs two network requests sequentially. First it retrieves recent Android Versions and then it requests the features of the latest version.
55-
There also exists an alternative implementation for this use case which uses traditional callbacks. It should demonstrate the simplicity of the Coroutine version compared to callback-based version.
54+
This use case performs two network requests sequentially. First it retrieves recent Android Versions and then it requests the features of the latest version.
55+
56+
There are also 2 alternative implementations included. One is using old-school [callbacks](app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/callbacks/SequentialNetworkRequestsCallbacksViewModel.kt).
57+
The other one uses [RxJava](app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/rx/SequentialNetworkRequestsRxViewModel.kt). You can compare each implementation.
58+
If you compare all implementation, it is really interesting to see, in my opinion, how simple the Coroutine-based version actually is.
5659

5760
[[code](app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/Perform2SequentialNetworkRequestsViewModel.kt)]
5861

app/build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ dependencies {
5959
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
6060

6161
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
62+
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
6263
implementation 'com.google.code.gson:gson:2.8.6'
6364
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
6465

66+
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
67+
implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
68+
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
69+
6570
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
6671

6772
def work_manager_version = "2.3.4"

app/src/main/AndroidManifest.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
<activity android:name=".base.UseCaseActivity" />
2222
<activity android:name=".usecases.coroutines.usecase1.PerformSingleNetworkRequestActivity" />
2323
<activity android:name=".usecases.coroutines.usecase2.Perform2SequentialNetworkRequestsActivity" />
24-
<activity android:name=".usecases.coroutines.usecase2.usingcallbacks.SequentialNetworkRequestsCallbacksActivity" />
24+
<activity android:name=".usecases.coroutines.usecase2.callbacks.SequentialNetworkRequestsCallbacksActivity" />
25+
<activity android:name=".usecases.coroutines.usecase2.rx.SequentialNetworkRequestsRxActivity" />
2526
<activity android:name=".usecases.coroutines.usecase3.PerformNetworkRequestsConcurrentlyActivity" />
2627
<activity android:name=".usecases.coroutines.usecase4.VariableAmountOfNetworkRequestsActivity" />
2728
<activity android:name=".usecases.coroutines.usecase5.NetworkRequestWithTimeoutActivity" />

app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/base/UseCase.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase14
1212
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase15.WorkManagerActivity
1313
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase16.PerformanceAnalysisActivity
1414
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.Perform2SequentialNetworkRequestsActivity
15-
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks.SequentialNetworkRequestsCallbacksActivity
15+
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks.SequentialNetworkRequestsCallbacksActivity
16+
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.rx.SequentialNetworkRequestsRxActivity
1617
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase3.PerformNetworkRequestsConcurrentlyActivity
1718
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase4.VariableAmountOfNetworkRequestsActivity
1819
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase5.NetworkRequestWithTimeoutActivity
@@ -35,6 +36,7 @@ data class UseCaseCategory(val categoryName: String, val useCases: List<UseCase>
3536
const val useCase1Description = "#1 Perform single network request"
3637
const val useCase2Description = "#2 Perform two sequential network requests"
3738
const val useCase2UsingCallbacksDescription = "#2 using Callbacks"
39+
const val useCase2UsingRxDescription = "#2 using RxJava"
3840
const val useCase3Description = "#3 Perform several network requests concurrently"
3941
const val useCase4Description = "#4 Perform variable amount of network requests"
4042
const val useCase5Description = "#5 Network request with TimeOut"
@@ -65,6 +67,9 @@ private val coroutinesUseCases =
6567
UseCase(
6668
useCase2UsingCallbacksDescription,
6769
SequentialNetworkRequestsCallbacksActivity::class.java
70+
), UseCase(
71+
useCase2UsingRxDescription,
72+
SequentialNetworkRequestsRxActivity::class.java
6873
),
6974
UseCase(
7075
useCase3Description,

app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/usingcallbacks/MockApi.kt app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/callbacks/MockApi.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.google.gson.Gson
44
import com.lukaslechner.coroutineusecasesonandroid.mock.AndroidVersion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import android.os.Bundle
44
import androidx.activity.viewModels
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.lukaslechner.coroutineusecasesonandroid.base.BaseViewModel
44
import com.lukaslechner.coroutineusecasesonandroid.mock.AndroidVersion

app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/usingcallbacks/UiState.kt app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/usecases/coroutines/usecase2/callbacks/UiState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.lukaslechner.coroutineusecasesonandroid.mock.VersionFeatures
44

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.rx
2+
3+
import com.google.gson.Gson
4+
import com.lukaslechner.coroutineusecasesonandroid.mock.AndroidVersion
5+
import com.lukaslechner.coroutineusecasesonandroid.mock.VersionFeatures
6+
import com.lukaslechner.coroutineusecasesonandroid.mock.mockAndroidVersions
7+
import com.lukaslechner.coroutineusecasesonandroid.mock.mockVersionFeaturesAndroid10
8+
import com.lukaslechner.coroutineusecasesonandroid.utils.MockNetworkInterceptor
9+
import io.reactivex.Single
10+
import io.reactivex.schedulers.Schedulers
11+
import okhttp3.OkHttpClient
12+
import retrofit2.Retrofit
13+
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
14+
import retrofit2.converter.gson.GsonConverterFactory
15+
import retrofit2.http.GET
16+
import retrofit2.http.Path
17+
18+
fun mockApi(): RxMockApi = createMockApi(
19+
MockNetworkInterceptor()
20+
.mock(
21+
"http://localhost/recent-android-versions",
22+
Gson().toJson(mockAndroidVersions),
23+
200,
24+
1500
25+
)
26+
.mock(
27+
"http://localhost/android-version-features/29",
28+
Gson().toJson(mockVersionFeaturesAndroid10),
29+
200,
30+
1500
31+
)
32+
)
33+
34+
interface RxMockApi {
35+
36+
@GET("recent-android-versions")
37+
fun getRecentAndroidVersions(): Single<List<AndroidVersion>>
38+
39+
@GET("android-version-features/{apiVersion}")
40+
fun getAndroidVersionFeatures(@Path("apiVersion") apiVersion: Int): Single<VersionFeatures>
41+
}
42+
43+
fun createMockApi(interceptor: MockNetworkInterceptor): RxMockApi {
44+
val okHttpClient = OkHttpClient.Builder()
45+
.addInterceptor(interceptor)
46+
.build()
47+
48+
val retrofit = Retrofit.Builder()
49+
.baseUrl("http://localhost/")
50+
.client(okHttpClient)
51+
.addConverterFactory(GsonConverterFactory.create())
52+
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
53+
.build()
54+
55+
return retrofit.create(RxMockApi::class.java)
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.rx
2+
3+
import android.os.Bundle
4+
import androidx.activity.viewModels
5+
import androidx.lifecycle.Observer
6+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseActivity
7+
import com.lukaslechner.coroutineusecasesonandroid.base.useCase2UsingRxDescription
8+
import com.lukaslechner.coroutineusecasesonandroid.databinding.ActivityPerform2sequentialnetworkrequestsBinding
9+
import com.lukaslechner.coroutineusecasesonandroid.utils.fromHtml
10+
import com.lukaslechner.coroutineusecasesonandroid.utils.setGone
11+
import com.lukaslechner.coroutineusecasesonandroid.utils.setVisible
12+
import com.lukaslechner.coroutineusecasesonandroid.utils.toast
13+
14+
class SequentialNetworkRequestsRxActivity : BaseActivity() {
15+
16+
private val binding by lazy {
17+
ActivityPerform2sequentialnetworkrequestsBinding.inflate(
18+
layoutInflater
19+
)
20+
}
21+
22+
private val viewModel: SequentialNetworkRequestsRxViewModel by viewModels()
23+
24+
override fun getToolbarTitle() = useCase2UsingRxDescription
25+
26+
override fun onCreate(savedInstanceState: Bundle?) {
27+
super.onCreate(savedInstanceState)
28+
setContentView(binding.root)
29+
viewModel.uiState().observe(this, Observer { uiState ->
30+
if (uiState != null) {
31+
render(uiState)
32+
}
33+
})
34+
binding.btnRequestsSequentially.setOnClickListener {
35+
viewModel.perform2SequentialNetworkRequest()
36+
}
37+
}
38+
39+
private fun render(uiState: UiState) {
40+
when (uiState) {
41+
is UiState.Loading -> {
42+
onLoad()
43+
}
44+
is UiState.Success -> {
45+
onSuccess(uiState)
46+
}
47+
is UiState.Error -> {
48+
onError(uiState)
49+
}
50+
}
51+
}
52+
53+
private fun onLoad() = with(binding) {
54+
progressBar.setVisible()
55+
textViewResult.text = ""
56+
}
57+
58+
private fun onSuccess(uiState: UiState.Success) = with(binding) {
59+
progressBar.setGone()
60+
textViewResult.text = fromHtml(
61+
"<b>Features of most recent Android Version \" ${uiState.versionFeatures.androidVersion.name} \"</b><br>" +
62+
uiState.versionFeatures.features.joinToString(
63+
prefix = "- ",
64+
separator = "<br>- "
65+
)
66+
)
67+
}
68+
69+
private fun onError(uiState: UiState.Error) = with(binding) {
70+
progressBar.setGone()
71+
btnRequestsSequentially.isEnabled = true
72+
toast(uiState.message)
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.rx
2+
3+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseViewModel
4+
import io.reactivex.android.schedulers.AndroidSchedulers
5+
import io.reactivex.disposables.CompositeDisposable
6+
import io.reactivex.rxkotlin.addTo
7+
import io.reactivex.rxkotlin.subscribeBy
8+
import io.reactivex.schedulers.Schedulers
9+
10+
class SequentialNetworkRequestsRxViewModel(
11+
private val mockApi: RxMockApi = mockApi()
12+
) : BaseViewModel<UiState>() {
13+
14+
private val disposables = CompositeDisposable()
15+
16+
fun perform2SequentialNetworkRequest() {
17+
uiState.value = UiState.Loading
18+
19+
mockApi.getRecentAndroidVersions()
20+
.flatMap { androidVersions ->
21+
val recentVersion = androidVersions.last()
22+
mockApi.getAndroidVersionFeatures(recentVersion.apiVersion)
23+
}
24+
.subscribeOn(Schedulers.io())
25+
.observeOn(AndroidSchedulers.mainThread())
26+
.subscribeBy(
27+
onSuccess = { featureVersions ->
28+
uiState.value = UiState.Success(featureVersions)
29+
},
30+
onError = {
31+
uiState.value = UiState.Error("Network Request failed.")
32+
}
33+
)
34+
.addTo(disposables)
35+
}
36+
37+
override fun onCleared() {
38+
super.onCleared()
39+
disposables.clear()
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.rx
2+
3+
import com.lukaslechner.coroutineusecasesonandroid.mock.VersionFeatures
4+
5+
sealed class UiState {
6+
object Loading : UiState()
7+
data class Success(
8+
val versionFeatures: VersionFeatures
9+
) : UiState()
10+
11+
data class Error(val message: String) : UiState()
12+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.lukaslechner.coroutineusecasesonandroid.mock.AndroidVersion
44
import com.lukaslechner.coroutineusecasesonandroid.mock.VersionFeatures
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.lukaslechner.coroutineusecasesonandroid.mock.*
44
import retrofit2.Call
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import com.lukaslechner.coroutineusecasesonandroid.mock.AndroidVersion
44
import com.lukaslechner.coroutineusecasesonandroid.mock.VersionFeatures
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.usingcallbacks
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase2.callbacks
22

33
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
44
import com.lukaslechner.coroutineusecasesonandroid.mock.mockVersionFeaturesAndroid10

0 commit comments

Comments
 (0)