├── .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 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | ![Coroutines-flows-examples-operators](https://github.com/ahmedshaban1/Coroutines-flows-examples/blob/master/160166456_3990980220990097_495071663197011529_n.jpg) 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 |