From 859218ad659d68eb8fafa13dea193f590813ae0e Mon Sep 17 00:00:00 2001 From: Theo Date: Thu, 20 Apr 2017 08:36:21 +0300 Subject: [PATCH 01/13] Add missing link to README for timeout example (#102) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 350170fa..0b85e1ee 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ I've also been giving talks about Learning Rx using many of the examples listed 13. [Networking with Volley](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#13-networking-with-volley) 14. [Pagination with Rx (using Subjects)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#14-pagination-with-rx-using-subjects) 15. [Orchestrating Observable: make parallel network calls, then combine the result into a single data point (using flatmap & zip)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#15-orchestrating-observable-make-parallel-network-calls-then-combine-the-result-into-a-single-data-point-using-flatmap--zip) -16. [Simple Timeout example (using timeout)]() +16. [Simple Timeout example (using timeout)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#16-simple-timeout-example-using-timeout) ## Description From 371524294b156b7f2a22c968bbcf998916946248 Mon Sep 17 00:00:00 2001 From: Quang Nguyen Date: Tue, 16 May 2017 00:43:59 +0900 Subject: [PATCH 02/13] Use relative links for inner headers. (#103) --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0b85e1ee..fa0e39b0 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,22 @@ I've also been giving talks about Learning Rx using many of the examples listed ## Examples: -1. [Background work & concurrency (using Schedulers)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#1-background-work--concurrency-using-schedulers) -2. [Accumulate calls (using buffer)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#2-accumulate-calls-using-buffer) -3. [Instant/Auto searching text listeners (using Subjects & debounce)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#3-instantauto-searching-text-listeners-using-subjects--debounce) -4. [Networking with Retrofit & RxJava (using zip, flatmap)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#4-networking-with-retrofit--rxjava-using-zip-flatmap) -5. [Two-way data binding for TextViews (using PublishSubject)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#5-two-way-data-binding-for-textviews-using-publishsubject) -6. [Simple and Advanced polling (using interval and repeatWhen)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#6-simple-and-advanced-polling-using-interval-and-repeatwhen) -7. [Simple and Advanced exponential backoff (using delay and retryWhen)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#7-simple-and-advanced-exponential-backoff-using-delay-and-retrywhen) -8. [Form validation (using combineLatest)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#8-form-validation-using-combinelatest) -9. [Pseudo caching : retrieve data first from a cache, then a network call (using concat, concatEager, merge or publish)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#9-pseudo-caching--retrieve-data-first-from-a-cache-then-a-network-call-using-concat-concateager-merge-or-publish) -10. [Simple timing demos (using timer, interval or delay)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#10-simple-timing-demos-using-timer-interval-and-delay) -11. [RxBus : event bus using RxJava (using RxRelay (never terminating Subjects) and debouncedBuffer)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#11-rxbus--event-bus-using-rxjava-using-rxrelay-never-terminating-subjects-and-debouncedbuffer) -12. [Persist data on Activity rotations (using Subjects and retained Fragments)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#12-persist-data-on-activity-rotations-using-subjects-and-retained-fragments) -13. [Networking with Volley](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#13-networking-with-volley) -14. [Pagination with Rx (using Subjects)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#14-pagination-with-rx-using-subjects) -15. [Orchestrating Observable: make parallel network calls, then combine the result into a single data point (using flatmap & zip)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#15-orchestrating-observable-make-parallel-network-calls-then-combine-the-result-into-a-single-data-point-using-flatmap--zip) -16. [Simple Timeout example (using timeout)](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/README.md#16-simple-timeout-example-using-timeout) +1. [Background work & concurrency (using Schedulers)](#1-background-work--concurrency-using-schedulers) +2. [Accumulate calls (using buffer)](#2-accumulate-calls-using-buffer) +3. [Instant/Auto searching text listeners (using Subjects & debounce)](#3-instantauto-searching-text-listeners-using-subjects--debounce) +4. [Networking with Retrofit & RxJava (using zip, flatmap)](#4-networking-with-retrofit--rxjava-using-zip-flatmap) +5. [Two-way data binding for TextViews (using PublishSubject)](#5-two-way-data-binding-for-textviews-using-publishsubject) +6. [Simple and Advanced polling (using interval and repeatWhen)](#6-simple-and-advanced-polling-using-interval-and-repeatwhen) +7. [Simple and Advanced exponential backoff (using delay and retryWhen)](#7-simple-and-advanced-exponential-backoff-using-delay-and-retrywhen) +8. [Form validation (using combineLatest)](#8-form-validation-using-combinelatest) +9. [Pseudo caching : retrieve data first from a cache, then a network call (using concat, concatEager, merge or publish)](#9-pseudo-caching--retrieve-data-first-from-a-cache-then-a-network-call-using-concat-concateager-merge-or-publish) +10. [Simple timing demos (using timer, interval or delay)](#10-simple-timing-demos-using-timer-interval-and-delay) +11. [RxBus : event bus using RxJava (using RxRelay (never terminating Subjects) and debouncedBuffer)](#11-rxbus--event-bus-using-rxjava-using-rxrelay-never-terminating-subjects-and-debouncedbuffer) +12. [Persist data on Activity rotations (using Subjects and retained Fragments)](#12-persist-data-on-activity-rotations-using-subjects-and-retained-fragments) +13. [Networking with Volley](#13-networking-with-volley) +14. [Pagination with Rx (using Subjects)](#14-pagination-with-rx-using-subjects) +15. [Orchestrating Observable: make parallel network calls, then combine the result into a single data point (using flatmap & zip)](#15-orchestrating-observable-make-parallel-network-calls-then-combine-the-result-into-a-single-data-point-using-flatmap--zip) +16. [Simple Timeout example (using timeout)](#16-simple-timeout-example-using-timeout) ## Description From bab7abd5b446c959d303ae5e2e93a1771ca252b3 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 09:30:30 -0700 Subject: [PATCH 03/13] chore: update dependencies --- app/build.gradle | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 99e29a4a..63cddc68 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ dependencies { android { compileSdkVersion sdkVersion - buildToolsVersion '23.0.3' + buildToolsVersion '25.0.0' defaultConfig { applicationId "com.morihacky.android.rxjava" diff --git a/build.gradle b/build.gradle index 85ab3d95..fc2e126d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0a2dce4..21b8e308 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Oct 01 19:11:24 PDT 2016 +#Wed May 24 09:29:38 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip From 743f24cd0a7ddb37a5806707f9abc8514c3293df Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 09:44:19 -0700 Subject: [PATCH 04/13] chore: add kotlin dependencies --- app/build.gradle | 20 ++++++++++++-------- build.gradle | 10 ++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 63cddc68..4ba57064 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,6 +8,7 @@ buildscript { // can be removed with android-gradle plugin is upgraded to 2.2 // https://twitter.com/JakeWharton/status/760836175586267136 classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" } // Exclude the lombok version that the android plugin depends on. @@ -16,13 +17,7 @@ buildscript { apply plugin: 'com.android.application' apply plugin: 'me.tatarka.retrolambda' - -ext { - okhttpVersion = "3.0.1" - retrofitVersion = "2.0.0" - sdkVersion = 24 - supportLibVersion = "24.2.1" -} +apply plugin: 'kotlin-android' dependencies { compile "com.android.support:support-v13:${supportLibVersion}" @@ -38,6 +33,9 @@ dependencies { compile "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}" compile 'com.mcxiaoke.volley:library:1.0.19' + compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}" + compile "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}" + // ---------------------------------- // Rx dependencies @@ -63,7 +61,7 @@ dependencies { android { compileSdkVersion sdkVersion - buildToolsVersion '25.0.0' + buildToolsVersion buildToolsVrs defaultConfig { applicationId "com.morihacky.android.rxjava" @@ -72,12 +70,18 @@ android { versionCode 2 versionName "1.2" } + buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/build.gradle b/build.gradle index fc2e126d..1fc3bccb 100644 --- a/build.gradle +++ b/build.gradle @@ -20,4 +20,14 @@ allprojects { url "https://jitpack.io" } } + + ext { + okhttpVersion = "3.0.1" + retrofitVersion = "2.0.0" + sdkVersion = 24 + buildToolsVrs = "25.0.0" + supportLibVersion = "25.3.1" + kotlinVersion = "1.1.2-4" + mockitoKotlinVersion = "1.4.0" + } } From 278547d1b061c24cfcb92f81883706dab59329de Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 10:30:38 -0700 Subject: [PATCH 05/13] feat: refactor playground fragment to kotlin --- .../rxjava/fragments/PlaygroundFragment.java | 138 ------------------ .../rxjava/fragments/PlaygroundFragment.kt | 77 ++++++++++ 2 files changed, 77 insertions(+), 138 deletions(-) delete mode 100644 app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.java create mode 100644 app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.java b/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.java deleted file mode 100644 index 21f61fe5..00000000 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.morihacky.android.rxjava.fragments; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.ProgressBar; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; -import com.morihacky.android.rxjava.R; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import timber.log.Timber; - -public class PlaygroundFragment - extends BaseFragment { - - @Bind(R.id.progress_operation_running) ProgressBar _progress; - @Bind(R.id.list_threading_log) ListView _logsList; - - private LogAdapter _adapter; - private int _attempt = 0; - private List _logs; - - @Override - public void onDestroy() { - super.onDestroy(); - ButterKnife.unbind(this); - } - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.fragment_concurrency_schedulers, container, false); - ButterKnife.bind(this, layout); - return layout; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - _setupLogger(); - } - - @OnClick(R.id.btn_start_operation) - public void startOperation() { - - _logs.clear(); - _log("Button Clicked"); - - Observable.fromIterable(Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"))// - .flatMap(s1 -> { - - _log(s1 + "start"); - - if (s1.equalsIgnoreCase("b") && _attempt < 5) { - _attempt++; - return Observable.error(new Throwable("b can't be processed (" + (_attempt - 1) + ")")); - } - - if (s1.equalsIgnoreCase("c") || s1.equalsIgnoreCase("f")) { - return Observable.just(s1); - } - else { - return Observable - .timer(2, TimeUnit.SECONDS) - .map(l -> s1); - } - }) - .retryWhen(source -> source.delay(8, TimeUnit.SECONDS)) - .doOnNext(s -> _log(s + "stop")) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - } - - // ----------------------------------------------------------------------------------- - // Method that help wiring up the example (irrelevant to RxJava) - - private void _doSomeLongOperation_thatBlocksCurrentThread() { - _log("performing long operation"); - - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - Timber.d("Operation was interrupted"); - } - } - - private void _log(String logMsg) { - - if (_isCurrentlyOnMainThread()) { - _logs.add(0, logMsg + " (main thread) "); - _adapter.clear(); - _adapter.addAll(_logs); - } - else { - _logs.add(0, logMsg + " (NOT main thread) "); - - // You can only do below stuff on main thread. - new Handler(Looper.getMainLooper()).post(() -> { - _adapter.clear(); - _adapter.addAll(_logs); - }); - } - } - - private void _setupLogger() { - _logs = new ArrayList<>(); - _adapter = new LogAdapter(getActivity(), new ArrayList<>()); - _logsList.setAdapter(_adapter); - } - - private boolean _isCurrentlyOnMainThread() { - return Looper.myLooper() == Looper.getMainLooper(); - } - - private class LogAdapter - extends ArrayAdapter { - - public LogAdapter(Context context, List logs) { - super(context, R.layout.item_log, R.id.item_log, logs); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt b/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt new file mode 100644 index 00000000..d61ba9c7 --- /dev/null +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt @@ -0,0 +1,77 @@ +package com.morihacky.android.rxjava.fragments + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.ListView +import butterknife.ButterKnife +import com.morihacky.android.rxjava.R + +class PlaygroundFragment : BaseFragment() { + + private var _logsList: ListView? = null + private var _adapter: LogAdapter? = null + + private var _attempt = 0 + private var _logs: MutableList = ArrayList() + + override fun onDestroy() { + super.onDestroy() + ButterKnife.unbind(this) + } + + override fun onCreateView(inflater: LayoutInflater?, + container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater!!.inflate(R.layout.fragment_concurrency_schedulers, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + _logsList = activity.findViewById(R.id.list_threading_log) as ListView + + activity.findViewById(R.id.btn_start_operation).setOnClickListener { _ -> + _log("Button clicked") + } + + _setupLogger() + } + + // ----------------------------------------------------------------------------------- + // Method that help wiring up the example (irrelevant to RxJava) + + private fun _log(logMsg: String) { + + if (_isCurrentlyOnMainThread()) { + _logs.add(0, logMsg + " (main thread) ") + _adapter!!.clear() + _adapter!!.addAll(_logs) + } else { + _logs.add(0, logMsg + " (NOT main thread) ") + + // You can only do below stuff on main thread. + Handler(Looper.getMainLooper()).post { + _adapter!!.clear() + _adapter!!.addAll(_logs) + } + } + } + + private fun _setupLogger() { + _logs = ArrayList() + _adapter = LogAdapter(activity, ArrayList()) + _logsList!!.adapter = _adapter + } + + private fun _isCurrentlyOnMainThread(): Boolean { + return Looper.myLooper() == Looper.getMainLooper() + } + + private inner class LogAdapter(context: Context, logs: List) : ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) +} \ No newline at end of file From 29b081f903858bca6877900df6ae5b446aaa7ca7 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 10:41:09 -0700 Subject: [PATCH 06/13] fix: remove BK unbind from Kotlin example --- .../android/rxjava/fragments/PlaygroundFragment.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt b/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt index d61ba9c7..37f76532 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt @@ -9,7 +9,6 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ListView -import butterknife.ButterKnife import com.morihacky.android.rxjava.R class PlaygroundFragment : BaseFragment() { @@ -20,11 +19,6 @@ class PlaygroundFragment : BaseFragment() { private var _attempt = 0 private var _logs: MutableList = ArrayList() - override fun onDestroy() { - super.onDestroy() - ButterKnife.unbind(this) - } - override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { From 1e88b58d5b7b04d6f1acd0a2216c8a605925b325 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 10:44:43 -0700 Subject: [PATCH 07/13] fix: update LeakCanary + use ContextCompat color --- app/build.gradle | 4 ++-- .../fragments/FormValidationCombineLatestFragment.java | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b81a6017..74a7d975 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,8 +51,8 @@ dependencies { // ---------------------------------- - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' } android { diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java b/app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java index 9bac505f..efc4c2f8 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -72,9 +73,11 @@ private void _combineLatestEvents() { @Override public void onNext(Boolean formValid) { if (formValid) { - _btnValidIndicator.setBackgroundColor(getResources().getColor(R.color.blue)); + _btnValidIndicator.setBackgroundColor( + ContextCompat.getColor(getContext(), R.color.blue)); } else { - _btnValidIndicator.setBackgroundColor(getResources().getColor(R.color.gray)); + _btnValidIndicator.setBackgroundColor( + ContextCompat.getColor(getContext(), R.color.gray)); } } From c8f7d796d7957c2fca391aa6ed7f1f77e95d1ddc Mon Sep 17 00:00:00 2001 From: Kishore Babu Date: Tue, 4 Jul 2017 21:23:05 +0530 Subject: [PATCH 08/13] feat: enable multi-dexing (#109) --- app/build.gradle | 3 +++ app/src/main/java/com/morihacky/android/rxjava/MyApp.java | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 74a7d975..ac1f4021 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,6 +18,7 @@ apply plugin: 'com.f2prateek.javafmt' apply plugin: 'kotlin-android' dependencies { + compile 'com.android.support:multidex:1.0.1' compile "com.android.support:support-v13:${supportLibVersion}" compile "com.android.support:appcompat-v7:${supportLibVersion}" compile "com.android.support:recyclerview-v7:${supportLibVersion}" @@ -65,6 +66,8 @@ android { targetSdkVersion sdkVersion versionCode 2 versionName "1.2" + + multiDexEnabled true } buildTypes { release { diff --git a/app/src/main/java/com/morihacky/android/rxjava/MyApp.java b/app/src/main/java/com/morihacky/android/rxjava/MyApp.java index a3b025ac..0b9f1c9e 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/MyApp.java +++ b/app/src/main/java/com/morihacky/android/rxjava/MyApp.java @@ -1,12 +1,13 @@ package com.morihacky.android.rxjava; -import android.app.Application; +import android.support.multidex.MultiDexApplication; + import com.morihacky.android.rxjava.volley.MyVolley; import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.RefWatcher; import timber.log.Timber; -public class MyApp extends Application { +public class MyApp extends MultiDexApplication { private static MyApp _instance; private RefWatcher _refWatcher; From dfb7b0a0374612c93f830dc783e9a8cba227c248 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Mon, 17 Jul 2017 10:18:12 -0700 Subject: [PATCH 09/13] fix: merge conflict --- README.md | 18 +- app/build.gradle | 4 +- .../com/morihacky/android/rxjava/MyApp.java | 1 - .../rxjava/fragments/MainFragment.java | 10 ++ .../com/morihacky/android/rxjava/ext/RxExt.kt | 11 ++ .../fragments/MulticastPlaygroundFragment.kt | 166 ++++++++++++++++++ .../rxjava/fragments/PlaygroundFragment.kt | 24 ++- .../android/rxjava/fragments/UsingFragment.kt | 94 ++++++++++ app/src/main/res/layout/fragment_buffer.xml | 3 +- app/src/main/res/layout/fragment_main.xml | 14 ++ .../layout/fragment_multicast_playground.xml | 72 ++++++++ app/src/main/res/values/strings.xml | 8 + build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 14 files changed, 409 insertions(+), 22 deletions(-) create mode 100644 app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt create mode 100644 app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt rename app/src/main/{java => kotlin}/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt (74%) create mode 100644 app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt create mode 100644 app/src/main/res/layout/fragment_multicast_playground.xml diff --git a/README.md b/README.md index fa0e39b0..36c7903d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ I've also been giving talks about Learning Rx using many of the examples listed 14. [Pagination with Rx (using Subjects)](#14-pagination-with-rx-using-subjects) 15. [Orchestrating Observable: make parallel network calls, then combine the result into a single data point (using flatmap & zip)](#15-orchestrating-observable-make-parallel-network-calls-then-combine-the-result-into-a-single-data-point-using-flatmap--zip) 16. [Simple Timeout example (using timeout)](#16-simple-timeout-example-using-timeout) +17. [Setup and teardown resources (using `using`)](#17-setup-and-teardown-resources-using-using) +18. [Multicast playground](#18-multicast-playground) ## Description @@ -161,7 +163,7 @@ Cases demonstrated here: 4. run a task constantly every 3s, but after running it 5 times, terminate automatically 5. run a task A, pause for sometime, then execute Task B, then terminate -### 11. RxBus : event bus using RxJava (using RxRelay (never terminating Subjects) and debouncedBuffer) +### 11. RxBus : event bus using RxJava (using RxRelay (never terminating Subjects) and debouncedBuffer) There are accompanying blog posts that do a much better job of explaining the details on this demo: @@ -222,6 +224,20 @@ This is a simple example demonstrating the use of the `.timeout` operator. Butto Notice how we can provide a custom Observable that indicates how to react under a timeout Exception. +### 17. Setup and teardown resources (using `using`) + +The [operator `using`](http://reactivex.io/documentation/operators/using.html) is relatively less known and notoriously hard to Google. It's a beautiful API that helps to setup a (costly) resource, use it and then dispose off in a clean way. + +The nice thing about this operator is that it provides a mechansim to use potentially costly resources in a tightly scoped manner. using -> setup, use and dispose. Think DB connections (like Realm instances), socket connections, thread locks etc. + +### 18. Multicast Playground + +Multicasting in Rx is like a dark art. Not too many folks know how to pull it off without concern. This example condiers two subscribers (in the forms of buttons) and allows you to add/remove subscribers at different points of time and see how the different operators behave under those circumstances. + +The source observale is a timer (`interval`) observable and the reason this was chosen was to intentionally pick a non-terminating observable, so you can test/confirm if your multicast experiment will leak. + +_I also gave a talk about [Multicasting in detail at 360|Andev](https://speakerdeck.com/kaushikgopal/rx-by-example-volume-3-the-multicast-edition). If you have the inclination and time, I highly suggest watching that talk first (specifically the Multicast operator permutation segment) and then messing around with the example here._ + ## Rx 2.x All the examples here have been migrated to use RxJava 2.X. diff --git a/app/build.gradle b/app/build.gradle index ac1f4021..c4e0b461 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ dependencies { compile 'com.github.kaushikgopal:CoreTextUtils:c703fa12b6' compile "com.jakewharton:butterknife:${butterKnifeVersion}" - annotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" + kapt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" compile 'com.jakewharton.timber:timber:4.5.1' compile "com.squareup.retrofit2:retrofit:${retrofitVersion}" compile "com.squareup.retrofit2:converter-gson:${retrofitVersion}" @@ -45,6 +45,7 @@ dependencies { // explicitly depend on RxJava's latest version for bug fixes and new features. compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + compile 'com.jakewharton.rx:replaying-share-kotlin:2.0.0' compile "com.github.akarnokd:rxjava2-extensions:0.16.0" compile 'com.jakewharton.rxrelay2:rxrelay:2.0.0' compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0' @@ -66,7 +67,6 @@ android { targetSdkVersion sdkVersion versionCode 2 versionName "1.2" - multiDexEnabled true } buildTypes { diff --git a/app/src/main/java/com/morihacky/android/rxjava/MyApp.java b/app/src/main/java/com/morihacky/android/rxjava/MyApp.java index 0b9f1c9e..23a90b23 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/MyApp.java +++ b/app/src/main/java/com/morihacky/android/rxjava/MyApp.java @@ -1,7 +1,6 @@ package com.morihacky.android.rxjava; import android.support.multidex.MultiDexApplication; - import com.morihacky.android.rxjava.volley.MyVolley; import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.RefWatcher; diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java b/app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java index c09925be..dd5709e1 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java @@ -116,6 +116,16 @@ void demoNetworkDetector() { clickedOn(new NetworkDetectorFragment()); } + @OnClick(R.id.btn_demo_using) + void demoUsing() { + clickedOn(new UsingFragment()); + } + + @OnClick(R.id.btn_demo_multicastPlayground) + void demoMulticastPlayground() { + clickedOn(new MulticastPlaygroundFragment()); + } + private void clickedOn(@NonNull Fragment fragment) { final String tag = fragment.getClass().toString(); getActivity() diff --git a/app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt b/app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt new file mode 100644 index 00000000..9c99ed69 --- /dev/null +++ b/app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt @@ -0,0 +1,11 @@ +package com.morihacky.android.rxjava.ext + +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +operator fun CompositeDisposable.plus(disposable: Disposable): CompositeDisposable { + add(disposable) + return this +} + + diff --git a/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt new file mode 100644 index 00000000..5ece0e32 --- /dev/null +++ b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt @@ -0,0 +1,166 @@ +package com.morihacky.android.rxjava.fragments + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import butterknife.BindView +import butterknife.ButterKnife +import butterknife.OnClick +import com.jakewharton.rx.replayingShare +import com.morihacky.android.rxjava.R +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import java.util.concurrent.TimeUnit + +class MulticastPlaygroundFragment : BaseFragment() { + + @BindView(R.id.list_threading_log) lateinit var logList: ListView + @BindView(R.id.dropdown) lateinit var pickOperatorDD: Spinner + @BindView(R.id.msg_text) lateinit var messageText: TextView + + private lateinit var sharedObservable: Observable + private lateinit var adapter: LogAdapter + + private var logs: MutableList = ArrayList() + private var disposable1: Disposable? = null + private var disposable2: Disposable? = null + + override fun onCreateView(inflater: LayoutInflater?, + container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val layout = inflater!!.inflate(R.layout.fragment_multicast_playground, container, false) + ButterKnife.bind(this, layout) + + _setupLogger() + _setupDropdown() + + return layout + } + + @OnClick(R.id.btn_1) + fun onBtn1Click() { + + disposable1?.let { + it.dispose() + _log("subscriber 1 disposed") + disposable1 = null + return + } + + disposable1 = + sharedObservable + .doOnSubscribe { _log("subscriber 1 (subscribed)") } + .subscribe({ long -> _log("subscriber 1: onNext $long") }) + + } + + @OnClick(R.id.btn_2) + fun onBtn2Click() { + disposable2?.let { + it.dispose() + _log("subscriber 2 disposed") + disposable2 = null + return + } + + disposable2 = + sharedObservable + .doOnSubscribe { _log("subscriber 2 (subscribed)") } + .subscribe({ long -> _log("subscriber 2: onNext $long") }) + } + + @OnClick(R.id.btn_3) + fun onBtn3Click() { + logs = ArrayList() + adapter.clear() + } + + // ----------------------------------------------------------------------------------- + // Method that help wiring up the example (irrelevant to RxJava) + + private fun _log(logMsg: String) { + + if (_isCurrentlyOnMainThread()) { + logs.add(0, logMsg + " (main thread) ") + adapter.clear() + adapter.addAll(logs) + } else { + logs.add(0, logMsg + " (NOT main thread) ") + + // You can only do below stuff on main thread. + Handler(Looper.getMainLooper()).post { + adapter.clear() + adapter.addAll(logs) + } + } + } + + private fun _setupLogger() { + logs = ArrayList() + adapter = LogAdapter(activity, ArrayList()) + logList.adapter = adapter + } + + private fun _setupDropdown() { + pickOperatorDD.adapter = ArrayAdapter(context, + android.R.layout.simple_spinner_dropdown_item, + arrayOf(".publish().refCount()", + ".publish().autoConnect(2)", + ".replay(1).autoConnect(2)", + ".replay(1).refCount()", + ".replayingShare()")) + + + pickOperatorDD.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + + override fun onItemSelected(p0: AdapterView<*>?, p1: View?, index: Int, p3: Long) { + + val sourceObservable = Observable.interval(0L, 3, TimeUnit.SECONDS) + .doOnSubscribe { _log("observer (subscribed)") } + .doOnDispose { _log("observer (disposed)") } + .doOnTerminate { _log("observer (terminated)") } + + sharedObservable = + when (index) { + 0 -> { + messageText.setText(R.string.msg_demo_multicast_publishRefCount) + sourceObservable.publish().refCount() + } + 1 -> { + messageText.setText(R.string.msg_demo_multicast_publishAutoConnect) + sourceObservable.publish().autoConnect(2) + } + 2 -> { + messageText.setText(R.string.msg_demo_multicast_replayAutoConnect) + sourceObservable.replay(1).autoConnect(2) + } + 3 -> { + messageText.setText(R.string.msg_demo_multicast_replayRefCount) + sourceObservable.replay(1).refCount() + } + 4 -> { + messageText.setText(R.string.msg_demo_multicast_replayingShare) + sourceObservable.replayingShare() + } + else -> throw RuntimeException("got to pick an op yo!") + } + } + + override fun onNothingSelected(p0: AdapterView<*>?) {} + } + } + + private fun _isCurrentlyOnMainThread(): Boolean { + return Looper.myLooper() == Looper.getMainLooper() + } + + private inner class LogAdapter(context: Context, logs: List) : + ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) + +} + diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt similarity index 74% rename from app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt rename to app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt index 37f76532..cf77e04f 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt +++ b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt @@ -16,25 +16,21 @@ class PlaygroundFragment : BaseFragment() { private var _logsList: ListView? = null private var _adapter: LogAdapter? = null - private var _attempt = 0 private var _logs: MutableList = ArrayList() override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater!!.inflate(R.layout.fragment_concurrency_schedulers, container, false) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + val view = inflater?.inflate(R.layout.fragment_concurrency_schedulers, container, false) - _logsList = activity.findViewById(R.id.list_threading_log) as ListView + _logsList = view?.findViewById(R.id.list_threading_log) as ListView + _setupLogger() - activity.findViewById(R.id.btn_start_operation).setOnClickListener { _ -> + view.findViewById(R.id.btn_start_operation).setOnClickListener { _ -> _log("Button clicked") } - _setupLogger() + return view } // ----------------------------------------------------------------------------------- @@ -44,15 +40,15 @@ class PlaygroundFragment : BaseFragment() { if (_isCurrentlyOnMainThread()) { _logs.add(0, logMsg + " (main thread) ") - _adapter!!.clear() - _adapter!!.addAll(_logs) + _adapter?.clear() + _adapter?.addAll(_logs) } else { _logs.add(0, logMsg + " (NOT main thread) ") // You can only do below stuff on main thread. Handler(Looper.getMainLooper()).post { - _adapter!!.clear() - _adapter!!.addAll(_logs) + _adapter?.clear() + _adapter?.addAll(_logs) } } } @@ -60,7 +56,7 @@ class PlaygroundFragment : BaseFragment() { private fun _setupLogger() { _logs = ArrayList() _adapter = LogAdapter(activity, ArrayList()) - _logsList!!.adapter = _adapter + _logsList?.adapter = _adapter } private fun _isCurrentlyOnMainThread(): Boolean { diff --git a/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt new file mode 100644 index 00000000..1b9ee372 --- /dev/null +++ b/app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt @@ -0,0 +1,94 @@ +package com.morihacky.android.rxjava.fragments + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.ListView +import android.widget.TextView +import com.morihacky.android.rxjava.R +import io.reactivex.Flowable +import io.reactivex.functions.Consumer +import io.reactivex.functions.Function +import org.reactivestreams.Publisher +import timber.log.Timber +import java.util.* +import java.util.concurrent.Callable + +class UsingFragment : BaseFragment() { + + private lateinit var _logs: MutableList + private lateinit var _logsList: ListView + private lateinit var _adapter: UsingFragment.LogAdapter + + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater?.inflate(R.layout.fragment_buffer, container, false) + _logsList = view?.findViewById(R.id.list_threading_log) as ListView + + (view.findViewById(R.id.text_description) as TextView).setText(R.string.msg_demo_using) + + _setupLogger() + view.findViewById(R.id.btn_start_operation).setOnClickListener { executeUsingOperation() } + return view + } + + private fun executeUsingOperation() { + val resourceSupplier = Callable { Realm() } + val sourceSupplier = Function> { realm -> + Flowable.just(true) + .map { + realm.doSomething() + // i would use the copyFromRealm and change it to a POJO + Random().nextInt(50) + } + } + val disposer = Consumer { realm -> + realm.clear() + } + + Flowable.using(resourceSupplier, sourceSupplier, disposer) + .subscribe({ i -> + _log("got a value $i - (look at the logs)") + }) + } + + inner class Realm { + init { + Timber.d("--- initializing Realm instance") + } + + fun doSomething() { + Timber.d("--- do something with Realm instance") + } + + fun clear() { + // notice how this is called even before you manually "dispose" + Timber.d("--- cleaning up the resources (happens before a manual 'dispose'") + } + } + + // ----------------------------------------------------------------------------------- + // Method that help wiring up the example (irrelevant to RxJava) + + private fun _log(logMsg: String) { + _logs.add(0, logMsg) + + // You can only do below stuff on main thread. + Handler(Looper.getMainLooper()).post { + _adapter.clear() + _adapter.addAll(_logs) + } + } + + private fun _setupLogger() { + _logs = ArrayList() + _adapter = LogAdapter(activity, ArrayList()) + _logsList.adapter = _adapter + } + + private class LogAdapter(context: Context, logs: List) : ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_buffer.xml b/app/src/main/res/layout/fragment_buffer.xml index 86d70150..3464ac5b 100644 --- a/app/src/main/res/layout/fragment_buffer.xml +++ b/app/src/main/res/layout/fragment_buffer.xml @@ -7,7 +7,8 @@ > + +