From ea5877bd6d58409ba6a79ccc5fd85b139b70dca3 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 15:17:31 -0700 Subject: [PATCH 01/11] --wip-- --- .../android/rxjava/fragments/UsingFragment.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt b/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt new file mode 100644 index 00000000..1767cf80 --- /dev/null +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt @@ -0,0 +1,17 @@ +package com.morihacky.android.rxjava.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +class UsingFragment : BaseFragment() { + + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + } +} \ No newline at end of file From bc5bcc392a45c8ca98332b021f365fdde4ff7d8b Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 20:45:22 -0700 Subject: [PATCH 02/11] fix: cleanup from auto java -> kotlin conversion --- .../rxjava/fragments/PlaygroundFragment.kt | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 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 37f76532..cf77e04f 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 @@ -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 { From c36b9ade6b4e0bc8429702d6afa32c7ab83c2866 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 21:19:13 -0700 Subject: [PATCH 03/11] feat: add using operator example [kotlin] --- README.md | 7 ++ .../android/rxjava/fragments/UsingFragment.kt | 86 ++++++++++++++++++- app/src/main/res/layout/fragment_buffer.xml | 3 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fa0e39b0..987733c9 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ 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) ## Description @@ -222,6 +223,12 @@ 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. + ## Rx 2.x All the examples here have been migrated to use RxJava 2.X. diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt b/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt index 1767cf80..b3108aa3 100644 --- a/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt +++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/UsingFragment.kt @@ -1,17 +1,97 @@ 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.disposables.Disposable +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 var _logs: MutableList = ArrayList() + private var _logsList: ListView? = null + private var _adapter: UsingFragment.LogAdapter? = null + private var disposable: Disposable? = null + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return super.onCreateView(inflater, container, savedInstanceState) + 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() + } + + disposable = + Flowable.using(resourceSupplier, sourceSupplier, disposer) + .subscribe({ i -> + _log("got a value $i - (look at the logs)") + }) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + 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 @@ > BTN 1: run single task once (after 2s complete)\nBTN 2: run task every 1s (start delay of 1s) toggle \nBTN 3: run task every 1s (start immediately) toggle \nBTN 4: run task 5 times every 3s (then complete) \nBTN 5: run task A, pause for sometime, then proceed with Task B This is an example of starting an Observable and using the result across rotations. There are many ways to do this, we use a retained fragment in this example This is a demo of how to use Subjects to detect Network connectivity\nToggle your Wifi/Network on or off and notice the logs + This is a demo of the somewhat unknown operator "using".\n\nmsg_demo_usingYou typically use it for managing setup/teardown of resources. Classic cases are DB connections (like Realm), sockets, locks etc.\n\nTap the button and look at the logcat. Particularly notice how the Realm instance is self-contained. That is, it is auto-disposed right after use. Concat merges the results sequentially. But notice that the latter subscription starts only AFTER the first one completes. Some unnecessary waiting there. Concat eager is cooler. Both subscriptions start at the same time (parallely) but the order of emission is respected. From f42c9a82112da63620b74ead30f0e9beb43196e2 Mon Sep 17 00:00:00 2001 From: Kaushik Gopal Date: Wed, 24 May 2017 21:21:49 -0700 Subject: [PATCH 04/11] feat: link it to a button --- .../morihacky/android/rxjava/fragments/MainFragment.java | 5 +++++ app/src/main/res/layout/fragment_main.xml | 7 +++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 13 insertions(+) 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..3874accf 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,11 @@ void demoNetworkDetector() { clickedOn(new NetworkDetectorFragment()); } + @OnClick(R.id.btn_demo_using) + void demoUsing() { + clickedOn(new UsingFragment()); + } + private void clickedOn(@NonNull Fragment fragment) { final String tag = fragment.getClass().toString(); getActivity() diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 51f0a694..adf5ff8f 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -127,5 +127,12 @@ android:layout_width="match_parent" android:text="@string/btn_demo_networkDetector" /> + +