├── .gitignore
├── .idea
├── .gitignore
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── 160166456_3990980220990097_495071663197011529_n.jpg
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── coroutines
│ │ └── examples
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── coroutines
│ │ │ └── examples
│ │ │ ├── MainActivity.kt
│ │ │ ├── di
│ │ │ └── RemoteModule.kt
│ │ │ ├── flowactivities
│ │ │ ├── CatchFlowActivity.kt
│ │ │ ├── CombineFlowActivity.kt
│ │ │ ├── FilterFlowActivity.kt
│ │ │ ├── FlatMapConcatActivity.kt
│ │ │ ├── FlatMapLatestActivity.kt
│ │ │ ├── FlatMapMergeActivity.kt
│ │ │ ├── MapFlowActivity.kt
│ │ │ ├── SimpleFlowActivity.kt
│ │ │ ├── TakeFlowActivity.kt
│ │ │ ├── ZipFlowActivity.kt
│ │ │ ├── full_network_example
│ │ │ │ ├── FullNetworkExampleActivity.kt
│ │ │ │ ├── NetworkBoundResource.kt
│ │ │ │ ├── RemoteExtensions.kt
│ │ │ │ └── Resource.kt
│ │ │ └── parallel_requests
│ │ │ │ ├── ParallelRequestsActivity.kt
│ │ │ │ └── ParallelRequestsViewModel.kt
│ │ │ ├── helpers
│ │ │ ├── App.kt
│ │ │ ├── Constants.kt
│ │ │ ├── Extensions.kt
│ │ │ └── MyHandlers.kt
│ │ │ ├── models
│ │ │ ├── Comment.kt
│ │ │ ├── PostModel.kt
│ │ │ └── UserModel.kt
│ │ │ └── network
│ │ │ └── ExampleApi.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_parallel_requests.xml
│ │ └── activity_simple_flow.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── coroutines
│ └── examples
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Coroutines flows examples
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/160166456_3990980220990097_495071663197011529_n.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/160166456_3990980220990097_495071663197011529_n.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Coroutines-flows-examples
2 | *usage of common flow operator with comments explain each example*
3 | # Implemented operators
4 |
5 | 
6 |
7 | ## Tech stack & Open-source libraries
8 | - Minimum SDK level 19
9 | - [Kotlin](https://kotlinlang.org/)
10 | - [Coroutines](https://github.com/Kotlin/kotlinx.coroutines)
11 | - [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/)
12 | - [Koin](https://insert-koin.io) for dependency injection.
13 | - [JetPack](https://developer.android.com/jetpack)
14 | - [ViewBinding](https://developer.android.com/topic/libraries/view-binding) - Allows you to more easily write code that interacts with views
15 | - Architecture
16 | - Repository pattern (NetworkBoundResource)
17 | - [Retrofit2 & OkHttp3](https://github.com/square/retrofit) - Construct the REST APIs.
18 | - [Gson](https://github.com/google/gson) - A Modern JSON library for Android and Java.
19 |
20 |
21 |
22 | ## Open API
23 | [Jsonplaceholder](https://jsonplaceholder.typicode.com) for required data.
24 |
25 | # License
26 | ```xml
27 | Designed and developed by 2020 Ahmedshaban
28 |
29 | ```
30 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 |
6 | }
7 |
8 | android {
9 | compileSdkVersion 31
10 | buildToolsVersion "30.0.2"
11 |
12 | defaultConfig {
13 | applicationId "com.coroutines.examples"
14 | minSdkVersion 19
15 | targetSdkVersion 30
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 |
22 | buildFeatures {
23 | dataBinding true
24 | }
25 |
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
44 | implementation 'androidx.core:core-ktx:1.3.2'
45 | implementation 'androidx.appcompat:appcompat:1.2.0'
46 | implementation 'com.google.android.material:material:1.3.0'
47 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
48 | testImplementation 'junit:junit:4.+'
49 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
51 |
52 | // -- Retrofit2
53 | def retrofit2_version = "2.9.0"
54 | implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
55 | implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
56 | implementation "com.squareup.okhttp3:logging-interceptor:3.14.1"
57 |
58 | // -- Lifecycle Components (ViewModel, LiveData and ReactiveStreams)
59 | def lifecycle_version = "2.4.1"
60 | implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
61 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
62 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
63 |
64 |
65 |
66 | // -- Coroutines
67 | def coroutines_version = "1.6.0"
68 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
69 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
70 | implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"
71 |
72 |
73 | // LiveData Coroutines
74 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
75 |
76 | // -- Room
77 | def room_version = "2.4.1"
78 | implementation "androidx.room:room-runtime:$room_version"
79 | kapt "androidx.room:room-compiler:$room_version"
80 | // Kotlin Extensions and Coroutines support for Room
81 | implementation "androidx.room:room-ktx:$room_version"
82 |
83 | // koin
84 | def koin_version = "2.1.6"
85 |
86 | // Koin for Android
87 | implementation "org.koin:koin-android:$koin_version"
88 | // or Koin for Lifecycle scoping
89 | implementation "org.koin:koin-android-scope:$koin_version"
90 | // or Koin for Android Architecture ViewModel
91 | implementation "org.koin:koin-android-viewmodel:$koin_version"
92 |
93 |
94 |
95 |
96 |
97 |
98 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/coroutines/examples/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.coroutines.examples", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.coroutines.examples.databinding.ActivityMainBinding
6 | import com.coroutines.examples.flowactivities.*
7 | import com.coroutines.examples.flowactivities.full_network_example.FullNetworkExampleActivity
8 | import com.coroutines.examples.flowactivities.parallel_requests.ParallelRequestsActivity
9 | import com.coroutines.examples.helpers.openActivity
10 | import com.coroutines.examples.helpers.showSnackbar
11 |
12 | class MainActivity : AppCompatActivity() {
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_main)
16 | val binding = ActivityMainBinding.inflate(layoutInflater)
17 | setContentView(binding.root)
18 | binding.simpleFlowBtn.setOnClickListener {
19 | openActivity(SimpleFlowActivity::class.java)
20 | }
21 |
22 |
23 | binding.mapFlowBtn.setOnClickListener {
24 | openActivity(MapFlowActivity::class.java)
25 | }
26 |
27 |
28 | binding.zipFlowBtn.setOnClickListener {
29 | openActivity(ZipFlowActivity::class.java)
30 | }
31 |
32 | binding.combineFlowBtn.setOnClickListener {
33 | openActivity(CombineFlowActivity::class.java)
34 | }
35 |
36 | binding.takeFlowBtn.setOnClickListener {
37 | openActivity(TakeFlowActivity::class.java)
38 | }
39 |
40 | binding.filterFlowBtn.setOnClickListener {
41 | openActivity(FilterFlowActivity::class.java)
42 | }
43 |
44 | binding.flatmapconactbtn.setOnClickListener {
45 | openActivity(FlatMapConcatActivity::class.java)
46 | }
47 |
48 |
49 | binding.merge.setOnClickListener {
50 | openActivity(FlatMapMergeActivity::class.java)
51 | }
52 |
53 | binding.parallelRequestsBtn.setOnClickListener {
54 | openActivity(ParallelRequestsActivity::class.java)
55 | }
56 |
57 |
58 | binding.catchFlowBtn.setOnClickListener {
59 | // openActivity(CatchFlowActivity::class.java)
60 | showSnackbar("under development")
61 |
62 | }
63 |
64 | binding.networkingFlowBtn.setOnClickListener {
65 | openActivity(FullNetworkExampleActivity::class.java)
66 | }
67 |
68 |
69 | }
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/di/RemoteModule.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.di
2 |
3 | import com.coroutines.examples.flowactivities.parallel_requests.ParallelRequestsViewModel
4 | import com.coroutines.examples.network.ExampleApi
5 | import com.google.gson.GsonBuilder
6 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
7 | import okhttp3.Interceptor
8 | import okhttp3.OkHttpClient
9 | import okhttp3.logging.HttpLoggingInterceptor
10 | import org.koin.android.viewmodel.dsl.viewModel
11 | import org.koin.dsl.module
12 | import retrofit2.Retrofit
13 | import retrofit2.converter.gson.GsonConverterFactory
14 |
15 | fun getRemoteModule(baseUrl: String) = module {
16 | single {
17 | Retrofit.Builder().client(get()).baseUrl(baseUrl)
18 | .addCallAdapterFactory(CoroutineCallAdapterFactory())
19 | .addConverterFactory(GsonConverterFactory.create(get()))
20 | .build()
21 | }
22 |
23 |
24 | factory {
25 | GsonBuilder()
26 | .setLenient()
27 | .create()
28 | }
29 |
30 |
31 | factory {
32 | HttpLoggingInterceptor()
33 | .setLevel(HttpLoggingInterceptor.Level.BODY)
34 | }
35 |
36 | factory {
37 | OkHttpClient.Builder()
38 | .addInterceptor(get())
39 | .build()
40 | }
41 |
42 |
43 | single {
44 | get().create(ExampleApi::class.java)
45 | }
46 |
47 |
48 | viewModel {
49 | ParallelRequestsViewModel(get())
50 | }
51 |
52 |
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/CatchFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | class CatchFlowActivity {
4 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/CombineFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.PostModel
11 | import com.coroutines.examples.models.UserModel
12 | import com.coroutines.examples.network.ExampleApi
13 | import kotlinx.coroutines.*
14 | import kotlinx.coroutines.flow.*
15 | import org.koin.android.ext.android.inject
16 |
17 | /*
18 | *
19 | * the same as Zip operator with simple difference
20 | * the different is zip operator wait for another flow to emit its values combine operator did not do this just get last emitted value of another flow and collect it for example
21 | * i have flow contain numbers 1,2,3 and emit every 1000 ms and another flow contain one,two,three values and emit value after 2000 ms
22 | * when collect for first time will emit 1 and wait for value from another flow that emit one value so collect has 1 and one value
23 | * for second emit will emit value 2 and collect will be has value with 2 and one why ? because combine does not wait for second emit get last emitted value
24 | * this the difference between zip and combine
25 | * */
26 |
27 | class CombineFlowActivity : AppCompatActivity() , MyHandlers {
28 | val api: ExampleApi by inject()
29 | val binding: ActivitySimpleFlowBinding by lazy {
30 | ActivitySimpleFlowBinding.inflate(
31 | layoutInflater
32 | )
33 | }
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(binding.root)
38 | supportActionBar?.title = "Combine Flow"
39 | binding.handlers = this
40 | }
41 |
42 | override fun onDoSomeWorkClicked(view: View) {
43 | binding.loading.visible()
44 | //flow must run in Coroutine scope
45 | //run this Coroutine in IO scope because it is large job
46 | CoroutineScope(Dispatchers.IO).launch {
47 | //here i use combine operator to combine two flows posts flow and user flow
48 | //hint: number of emits of each flow must be the same because results mapped by order
49 | getPosts().combine(getUsers()) { posts, users ->
50 | val list: MutableList = mutableListOf()
51 | users.forEach { user ->
52 | list.add(
53 | UserPosts(
54 | user = user,
55 | posts = posts.filter { it.userId == user.id }.toMutableList()
56 | )
57 | )
58 | }
59 | list
60 | }.collect { results ->
61 | //switch to main thread do bind data
62 | withContext(Dispatchers.Main) {
63 | //display data
64 | results.forEach {
65 | binding.dataViewText.append("${it.user.name} number of posts ${it.posts.size}\n")
66 | }
67 |
68 | binding.loading.gone()
69 | }
70 | }
71 |
72 |
73 | }
74 |
75 |
76 | }
77 |
78 | private fun getPosts(): Flow> {
79 | return flow {
80 | //some delay because api response very fast
81 | delay(1000)
82 | emit(api.getPosts())
83 | delay(1000)
84 | emit(api.getPosts())
85 | delay(1000)
86 | emit(api.getPosts())
87 | }
88 | }
89 |
90 |
91 | private fun getUsers(): Flow> {
92 | return flow {
93 | //some delay because api response very fast
94 | delay(2000)
95 | emit(api.getUsers())
96 | delay(2000)
97 | emit(api.getUsers())
98 | delay(2000)
99 | emit(api.getUsers())
100 | }
101 | }
102 |
103 | data class UserPosts(val user: UserModel, val posts: MutableList)
104 |
105 |
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/FilterFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.PostModel
11 | import com.coroutines.examples.network.ExampleApi
12 | import kotlinx.coroutines.*
13 | import kotlinx.coroutines.flow.Flow
14 | import kotlinx.coroutines.flow.collect
15 | import kotlinx.coroutines.flow.filter
16 | import kotlinx.coroutines.flow.flow
17 | import org.koin.android.ext.android.inject
18 | /*
19 | * filter operator very useful if we have some conditions needed on data,
20 | * so filter in flow triggered in evey emit and collect triggered if condition in filter operator is true,
21 | * in our example call getposts apis and emit every post and check if userid in post object equals 1 so, we will collect only posts with
22 | * user id equals 1
23 | * */
24 |
25 | class FilterFlowActivity : AppCompatActivity(), MyHandlers {
26 | val api: ExampleApi by inject()
27 | val binding: ActivitySimpleFlowBinding by lazy {
28 | ActivitySimpleFlowBinding.inflate(
29 | layoutInflater
30 | )
31 | }
32 |
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | setContentView(binding.root)
36 | supportActionBar?.title = "Filter Flow"
37 | binding.handlers = this
38 | }
39 |
40 | override fun onDoSomeWorkClicked(view: View) {
41 | binding.loading.visible()
42 | //flow must run in Coroutine scope
43 | //run this Coroutine in IO scope because it is large job
44 | CoroutineScope(Dispatchers.IO).launch {
45 | //here i use filter operator to some condition before collecting data
46 | getPosts().filter { data ->
47 | data.userId == 1
48 | }.collect { post ->
49 | //switch to main thread do bind data
50 | withContext(Dispatchers.Main) {
51 | binding.dataViewText.append("post title ${post.title} with user id ${post.userId} \n")
52 | binding.loading.gone()
53 | }
54 | }
55 |
56 |
57 | }
58 |
59 |
60 | }
61 |
62 | private fun getPosts(): Flow {
63 | return flow {
64 | //some delay because api response very fast
65 | delay(1000)
66 | val results = api.getPosts()
67 | results.forEach {
68 | emit(it)
69 | //some delay between each emit
70 | delay(200)
71 |
72 | }
73 | }
74 | }
75 |
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/FlatMapConcatActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.Comment
11 | import com.coroutines.examples.models.PostModel
12 | import com.coroutines.examples.network.ExampleApi
13 | import kotlinx.coroutines.*
14 | import kotlinx.coroutines.flow.*
15 | import org.koin.android.ext.android.inject
16 |
17 | /*
18 | * FlatMapConcat operator from its name it concat flow with specific mode, i will focus on FlatMapConcat for now ,
19 | * for example i have flow emit values 1,2 and 3 and have some code in flatMapConcat operator what will heppend in this case
20 | * flow will wait code inside flatMapConcat to complete and collect data triggred then flow emit next value
21 | * in our example we need to emit three posts for each post we need to load its comments and then emit next post and load its comments etc....
22 | *
23 | * */
24 |
25 | @FlowPreview
26 | class FlatMapConcatActivity : AppCompatActivity(), MyHandlers {
27 | val api: ExampleApi by inject()
28 | val binding: ActivitySimpleFlowBinding by lazy {
29 | ActivitySimpleFlowBinding.inflate(
30 | layoutInflater
31 | )
32 | }
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContentView(binding.root)
37 | supportActionBar?.title = "Flat Map Concat Flow"
38 | binding.handlers = this
39 | }
40 |
41 | override fun onDoSomeWorkClicked(view: View) {
42 | binding.loading.visible()
43 | //flow must run in Coroutine scope
44 | //run this Coroutine in IO scope because it is large job
45 | CoroutineScope(Dispatchers.IO).launch {
46 | //here i used flatMapConcat operator
47 | getPosts()
48 | .flatMapConcat{ post ->
49 | requestPostCommentsPostID(post)
50 | }.collect { postComments ->
51 | //switch to main thread do bind data
52 | withContext(Dispatchers.Main) {
53 | postComments.forEach{
54 | binding.dataViewText.append("post id ${it.postId} with commenter email ${it.email} \n")
55 | }
56 | binding.loading.gone()
57 | }
58 | }
59 |
60 |
61 | }
62 |
63 |
64 | }
65 |
66 | private fun getPosts(): Flow {
67 | return flow {
68 | //some delay because api response very fast
69 | delay(1000)
70 | val results = api.getPosts()
71 | for (i in 0..2) {
72 | emit(results[i])
73 | //some delay between each emit
74 | delay(200)
75 | }
76 | }
77 | }
78 |
79 | private fun requestPostCommentsPostID(postModel: PostModel):Flow>{
80 | return flow {
81 | //some delay because api response very fast
82 | delay(500)
83 | emit(api.getPostsComments(postModel.id!!))
84 | }
85 | }
86 |
87 |
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/FlatMapLatestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | class FlatMapLatestActivity {
4 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/FlatMapMergeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.Comment
11 | import com.coroutines.examples.models.PostModel
12 | import com.coroutines.examples.network.ExampleApi
13 | import kotlinx.coroutines.*
14 | import kotlinx.coroutines.flow.*
15 | import org.koin.android.ext.android.inject
16 | /*
17 | * the same as flatmapconcat but with different mode the main difference is collect all incoming value into one flow and value
18 | * emitted as soon as possible no need to wait any thing to complete so flatmapmerge faster than flatmapconcat but the usage of
19 | * them depend on your use case
20 | *
21 | * */
22 |
23 | @FlowPreview
24 | class FlatMapMergeActivity : AppCompatActivity(), MyHandlers {
25 | val api: ExampleApi by inject()
26 | val binding: ActivitySimpleFlowBinding by lazy {
27 | ActivitySimpleFlowBinding.inflate(
28 | layoutInflater
29 | )
30 | }
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(binding.root)
35 | supportActionBar?.title = "Flat Map Merge Flow"
36 | binding.handlers = this
37 | }
38 |
39 | override fun onDoSomeWorkClicked(view: View) {
40 | binding.loading.visible()
41 | //flow must run in Coroutine scope
42 | //run this Coroutine in IO scope because it is large job
43 | CoroutineScope(Dispatchers.IO).launch {
44 | //here i used flatMapConcat operator
45 | getPosts()
46 | .flatMapMerge{ post ->
47 | requestPostCommentsPostID(post)
48 | }.collect { postComments ->
49 | //switch to main thread do bind data
50 | withContext(Dispatchers.Main) {
51 | postComments.forEach{
52 | binding.dataViewText.append("post id ${it.postId} with commenter email ${it.email} \n")
53 | }
54 | binding.loading.gone()
55 | }
56 | }
57 |
58 |
59 | }
60 |
61 |
62 | }
63 |
64 | private fun getPosts(): Flow {
65 | return flow {
66 | //some delay because api response very fast
67 | delay(1000)
68 | val results = api.getPosts()
69 | for (i in 0..2) {
70 | emit(results[i])
71 | //some delay between each emit
72 | delay(200)
73 | }
74 | }
75 | }
76 |
77 | private fun requestPostCommentsPostID(postModel: PostModel): Flow> {
78 | return flow {
79 | //some delay because api response very fast
80 | delay(500)
81 | emit(api.getPostsComments(postModel.id!!))
82 | }
83 | }
84 |
85 |
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/MapFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.PostEntity
11 | import com.coroutines.examples.models.PostModel
12 | import com.coroutines.examples.network.ExampleApi
13 | import kotlinx.coroutines.*
14 | import kotlinx.coroutines.flow.Flow
15 | import kotlinx.coroutines.flow.collect
16 | import kotlinx.coroutines.flow.flow
17 | import kotlinx.coroutines.flow.map
18 | import org.koin.android.ext.android.inject
19 | /*
20 | * in this example we will use retofit to get some posts from api
21 | * the main goal of this example that getting data from api and map it to view with another data type
22 | *
23 | * how to use this example in real app ->
24 | * assume that we use the model as backend returned and backend developer suddenly change model structure is this case we need to go to app and
25 | * change this model like backend so the solution is two thing one for backend model and one for app and mapping layer between them
26 | * in our example i will user postmodel for back end and postentity for app and user flow to map post model to post entity so if backend change anything
27 | * i just need to change change mapping code only and app will work fine
28 | *
29 | * */
30 | class MapFlowActivity : AppCompatActivity(), MyHandlers {
31 | val api: ExampleApi by inject()
32 | val binding: ActivitySimpleFlowBinding by lazy {
33 | ActivitySimpleFlowBinding.inflate(
34 | layoutInflater
35 | )
36 | }
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContentView(binding.root)
41 | supportActionBar?.title = "Map Flow"
42 | binding.handlers = this
43 | }
44 |
45 | override fun onDoSomeWorkClicked(view: View) {
46 | binding.loading.visible()
47 | //flow must run in Coroutine scope
48 | //run this Coroutine in IO scope because it is large job
49 | CoroutineScope(Dispatchers.IO).launch {
50 | //here i use map operator to transform data from postmodel to postentity
51 | getPosts().map {dataBeforeMapping->
52 | dataBeforeMapping.map {post-> PostEntity(post.body!!,post.title!!) }
53 | }.collect {dataAfterMapping->
54 | //switch to main third do bind data
55 | withContext(Dispatchers.Main){
56 | binding.dataViewText.text = dataAfterMapping.toString()
57 | binding.loading.gone()
58 | }
59 | }
60 |
61 |
62 | }
63 |
64 |
65 | }
66 |
67 | private fun getPosts() : Flow>{
68 | return flow{
69 | //some delay because api response very fast
70 | delay(1000)
71 | emit(api.getPosts())
72 | }
73 | }
74 |
75 |
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/SimpleFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import kotlinx.coroutines.CoroutineScope
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.delay
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.collect
13 | import kotlinx.coroutines.flow.flow
14 | import kotlinx.coroutines.launch
15 | /* Flow is :
16 | * An asynchronous data stream that sequentially emits values and completes normally or with an exception.
17 | **/
18 |
19 |
20 | /*
21 | * the following example explain simple flow hwo ti work
22 | * for example we are waiting some friends and to come the parity and when everyone come
23 | * the parity manager say his or her name so we are waiting 4 friends ahmed,mohamed,ali and ashraf
24 | * the duration between each one 1 second for example
25 | *
26 | * */
27 | class SimpleFlowActivity : AppCompatActivity(), MyHandlers {
28 | val binding: ActivitySimpleFlowBinding by lazy {
29 | ActivitySimpleFlowBinding.inflate(
30 | layoutInflater
31 | )
32 | }
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContentView(binding.root)
37 | supportActionBar?.title = "Simple Flow"
38 | binding.handlers = this
39 | }
40 |
41 | override fun onDoSomeWorkClicked(view: View) {
42 | //flow must run in Coroutine scope
43 | //run this Coroutine in main scope because it is sample job
44 | CoroutineScope(Dispatchers.Main).launch {
45 | //call collect function to collect every emit data
46 | getFriendsFlow().collect{friend ->
47 | binding.dataViewText.append("Your friend $friend arrived\n")
48 | }
49 | //code run after flow finish its work
50 | binding.dataViewText.append("Your friends arrived ")
51 |
52 | }
53 |
54 | }
55 |
56 | private fun getFriendsFlow(): Flow {
57 | //create flow emit 4 friends one by one sequentially and delay one second between each emit
58 | return flow {
59 | emit("Ahmed")
60 | delay(1000)
61 | emit("Mohamed")
62 | delay(1000)
63 | emit("ali")
64 | delay(1000)
65 | emit("ashraf")
66 |
67 | }
68 | }
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/TakeFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.PostModel
11 | import com.coroutines.examples.network.ExampleApi
12 | import kotlinx.coroutines.*
13 | import kotlinx.coroutines.flow.Flow
14 | import kotlinx.coroutines.flow.collect
15 | import kotlinx.coroutines.flow.flow
16 | import kotlinx.coroutines.flow.take
17 | import org.koin.android.ext.android.inject
18 |
19 |
20 | /*
21 | * Take operator is very simple operator for we example we have flow and inside flow we have 100 emits and i need to get only first 10 emits
22 | * just use take operator as following example
23 | *
24 | *
25 | * */
26 |
27 | class TakeFlowActivity : AppCompatActivity(), MyHandlers {
28 | val api: ExampleApi by inject()
29 | val binding: ActivitySimpleFlowBinding by lazy {
30 | ActivitySimpleFlowBinding.inflate(
31 | layoutInflater
32 | )
33 | }
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(binding.root)
38 | supportActionBar?.title = "Take Flow"
39 | binding.handlers = this
40 | }
41 |
42 | override fun onDoSomeWorkClicked(view: View) {
43 | binding.loading.visible()
44 | //flow must run in Coroutine scope
45 | //run this Coroutine in IO scope because it is large job
46 | CoroutineScope(Dispatchers.IO).launch {
47 | //here i use take operator to just get 10 emits
48 | getPosts().take(10).collect { post ->
49 | //switch to main thread do bind data
50 | withContext(Dispatchers.Main) {
51 | binding.dataViewText.append("title is : ${post.title}\n")
52 | binding.loading.gone()
53 | }
54 | }
55 |
56 |
57 | }
58 |
59 |
60 | }
61 |
62 | private fun getPosts(): Flow {
63 | return flow {
64 | //some delay because api response very fast
65 | delay(1000)
66 | val result = api.getPosts()
67 |
68 | result.forEach {
69 | emit(it)
70 | }
71 | }
72 | }
73 |
74 |
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/ZipFlowActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.PostModel
11 | import com.coroutines.examples.models.UserModel
12 | import com.coroutines.examples.network.ExampleApi
13 | import kotlinx.coroutines.*
14 | import kotlinx.coroutines.flow.Flow
15 | import kotlinx.coroutines.flow.collect
16 | import kotlinx.coroutines.flow.flow
17 | import kotlinx.coroutines.flow.zip
18 | import org.koin.android.ext.android.inject
19 |
20 |
21 | /*
22 | * this time for zip operator this operator very important to do magic work
23 | * i will explain with example directly imagine we have two or more api calls or large job
24 | * and we need to wait all calls and combine it in one data class and use it , zip operator solve this problem
25 | * Our example call two apis one for posts and another for users and i need to combine two responses in one list
26 | * each item in list represents one user and user's posts
27 | *
28 | *
29 | * */
30 |
31 | class ZipFlowActivity : AppCompatActivity(), MyHandlers {
32 | val api: ExampleApi by inject()
33 | val binding: ActivitySimpleFlowBinding by lazy {
34 | ActivitySimpleFlowBinding.inflate(
35 | layoutInflater
36 | )
37 | }
38 |
39 | override fun onCreate(savedInstanceState: Bundle?) {
40 | super.onCreate(savedInstanceState)
41 | setContentView(binding.root)
42 | supportActionBar?.title = "Zip Flow"
43 | binding.handlers = this
44 | }
45 |
46 | override fun onDoSomeWorkClicked(view: View) {
47 | binding.loading.visible()
48 | //flow must run in Coroutine scope
49 | //run this Coroutine in IO scope because it is large job
50 | CoroutineScope(Dispatchers.IO).launch {
51 | //here i use zip operator to combine two flows posts flow and user flow
52 | //hint: number of emits of each flow must be the same because results mapped by order
53 | //that mean results in first emit in posts flow wait the results of first emit in users flow
54 | // and the emit in posts flow wait the results of the first emit in users flow
55 | //in our example i emit one time in every flow
56 | getPosts().zip(getUsers()) { posts, users ->
57 | val list: MutableList = mutableListOf()
58 | users.forEach { user ->
59 | list.add(
60 | UserPosts(
61 | user = user,
62 | posts = posts.filter { it.userId == user.id }.toMutableList()
63 | )
64 | )
65 | }
66 | list
67 | }.collect { results ->
68 | //switch to main thread do bind data
69 | withContext(Dispatchers.Main) {
70 | //display data
71 | results.forEach {
72 | binding.dataViewText.append("${it.user.name} number of posts ${it.posts.size}\n")
73 | }
74 |
75 | binding.loading.gone()
76 | }
77 | }
78 |
79 |
80 | }
81 |
82 |
83 | }
84 |
85 | private fun getPosts(): Flow> {
86 | return flow {
87 | //some delay because api response very fast
88 | delay(1000)
89 | emit(api.getPosts())
90 | }
91 | }
92 |
93 |
94 | private fun getUsers(): Flow> {
95 | return flow {
96 | //some delay because api response very fast
97 | delay(1000)
98 | emit(api.getUsers())
99 | }
100 | }
101 |
102 | data class UserPosts(val user: UserModel, val posts: MutableList)
103 |
104 |
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/full_network_example/FullNetworkExampleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.full_network_example
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.coroutines.examples.databinding.ActivitySimpleFlowBinding
7 | import com.coroutines.examples.helpers.MyHandlers
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import com.coroutines.examples.models.UserModel
11 | import com.coroutines.examples.network.ExampleApi
12 | import kotlinx.coroutines.CoroutineScope
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.flow.Flow
15 | import kotlinx.coroutines.flow.collect
16 | import kotlinx.coroutines.launch
17 | import org.koin.android.ext.android.inject
18 |
19 | class FullNetworkExampleActivity : AppCompatActivity(), MyHandlers {
20 | val api: ExampleApi by inject()
21 | val binding: ActivitySimpleFlowBinding by lazy {
22 | ActivitySimpleFlowBinding.inflate(
23 | layoutInflater
24 | )
25 | }
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | setContentView(binding.root)
30 | supportActionBar?.title = "Network example"
31 | binding.handlers = this
32 |
33 |
34 | }
35 |
36 | override fun onDoSomeWorkClicked(view: View) {
37 | CoroutineScope(Dispatchers.Main).launch {
38 | getUsers().collect { response ->
39 | when (response.status) {
40 | Resource.Status.LOADING -> binding.loading.visible()
41 | Resource.Status.ERROR -> {
42 | binding.loading.gone()
43 | //handel error message
44 | response.messageType
45 | }
46 | Resource.Status.SUCCESS -> {
47 | binding.loading.gone()
48 | binding.dataViewText.text = response.data.toString()
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
55 |
56 | fun getUsers(): Flow>> {
57 | return object : NetworkBoundResource>() {
58 | override suspend fun remoteFetch(): List {
59 | return api.getUsers()
60 | }
61 |
62 | override suspend fun saveFetchResult(data: List?) {
63 | //update local database if need
64 | }
65 |
66 | override suspend fun localFetch(): List {
67 | //if you need to load from local database before request
68 | return emptyList()
69 | }
70 |
71 | override fun shouldFetch() = true
72 |
73 | }.asFlow()
74 |
75 | }
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/full_network_example/NetworkBoundResource.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.full_network_example
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flow
7 | import kotlinx.coroutines.flow.onStart
8 |
9 | abstract class NetworkBoundResource {
10 |
11 | @ExperimentalCoroutinesApi
12 | fun asFlow(): Flow> = flow {
13 |
14 | if (shouldFetch()) {
15 | if (shouldFetchWithLocalData()) {
16 | emit(Resource.loading(data = localFetch()))
17 | }
18 | if (isHasInterNet()) {
19 | val results = safeApiCall(dispatcher = Dispatchers.IO) {
20 | remoteFetch()
21 | }
22 | when (results) {
23 | is ResultWrapper.Success -> {
24 | results.value?.let {
25 | saveFetchResult(results.value)
26 | }
27 | emit(Resource.success(data = results.value))
28 | updateEntry(results.value)
29 | }
30 |
31 | is ResultWrapper.GenericError -> {
32 | emit(Resource.error(data = null, error = results.messageType))
33 | }
34 | }
35 | } else {
36 | saveFetchResult(null)
37 | emit(Resource.success(dataSource = Resource.DataSource.LOCAL, data = localFetch()))
38 | }
39 |
40 | } else {
41 | //get from cash
42 | val data = localFetch()
43 | emit(Resource.loading(data = data))
44 | }
45 |
46 |
47 | }.onStart {
48 | //get From cache
49 | if(shouldFetch())
50 | emit(Resource.loading(data = null))
51 | }
52 |
53 |
54 | abstract suspend fun remoteFetch(): T
55 | abstract suspend fun saveFetchResult(data: T?)
56 | abstract suspend fun localFetch(): T
57 | open fun onFetchFailed(throwable: Throwable) = Unit
58 | open fun shouldFetch() = true
59 | open fun shouldFetchWithLocalData() = false
60 | open fun isHasInterNet() = true
61 | open suspend fun updateEntry(data: T?) {}
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/full_network_example/RemoteExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.full_network_example
2 |
3 |
4 |
5 | import com.coroutines.examples.flowactivities.full_network_example.NetworkCodes.GENERAL_ERROR
6 | import com.google.gson.Gson
7 | import com.google.gson.JsonObject
8 | import kotlinx.coroutines.CoroutineDispatcher
9 | import kotlinx.coroutines.TimeoutCancellationException
10 | import kotlinx.coroutines.withContext
11 | import org.json.JSONArray
12 | import org.json.JSONObject
13 | import org.json.JSONTokener
14 | import retrofit2.HttpException
15 | import java.io.IOException
16 |
17 | sealed class ResultWrapper {
18 | data class Success(val value: T) : ResultWrapper()
19 | data class GenericError(val messageType: MessageType) : ResultWrapper()
20 | }
21 |
22 |
23 | suspend fun safeApiCall(
24 | dispatcher: CoroutineDispatcher,
25 | apiCall: suspend () -> T?
26 | ): ResultWrapper {
27 | return withContext(dispatcher) {
28 | try {
29 | val call = apiCall.invoke()
30 | ResultWrapper.Success(call)
31 | } catch (throwable: Throwable) {
32 | throwable.printStackTrace()
33 | when (throwable) {
34 | is TimeoutCancellationException -> {
35 | ResultWrapper.GenericError(messageType = MessageType.SnackBar(NetworkCodes.TIMEOUT_ERROR))
36 | }
37 | is IOException -> {
38 | ResultWrapper.GenericError(
39 | MessageType.SnackBar(NetworkCodes.CONNECTION_ERROR)
40 | )
41 | }
42 | is HttpException -> {
43 | val code = throwable.code()
44 | val errorResponse = convertErrorBody(throwable)
45 | errorResponse?.errors?.let {
46 | ResultWrapper.GenericError(
47 | MessageType.Dialog(code, message = errorResponse)
48 | )
49 | }?: kotlin.run {
50 | ResultWrapper.GenericError(
51 | MessageType.SnackBar(code, message = errorResponse)
52 | )
53 | }
54 |
55 | }
56 | else -> {
57 | ResultWrapper.GenericError(
58 | MessageType.Dialog(GENERAL_ERROR)
59 | )
60 | }
61 | }
62 |
63 | }
64 | }
65 | }
66 |
67 |
68 | //for custom error body
69 | private fun convertErrorBody(throwable: HttpException): ErrorResponse? {
70 | try {
71 | val json = JSONTokener(throwable.response()?.errorBody()?.string()).nextValue();
72 | if (json is JSONObject || json is JSONArray) {
73 | val errorResponse = Gson().fromJson(json.toString(), ErrorResponse::class.java)
74 | errorResponse?.let { return it }
75 | }
76 | return null
77 |
78 | } catch (exception: Exception) {
79 | return null
80 | }
81 | }
82 |
83 | class ErrorResponse(val message: String? = "",val errors:JsonObject)
84 |
85 | sealed class MessageType(var code:Int, var message: ErrorResponse? = null){
86 | class SnackBar(code:Int, message: ErrorResponse? = null) : MessageType(code,message)
87 | class Dialog(code:Int,message:ErrorResponse? = null) : MessageType(code,message)
88 | class Toast(code:Int,message:ErrorResponse? = null): MessageType(code,message)
89 | class None(code:Int,message:ErrorResponse? = null): MessageType(code,message)
90 | }
91 |
92 |
93 | object NetworkCodes {
94 | const val BENEFICIARY_NOT_COMPLETED = 1003
95 | const val CONNECTION_ERROR = 100
96 | const val TIMEOUT_ERROR = 408
97 | const val GENERAL_ERROR = 1000
98 | const val CONNECTION_NOT_FOUND = 1001
99 | const val BENEFICIARY_LIMIT_ERROR = 1002
100 |
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/full_network_example/Resource.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.full_network_example
2 |
3 | data class Resource(
4 | val status: Status, val data: T?, val messageType: MessageType?,
5 | val dataSource: DataSource? = null
6 | ) {
7 | companion object {
8 | fun success(data: T?,dataSource:DataSource = DataSource.REMOTE): Resource {
9 | return Resource(
10 | Status.SUCCESS,
11 | data,
12 | null,
13 | dataSource = dataSource
14 | )
15 | }
16 |
17 | fun error(error: MessageType, data: T?): Resource {
18 | return Resource(
19 | Status.ERROR,
20 | data,
21 | error
22 | )
23 | }
24 |
25 | fun loading(data: T?): Resource {
26 | return Resource(
27 | Status.LOADING,
28 | data,
29 | null
30 | )
31 | }
32 | }
33 |
34 | enum class Status {
35 | SUCCESS,
36 | ERROR,
37 | LOADING
38 | }
39 |
40 | enum class DataSource {
41 | REMOTE,
42 | LOCAL,
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/parallel_requests/ParallelRequestsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.parallel_requests
2 |
3 | import android.os.Bundle
4 | import android.widget.ProgressBar
5 | import android.widget.TextView
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.coroutines.examples.R
8 | import com.coroutines.examples.helpers.gone
9 | import com.coroutines.examples.helpers.visible
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.launch
13 | import org.koin.android.scope.lifecycleScope
14 | import org.koin.android.viewmodel.ext.android.viewModel
15 |
16 | class ParallelRequestsActivity : AppCompatActivity() {
17 | val viewModel:ParallelRequestsViewModel by viewModel()
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_parallel_requests)
21 | val resultTv = findViewById(R.id.results)
22 | val loadingView = findViewById(R.id.loading)
23 | viewModel.loadUserDataAndComments()
24 |
25 | CoroutineScope(Dispatchers.Main).launch {
26 | viewModel.state.collect{
27 | when(it){
28 | is Resource.Error -> {}
29 | Resource.Loading -> {
30 | loadingView.visible()
31 | }
32 | is Resource.Success -> {
33 | it.data.forEach {
34 | loadingView.gone()
35 | resultTv.append("${it.title}\nnumber of post comments ${it.comments.size}\n\n")
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
42 |
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/flowactivities/parallel_requests/ParallelRequestsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.flowactivities.parallel_requests
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.coroutines.examples.models.Comment
7 | import com.coroutines.examples.models.PostModel
8 | import com.coroutines.examples.network.ExampleApi
9 | import kotlinx.coroutines.async
10 | import kotlinx.coroutines.awaitAll
11 | import kotlinx.coroutines.flow.MutableSharedFlow
12 | import kotlinx.coroutines.flow.asSharedFlow
13 | import kotlinx.coroutines.launch
14 |
15 | class ParallelRequestsViewModel(
16 | private val api: ExampleApi
17 | ) : ViewModel() {
18 |
19 | private val _state = MutableSharedFlow>>()
20 | val state = _state.asSharedFlow()
21 |
22 | fun loadUserDataAndComments() {
23 | viewModelScope.launch {
24 | _state.emit(Resource.Loading)
25 | val posts = api.getPosts()
26 | val comments = posts.map {
27 | async { api.getPostsComments(it.id!!) }
28 | }
29 | val data = comments.awaitAll()
30 | posts.forEach { post ->
31 | post.comments =
32 | data.first { comment -> post.id == comment[0].postId }.toMutableList()
33 | }
34 | _state.emit(Resource.Success(posts))
35 |
36 | }
37 | }
38 |
39 | }
40 |
41 |
42 | sealed class Resource {
43 | object Loading : Resource()
44 | data class Success(val data: T) : Resource()
45 | data class Error(val message: String) : Resource()
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/helpers/App.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.helpers
2 |
3 | import android.app.Application
4 | import com.coroutines.examples.di.getRemoteModule
5 | import com.coroutines.examples.helpers.Constants.BASE_URL
6 | import org.koin.android.ext.koin.androidContext
7 | import org.koin.core.context.startKoin
8 |
9 | class App : Application() {
10 | override fun onCreate() {
11 | super.onCreate()
12 | startKoin {
13 | modules(getRemoteModule(BASE_URL))
14 | androidContext(this@App)
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/helpers/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.helpers
2 |
3 | object Constants {
4 |
5 | const val BASE_URL = "https://jsonplaceholder.typicode.com/"
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/helpers/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.helpers
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.text.format.DateFormat
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.widget.Toast
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.appcompat.widget.AppCompatEditText
14 | import androidx.core.content.ContextCompat
15 | import androidx.fragment.app.Fragment
16 | import com.google.android.material.snackbar.Snackbar
17 | import java.util.*
18 |
19 | fun AppCompatActivity.showSnackbar(snackbarText: String, timeLength: Int = 2000) {
20 | Snackbar.make(findViewById(android.R.id.content), snackbarText, timeLength).show()
21 | }
22 |
23 | fun Fragment.showSnackbar(snackbarText: String, timeLength: Int = 2000) {
24 | (activity as AppCompatActivity).showSnackbar(snackbarText, timeLength)
25 | }
26 |
27 |
28 | fun Context.openActivity(className: Class<*>, bundle: Bundle? = null, closeAll: Boolean = false) {
29 |
30 | val intent = Intent(this, className)
31 | bundle?.let {
32 | intent.putExtras(it)
33 | }
34 |
35 | if (closeAll) {
36 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
37 | }
38 | startActivity(intent)
39 |
40 | }
41 |
42 | fun Context.openActivityForResult(
43 | className: Class<*>,
44 | bundle: Bundle? = null,
45 | requestCode: Int = 0
46 | ) {
47 |
48 | val intent = Intent(this, className)
49 | bundle?.let {
50 | intent.putExtras(it)
51 | }
52 |
53 |
54 | try {
55 | (this as AppCompatActivity).startActivityForResult(intent, requestCode)
56 | } catch (e: ClassCastException) {
57 |
58 | }
59 |
60 | }
61 |
62 | fun Fragment.openActivityForResult(
63 | className: Class<*>,
64 | bundle: Bundle? = null,
65 | requestCode: Int = 0
66 | ) {
67 |
68 | val intent = Intent(activity, className)
69 | bundle?.let {
70 | intent.putExtras(it)
71 | }
72 | startActivityForResult(intent, requestCode)
73 |
74 | }
75 |
76 | fun ViewGroup.inflate(id: Int): View {
77 | return LayoutInflater.from(context).inflate(id, this, false)
78 | }
79 |
80 | fun Date.getCurrentDateForBackEnd(format: String = "yyyy-MM-dd hh:mm:ss"): String? {
81 | return DateFormat.format(format, time).toString()
82 | }
83 |
84 |
85 | fun Context.convertArrayResourceToList(resID: Int): List {
86 | return resources.getStringArray(resID).toList()
87 |
88 | }
89 |
90 | fun AppCompatEditText.isValidEmail(): Boolean {
91 | val email = text.toString()
92 | return if (email.isEmpty()) true
93 | else android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
94 | }
95 |
96 | fun AppCompatEditText.isValidMobile(): Boolean {
97 | val mobile = text.toString()
98 | return mobile.length == 11
99 | }
100 |
101 | fun View.gone() {
102 | visibility = View.GONE
103 | }
104 |
105 | fun View.visible() {
106 | visibility = View.VISIBLE
107 | }
108 |
109 | fun View.invisible() {
110 | visibility = View.INVISIBLE
111 | }
112 |
113 | @SuppressLint("RestrictedApi")
114 | fun AppCompatActivity.validateActionBar(title: String = "") {
115 | supportActionBar?.apply {
116 | setTitle(title)
117 | setDefaultDisplayHomeAsUpEnabled(true)
118 | setDisplayHomeAsUpEnabled(true)
119 | }
120 | }
121 |
122 |
123 |
124 |
125 |
126 | fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
127 | Toast.makeText(this, message, duration).show()
128 | }
129 |
130 |
131 | fun Context.getColorRes(resId: Int): Int {
132 | return ContextCompat.getColor(this, resId)
133 | }
134 |
135 | fun Context.dp(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/helpers/MyHandlers.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.helpers
2 |
3 | import android.view.View
4 |
5 | interface MyHandlers {
6 | fun onDoSomeWorkClicked(view: View)
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/models/Comment.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.models
2 |
3 |
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class Comment(
7 | @SerializedName("body")
8 | var body: String?,
9 | @SerializedName("email")
10 | var email: String?,
11 | @SerializedName("id")
12 | var id: Int?,
13 | @SerializedName("name")
14 | var name: String?,
15 | @SerializedName("postId")
16 | var postId: Int?
17 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/models/PostModel.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.models
2 |
3 |
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class PostModel(
7 | @SerializedName("body")
8 | var body: String?,
9 | @SerializedName("id")
10 | var id: Int?,
11 | @SerializedName("title")
12 | var title: String?,
13 | @SerializedName("userId")
14 | var userId: Int?,
15 | var comments:MutableList = mutableListOf()
16 | )
17 |
18 |
19 | data class PostEntity(
20 | var body: String,
21 | var title: String
22 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/models/UserModel.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.models
2 |
3 |
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class UserModel(
7 | @SerializedName("address")
8 | var address: Address?,
9 | @SerializedName("company")
10 | var company: Company?,
11 | @SerializedName("email")
12 | var email: String?,
13 | @SerializedName("id")
14 | var id: Int?,
15 | @SerializedName("name")
16 | var name: String?,
17 | @SerializedName("phone")
18 | var phone: String?,
19 | @SerializedName("username")
20 | var username: String?,
21 | @SerializedName("website")
22 | var website: String?
23 | )
24 |
25 |
26 | data class Address(
27 | @SerializedName("city")
28 | var city: String?,
29 | @SerializedName("geo")
30 | var geo: Geo?,
31 | @SerializedName("street")
32 | var street: String?,
33 | @SerializedName("suite")
34 | var suite: String?,
35 | @SerializedName("zipcode")
36 | var zipcode: String?
37 | )
38 |
39 |
40 | data class Geo(
41 | @SerializedName("lat")
42 | var lat: String?,
43 | @SerializedName("lng")
44 | var lng: String?
45 | )
46 |
47 | data class Company(
48 | @SerializedName("bs")
49 | var bs: String?,
50 | @SerializedName("catchPhrase")
51 | var catchPhrase: String?,
52 | @SerializedName("name")
53 | var name: String?
54 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/coroutines/examples/network/ExampleApi.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples.network
2 |
3 | import com.coroutines.examples.models.Comment
4 | import com.coroutines.examples.models.PostModel
5 | import com.coroutines.examples.models.UserModel
6 | import retrofit2.http.GET
7 | import retrofit2.http.Query
8 |
9 | interface ExampleApi {
10 | @GET("posts")
11 | suspend fun getPosts():List
12 |
13 | @GET("users")
14 | suspend fun getUsers():List
15 |
16 | @GET("comments")
17 | suspend fun getPostsComments(@Query("postId") id: Int): List
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
13 |
19 |
29 |
30 |
40 |
50 |
51 |
61 |
62 |
63 |
64 |
65 |
75 |
76 |
86 |
87 |
88 |
98 |
99 |
100 |
101 |
111 |
112 |
122 |
123 |
133 |
134 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_parallel_requests.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
13 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_simple_flow.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
17 |
18 |
21 |
22 |
30 |
31 |
41 |
42 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Coroutines flows examples
3 | Simple flow
4 | Map
5 | Zip
6 | Combine
7 | Take
8 | Filter
9 | Reduce
10 | FlatMapConcat
11 | FlatMapMerge
12 | FlatMapLatest
13 | Catch
14 | Full networking example
15 | ParallelRequestsActivity
16 | Parallel Request
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/test/java/com/coroutines/examples/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.coroutines.examples
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | compose_version = '1.0.1'
5 | }
6 | ext.kotlin_version = "1.4.10"
7 | repositories {
8 | google()
9 | jcenter()
10 | }
11 | dependencies {
12 | classpath "com.android.tools.build:gradle:4.1.3"
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21"
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahmedshaban1/Coroutines-flows-examples/3d8b94db6b293a7401df3ec9d50f2640ac1fc8f9/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 07 14:21:24 EET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "Coroutines flows examples"
--------------------------------------------------------------------------------