├── .DS_Store ├── .gitignore ├── .idea ├── .gitignore ├── .name └── compiler.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── playground │ │ └── streams │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── playground │ │ │ └── streams │ │ │ ├── MainActivity.kt │ │ │ ├── StreamsApplication.kt │ │ │ ├── consumeresource │ │ │ ├── data │ │ │ │ ├── ContentEntityMapper.kt │ │ │ │ ├── ProductDataSource.kt │ │ │ │ ├── ProductDataSourceModule.kt │ │ │ │ ├── ProductRepositoryImpl.kt │ │ │ │ └── model │ │ │ │ │ └── ContentResponse.kt │ │ │ ├── domain │ │ │ │ ├── FetchDetailUseCase.kt │ │ │ │ ├── ProductRepository.kt │ │ │ │ ├── Resource.kt │ │ │ │ └── model │ │ │ │ │ └── ContentEntity.kt │ │ │ └── presentation │ │ │ │ ├── ConsumeResourceActivity.kt │ │ │ │ ├── ConsumeResourceFragment.kt │ │ │ │ ├── ConsumeResourceViewModel.kt │ │ │ │ └── data │ │ │ │ └── ConsumeResourceUIState.kt │ │ │ ├── presentation │ │ │ ├── PresentationActivity.kt │ │ │ ├── SampleFragment.kt │ │ │ ├── SampleViewModel.kt │ │ │ └── data │ │ │ │ ├── SampleEvent.kt │ │ │ │ ├── SampleState.kt │ │ │ │ └── UIEvent.kt │ │ │ ├── sharedevent │ │ │ ├── SharedEventActivity.kt │ │ │ ├── SharedEventFragment.kt │ │ │ └── SharedViewModel.kt │ │ │ ├── subjects │ │ │ ├── BehaviorType.kt │ │ │ ├── DataSourceWithSharedFlow.kt │ │ │ ├── DataSourceWithSubjects.kt │ │ │ ├── SharedFlowViewModel.kt │ │ │ ├── SubjectsActivity.kt │ │ │ └── SubjectsViewModel.kt │ │ │ └── util │ │ │ ├── SingleLiveEvent.kt │ │ │ └── Timer.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_consume_resource.xml │ │ ├── activity_main.xml │ │ ├── activity_presentation.xml │ │ ├── activity_shared_event.xml │ │ ├── activity_subjects.xml │ │ ├── fragment_consume_resource.xml │ │ ├── fragment_sample.xml │ │ └── fragment_shared_event.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── playground │ └── streams │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cifo19/CoroutineForAll/24d1f927d05a75776dff2f0680a0b63bbb930156/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | .idea/jarRepositories.xml 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | .idea/deploymentTargetDropDown.xml 54 | .idea/misc.xml 55 | 56 | # Keystore files 57 | # Uncomment the following lines if you do not want to check your keystore files in. 58 | #*.jks 59 | #*.keystore 60 | 61 | # External native build folder generated in Android Studio 2.2 and later 62 | .externalNativeBuild 63 | .cxx/ 64 | 65 | # Google Services (e.g. APIs or Firebase) 66 | # google-services.json 67 | 68 | # Freeline 69 | freeline.py 70 | freeline/ 71 | freeline_project_description.json 72 | 73 | # fastlane 74 | fastlane/report.xml 75 | fastlane/Preview.html 76 | fastlane/screenshots 77 | fastlane/test_output 78 | fastlane/readme.md 79 | 80 | # Version control 81 | vcs.xml 82 | 83 | # lint 84 | lint/intermediates/ 85 | lint/generated/ 86 | lint/outputs/ 87 | lint/tmp/ 88 | # lint/reports/ 89 | 90 | # Android Profiling 91 | *.hprof -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | My Application -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | } 7 | 8 | android { 9 | compileSdk 32 10 | 11 | defaultConfig { 12 | applicationId "com.playground.streams" 13 | minSdk 23 14 | targetSdk 32 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | 36 | buildFeatures { 37 | viewBinding true 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | implementation 'com.google.dagger:hilt-android:2.40.5' 44 | kapt 'com.google.dagger:hilt-compiler:2.40.5' 45 | 46 | implementation "io.reactivex.rxjava3:rxjava:3.1.3" 47 | implementation "io.reactivex.rxjava3:rxkotlin:3.0.1" 48 | implementation "io.reactivex.rxjava3:rxandroid:3.0.0" 49 | 50 | implementation "androidx.fragment:fragment-ktx:1.4.1" 51 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" 52 | 53 | implementation 'androidx.core:core-ktx:1.7.0' 54 | implementation 'androidx.appcompat:appcompat:1.4.1' 55 | implementation 'com.google.android.material:material:1.5.0' 56 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 57 | testImplementation 'junit:junit:4.+' 58 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 59 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 60 | } -------------------------------------------------------------------------------- /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/playground/streams/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams 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.playground.streams", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.playground.streams.consumeresource.presentation.ConsumeResourceActivity 7 | import com.playground.streams.databinding.ActivityMainBinding 8 | import com.playground.streams.presentation.PresentationActivity 9 | import com.playground.streams.sharedevent.SharedEventActivity 10 | import com.playground.streams.subjects.SubjectsActivity 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class MainActivity : AppCompatActivity() { 15 | 16 | private lateinit var binding: ActivityMainBinding 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | binding = ActivityMainBinding.inflate(layoutInflater) 21 | setContentView(binding.root) 22 | 23 | binding.buttonShowPresentation.setOnClickListener { 24 | startActivity(Intent(this, PresentationActivity::class.java)) 25 | } 26 | binding.buttonShowConsumeResource.setOnClickListener { 27 | startActivity(Intent(this, ConsumeResourceActivity::class.java)) 28 | } 29 | binding.buttonShowSharedEvent.setOnClickListener { 30 | startActivity(Intent(this, SharedEventActivity::class.java)) 31 | } 32 | binding.buttonSubjects.setOnClickListener { 33 | startActivity(Intent(this, SubjectsActivity::class.java)) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/StreamsApplication.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class StreamsApplication: Application() { 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/data/ContentEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.data 2 | 3 | import com.playground.streams.consumeresource.data.model.ContentResponse 4 | import com.playground.streams.consumeresource.domain.model.ContentEntity 5 | import javax.inject.Inject 6 | 7 | class ContentEntityMapper @Inject constructor() { 8 | 9 | fun map(contentResponse: ContentResponse): ContentEntity { 10 | return ContentEntity( 11 | title = contentResponse.title, 12 | description = contentResponse.description 13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/data/ProductDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.data 2 | 3 | import com.playground.streams.consumeresource.data.model.ContentResponse 4 | import io.reactivex.rxjava3.core.Single 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.withContext 7 | import java.util.concurrent.TimeUnit 8 | import javax.inject.Inject 9 | 10 | class ProductDataSource @Inject constructor() { 11 | 12 | fun fetchDetail(id: String): Single { 13 | return Single.create { 14 | val response = getContentResponse(id) 15 | it.onSuccess(response) 16 | } 17 | .delay(2, TimeUnit.SECONDS) 18 | } 19 | 20 | // region suspend 21 | suspend fun fetchDetailSuspend(id: String): ContentResponse { 22 | delay(2000) 23 | return getContentResponse(id) 24 | } 25 | // endregion 26 | 27 | private fun getContentResponse(id: String): ContentResponse { 28 | return ContentResponse("Hello $id", "Hello Description", 10.0) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/data/ProductDataSourceModule.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.data 2 | 3 | import com.playground.streams.consumeresource.domain.ProductRepository 4 | import dagger.Binds 5 | import dagger.Module 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | interface ProductDataSourceModule { 12 | 13 | @Binds 14 | fun bindProductRepository(productRepositoryImpl: ProductRepositoryImpl): ProductRepository 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/data/ProductRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.data 2 | 3 | import com.playground.streams.consumeresource.domain.ProductRepository 4 | import com.playground.streams.consumeresource.domain.Resource 5 | import com.playground.streams.consumeresource.domain.model.ContentEntity 6 | import io.reactivex.rxjava3.core.Observable 7 | import io.reactivex.rxjava3.schedulers.Schedulers 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.catch 11 | import kotlinx.coroutines.flow.flow 12 | import kotlinx.coroutines.flow.flowOn 13 | import kotlinx.coroutines.flow.map 14 | import kotlinx.coroutines.flow.onStart 15 | import javax.inject.Inject 16 | 17 | class ProductRepositoryImpl @Inject constructor( 18 | private val productDataSource: ProductDataSource, 19 | private val contentEntityMapper: ContentEntityMapper 20 | ) : ProductRepository { 21 | 22 | override fun fetchDetail(id: String): Observable> { 23 | return productDataSource.fetchDetail(id) 24 | .toObservable() 25 | .observeOn(Schedulers.computation()) 26 | .map { contentEntityMapper.map(it) } 27 | .map> { Resource.Success(it) } 28 | .onErrorReturn { Resource.Failure(it) } 29 | .startWithItem(Resource.Loading()) 30 | } 31 | 32 | // region suspend 33 | override fun fetchDetailFlow(id: String): Flow> { 34 | return flow { emit(productDataSource.fetchDetailSuspend(id)) } 35 | .map { contentEntityMapper.map(it) } 36 | .map> { Resource.Success(it) } 37 | .flowOn(Dispatchers.Default) 38 | .catch { emit(Resource.Failure(it)) } 39 | .onStart { emit(Resource.Loading()) } 40 | } 41 | // endregion 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/data/model/ContentResponse.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.data.model 2 | 3 | data class ContentResponse( 4 | val title: String, 5 | val description: String, 6 | val price: Double 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/domain/FetchDetailUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.domain 2 | 3 | import com.playground.streams.consumeresource.domain.model.ContentEntity 4 | import io.reactivex.rxjava3.core.Observable 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class FetchDetailUseCase @Inject constructor( 9 | private val productRepository: ProductRepository 10 | ) { 11 | 12 | fun fetchDetail(id: String): Observable> { 13 | return productRepository.fetchDetail(id) 14 | } 15 | 16 | fun fetchDetailFlow(id: String): Flow> { 17 | return productRepository.fetchDetailFlow(id) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/domain/ProductRepository.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.domain 2 | 3 | import com.playground.streams.consumeresource.domain.model.ContentEntity 4 | import io.reactivex.rxjava3.core.Observable 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface ProductRepository { 8 | 9 | fun fetchDetail(id: String): Observable> 10 | 11 | fun fetchDetailFlow(id: String): Flow> 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/domain/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.domain 2 | 3 | sealed class Resource { 4 | 5 | data class Loading(private val nothing: Nothing? = null) : Resource() 6 | data class Success(val data: T) : Resource() 7 | data class Failure(val throwable: Throwable) : Resource() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/domain/model/ContentEntity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.domain.model 2 | 3 | data class ContentEntity( 4 | val title: String, 5 | val description: String 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/presentation/ConsumeResourceActivity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.playground.streams.R 6 | import com.playground.streams.databinding.ActivityConsumeResourceBinding 7 | import dagger.hilt.android.AndroidEntryPoint 8 | 9 | @AndroidEntryPoint 10 | class ConsumeResourceActivity : AppCompatActivity() { 11 | 12 | private lateinit var binding: ActivityConsumeResourceBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = ActivityConsumeResourceBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | 19 | if (savedInstanceState == null) { 20 | showConsumeResourcesFragment() 21 | } 22 | } 23 | 24 | private fun showConsumeResourcesFragment() { 25 | supportFragmentManager.beginTransaction() 26 | .add( 27 | R.id.fragmentContainer, 28 | ConsumeResourceFragment.newInstance(), 29 | ConsumeResourceFragment.TAG 30 | ) 31 | .commit() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/presentation/ConsumeResourceFragment.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.presentation 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.fragment.app.viewModels 10 | import androidx.lifecycle.Lifecycle 11 | import androidx.lifecycle.lifecycleScope 12 | import androidx.lifecycle.repeatOnLifecycle 13 | import com.playground.streams.consumeresource.presentation.data.ConsumeResourceUIState 14 | import com.playground.streams.databinding.FragmentConsumeResourceBinding 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.flow.collect 17 | import kotlinx.coroutines.launch 18 | 19 | @AndroidEntryPoint 20 | class ConsumeResourceFragment: Fragment() { 21 | 22 | private var _binding: FragmentConsumeResourceBinding? = null 23 | private val binding get() = _binding!! 24 | 25 | private val consumeResourceViewModel: ConsumeResourceViewModel by viewModels() 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View { 32 | _binding = FragmentConsumeResourceBinding.inflate(inflater, container, false) 33 | return binding.root 34 | } 35 | 36 | override fun onDestroyView() { 37 | super.onDestroyView() 38 | _binding = null 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | 44 | consumeResourceViewModel.getDetailAsFlow("Android") 45 | 46 | viewLifecycleOwner.lifecycleScope.launch { 47 | viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 48 | consumeResourceViewModel.uiState.collect { uiState -> renderUIState(uiState) } 49 | } 50 | } 51 | } 52 | 53 | @SuppressLint("SetTextI18n") 54 | private fun renderUIState(uiState: ConsumeResourceUIState) { 55 | when(uiState) { 56 | is ConsumeResourceUIState.Content -> { 57 | binding.title.text = uiState.contentEntity.title 58 | binding.description.text = uiState.contentEntity.description 59 | } 60 | ConsumeResourceUIState.Loading -> { 61 | binding.title.text = "Loading" 62 | } 63 | is ConsumeResourceUIState.Failure -> { 64 | binding.title.text = "Failed" 65 | binding.description.text = "" 66 | } 67 | } 68 | } 69 | 70 | companion object { 71 | 72 | const val TAG = "ConsumeResourceFragment" 73 | 74 | fun newInstance(): ConsumeResourceFragment = ConsumeResourceFragment() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/presentation/ConsumeResourceViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.playground.streams.consumeresource.domain.FetchDetailUseCase 6 | import com.playground.streams.consumeresource.domain.Resource 7 | import com.playground.streams.consumeresource.domain.model.ContentEntity 8 | import com.playground.streams.consumeresource.presentation.data.ConsumeResourceUIState 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers 11 | import io.reactivex.rxjava3.disposables.CompositeDisposable 12 | import io.reactivex.rxjava3.kotlin.plusAssign 13 | import io.reactivex.rxjava3.schedulers.Schedulers 14 | import kotlinx.coroutines.flow.MutableStateFlow 15 | import kotlinx.coroutines.flow.asStateFlow 16 | import kotlinx.coroutines.flow.catch 17 | import kotlinx.coroutines.flow.launchIn 18 | import kotlinx.coroutines.flow.onEach 19 | import javax.inject.Inject 20 | 21 | @HiltViewModel 22 | class ConsumeResourceViewModel @Inject constructor( 23 | private val fetchDetailUseCase: FetchDetailUseCase 24 | ) : ViewModel() { 25 | 26 | private val compositeDisposable = CompositeDisposable() 27 | 28 | private val _uiState = MutableStateFlow(ConsumeResourceUIState.Loading) 29 | val uiState = _uiState.asStateFlow() 30 | 31 | fun getDetail(id: String) { 32 | compositeDisposable += fetchDetailUseCase.fetchDetail(id) 33 | .observeOn(AndroidSchedulers.mainThread()) 34 | .subscribeOn(Schedulers.io()) 35 | .subscribe( 36 | { onResource(it) }, 37 | { 38 | // Log 39 | } 40 | ) 41 | } 42 | 43 | fun getDetailAsFlow(id: String) { 44 | fetchDetailUseCase.fetchDetailFlow(id) 45 | .onEach { onResource(it) } 46 | .catch { 47 | // Log 48 | } 49 | .launchIn(viewModelScope) 50 | } 51 | 52 | private fun onResource(resource: Resource) { 53 | _uiState.value = when (resource) { 54 | is Resource.Failure -> { 55 | ConsumeResourceUIState.Failure(resource.throwable) 56 | // Log 57 | } 58 | is Resource.Loading -> { 59 | ConsumeResourceUIState.Loading 60 | } 61 | is Resource.Success -> { 62 | ConsumeResourceUIState.Content(resource.data) 63 | } 64 | } 65 | } 66 | 67 | override fun onCleared() { 68 | compositeDisposable.clear() 69 | super.onCleared() 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/consumeresource/presentation/data/ConsumeResourceUIState.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.consumeresource.presentation.data 2 | 3 | import com.playground.streams.consumeresource.domain.model.ContentEntity 4 | 5 | sealed class ConsumeResourceUIState { 6 | object Loading: ConsumeResourceUIState() 7 | data class Content(val contentEntity: ContentEntity): ConsumeResourceUIState() 8 | data class Failure(val throwable: Throwable): ConsumeResourceUIState() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/PresentationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.playground.streams.R 6 | import com.playground.streams.databinding.ActivityPresentationBinding 7 | import dagger.hilt.android.AndroidEntryPoint 8 | 9 | @AndroidEntryPoint 10 | class PresentationActivity : AppCompatActivity() { 11 | 12 | private lateinit var binding: ActivityPresentationBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = ActivityPresentationBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | if (savedInstanceState == null) { 19 | showSampleFragment() 20 | } 21 | } 22 | 23 | private fun showSampleFragment() { 24 | supportFragmentManager.beginTransaction() 25 | .add(R.id.fragmentContainer, SampleFragment.newInstance(), SampleFragment.TAG) 26 | .commit() 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/SampleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.lifecycle.Lifecycle 10 | import androidx.lifecycle.lifecycleScope 11 | import androidx.lifecycle.repeatOnLifecycle 12 | import com.google.android.material.snackbar.Snackbar 13 | import com.playground.streams.databinding.FragmentSampleBinding 14 | import com.playground.streams.presentation.data.UIEvent 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.flow.collect 17 | import kotlinx.coroutines.launch 18 | 19 | @AndroidEntryPoint 20 | class SampleFragment : Fragment() { 21 | 22 | private var _binding: FragmentSampleBinding? = null 23 | private val binding get() = _binding!! 24 | 25 | private val sampleViewModel: SampleViewModel by viewModels() 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View { 32 | _binding = FragmentSampleBinding.inflate(inflater, container, false) 33 | return binding.root 34 | } 35 | 36 | override fun onDestroyView() { 37 | super.onDestroyView() 38 | _binding = null 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | 44 | observeLiveData() 45 | observeFlow() 46 | setClickListeners() 47 | } 48 | 49 | // region ObserveLiveData 50 | private fun observeLiveData() = with(sampleViewModel) { 51 | sampleEventLiveData.observe(viewLifecycleOwner) { 52 | Snackbar.make(requireView(), it.message, Snackbar.LENGTH_SHORT).show() 53 | } 54 | sampleStateLiveData.observe(viewLifecycleOwner) { 55 | binding.textViewSampleName.text = it.message 56 | } 57 | } 58 | // endregion 59 | 60 | // region observeFlow 61 | private fun observeFlow() = with(sampleViewModel) { 62 | viewLifecycleOwner.lifecycleScope.launch { 63 | viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 64 | sampleEventFlow.collect { 65 | Snackbar.make(requireView(), it.message, Snackbar.LENGTH_SHORT).show() 66 | } 67 | } 68 | } 69 | 70 | viewLifecycleOwner.lifecycleScope.launch { 71 | viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 72 | sampleStateStateFlow.collect { 73 | binding.textViewSampleName.text = it.message 74 | } 75 | } 76 | } 77 | } 78 | // endregion 79 | 80 | private fun setClickListeners() = with(binding) { 81 | buttonEvent.setOnClickListener { 82 | sampleViewModel.onUIEvent(UIEvent.TRIGGER_ONE_SHOW_EVENT) 83 | } 84 | buttonState.setOnClickListener { 85 | sampleViewModel.onUIEvent(UIEvent.UPDATE_STATE) 86 | } 87 | } 88 | 89 | companion object { 90 | 91 | const val TAG = "SampleFragment" 92 | 93 | fun newInstance(): SampleFragment = SampleFragment() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/SampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.playground.streams.presentation.data.SampleEvent 7 | import com.playground.streams.presentation.data.SampleState 8 | import com.playground.streams.presentation.data.UIEvent 9 | import com.playground.streams.util.SingleLiveEvent 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.channels.Channel 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.flow.asStateFlow 14 | import kotlinx.coroutines.flow.consumeAsFlow 15 | import kotlinx.coroutines.flow.receiveAsFlow 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class SampleViewModel @Inject constructor() : ViewModel() { 20 | 21 | // region LiveData 22 | private val _sampleEventLiveData = SingleLiveEvent() 23 | val sampleEventLiveData: LiveData = _sampleEventLiveData 24 | 25 | private val _sampleStateLiveData = MutableLiveData() 26 | val sampleStateLiveData: LiveData = _sampleStateLiveData 27 | // endregion 28 | 29 | // region Flow 30 | private val _sampleEventChannel = Channel(capacity = Channel.BUFFERED) 31 | val sampleEventFlow = _sampleEventChannel.receiveAsFlow() 32 | 33 | private val _sampleStateStateFlow = MutableStateFlow(SampleState("")) 34 | val sampleStateStateFlow = _sampleStateStateFlow.asStateFlow() 35 | // endregion 36 | 37 | fun onUIEvent(uiEvent: UIEvent) { 38 | when(uiEvent) { 39 | UIEvent.TRIGGER_ONE_SHOW_EVENT -> { 40 | _sampleEventLiveData.value = SampleEvent("Foo") 41 | _sampleEventChannel.trySend(SampleEvent("Foo")) 42 | } 43 | UIEvent.UPDATE_STATE -> { 44 | _sampleStateLiveData.value = SampleState("Foo") 45 | _sampleStateStateFlow.value = SampleState("Foo") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/data/SampleEvent.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation.data 2 | 3 | data class SampleEvent(val message: String) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/data/SampleState.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation.data 2 | 3 | data class SampleState(val message: String) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/presentation/data/UIEvent.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.presentation.data 2 | 3 | enum class UIEvent { 4 | TRIGGER_ONE_SHOW_EVENT, 5 | UPDATE_STATE 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/sharedevent/SharedEventActivity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.sharedevent 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.playground.streams.R 7 | import com.playground.streams.databinding.ActivitySharedEventBinding 8 | import dagger.hilt.android.AndroidEntryPoint 9 | 10 | @AndroidEntryPoint 11 | class SharedEventActivity : AppCompatActivity() { 12 | 13 | private var _binding: ActivitySharedEventBinding? = null 14 | private val binding get() = _binding!! 15 | 16 | private val sharedViewModel: SharedViewModel by viewModels() 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | _binding = ActivitySharedEventBinding.inflate(layoutInflater) 21 | setContentView(binding.root) 22 | 23 | renderView() 24 | if (savedInstanceState == null) { 25 | addFragments() 26 | } 27 | } 28 | 29 | private fun renderView() = with(binding) { 30 | buttonTriggerSharedEvent.setOnClickListener { 31 | sharedViewModel.triggerSharedEvent() 32 | } 33 | buttonTriggerSingleEvent.setOnClickListener { 34 | sharedViewModel.triggerSingleEvent() 35 | } 36 | buttonTriggerLiveEvent.setOnClickListener { 37 | sharedViewModel.triggerLiveEvent() 38 | } 39 | } 40 | 41 | private fun addFragments() { 42 | addFragment(R.id.fragmentContainer1, 0) 43 | addFragment(R.id.fragmentContainer2, 1) 44 | addFragment(R.id.fragmentContainer3, 2) 45 | } 46 | 47 | private fun addFragment(containerId: Int, index: Int) { 48 | supportFragmentManager.beginTransaction() 49 | .add( 50 | containerId, 51 | SharedEventFragment.newInstance(index), 52 | SharedEventFragment.TAG 53 | ) 54 | .commit() 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/sharedevent/SharedEventFragment.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.sharedevent 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.os.bundleOf 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.activityViewModels 11 | import androidx.lifecycle.Lifecycle 12 | import androidx.lifecycle.lifecycleScope 13 | import androidx.lifecycle.repeatOnLifecycle 14 | import com.playground.streams.databinding.FragmentSharedEventBinding 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.flow.collect 17 | import kotlinx.coroutines.launch 18 | 19 | @AndroidEntryPoint 20 | class SharedEventFragment : Fragment() { 21 | 22 | private var _binding: FragmentSharedEventBinding? = null 23 | private val binding get() = _binding!! 24 | 25 | private val sharedViewModel: SharedViewModel by activityViewModels() 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View { 32 | _binding = FragmentSharedEventBinding.inflate(inflater, container, false) 33 | return binding.root 34 | } 35 | 36 | override fun onDestroyView() { 37 | super.onDestroyView() 38 | _binding = null 39 | } 40 | 41 | @SuppressLint("SetTextI18n") 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | 45 | binding.textViewSampleName.text = "Fragment ${requireArguments().getInt(INDEX_KEY)}" 46 | 47 | viewLifecycleOwner.lifecycleScope.launch { 48 | viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { 49 | sharedViewModel.sharedEvent.collect { 50 | binding.textViewSampleName.text = it 51 | } 52 | } 53 | } 54 | 55 | sharedViewModel.singleEvent.observe(viewLifecycleOwner) { 56 | binding.textViewSampleName.text = it 57 | } 58 | 59 | sharedViewModel.liveEvent.observe(viewLifecycleOwner) { 60 | binding.textViewSampleName.text = it 61 | } 62 | } 63 | 64 | companion object { 65 | 66 | private const val INDEX_KEY = "index" 67 | const val TAG = "SharedEventFragment" 68 | 69 | fun newInstance(index: Int) = SharedEventFragment().apply { 70 | arguments = bundleOf(INDEX_KEY to index) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/sharedevent/SharedViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.sharedevent 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.playground.streams.util.SingleLiveEvent 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableSharedFlow 10 | import kotlinx.coroutines.flow.SharedFlow 11 | import kotlinx.coroutines.flow.asSharedFlow 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class SharedViewModel @Inject constructor() : ViewModel() { 17 | 18 | // region sharedFlow 19 | private val _sharedEvent: MutableSharedFlow = MutableSharedFlow() 20 | val sharedEvent: SharedFlow = _sharedEvent.asSharedFlow() 21 | 22 | fun triggerSharedEvent() { 23 | viewModelScope.launch { 24 | _sharedEvent.emit("Hello") 25 | } 26 | } 27 | // endregion 28 | 29 | // region singleLiveEvent 30 | private val _singleEvent: SingleLiveEvent = SingleLiveEvent() 31 | val singleEvent: LiveData = _singleEvent 32 | 33 | fun triggerSingleEvent() { 34 | _singleEvent.value = "Hello" 35 | } 36 | // endregion 37 | 38 | // region mutableLiveData 39 | private val _liveEvent: MutableLiveData = MutableLiveData() 40 | val liveEvent: LiveData = _liveEvent 41 | 42 | fun triggerLiveEvent(){ 43 | _liveEvent.value = "Hello" 44 | } 45 | // endregion 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/BehaviorType.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | sealed class BehaviorType(open val value: Int) { 4 | data class Publish(override val value: Int) : BehaviorType(value) 5 | data class Behavior(override val value: Int) : BehaviorType(value) 6 | data class Replay(override val value: Int) : BehaviorType(value) 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/DataSourceWithSharedFlow.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | import com.playground.streams.util.Timer 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.MutableSharedFlow 6 | import kotlinx.coroutines.flow.onEach 7 | import javax.inject.Inject 8 | 9 | class DataSourceWithSharedFlow @Inject constructor(private val timer: Timer) { 10 | 11 | val publishFlow = MutableSharedFlow(extraBufferCapacity = 1) 12 | val behaviorFlow = MutableSharedFlow(replay = 1) 13 | val replayFlow = MutableSharedFlow(replay = Int.MAX_VALUE) 14 | 15 | fun startEmission(): Flow { 16 | return timer() 17 | .onEach { number -> 18 | publishFlow.tryEmit(number) 19 | behaviorFlow.tryEmit(number) 20 | replayFlow.tryEmit(number) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/DataSourceWithSubjects.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | import com.playground.streams.util.Timer 4 | import io.reactivex.rxjava3.subjects.BehaviorSubject 5 | import io.reactivex.rxjava3.subjects.PublishSubject 6 | import io.reactivex.rxjava3.subjects.ReplaySubject 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.onEach 9 | import javax.inject.Inject 10 | 11 | class DataSourceWithSubjects @Inject constructor(private val timer: Timer) { 12 | 13 | val publishSubject: PublishSubject = PublishSubject.create() 14 | val behaviorSubject: BehaviorSubject = BehaviorSubject.create() 15 | val replaySubject: ReplaySubject = ReplaySubject.create() 16 | 17 | fun startEmission(): Flow { 18 | return timer() 19 | .onEach { number -> 20 | publishSubject.onNext(number) 21 | behaviorSubject.onNext(number) 22 | replaySubject.onNext(number) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/SharedFlowViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import kotlinx.coroutines.channels.Channel 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.collect 9 | import kotlinx.coroutines.flow.launchIn 10 | import kotlinx.coroutines.flow.onEach 11 | import kotlinx.coroutines.flow.receiveAsFlow 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class SharedFlowViewModel @Inject constructor( 17 | private val dataSourceWithSharedFlow: DataSourceWithSharedFlow 18 | ) : ViewModel() { 19 | 20 | private val _time = Channel(Channel.BUFFERED) 21 | val time: Flow = _time.receiveAsFlow() 22 | 23 | private val _behaviorType = Channel(Channel.BUFFERED) 24 | val behaviorType: Flow = _behaviorType.receiveAsFlow() 25 | 26 | init { 27 | dataSourceWithSharedFlow.startEmission() 28 | .onEach { number -> _time.send(number) } 29 | .launchIn(viewModelScope) 30 | } 31 | 32 | fun startObserving() = viewModelScope.launch { 33 | launch { 34 | dataSourceWithSharedFlow.publishFlow.collect { 35 | _behaviorType.send(BehaviorType.Publish(it)) 36 | } 37 | } 38 | launch { 39 | dataSourceWithSharedFlow.behaviorFlow.collect { 40 | _behaviorType.send(BehaviorType.Behavior(it)) 41 | } 42 | } 43 | launch { 44 | dataSourceWithSharedFlow.replayFlow.collect { 45 | _behaviorType.send(BehaviorType.Replay(it)) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/SubjectsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.widget.TextView 6 | import androidx.activity.viewModels 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.lifecycleScope 10 | import androidx.lifecycle.repeatOnLifecycle 11 | import com.playground.streams.databinding.ActivitySubjectsBinding 12 | import dagger.hilt.android.AndroidEntryPoint 13 | import kotlinx.coroutines.flow.collect 14 | import kotlinx.coroutines.launch 15 | 16 | @AndroidEntryPoint 17 | class SubjectsActivity : AppCompatActivity() { 18 | 19 | private lateinit var binding: ActivitySubjectsBinding 20 | 21 | private val subjectsViewModel: SubjectsViewModel by viewModels() 22 | private val sharedFlowViewModel: SharedFlowViewModel by viewModels() 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | binding = ActivitySubjectsBinding.inflate(layoutInflater) 27 | setContentView(binding.root) 28 | 29 | observeTimers() 30 | observeSubjects() 31 | observeFlows() 32 | renderView() 33 | } 34 | 35 | private fun observeTimers() { 36 | lifecycleScope.launch { 37 | repeatOnLifecycle(Lifecycle.State.STARTED) { 38 | subjectsViewModel.time.collect { 39 | setText(binding.textViewTimelineSubjects, it) 40 | } 41 | } 42 | } 43 | 44 | lifecycleScope.launch { 45 | repeatOnLifecycle(Lifecycle.State.STARTED) { 46 | sharedFlowViewModel.time.collect { 47 | setText(binding.textViewTimelineSharedFlow, it) 48 | } 49 | } 50 | } 51 | } 52 | 53 | private fun observeSubjects() { 54 | lifecycleScope.launch { 55 | repeatOnLifecycle(Lifecycle.State.STARTED) { 56 | subjectsViewModel.behaviorType.collect { 57 | when (it) { 58 | is BehaviorType.Publish -> setText(binding.textView1, it.value) 59 | is BehaviorType.Behavior -> setText(binding.textView2, it.value) 60 | is BehaviorType.Replay -> setText(binding.textView3, it.value) 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | private fun observeFlows() { 68 | lifecycleScope.launch { 69 | repeatOnLifecycle(Lifecycle.State.STARTED) { 70 | sharedFlowViewModel.behaviorType.collect { 71 | when (it) { 72 | is BehaviorType.Publish -> setText(binding.textView4, it.value) 73 | is BehaviorType.Behavior -> setText(binding.textView5, it.value) 74 | is BehaviorType.Replay -> setText(binding.textView6, it.value) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | private fun renderView() { 82 | binding.startObservingSubjects.setOnClickListener { 83 | subjectsViewModel.startObserving() 84 | } 85 | binding.startObservingSharedFlows.setOnClickListener { 86 | sharedFlowViewModel.startObserving() 87 | } 88 | } 89 | 90 | @SuppressLint("SetTextI18n") 91 | private fun setText(textView: TextView, num: Int) { 92 | val oldText = textView.text 93 | textView.text = "$oldText $num " 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/subjects/SubjectsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.subjects 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers 7 | import kotlinx.coroutines.channels.Channel 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.launchIn 10 | import kotlinx.coroutines.flow.onEach 11 | import kotlinx.coroutines.flow.receiveAsFlow 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class SubjectsViewModel @Inject constructor( 16 | private val dataSourceWithSubjects: DataSourceWithSubjects 17 | ) : ViewModel() { 18 | 19 | private val _time = Channel(Channel.BUFFERED) 20 | val time: Flow = _time.receiveAsFlow() 21 | 22 | private val _behaviorType = Channel(Channel.BUFFERED) 23 | val behaviorType: Flow = _behaviorType.receiveAsFlow() 24 | 25 | init { 26 | dataSourceWithSubjects.startEmission() 27 | .onEach { number -> _time.send(number) } 28 | .launchIn(viewModelScope) 29 | } 30 | 31 | fun startObserving() { 32 | dataSourceWithSubjects.publishSubject 33 | .observeOn(AndroidSchedulers.mainThread()) 34 | .subscribe { _behaviorType.trySend(BehaviorType.Publish(it)) } 35 | 36 | dataSourceWithSubjects.behaviorSubject 37 | .observeOn(AndroidSchedulers.mainThread()) 38 | .subscribe { _behaviorType.trySend(BehaviorType.Behavior(it)) } 39 | 40 | dataSourceWithSubjects.replaySubject 41 | .observeOn(AndroidSchedulers.mainThread()) 42 | .subscribe { _behaviorType.trySend(BehaviorType.Replay(it)) } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/util/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.util 2 | 3 | import androidx.annotation.MainThread 4 | import androidx.annotation.Nullable 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.Observer 8 | import java.util.concurrent.atomic.AtomicBoolean 9 | 10 | class SingleLiveEvent : MutableLiveData() { 11 | 12 | private val mPending = AtomicBoolean(false) 13 | 14 | @MainThread 15 | override fun observe(owner: LifecycleOwner, observer: Observer) { 16 | 17 | // Observe the internal MutableLiveData 18 | super.observe(owner, Observer { t -> 19 | if (mPending.compareAndSet(true, false)) { 20 | observer.onChanged(t) 21 | } 22 | }) 23 | } 24 | 25 | @MainThread 26 | override fun setValue(@Nullable t: T?) { 27 | mPending.set(true) 28 | super.setValue(t) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/playground/streams/util/Timer.kt: -------------------------------------------------------------------------------- 1 | package com.playground.streams.util 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.flow 6 | import javax.inject.Inject 7 | 8 | class Timer @Inject constructor() { 9 | 10 | operator fun invoke(): Flow = flow { 11 | repeat(10) { 12 | emit(it) 13 | delay(1000) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /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_consume_resource.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |