├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── motondon │ │ └── rxjavademoapp │ │ └── ApplicationTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── motondon │ │ │ └── rxjavademoapp │ │ │ └── view │ │ │ ├── adapter │ │ │ ├── CategoryItemsAdapter.kt │ │ │ ├── MainActivityAdapter.kt │ │ │ └── SimpleStringAdapter.kt │ │ │ ├── backpressure │ │ │ ├── BackpressureBasicExampleActivity.kt │ │ │ ├── BackpressureManualRequestExampleActivity.kt │ │ │ ├── BackpressureReactivePullExampleActivity.kt │ │ │ └── BackpressureSpecificOperatorsExampleActivity.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ └── HotObservablesBaseActivity.kt │ │ │ ├── generalexamples │ │ │ ├── BroadcastSystemStatusExampleActivity.kt │ │ │ ├── DrawingExampleActivity.kt │ │ │ ├── ServerPollingAfterDataProcessingExampleActivity.kt │ │ │ ├── ServerPollingExampleActivity.kt │ │ │ └── TypingIndicatorExampleActivity.kt │ │ │ ├── hotobservables │ │ │ ├── HotObservableCacheExampleActivity.kt │ │ │ ├── HotObservableConnectExampleActivity.kt │ │ │ ├── HotObservableRefCountExampleActivity.kt │ │ │ └── HotObservableReplayExampleActivity.kt │ │ │ ├── main │ │ │ ├── CategoryItem.kt │ │ │ ├── ExampleByCategoryActivity.kt │ │ │ └── MainActivity.kt │ │ │ ├── operators │ │ │ ├── AggregateOperatorsExampleActivity.kt │ │ │ ├── CombiningObservablesExampleActivity.kt │ │ │ ├── ConcatMapAndFlatMapExampleActivity.kt │ │ │ ├── ConditionalOperatorsExampleActivity.kt │ │ │ ├── ErrorHandlingExampleActivity.kt │ │ │ ├── FilteringExampleActivity.kt │ │ │ ├── JoinExampleActivity.kt │ │ │ ├── MoreFilteringOperatorsExampleActivity.kt │ │ │ ├── RetryExampleActivity.kt │ │ │ ├── TimeoutExampleActivity.kt │ │ │ └── TransformingOperatorsExampleActivity.kt │ │ │ └── parallelization │ │ │ └── ParallelizationExampleActivity.kt │ └── res │ │ ├── drawable-hdpi │ │ └── ic_keyboard_arrow_down_black_24dp.png │ │ ├── drawable-mdpi │ │ └── ic_keyboard_arrow_down_black_24dp.png │ │ ├── drawable-xhdpi │ │ └── ic_keyboard_arrow_down_black_24dp.png │ │ ├── drawable-xxhdpi │ │ └── ic_keyboard_arrow_down_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ └── ic_keyboard_arrow_down_black_24dp.png │ │ ├── drawable │ │ ├── background_spinner.xml │ │ ├── battery_charging_ac.png │ │ ├── battery_charging_usb.png │ │ ├── battery_status_almost_full.png │ │ ├── battery_status_empty.png │ │ ├── battery_status_full.png │ │ ├── battery_status_half.png │ │ ├── battery_status_low.png │ │ ├── battery_status_very_low.png │ │ ├── brush.png │ │ ├── eraser.png │ │ ├── ic_movie_placeholder.png │ │ ├── ic_no_cover.png │ │ ├── ic_toggle_off.png │ │ ├── ic_toggle_on.png │ │ ├── list_item_background.xml │ │ ├── new_file.png │ │ ├── toggle_selector.xml │ │ ├── toggle_state_off.xml │ │ └── toggle_state_on.xml │ │ ├── layout │ │ ├── activity_backpressure_basic_example.xml │ │ ├── activity_backpressure_manual_request_example.xml │ │ ├── activity_backpressure_reactive_pull_example.xml │ │ ├── activity_backpressure_specific_operators_example.xml │ │ ├── activity_example_by_category.xml │ │ ├── activity_general_broadcast_system_status_example.xml │ │ ├── activity_general_drawing_example.xml │ │ ├── activity_general_server_polling_after_data_processing_example.xml │ │ ├── activity_general_server_polling_example.xml │ │ ├── activity_general_typing_indicator.xml │ │ ├── activity_hot_observable_cache_example.xml │ │ ├── activity_hot_observable_connect_example.xml │ │ ├── activity_hot_observable_refcount_example.xml │ │ ├── activity_hot_observable_replay_example.xml │ │ ├── activity_main.xml │ │ ├── activity_operators_aggregate_operators_example.xml │ │ ├── activity_operators_combining_observables_example.xml │ │ ├── activity_operators_concatmap_flatmap_example.xml │ │ ├── activity_operators_conditional_operators_example.xml │ │ ├── activity_operators_error_handling_example.xml │ │ ├── activity_operators_filtering_example.xml │ │ ├── activity_operators_join_example.xml │ │ ├── activity_operators_more_filtering_example.xml │ │ ├── activity_operators_retry_and_retrywhen_example.xml │ │ ├── activity_operators_timeout_example.xml │ │ ├── activity_operators_transforming_operators_example.xml │ │ ├── activity_parallelization_example.xml │ │ ├── item_cardview_options.xml │ │ ├── item_category_list.xml │ │ └── item_list_single.xml │ │ ├── menu │ │ ├── menu_main.xml │ │ └── menu_reactive_ui_drawing_example.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── motondon │ └── rxjavademoapp │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── projectFilesBackup └── .idea │ └── workspace.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxJava for Android (100+ Examples Pack) - 🔹Now in Kotlin!!! 🔹 2 | This repository is intended to provide in a single place (i.e. a single Android app) over than 100 examples of RxJava for Android which you can use as a reference when dealing with RxJava. 3 | 4 | # **_UPDATE: This project was entirely convert to Kotlin._** 5 | 6 | ![](https://user-images.githubusercontent.com/4574670/38424192-a2a65350-3986-11e8-980b-a359764b2e9d.png) 7 | ![](https://user-images.githubusercontent.com/4574670/38424190-a2611f60-3986-11e8-9f19-8993a905d2bf.png) 8 | 9 | ![](https://user-images.githubusercontent.com/4574670/38424191-a2839194-3986-11e8-9779-5df3a3416113.png) 10 | ![](https://user-images.githubusercontent.com/4574670/38424193-a2ca1628-3986-11e8-9610-5b4daa6b30bb.png) 11 | 12 | In order to make it easy to find the examples throughout the app, we divided them in different categories. They are: 13 | - **Operators Examples:** Basically it contains examples of operators used to filtering, combining, transforming data, etc as well as some error handling examples. 14 | - **Backpressure Examples:** Contain different approaches to deal with backpressure. 15 | - **Hot Observable Examples:** Show how to use Hot Observables by using operators such as cache, replay, etc. 16 | - **Parallelization:** Demonstrate how to do real parallelization using RxJava operators and schedulers. 17 | - **General Examples:** Contain some examples that do not fit in any of the previous categories. 18 | 19 | Some examples were already covered into details in one of the articles of our RxJava series (you can find the links below), while some other were based on external articles, so you can go directly to those articles and see the the authors' explanations. 20 | 21 | It's worth mentioning whenever an example was based on an external article, we added a reference to the sources, so all the credits go to the authors. 22 | 23 | You can find details about this repository [here](http://androidahead.com/2018/04/06/rxjava-for-android-100-examples-pack/). 24 | 25 | Here is a list of all covered examples on the demo app and the articles on which they were based: 26 | 27 | - **Operators Examples** 28 | - [Filtering Operators](http://androidahead.com/2017/09/11/rxjava-operators-part-1-filtering-operators/) (12 examples) 29 | - [More about Filtering Operators](http://androidahead.com/2017/09/25/rxjava-operators-part-2-more-about-filtering-operators/) (5 examples) 30 | - [Combining Observables](http://androidahead.com/2017/10/17/rxjava-operators-part-3-combining-observables/) (9 examples) 31 | - [Conditional Operators](http://androidahead.com/2017/10/31/rxjava-operators-part-4-conditional-operators/) (10 examples) 32 | - [Transforming Operators](http://androidahead.com/2017/11/16/rxjava-operators-part-5-transforming-operators/) (9 examples) 33 | - [Timeout Operator](http://androidahead.com/2017/12/05/rxjava-operators-part-6-timeout-operator/) (7 examples) 34 | - [Mathematical and Aggregate Operators](http://androidahead.com/2017/12/21/rxjava-operators-part-7-mathematical-and-aggregate-operators/) (7 examples) 35 | - [Join Operator](http://androidahead.com/2018/01/09/rxjava-operators-part-8-join-operator/) (1 examples) 36 | - [Retry and RetryWhen](http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/) (6 examples) 37 | - [ConcatMap and FlatMap](http://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/) (2 examples) 38 | - [Error Handling](http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/) (5 examples) 39 | 40 | - **Back Pressure Examples** 41 | - [Backpressure](http://androidahead.com/2018/01/30/rxjava-operators-part-9-backpressure/) (8 examples) 42 | 43 | - **Hot Observable Examples** 44 | - [Hot Observables](http://androidahead.com/2018/02/17/rxjava-operators-part-10-hot-observables/)` (6 examples) 45 | 46 | - **Parallelization Examples** 47 | - [Achiving Parallelization](http://tomstechnicalblog.blogspot.com.br/2015/11/rxjava-achieving-parallelization.html) and [Maximizing parallelization](http://tomstechnicalblog.blogspot.com.br/2016/02/rxjava-maximizing-parallelization.html) (8 examples) 48 | 49 | - **General Examples** 50 | - [Simulate Server Polling](https://medium.com/@v.danylo/server-polling-and-retrying-failed-operations-with-retrofit-and-rxjava-8bcc7e641a5a) (4 examples) - This example is useful when we need to poll a server periodically until it finishes a certain task. 51 | - [Server Polling After Local Data Processing](https://github.com/ReactiveX/RxJava/issues/448) (3 examples) - This example is useful when we need to poll a server and process some data locally, and only after finish local processing, poll the server again. 52 | - [Typing Indicator](http://androidahead.com/2017/04/03/typing-indicator-using-rxjava/) (1 example) 53 | - [Drawing Example](http://choruscode.blogspot.com.br/2014/07/rxjava-for-ui-events-on-android-example.html) (1 example) - This example shows how to react to mouse events as well as listen for SeekBar and menu item click events by using RxBindings library. 54 | - Broadcast System Status (1 example) - This example shows how to listen for broadcast system messages by using RxBroadcast library. 55 | 56 | # License 57 | This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE) file for details 58 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 26 7 | 8 | defaultConfig { 9 | applicationId "com.motondon.rxjavademoapp" 10 | minSdkVersion 21 11 | targetSdkVersion 26 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | testImplementation'junit:junit:4.12' 28 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 29 | implementation 'com.android.support:appcompat-v7:26.1.0' 30 | implementation 'com.android.support:design:26.1.0' 31 | implementation 'com.android.support:recyclerview-v7:26.1.0' 32 | implementation 'com.android.support:cardview-v7:26.1.0' 33 | 34 | // Coroutines dependencies 35 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.2" 36 | 37 | // RxJava dependency 38 | implementation 'io.reactivex:rxjava:1.1.9' 39 | 40 | // RxAndroid dependency 41 | implementation 'io.reactivex:rxandroid:1.2.1' 42 | 43 | // RxBindings dependency 44 | implementation 'com.jakewharton.rxbinding:rxbinding:0.4.0' 45 | 46 | // RxBroadcast dependency 47 | implementation 'com.cantrowitz:rxbroadcast:1.0.0' 48 | 49 | // Mathematical operators dependency 50 | implementation 'io.reactivex:rxjava-math:1.0.0' 51 | } 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\android_dev_tools\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/motondon/rxjavademoapp/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp 2 | 3 | import android.app.Application 4 | import android.test.ApplicationTestCase 5 | 6 | /** 7 | * [Testing Fundamentals](http://d.android.com/tools/testing/testing_android.html) 8 | */ 9 | class ApplicationTest : ApplicationTestCase(Application::class.java) -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 32 | 33 | 37 | 38 | 42 | 43 | 47 | 48 | 52 | 53 | 57 | 58 | 62 | 63 | 67 | 68 | 72 | 73 | 77 | 78 | 82 | 83 | 87 | 88 | 92 | 93 | 97 | 98 | 102 | 103 | 107 | 108 | 112 | 113 | 117 | 118 | 122 | 123 | 127 | 128 | 129 | 133 | 134 | 138 | 139 | 143 | 144 | 148 | 149 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/adapter/CategoryItemsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.adapter 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | 10 | import com.motondon.rxjavademoapp.R 11 | import com.motondon.rxjavademoapp.view.main.CategoryItem 12 | import kotlinx.android.synthetic.main.item_category_list.view.* 13 | 14 | class CategoryItemsAdapter(private val mContext: Context, private val mCategoryItemList: List) : RecyclerView.Adapter() { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 17 | val v = LayoutInflater.from(mContext).inflate(R.layout.item_category_list, parent, false) 18 | return ViewHolder(v) 19 | } 20 | 21 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 22 | holder.bind(position) 23 | } 24 | 25 | override fun getItemCount(): Int { 26 | return mCategoryItemList.size 27 | } 28 | 29 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 30 | 31 | fun bind(position: Int) { 32 | itemView.mCategoryItemName.text = mCategoryItemList[position].mExampleName 33 | itemView.mCategoryItemDetails.text = mCategoryItemList[position].mExampleDetails 34 | 35 | // When user clicks on an example, extract a class that implements that example and call it by using an intent. 36 | itemView.setOnClickListener { _ -> 37 | val exampleIntent = Intent(mContext, mCategoryItemList[position].mExampleActivityClass) 38 | exampleIntent.putExtra("TITLE", mCategoryItemList[position].mExampleName) 39 | 40 | mContext.startActivity(exampleIntent) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/adapter/MainActivityAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.adapter 2 | 3 | import android.content.Intent 4 | import android.support.v4.util.Pair 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | 10 | import com.motondon.rxjavademoapp.R 11 | import com.motondon.rxjavademoapp.view.main.ExampleByCategoryActivity 12 | import com.motondon.rxjavademoapp.view.main.CategoryItem 13 | import com.motondon.rxjavademoapp.view.main.MainActivity 14 | 15 | import java.util.ArrayList 16 | 17 | import kotlinx.android.synthetic.main.item_cardview_options.view.* 18 | 19 | class MainActivityAdapter(private val mMainActivity: MainActivity, mExampleCategoriesList: List, Pair>>) : RecyclerView.Adapter() { 20 | 21 | private var mExampleCategoriesList = ArrayList, Pair>>() 22 | 23 | init { 24 | this.mExampleCategoriesList = mExampleCategoriesList as ArrayList, Pair>> 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 28 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_cardview_options, parent, false) 29 | return ViewHolder(view) 30 | } 31 | 32 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 33 | holder.bind(position) 34 | } 35 | 36 | override fun getItemCount(): Int { 37 | return mExampleCategoriesList.size 38 | } 39 | 40 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 41 | fun bind(position: Int) { 42 | // Not too much to say here. We just get the item and set a listener for it. When user clicks on it, he will be redirected 43 | // to the activity that implements the selected example. 44 | val categoriesDetailsList = mExampleCategoriesList[position] 45 | 46 | val categoryItemList = categoriesDetailsList.first 47 | val item = categoriesDetailsList.second 48 | 49 | itemView.mCategoryName.text = item.first 50 | itemView.mCategoryDetails.text = item.second 51 | 52 | itemView.setOnClickListener { _ -> 53 | val exampleByCategoryIntent = Intent(mMainActivity.applicationContext, ExampleByCategoryActivity::class.java) 54 | 55 | exampleByCategoryIntent.putExtra("CATEGORY_ITEMS", categoryItemList as ArrayList<*>) 56 | exampleByCategoryIntent.putExtra("TITLE", itemView.mCategoryName.text.toString()) 57 | 58 | mMainActivity.startActivity(exampleByCategoryIntent) 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/adapter/SimpleStringAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.adapter 2 | 3 | import android.content.Context 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Toast 9 | 10 | import com.motondon.rxjavademoapp.R 11 | 12 | import java.util.ArrayList 13 | import kotlinx.android.synthetic.main.item_list_single.view.* 14 | 15 | /** 16 | * Adapter used to map a String to a text view. 17 | */ 18 | class SimpleStringAdapter(private val mContext: Context) : RecyclerView.Adapter() { 19 | private val mStrings = ArrayList() 20 | 21 | fun setStrings(newStrings: List) { 22 | mStrings.clear() 23 | mStrings.addAll(newStrings) 24 | notifyDataSetChanged() 25 | } 26 | 27 | fun addString(newString: String) { 28 | mStrings.add(newString) 29 | notifyDataSetChanged() 30 | } 31 | 32 | fun clear() { 33 | mStrings.clear() 34 | notifyDataSetChanged() 35 | } 36 | 37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 38 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list_single, parent, false) 39 | return ViewHolder(view) 40 | } 41 | 42 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 43 | holder.bind(position) 44 | } 45 | 46 | override fun getItemCount(): Int { 47 | return mStrings.size 48 | } 49 | 50 | inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 51 | fun bind(position: Int) { 52 | itemView.mItemName.text = mStrings[position] 53 | itemView.setOnClickListener { Toast.makeText(mContext, mStrings[position], Toast.LENGTH_SHORT).show() } 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/backpressure/BackpressureBasicExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.backpressure 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | 6 | import android.widget.Toast 7 | 8 | import com.motondon.rxjavademoapp.R 9 | import com.motondon.rxjavademoapp.view.base.BaseActivity 10 | 11 | import java.util.concurrent.TimeUnit 12 | 13 | import kotlinx.android.synthetic.main.activity_backpressure_basic_example.* 14 | import rx.Observable 15 | 16 | import rx.Subscriber 17 | import rx.Subscription 18 | import rx.android.schedulers.AndroidSchedulers 19 | 20 | /** 21 | * 22 | * This activity shows two examples: one that emits items faster than they can be consumed. Quickly it will finish with a MissingBackpressureException. 23 | * The second one adds the throttleLast() operator to the chain in order to try to alliviate emitted items downstream to try to avoid that exception. 24 | * 25 | */ 26 | class BackpressureBasicExampleActivity : BaseActivity() { 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | setContentView(R.layout.activity_backpressure_basic_example) 31 | 32 | btnMissingBackpressureExceptionTest.setOnClickListener { onMissingBackPressureExceptionButtonClick() } 33 | btnThrottleOperatorTest.setOnClickListener { onThrottleOperatorButtonClick() } 34 | 35 | supportActionBar?.title = intent.getStringExtra("TITLE") 36 | } 37 | 38 | private fun resetData() { 39 | tvEmittedNumbers.text = "" 40 | tvResult.text = "" 41 | } 42 | 43 | private fun onMissingBackPressureExceptionButtonClick() { 44 | if (isUnsubscribed()) { 45 | Log.v(TAG, "onMissingBackPressureExceptionButtonClick()") 46 | resetData() 47 | mSubscription = starMissingBackpressureExceptionTest() 48 | } else { 49 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_LONG).show() 50 | } 51 | } 52 | 53 | private fun onThrottleOperatorButtonClick() { 54 | if (isUnsubscribed()) { 55 | Log.v(TAG, "onThrottleOperatorButtonClick()") 56 | resetData() 57 | tvEmittedNumbers.text = "Check the logs to see all emitted items" 58 | startThrottleOperatorTest() 59 | } else { 60 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_LONG).show() 61 | } 62 | } 63 | 64 | /** 65 | * This example will throw a MissingBackPressureException since the Observable is emitting items much faster 66 | * than they can be consumed. 67 | * 68 | * @return 69 | */ 70 | private fun starMissingBackpressureExceptionTest(): Subscription { 71 | 72 | // Emit one item per millisecond... 73 | return Observable 74 | .interval(1, TimeUnit.MILLISECONDS) 75 | 76 | .doOnNext { number -> 77 | Log.v(TAG, "oOnNext() - Emitted number: $number") 78 | val w = AndroidSchedulers.mainThread().createWorker() 79 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 80 | } 81 | 82 | .compose(applySchedulers()) 83 | 84 | // Sleep for 100ms for each emitted item. This will make we receive a BackpressureMissingException quickly. 85 | .subscribe(resultSubscriber(100)) 86 | } 87 | 88 | /** 89 | * By using throttleLast operator (which is pretty much similar to collect) we reduce the chances to get a 90 | * MissingBackpressureException, since it will only emit the last item emitted in a certain period of time. 91 | * 92 | * For this example we are using throttleLast intervalDuration to 100ms, which might be enough to let observer to 93 | * process all emitted items. If we change intervalDuration to a small value (e.g.: 10ms), although we will still 94 | * use throttleLast operator, it will not be enough to prevent buffer's capacity to be full. Try both values to see 95 | * that in action. 96 | * 97 | * @return 98 | */ 99 | private fun startThrottleOperatorTest(): Subscription { 100 | 101 | return Observable 102 | .interval(1, TimeUnit.MILLISECONDS) 103 | 104 | .doOnNext { number -> 105 | Log.v(TAG, "doOnNext() - Emitted number: $number") 106 | 107 | // For this example we will not print emitted items on the GUI, since it would freeze it. Check the logs to see all emitted items 108 | // final Scheduler.Worker w = AndroidSchedulers.mainThread().createWorker(); 109 | // w.schedule(() -> tvEmittedNumbers.setText(tvEmittedNumbers.getText() + " " + number)); 110 | } 111 | 112 | // Using throttleLast intervalDuration equals to 100ms, we will probably not end up in an exception, since our subscriber will be able to 113 | // process all emitted items accordingly. If we change it to 10ms, it will quickly throw a MissingBackpressureException. 114 | .throttleLast(100, TimeUnit.MILLISECONDS) 115 | 116 | // Just for log purpose 117 | .compose(showDebugMessages("throttleLast(100)")) 118 | 119 | // Just adding some boundaries here 120 | .take(20) 121 | 122 | .compose(applySchedulers()) 123 | 124 | // Finally subscribe it. 125 | .subscribe(resultSubscriber(100)) 126 | } 127 | 128 | private fun resultSubscriber(timeToSleep: Int): Subscriber { 129 | return object : Subscriber() { 130 | 131 | override fun onCompleted() { 132 | Log.v(TAG, "subscribe.onCompleted") 133 | val w2 = AndroidSchedulers.mainThread().createWorker() 134 | w2.schedule { tvResult.text = "${tvResult.text} - onCompleted" } 135 | } 136 | 137 | override fun onError(e: Throwable) { 138 | Log.v(TAG, "subscribe.doOnError: $e") 139 | val w2 = AndroidSchedulers.mainThread().createWorker() 140 | w2.schedule { tvResult.text = "${tvResult.text} - doOnError$e" } 141 | } 142 | 143 | override fun onNext(number: Long?) { 144 | Log.v(TAG, "subscribe.onNext $number") 145 | val w2 = AndroidSchedulers.mainThread().createWorker() 146 | w2.schedule { tvResult.text = "${tvResult.text} $number" } 147 | 148 | try { 149 | Thread.sleep(timeToSleep.toLong()) 150 | } catch (e: InterruptedException) { 151 | Log.v(TAG, "subscribe.onNext. We got a InterruptedException!") 152 | } 153 | } 154 | } 155 | } 156 | 157 | companion object { 158 | private val TAG = BackpressureBasicExampleActivity::class.java.simpleName 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/backpressure/BackpressureReactivePullExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.backpressure 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.widget.Toast 6 | 7 | import com.motondon.rxjavademoapp.R 8 | import com.motondon.rxjavademoapp.view.base.BaseActivity 9 | 10 | import java.util.concurrent.TimeUnit 11 | 12 | import kotlinx.android.synthetic.main.activity_backpressure_reactive_pull_example.* 13 | import rx.Observable 14 | 15 | import rx.Subscriber 16 | import rx.Subscription 17 | import rx.android.schedulers.AndroidSchedulers 18 | 19 | 20 | class BackpressureReactivePullExampleActivity : BaseActivity() { 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_backpressure_reactive_pull_example) 25 | 26 | btnSubscriberWithRequestMethodCallTest.setOnClickListener { onSubscriberWithRequestMethodCallButtonClick() } 27 | 28 | supportActionBar?.title = intent.getStringExtra("TITLE") 29 | } 30 | 31 | private fun resetData() { 32 | tvEmittedNumbers.text = "" 33 | tvResult.text = "" 34 | } 35 | 36 | private fun onSubscriberWithRequestMethodCallButtonClick() { 37 | if (isUnsubscribed()) { 38 | Log.v(TAG, "onSubscriberWithRequestMethodCallButtonClick()") 39 | resetData() 40 | startSubscriberWithRequestMethodCallTest() 41 | } else { 42 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_LONG).show() 43 | } 44 | } 45 | 46 | private fun emitNumbers(numberOfItemsToEmit: Int, timeToSleep: Int): Observable { 47 | Log.v(TAG, "emitNumbers()") 48 | 49 | return Observable 50 | .range(0, numberOfItemsToEmit) 51 | .doOnNext { number -> 52 | try { 53 | Log.v(TAG, "emitNumbers() - Emitting number: $number") 54 | Thread.sleep(timeToSleep.toLong()) 55 | 56 | val w = AndroidSchedulers.mainThread().createWorker() 57 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 58 | 59 | } catch (e: InterruptedException) { 60 | Log.v(TAG, "Got an InterruptedException!") 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * This example demonstrates how to use Subscriber::request() method. Since our subscriber was initialized by calling 67 | * Subscriber::request(1), that means one item will be requested at a time. Later, in Subscriber's onNext, only after 68 | * it processes an item, another one will be requested. 69 | * 70 | * @return 71 | */ 72 | private fun startSubscriberWithRequestMethodCallTest(): Subscription { 73 | 74 | return Observable 75 | .timer(0, TimeUnit.SECONDS) 76 | .flatMap { _ -> emitNumbers(20, 10) } 77 | 78 | // Since our subscriber will request for item, this is one way on how we can log it. 79 | .doOnRequest { number -> Log.v(TAG, "Requested $number") } 80 | 81 | // Subscribe our subscriber which will request for items. 82 | .subscribe(resultSubscriber()) 83 | } 84 | 85 | private fun resultSubscriber(): Subscriber { 86 | return object : Subscriber() { 87 | 88 | override fun onStart() { 89 | request(1) 90 | } 91 | 92 | override fun onCompleted() { 93 | Log.v(TAG, "subscribe.onCompleted") 94 | val w2 = AndroidSchedulers.mainThread().createWorker() 95 | w2.schedule { tvResult.text = "${tvResult.text} - onCompleted" } 96 | } 97 | 98 | override fun onError(e: Throwable) { 99 | Log.v(TAG, "subscribe.doOnError: $e") 100 | val w2 = AndroidSchedulers.mainThread().createWorker() 101 | w2.schedule { tvResult.text = "${tvResult.text} - doOnError$e" } 102 | } 103 | 104 | override fun onNext(number: Int?) { 105 | Log.v(TAG, "subscribe.onNext $number") 106 | 107 | try { 108 | // Sleep for a while. We could do whatever we want here prior to request a new item. This is totally 109 | // up to us 110 | Thread.sleep(500) 111 | 112 | // Now, after "processing" the item, request observable to emit another one 113 | request(1) 114 | } catch (e: InterruptedException) { 115 | Log.v(TAG, "subscribe.onNext. We got a InterruptedException!") 116 | } 117 | 118 | val w2 = AndroidSchedulers.mainThread().createWorker() 119 | w2.schedule { tvResult.text = tvResult.text.toString() + " " + number } 120 | } 121 | } 122 | } 123 | 124 | companion object { 125 | private val TAG = BackpressureReactivePullExampleActivity::class.java.simpleName 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.base 2 | 3 | import android.content.Intent 4 | import android.support.v4.app.NavUtils 5 | import android.support.v7.app.AppCompatActivity 6 | import android.util.Log 7 | import android.view.MenuItem 8 | import android.widget.TextView 9 | 10 | import rx.Observable 11 | import rx.Subscriber 12 | import rx.Subscription 13 | import rx.android.schedulers.AndroidSchedulers 14 | import rx.schedulers.Schedulers 15 | 16 | open class BaseActivity : AppCompatActivity() { 17 | 18 | protected var mSubscription: Subscription? = null 19 | 20 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 21 | when (item.itemId) { 22 | // Respond to the action bar's Up/Home button 23 | android.R.id.home -> { 24 | val intent = NavUtils.getParentActivityIntent(this) 25 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 26 | NavUtils.navigateUpTo(this, intent) 27 | return true 28 | } 29 | } 30 | return super.onOptionsItemSelected(item) 31 | } 32 | 33 | protected fun resultSubscriber(view: TextView): Subscriber { 34 | return object : Subscriber() { 35 | 36 | override fun onCompleted() { 37 | Log.v(TAG, "subscribe.onCompleted") 38 | view.text = "${view.text} - onCompleted" 39 | } 40 | 41 | override fun onError(e: Throwable) { 42 | Log.v(TAG, "subscribe.doOnError: ${e.message}") 43 | view.text = "${view.text} - doOnError" 44 | } 45 | 46 | override fun onNext(number: T) { 47 | Log.v(TAG, "subscribe.onNext") 48 | view.text = "${view.text} $number" 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Print log messages for some side effects methods (or utility methods). 55 | * 56 | * Note that, when calling this method by using Java 7, we must inform explicitly the type T. Otherwise it will assume the Object type, which 57 | * is not compatible,and we will get an error. But when using Java 8, this is no longer needed, since the compiler will infer the correct type. 58 | * This is called a type witness. 59 | * 60 | * From the docs: 61 | * 62 | * "The Java SE 7 compiler [...] requires a value for the type argument T so it starts with the value Object. Consequently, the invocation 63 | * of Collections.emptyList returns a value of type List, which is incompatible with the method..." 64 | * 65 | * "This is no longer necessary in Java SE 8. The notion of what is a target type has been expanded to include method arguments [...]" 66 | * * http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html 67 | * 68 | * @param operatorName 69 | * @param 70 | * @return 71 | */ 72 | protected fun showDebugMessages(operatorName: String): Observable.Transformer { 73 | 74 | return Observable.Transformer { observable -> 75 | observable 76 | .doOnSubscribe { Log.v(TAG, "$operatorName.doOnSubscribe") } 77 | .doOnUnsubscribe { Log.v(TAG, "$operatorName.doOnUnsubscribe") } 78 | .doOnNext { doOnNext -> Log.v(TAG, "$operatorName.doOnNext. Data: $doOnNext") } 79 | .doOnCompleted { Log.v(TAG, "$operatorName.doOnCompleted") } 80 | .doOnTerminate { Log.v(TAG, "$operatorName.doOnTerminate") } 81 | .doOnError { throwable -> Log.v(TAG, "$operatorName.doOnError: ${throwable.message}") } 82 | } 83 | } 84 | 85 | /** 86 | * Code downloaded from: http://blog.danlew.net/2015/03/02/dont-break-the-chain/ 87 | * 88 | * @param 89 | * @return 90 | */ 91 | protected fun applySchedulers(): Observable.Transformer { 92 | return Observable.Transformer { observable -> 93 | observable.subscribeOn(Schedulers.computation()) 94 | .observeOn(AndroidSchedulers.mainThread()) 95 | } 96 | } 97 | 98 | protected fun isUnsubscribed(): Boolean { 99 | return mSubscription?.let { 100 | it.isUnsubscribed 101 | } ?: true 102 | } 103 | 104 | companion object { 105 | private val TAG = BaseActivity::class.java.simpleName 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/base/HotObservablesBaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.base 2 | 3 | import android.util.Log 4 | import android.widget.Toast 5 | 6 | import rx.Subscription 7 | import rx.observables.ConnectableObservable 8 | 9 | open class HotObservablesBaseActivity : BaseActivity() { 10 | 11 | protected var connectable: ConnectableObservable? = null 12 | protected var firstSubscription: Subscription? = null 13 | protected var secondSubscription: Subscription? = null 14 | 15 | /** 16 | * When unsubscribing a subscriber, that means it will stop receiving emitted items. 17 | * 18 | */ 19 | protected fun unsubscribeFirst(showMessage: Boolean) { 20 | Log.v(TAG, "unsubscribeFirst()") 21 | 22 | firstSubscription?.let { 23 | if (!it.isUnsubscribed) { 24 | Log.v(TAG, "unsubscribeFirst() - Calling unsubscribe...") 25 | it.unsubscribe() 26 | } else { 27 | if (showMessage) { 28 | Toast.makeText(applicationContext, "Subscriber not started", Toast.LENGTH_SHORT).show() 29 | } 30 | } 31 | } ?: run { 32 | if (showMessage) { 33 | Toast.makeText(applicationContext, "Subscriber not started", Toast.LENGTH_SHORT).show() 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * When unsubscribing a subscriber, that means it will stop receiving emitted items. 40 | * 41 | */ 42 | protected fun unsubscribeSecond(showMessage: Boolean) { 43 | Log.v(TAG, "unsubscribeSecond()") 44 | 45 | secondSubscription?.let { 46 | if (!it.isUnsubscribed) { 47 | Log.v(TAG, "unsubscribeSecond() - Calling unsubscribe...") 48 | it.unsubscribe() 49 | } else { 50 | if (showMessage) { 51 | Toast.makeText(applicationContext, "Subscriber not started", Toast.LENGTH_SHORT).show() 52 | } 53 | } 54 | } ?: run { 55 | if (showMessage) { 56 | Toast.makeText(applicationContext, "Subscriber not started", Toast.LENGTH_SHORT).show() 57 | } 58 | } 59 | } 60 | 61 | protected fun isFirstSubscriptionUnsubscribed(): Boolean { 62 | return firstSubscription?.let { 63 | it.isUnsubscribed 64 | } ?: true 65 | } 66 | 67 | protected fun isSecondSubscriptionUnsubscribed(): Boolean { 68 | return secondSubscription?.let { 69 | it.isUnsubscribed 70 | } ?: true 71 | } 72 | 73 | companion object { 74 | private val TAG = HotObservablesBaseActivity::class.java.simpleName 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/hotobservables/HotObservableCacheExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.hotobservables 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.ActionBar 5 | import android.util.Log 6 | import android.widget.TextView 7 | import android.widget.Toast 8 | 9 | import com.motondon.rxjavademoapp.R 10 | import com.motondon.rxjavademoapp.view.base.HotObservablesBaseActivity 11 | 12 | import java.util.concurrent.TimeUnit 13 | 14 | import kotlinx.android.synthetic.main.activity_hot_observable_cache_example.* 15 | import rx.Observable 16 | import rx.Scheduler 17 | import rx.Subscription 18 | import rx.android.schedulers.AndroidSchedulers 19 | 20 | /** 21 | * This example demonstrates how to use ConnectableObservable::cache() operator. 22 | * 23 | * It ensures that all observers see the same sequence of emitted items, even if they subscribe after the Observable has begun emitting items. 24 | * 25 | */ 26 | class HotObservableCacheExampleActivity : HotObservablesBaseActivity() { 27 | 28 | private var observable: Observable? = null 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_hot_observable_cache_example) 33 | 34 | btnCache.setOnClickListener{ onCacheButtonClick() } 35 | btnSubscribeFirst.setOnClickListener{ onSubscribeFirstButtonClick() } 36 | btnSubscribeSecond.setOnClickListener{ onSubscribeSecondButtonClick() } 37 | btnUnsubscribeFirst.setOnClickListener{ onUnsubscribeFirstButtonClick() } 38 | btnUnsubscribeSecond.setOnClickListener{ onUnsubscribeSecondButtonClick() } 39 | 40 | supportActionBar?.title = intent.getStringExtra("TITLE") 41 | } 42 | 43 | private fun resetData() { 44 | tvEmittedNumbers.text = "" 45 | tvResultFirstSubscription.text = "" 46 | tvResultSecondSubscription.text = "" 47 | } 48 | 49 | private fun onCacheButtonClick() { 50 | if (isUnsubscribed()) { 51 | Log.v(TAG, "onCacheButtonClick()") 52 | resetData() 53 | cache() 54 | } else { 55 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 56 | } 57 | } 58 | 59 | private fun onSubscribeFirstButtonClick() { 60 | 61 | if (observable == null) { 62 | Log.v(TAG, "onSubscribeFirstButtonClick() - Cannot start a subscriber. You must first call cache().") 63 | 64 | Toast.makeText(applicationContext, "You must first call cache()", Toast.LENGTH_SHORT).show() 65 | return 66 | } 67 | 68 | if (isFirstSubscriptionUnsubscribed()) { 69 | Log.v(TAG, "onSubscribeFirstButtonClick()") 70 | firstSubscription = subscribeFirst() 71 | } else { 72 | Toast.makeText(applicationContext, "First subscriber already started", Toast.LENGTH_SHORT).show() 73 | } 74 | } 75 | 76 | private fun onSubscribeSecondButtonClick() { 77 | 78 | if (observable == null) { 79 | Log.v(TAG, "onSubscribeFirstButtonClick() - Cannot start a subscriber. You must first call cache().") 80 | 81 | Toast.makeText(applicationContext, "You must first call cache()", Toast.LENGTH_SHORT).show() 82 | return 83 | } 84 | if (isSecondSubscriptionUnsubscribed()) { 85 | Log.v(TAG, "onSubscribeSecondButtonClick()") 86 | secondSubscription = subscribeSecond() 87 | } else { 88 | Toast.makeText(applicationContext, "Second subscriber already started", Toast.LENGTH_SHORT).show() 89 | } 90 | } 91 | 92 | private fun onUnsubscribeFirstButtonClick() { 93 | Log.v(TAG, "onUnsubscribeFirstButtonClick()") 94 | unsubscribeFirst(true) 95 | } 96 | 97 | private fun onUnsubscribeSecondButtonClick() { 98 | Log.v(TAG, "onUnsubscribeSecondButtonClick()") 99 | unsubscribeSecond(true) 100 | } 101 | 102 | /** 103 | * When calling cache, that will NOT make observable to start emit items. It will only start emitting items when a first 104 | * subscriber subscribes to it. Then, it will receive all cached items. 105 | * 106 | * Just using take(30) in order to prevent it to emit forever. 107 | * 108 | * @return 109 | */ 110 | private fun cache() { 111 | Log.v(TAG, "cache()") 112 | 113 | // cache returns Observable that is connected as long as there are subscribers to it. 114 | observable = Observable 115 | .interval(750, TimeUnit.MILLISECONDS) 116 | .doOnNext { number -> 117 | val w = AndroidSchedulers.mainThread().createWorker() 118 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 119 | } 120 | 121 | // Prevent our observable to emit forever 122 | .take(30) 123 | 124 | .cache() 125 | } 126 | 127 | /** 128 | * If this is the first mSubscription, it will make observable to start emitting items. But, if there is 129 | * already another mSubscription, it means that observable has already started emitting items and collecting them. 130 | * So, after we subscribe to it, it will first receive all collected items. 131 | * 132 | * @return 133 | */ 134 | private fun subscribeFirst(): Subscription? { 135 | Log.v(TAG, "subscribeFirst()") 136 | 137 | return observable?.let { 138 | it 139 | .compose(applySchedulers()) 140 | .subscribe(this@HotObservableCacheExampleActivity.resultSubscriber(tvResultFirstSubscription)) 141 | } ?: null 142 | } 143 | 144 | /** 145 | * If this is the first mSubscription, it will make observable to start emitting items. But, if there is 146 | * already another mSubscription, it means that observable has already started emitting items and collecting them. 147 | * So, after we subscribe to it, it will first receive all collected items. 148 | * 149 | * @return 150 | */ 151 | private fun subscribeSecond(): Subscription? { 152 | Log.v(TAG, "subscribeSecond()") 153 | 154 | return observable?.let { 155 | it 156 | .compose(applySchedulers()) 157 | .subscribe(resultSubscriber(tvResultSecondSubscription)) 158 | } ?: null 159 | } 160 | 161 | companion object { 162 | private val TAG = HotObservableCacheExampleActivity::class.java.simpleName 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/hotobservables/HotObservableConnectExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.hotobservables 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.ActionBar 5 | import android.util.Log 6 | import android.widget.TextView 7 | import android.widget.Toast 8 | 9 | import com.motondon.rxjavademoapp.R 10 | import com.motondon.rxjavademoapp.view.base.HotObservablesBaseActivity 11 | 12 | import java.util.concurrent.TimeUnit 13 | 14 | import kotlinx.android.synthetic.main.activity_hot_observable_connect_example.* 15 | import rx.Observable 16 | import rx.Scheduler 17 | import rx.Subscription 18 | import rx.android.schedulers.AndroidSchedulers 19 | 20 | /** 21 | * This example demonstrates how to use ConnectableObservable::connect() operator. 22 | * 23 | */ 24 | class HotObservableConnectExampleActivity : HotObservablesBaseActivity() { 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_hot_observable_connect_example) 29 | 30 | btnConnect.setOnClickListener{ onConnectButtonClick() } 31 | btnSubscribeFirst.setOnClickListener{ onSubscribeFirstButtonClick() } 32 | btnSubscribeSecond.setOnClickListener{ onSubscribeSecondButtonClick() } 33 | btnUnsubscribeFirst.setOnClickListener{ onUnsubscribeFirstButtonClick() } 34 | btnUnsubscribeSecond.setOnClickListener{ onUnsubscribeSecondButtonClick() } 35 | btnDisconnect.setOnClickListener{ onDisconnectButtonClick() } 36 | 37 | supportActionBar?.title = intent.getStringExtra("TITLE") 38 | 39 | // We will create the ConnectableObservable in the onCreate() method, so it will be available for the two 40 | // subscribers to subscribe to it, even before call ConnectionObservable::connect(). 41 | connectable = Observable 42 | .interval(500, TimeUnit.MILLISECONDS) 43 | .doOnNext { number -> 44 | val w = AndroidSchedulers.mainThread().createWorker() 45 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 46 | } 47 | 48 | // This will convert the Observable to a ConnectableObservable 49 | .publish() 50 | } 51 | 52 | private fun resetData() { 53 | tvEmittedNumbers.text = "" 54 | tvResultFirstSubscription.text = "" 55 | tvResultSecondSubscription.text = "" 56 | } 57 | 58 | private fun onConnectButtonClick() { 59 | if (isUnsubscribed()) { 60 | Log.v(TAG, "onConnectButtonClick()") 61 | resetData() 62 | mSubscription = connect() 63 | } else { 64 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 65 | } 66 | } 67 | 68 | private fun onSubscribeFirstButtonClick() { 69 | if (isFirstSubscriptionUnsubscribed()) { 70 | Log.v(TAG, "onSubscribeFirstButtonClick()") 71 | firstSubscription = subscribeFirst() 72 | } else { 73 | Toast.makeText(applicationContext, "First subscriber already started", Toast.LENGTH_SHORT).show() 74 | } 75 | } 76 | 77 | private fun onSubscribeSecondButtonClick() { 78 | if (isSecondSubscriptionUnsubscribed()) { 79 | Log.v(TAG, "onSubscribeSecondButtonClick()") 80 | secondSubscription = subscribeSecond() 81 | } else { 82 | Toast.makeText(applicationContext, "Second subscriber already started", Toast.LENGTH_SHORT).show() 83 | } 84 | } 85 | 86 | private fun onUnsubscribeFirstButtonClick() { 87 | Log.v(TAG, "onUnsubscribeFirstButtonClick()") 88 | unsubscribeFirst(true) 89 | } 90 | 91 | private fun onUnsubscribeSecondButtonClick() { 92 | Log.v(TAG, "onUnsubscribeSecondButtonClick()") 93 | unsubscribeSecond(true) 94 | } 95 | 96 | private fun onDisconnectButtonClick() { 97 | Log.v(TAG, "onDisconnectButtonClick()") 98 | 99 | mSubscription?.let { 100 | if (!it.isUnsubscribed) { 101 | unsubscribeFirst(false) 102 | unsubscribeSecond(false) 103 | disconnect() 104 | } 105 | } ?: Toast.makeText(applicationContext, "Observable not connected", Toast.LENGTH_SHORT).show() 106 | } 107 | 108 | /** 109 | * From the docs: 110 | * 111 | * "A Connectable Observable resembles an ordinary Observable, except that it does not begin emitting items when 112 | * it is subscribed to, but only when its connect() method is called." 113 | * 114 | * This means that when user clicks on "connect" button, if there is already any subscriber subscribed to it, it will start 115 | * receiving emitted items, otherwise, emitted items will be discarded. 116 | * 117 | * After connecting to the observable, new subscribers will only receive new emitted items. 118 | * 119 | * @return 120 | */ 121 | private fun connect(): Subscription? { 122 | Log.v(TAG, "connect()") 123 | 124 | // This will instruct the connectable observable to begin emitting items. If there is any subscriber subscribed to it, 125 | // it will start receiving items. 126 | return connectable?.connect() 127 | } 128 | 129 | /** 130 | * If observable is already connected, when this button is pressed, this subscriber will start receiving items. If there is no 131 | * connection yet, nothing will happen (until a mSubscription) 132 | * 133 | * @return 134 | */ 135 | private fun subscribeFirst(): Subscription? { 136 | Log.v(TAG, "subscribeFirst()") 137 | 138 | return connectable?.let { 139 | it 140 | .compose(applySchedulers()) 141 | .subscribe(resultSubscriber(tvResultFirstSubscription)) 142 | } ?: null 143 | } 144 | 145 | /** 146 | * If observable is already connected, when this button is pressed, this subscriber will start receiving items. If there is no 147 | * connection yet, nothing will happen (until a mSubscription) 148 | * 149 | * @return 150 | */ 151 | private fun subscribeSecond(): Subscription? { 152 | Log.v(TAG, "subscribeSecond()") 153 | 154 | return connectable?.let { 155 | it 156 | .compose(applySchedulers()) 157 | .subscribe(resultSubscriber(tvResultSecondSubscription)) 158 | } ?: null 159 | } 160 | 161 | /** 162 | * By unsubscribing the mSubscription returned by the connect() method, all subscriptions will stop receiving items. 163 | * 164 | */ 165 | private fun disconnect() { 166 | Log.v(TAG, "disconnect()") 167 | mSubscription?.unsubscribe() 168 | mSubscription = null 169 | } 170 | 171 | companion object { 172 | private val TAG = HotObservableConnectExampleActivity::class.java.simpleName 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/hotobservables/HotObservableRefCountExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.hotobservables 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.ActionBar 5 | import android.util.Log 6 | import android.widget.TextView 7 | import android.widget.Toast 8 | 9 | import com.motondon.rxjavademoapp.R 10 | import com.motondon.rxjavademoapp.view.base.HotObservablesBaseActivity 11 | 12 | import java.util.concurrent.TimeUnit 13 | 14 | import kotlinx.android.synthetic.main.activity_hot_observable_refcount_example.* 15 | import rx.Observable 16 | import rx.Scheduler 17 | import rx.Subscription 18 | import rx.android.schedulers.AndroidSchedulers 19 | 20 | /** 21 | * This example demonstrates how to use ConnectableObservable::refCount() operator. 22 | * 23 | * refCount keeps a reference to all the subscribers subscribed to it. When we call refCount, observable does not start emitting items, but only when 24 | * the first subscriber subscribe to it. 25 | * 26 | */ 27 | class HotObservableRefCountExampleActivity : HotObservablesBaseActivity() { 28 | 29 | // This is the Observable returned by the refCount() method call. Subscribers must use it to subscribe. 30 | private var observable: Observable? = null 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(R.layout.activity_hot_observable_refcount_example) 35 | 36 | btnRefCount.setOnClickListener{ onRefCountButtonClick() } 37 | btnSubscribeFirst.setOnClickListener{ onSubscribeFirstButtonClick() } 38 | btnSubscribeSecond.setOnClickListener{ onSubscribeSecondButtonClick() } 39 | btnUnsubscribeFirst.setOnClickListener{ onUnsubscribeFirstButtonClick() } 40 | btnUnsubscribeSecond.setOnClickListener{ onUnsubscribeSecondButtonClick() } 41 | 42 | supportActionBar?.title = intent.getStringExtra("TITLE") 43 | 44 | // Just start our Hot Observable. Note that this will NOT make it to start emitting items 45 | connectable = Observable 46 | .interval(500, TimeUnit.MILLISECONDS) 47 | .doOnNext { number -> 48 | val w = AndroidSchedulers.mainThread().createWorker() 49 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 50 | } 51 | 52 | // This will convert the Observable to a ConnectableObservable 53 | .publish() 54 | } 55 | 56 | private fun resetData() { 57 | tvEmittedNumbers.text = "" 58 | tvResultFirstSubscription.text = "" 59 | tvResultSecondSubscription.text = "" 60 | } 61 | 62 | private fun onRefCountButtonClick() { 63 | 64 | if (isFirstSubscriptionUnsubscribed() && (isSecondSubscriptionUnsubscribed())) { 65 | 66 | Log.v(TAG, "onRefCountButtonClick()") 67 | resetData() 68 | refCount() 69 | } else { 70 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 71 | } 72 | } 73 | 74 | private fun onSubscribeFirstButtonClick() { 75 | if (observable == null) { 76 | Log.v(TAG, "onSubscribeFirstButtonClick() - Cannot start a subscriber. You must first call refCount().") 77 | 78 | // When using refCount, we must subscribe our subscriber's upon the Observable returned by the refCount, and not on the 79 | // ConnectableObservable returned by the publish() (as we do when using connect() operator). 80 | Toast.makeText(applicationContext, "You must first call refCount()", Toast.LENGTH_SHORT).show() 81 | return 82 | } 83 | 84 | if (isFirstSubscriptionUnsubscribed()) { 85 | 86 | // Just clean up GUI in order to make things clear. 87 | secondSubscription?.let { 88 | if (it.isUnsubscribed) resetData() 89 | } 90 | 91 | Log.v(TAG, "onSubscribeFirstButtonClick()") 92 | firstSubscription = subscribeFirst() 93 | 94 | } else { 95 | Toast.makeText(applicationContext, "First subscriber already started", Toast.LENGTH_SHORT).show() 96 | } 97 | } 98 | 99 | private fun onSubscribeSecondButtonClick() { 100 | if (observable == null) { 101 | Log.v(TAG, "onSubscribeSecondButtonClick() - Cannot start a subscriber. You must first call refCount().") 102 | 103 | // When using refCount, we must subscribe our subscriber's upon the Observable returned by the refCount, and not on the 104 | // ConnectableObservable returned by the publish() (as we do when using connect() operator). 105 | Toast.makeText(applicationContext, "You must first call refCount()", Toast.LENGTH_SHORT).show() 106 | return 107 | } 108 | 109 | if (isSecondSubscriptionUnsubscribed()) { 110 | 111 | // Just clean up GUI in order to make things clear. 112 | firstSubscription?.let { 113 | if (it.isUnsubscribed) resetData() 114 | } 115 | 116 | Log.v(TAG, "onSubscribeSecondButtonClick()") 117 | secondSubscription = subscribeSecond() 118 | 119 | } else { 120 | Toast.makeText(applicationContext, "Second subscriber already started", Toast.LENGTH_SHORT).show() 121 | } 122 | } 123 | 124 | /** 125 | * When unsubscribing a subscriber, if there is no more subscriber subscribed to the observable, it will stop emit items. 126 | * 127 | */ 128 | private fun onUnsubscribeFirstButtonClick() { 129 | Log.v(TAG, "onUnsubscribeFirstButtonClick()") 130 | unsubscribeFirst(true) 131 | } 132 | 133 | /** 134 | * When unsubscribing a subscriber, if there is no more subscriber subscribed to the observable, it will stop emit items. 135 | * 136 | */ 137 | private fun onUnsubscribeSecondButtonClick() { 138 | Log.v(TAG, "onUnsubscribeSecondButtonClick()") 139 | unsubscribeSecond(true) 140 | } 141 | 142 | private fun refCount() { 143 | Log.v(TAG, "refCount()") 144 | 145 | // refCount returns Observable that is connected as long as there are subscribers to it. 146 | observable = connectable?.refCount() 147 | } 148 | 149 | /** 150 | * If this is the first subscriber to subscribe to the observable, it will make observable to start emitting items. 151 | * 152 | * @return 153 | */ 154 | private fun subscribeFirst(): Subscription? { 155 | Log.v(TAG, "subscribeFirst()") 156 | 157 | return observable?.let { 158 | it 159 | .compose(applySchedulers()) 160 | .subscribe(resultSubscriber(tvResultFirstSubscription)) 161 | } ?: null 162 | } 163 | 164 | /** 165 | * If this is the first subscriber to subscribe to the observable, it will make observable to start emitting items. 166 | * 167 | * @return 168 | */ 169 | private fun subscribeSecond(): Subscription? { 170 | Log.v(TAG, "subscribeSecond()") 171 | 172 | return observable?.let { 173 | it 174 | .compose(applySchedulers()) 175 | .subscribe(resultSubscriber(tvResultSecondSubscription)) 176 | } ?: null 177 | } 178 | 179 | companion object { 180 | private val TAG = HotObservableRefCountExampleActivity::class.java.simpleName 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/main/CategoryItem.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.main 2 | 3 | import android.app.Activity 4 | 5 | import java.io.Serializable 6 | 7 | /** 8 | * Pair consisting of the name of an example and the activity corresponding to the example. 9 | * 10 | */ 11 | class CategoryItem( 12 | val mExampleActivityClass: Class, val mExampleName: String, val mExampleDetails: String) : Serializable 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/main/ExampleByCategoryActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.main 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.LinearLayoutManager 6 | 7 | import com.motondon.rxjavademoapp.R 8 | import com.motondon.rxjavademoapp.view.adapter.CategoryItemsAdapter 9 | 10 | import java.util.ArrayList 11 | 12 | import kotlinx.android.synthetic.main.activity_example_by_category.* 13 | 14 | class ExampleByCategoryActivity : AppCompatActivity() { 15 | 16 | private var title: String? = null 17 | private var categoryItems = ArrayList() 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_example_by_category) 22 | 23 | if (supportActionBar != null) { 24 | 25 | if (savedInstanceState != null) { 26 | title = savedInstanceState.getString("TITLE") 27 | categoryItems = savedInstanceState.getSerializable("CATEGORY_ITEMS") as ArrayList 28 | } else { 29 | title = intent.extras?.getString("TITLE") 30 | 31 | // Get the examples lists from the intent and set it to the adapter. They will be available to users and will allow them to 32 | // click over an option and be redirected to an activity which implements that example. 33 | categoryItems = intent.extras?.getSerializable("CATEGORY_ITEMS") as ArrayList 34 | } 35 | supportActionBar?.title = title 36 | } 37 | 38 | examplesByCategoryList.setHasFixedSize(true) 39 | examplesByCategoryList.layoutManager = LinearLayoutManager(this) 40 | examplesByCategoryList.adapter = CategoryItemsAdapter(this, categoryItems) 41 | } 42 | 43 | override fun onSaveInstanceState(outState: Bundle) { 44 | super.onSaveInstanceState(outState) 45 | 46 | outState.putString("TITLE", title) 47 | outState.putSerializable("CATEGORY_ITEMS", categoryItems) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/operators/ConcatMapAndFlatMapExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.operators 2 | 3 | import android.content.ContentValues.TAG 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.widget.TextView 7 | import android.widget.Toast 8 | 9 | import com.motondon.rxjavademoapp.R 10 | import com.motondon.rxjavademoapp.R.id.* 11 | import com.motondon.rxjavademoapp.view.base.BaseActivity 12 | 13 | import java.util.concurrent.TimeUnit 14 | 15 | import kotlinx.android.synthetic.main.activity_operators_concatmap_flatmap_example.* 16 | import rx.Observable 17 | import rx.Subscription 18 | import rx.android.schedulers.AndroidSchedulers 19 | 20 | /** 21 | * Examples on this activity are based on the following article: 22 | * 23 | * http://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/ 24 | * 25 | * Please, visit it in order to get more details about it. 26 | * 27 | */ 28 | class ConcatMapAndFlatMapExampleActivity : BaseActivity() { 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_operators_concatmap_flatmap_example) 33 | 34 | btnFlatMapTest.setOnClickListener { onFlatMapTestButtonClick() } 35 | btnConcatMapTest.setOnClickListener { onConcatMapTestButtonClick() } 36 | 37 | supportActionBar?.title = intent.getStringExtra("TITLE") 38 | } 39 | 40 | private fun onFlatMapTestButtonClick() { 41 | 42 | if (isUnsubscribed()) { 43 | Log.v(TAG, "onFlatMapTestButtonClick") 44 | tvOriginalEmittedItems.text = "" 45 | tvFlatMapResult.text = "" 46 | mSubscription = flatMapTest() 47 | } else { 48 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 49 | } 50 | } 51 | 52 | private fun onConcatMapTestButtonClick() { 53 | 54 | if (isUnsubscribed()) { 55 | Log.v(TAG, "onConcatMapTestButtonClick") 56 | tvOriginalEmittedItems.text = "" 57 | tvConcatMapResult.text = "" 58 | mSubscription = concatMapTest() 59 | } else { 60 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 61 | } 62 | } 63 | 64 | private fun emitData(): Observable { 65 | 66 | return Observable 67 | .range(1, 50) 68 | .doOnNext { number -> 69 | try { 70 | Log.v(TAG, "emitData() - Emitting number: $number") 71 | Thread.sleep(20) 72 | 73 | } catch (e: InterruptedException) { 74 | Log.v(TAG, "Got an InterruptedException!") 75 | } 76 | 77 | val w = AndroidSchedulers.mainThread().createWorker() 78 | w.schedule { tvOriginalEmittedItems.text = "${tvOriginalEmittedItems.text} $number" } 79 | } 80 | } 81 | 82 | /** 83 | * This is a very simple test just to demonstrate how flatMap operator works. 84 | * 85 | * Basically (and according to the documentation), FlatMap merges the emissions of these Observables, so that they may interleave. 86 | * 87 | * @return 88 | */ 89 | private fun flatMapTest(): Subscription { 90 | 91 | return emitData() 92 | 93 | .flatMap { data -> 94 | Observable 95 | 96 | .just(data) 97 | 98 | .compose(showDebugMessages("just")) 99 | 100 | // Just adding a delay here, so that we can better see elements being emitted in the GUI 101 | .delay(200, TimeUnit.MILLISECONDS) 102 | 103 | .compose(showDebugMessages("delay")) 104 | } 105 | 106 | // Just for log purpose 107 | .compose(showDebugMessages("flatMap")) 108 | 109 | .map { data -> "$data" } 110 | 111 | // Just for log purpose 112 | .compose(showDebugMessages("map")) 113 | 114 | // Now, apply on which thread observable will run and also on which one it will be observed. 115 | .compose(applySchedulers()) 116 | 117 | // Finally subscribe it. 118 | .subscribe(resultSubscriber(tvFlatMapResult)) 119 | } 120 | 121 | /** 122 | * This example is similar to the flatMapTest, but as the name implies, it uses concatMap operator instead. 123 | * 124 | * Note that concatMap() uses concat operator so that it cares about the order of the emitted elements. 125 | * 126 | * @return 127 | */ 128 | private fun concatMapTest(): Subscription { 129 | 130 | return emitData() 131 | 132 | .concatMap { data -> 133 | // Here we added some log messages allowing us to analyse the concatMap() operator behavior. 134 | // We can see in the log messages that concatMap emits its items as they are received (after applies its function) 135 | Observable 136 | 137 | .just(data) 138 | 139 | .compose(showDebugMessages("just")) 140 | 141 | // Just adding a delay here, so that we can better see elements being emitted in the GUI 142 | .delay(200, TimeUnit.MILLISECONDS) 143 | 144 | .compose(showDebugMessages("delay")) 145 | } 146 | 147 | // Just for log purpose 148 | .compose(showDebugMessages("concatMap")) 149 | 150 | .map { data -> data.toString() } 151 | 152 | // Just for log purpose 153 | .compose(showDebugMessages("map")) 154 | 155 | // Now, apply on which thread observable will run and also on which one it will be observed. 156 | .compose(applySchedulers()) 157 | 158 | // Finally subscribe it. 159 | .subscribe(resultSubscriber(tvConcatMapResult)) 160 | } 161 | 162 | companion object { 163 | private val TAG = ConcatMapAndFlatMapExampleActivity::class.java.simpleName 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/motondon/rxjavademoapp/view/operators/JoinExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.motondon.rxjavademoapp.view.operators 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.ActionBar 5 | import android.util.Log 6 | import android.view.WindowManager 7 | import android.widget.EditText 8 | import android.widget.TextView 9 | import android.widget.Toast 10 | 11 | import com.motondon.rxjavademoapp.R 12 | import com.motondon.rxjavademoapp.R.id.* 13 | import com.motondon.rxjavademoapp.view.base.BaseActivity 14 | 15 | import java.util.Arrays 16 | import java.util.concurrent.TimeUnit 17 | 18 | import kotlinx.android.synthetic.main.activity_operators_join_example.* 19 | import rx.Observable 20 | import rx.Scheduler 21 | import rx.Subscription 22 | import rx.android.schedulers.AndroidSchedulers 23 | import rx.schedulers.Schedulers 24 | 25 | /** 26 | * This activity allows users to test different values for join operator: 27 | * - left Observable emission delay 28 | * - right Observable emission delay 29 | * - left window duration 30 | * - right window duration 31 | * - left Observable number of items to emit 32 | * - right Observable number of items to emit 33 | * 34 | */ 35 | class JoinExampleActivity : BaseActivity() { 36 | 37 | private var leftDelayBetweenEmission: Int = 0 38 | private var rightDelayBetweenEmission: Int = 0 39 | private var leftWindowDuration: Int = 0 40 | private var rightWindowDuration: Int = 0 41 | private var leftNumberOfItemsToEmit: Int = 0 42 | private var rightNumberOfItemsToEmit: Int = 0 43 | 44 | override fun onCreate(savedInstanceState: Bundle?) { 45 | super.onCreate(savedInstanceState) 46 | setContentView(R.layout.activity_operators_join_example) 47 | 48 | btnJoinOperatorTest.setOnClickListener{ onSstartJoinOperatorTestButtonClick() } 49 | btnStopTest.setOnClickListener{ onStopSubscription() } 50 | 51 | supportActionBar?.title = intent.getStringExtra("TITLE") 52 | 53 | // Prevent keyboard to be visible when activity resumes. 54 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) 55 | } 56 | 57 | private fun resetData() { 58 | tvEmittedNumbers.text = "" 59 | tvResult.text = "" 60 | } 61 | 62 | private fun onSstartJoinOperatorTestButtonClick() { 63 | if (isUnsubscribed()) { 64 | Log.v(TAG, "onSstartJoinOperatorTestButtonClick()") 65 | resetData() 66 | readData() 67 | mSubscription = startJoinOperatorTest() 68 | } else { 69 | Toast.makeText(applicationContext, "Test is already running", Toast.LENGTH_SHORT).show() 70 | } 71 | } 72 | 73 | private fun onStopSubscription() { 74 | mSubscription?.unsubscribe() 75 | } 76 | 77 | private fun readData() { 78 | leftDelayBetweenEmission = Integer.parseInt(etLeftObservableDelayBetweenEmission.text.toString()) 79 | if (leftDelayBetweenEmission < 0) { 80 | leftDelayBetweenEmission = 0 81 | } else if (leftDelayBetweenEmission > 5000) { 82 | leftDelayBetweenEmission = 5000 83 | } 84 | Log.v(TAG, "readData() - leftDelayBetweenEmission: $leftDelayBetweenEmission") 85 | 86 | rightDelayBetweenEmission = Integer.parseInt(etRightObservableDelayBetweenEmission.text.toString()) 87 | if (rightDelayBetweenEmission < 0) { 88 | rightDelayBetweenEmission = 0 89 | } else if (rightDelayBetweenEmission > 5000) { 90 | rightDelayBetweenEmission = 5000 91 | } 92 | Log.v(TAG, "readData() - rightDelayBetweenEmission: $rightDelayBetweenEmission") 93 | 94 | if (etLeftWindowDuration.text.toString().isEmpty()) { 95 | leftWindowDuration = NEVER_CLOSE 96 | } else { 97 | leftWindowDuration = Integer.parseInt(etLeftWindowDuration.text.toString()) 98 | if (leftWindowDuration < 0) { 99 | leftWindowDuration = 0 100 | } else if (leftWindowDuration > 5000) { 101 | leftWindowDuration = 5000 102 | } 103 | } 104 | Log.v(TAG, "readData() - leftWindowDuration: $leftWindowDuration") 105 | 106 | if (etRightWindowDuration.text.toString().isEmpty()) { 107 | rightWindowDuration = NEVER_CLOSE 108 | } else { 109 | rightWindowDuration = Integer.parseInt(etRightWindowDuration.text.toString()) 110 | if (rightWindowDuration < 0) { 111 | rightWindowDuration = 0 112 | } else if (rightWindowDuration > 5000) { 113 | rightWindowDuration = 5000 114 | } 115 | } 116 | Log.v(TAG, "readData() - rightWindowDuration: $rightWindowDuration") 117 | 118 | leftNumberOfItemsToEmit = Integer.parseInt(etLeftObservableNumberOfItemsToEmit.text.toString()) 119 | if (leftNumberOfItemsToEmit < 1) { 120 | leftNumberOfItemsToEmit = 1 121 | } else if (leftNumberOfItemsToEmit > 40) { 122 | leftNumberOfItemsToEmit = 40 123 | } 124 | Log.v(TAG, "readData() - leftNumberOfItemsToEmit: $leftNumberOfItemsToEmit") 125 | 126 | rightNumberOfItemsToEmit = Integer.parseInt(etRightObservableNumberOfItemsToEmit.text.toString()) 127 | if (rightNumberOfItemsToEmit < 1) { 128 | rightNumberOfItemsToEmit = 1 129 | } else if (rightNumberOfItemsToEmit > 40) { 130 | rightNumberOfItemsToEmit = 40 131 | } 132 | Log.v(TAG, "readData() - rightNumberOfItemsToEmit: $rightNumberOfItemsToEmit") 133 | } 134 | 135 | private fun emitItems(numberOfItemsToBeEmitted: Int, delayBetweenEmission: Int, caption: String): Observable { 136 | return Observable.interval(delayBetweenEmission.toLong(), TimeUnit.MILLISECONDS) 137 | .map { number -> number.toInt() } 138 | .doOnNext { number -> 139 | Log.v(TAG, "emitItems() - $caption Observable. Emitting number: $number") 140 | val w = AndroidSchedulers.mainThread().createWorker() 141 | w.schedule { tvEmittedNumbers.text = "${tvEmittedNumbers.text} $number" } 142 | 143 | } 144 | .take(numberOfItemsToBeEmitted) 145 | .subscribeOn(Schedulers.newThread()) 146 | } 147 | 148 | 149 | private fun startJoinOperatorTest(): Subscription { 150 | 151 | val left = emitItems(leftNumberOfItemsToEmit, leftDelayBetweenEmission, "left") 152 | val right = emitItems(rightNumberOfItemsToEmit, rightDelayBetweenEmission, "right") 153 | 154 | return left 155 | .join>(right, 156 | { _ -> 157 | if (leftWindowDuration === NEVER_CLOSE) { 158 | return@join Observable.never() 159 | } else { 160 | return@join Observable.timer(leftWindowDuration.toLong(), TimeUnit.MILLISECONDS).compose(showDebugMessages("leftDuration")).subscribeOn(Schedulers.computation()) 161 | } 162 | }, 163 | { _ -> 164 | if (rightWindowDuration === NEVER_CLOSE) { 165 | return@join Observable.never() 166 | } else { 167 | return@join Observable.timer(rightWindowDuration.toLong(), TimeUnit.MILLISECONDS).compose(showDebugMessages("rightDuration")).subscribeOn(Schedulers.computation()) 168 | } 169 | }, 170 | { l, r -> 171 | Log.v(TAG, "join() - Joining left number: $l with right number: $r") 172 | Arrays.asList(l.toInt(), r.toInt()) 173 | } 174 | ) 175 | .compose(applySchedulers()) 176 | .subscribe(resultSubscriber(tvResult)) 177 | } 178 | 179 | companion object { 180 | 181 | private val TAG = JoinExampleActivity::class.java.simpleName 182 | private const val NEVER_CLOSE = -1 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_keyboard_arrow_down_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_down_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_keyboard_arrow_down_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_down_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_down_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_down_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_down_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_down_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_down_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_down_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_charging_ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_charging_ac.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_charging_usb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_charging_usb.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_almost_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_almost_full.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_empty.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_full.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_half.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_low.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_status_very_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/battery_status_very_low.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/eraser.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_movie_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/ic_movie_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_no_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/ic_no_cover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toggle_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/ic_toggle_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toggle_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/ic_toggle_on.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_item_background.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/new_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoaoMotondon/RxJavaDemoApp/2cf453c3778c480226a8391cde00127b091c391a/app/src/main/res/drawable/new_file.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/toggle_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/toggle_state_off.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/toggle_state_on.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_backpressure_basic_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 17 | 21 | 22 | 29 | 30 | 35 | 36 | 42 | 43 | 48 | 49 | 50 | 54 | 55 |