+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index d403de60..0f796a1a 100644
--- a/README.md
+++ b/README.md
@@ -1,79 +1,121 @@
Learning RxJava for Android by example
==============
-This is a repository with real-world useful examples of using RxJava with Android. [It usually will be in a constant state of "Work in Progress" (WIP)](http://nerds.weddingpartyapp.com/tech/2014/09/15/learning-rxjava-with-android-by-example/).
+This is a repository with real-world useful examples of using RxJava with Android. [It usually will be in a constant state of "Work in Progress" (WIP)](https://kau.sh/blog/learning-rxjava-with-android-by-example/).
-## Examples:
+I've also been giving talks about Learning Rx using many of the examples listed in this repo.
+
+* [Learning RxJava For Android by Example : Part 1](https://www.youtube.com/watch?v=k3D0cWyNno4) \[[slides](https://speakerdeck.com/kaushikgopal/learning-rxjava-for-android-by-example)\] (SF Android Meetup 2015)
+* [Learning Rx by Example : Part 2](https://vimeo.com/190922794) \[[slides](https://speakerdeck.com/kaushikgopal/learning-rx-by-example-2)\] (Øredev 2016)
-### Concurrency using schedulers
+## Examples:
-A common requirement is to offload lengthy heavy I/O intensive operations to a background thread (non-UI thread), and feed the results back to the UI/main thread, on completion. This is a demo of how long running operations can be offloaded to a background thread. After the operation is done, we resume back on the main thread. All using RxJava! Think of this as a replacement to AsyncTasks.
+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)
+17. [Setup and teardown resources (using `using`)](#17-setup-and-teardown-resources-using-using)
+18. [Multicast playground](#18-multicast-playground)
+
+## Description
+
+### 1. Background work & concurrency (using Schedulers)
+
+A common requirement is to offload lengthy heavy I/O intensive operations to a background thread (non-UI thread) and feed the results back to the UI/main thread, on completion. This is a demo of how long-running operations can be offloaded to a background thread. After the operation is done, we resume back on the main thread. All using RxJava! Think of this as a replacement to AsyncTasks.
The long operation is simulated by a blocking Thread.sleep call (since this is done in a background thread, our UI is never interrupted).
-To really see this example shine. Hit the button multiple times and see how the button click (which is a ui operation) is never blocked because the long operation only runs in the background.
+To really see this example shine. Hit the button multiple times and see how the button click (which is a UI operation) is never blocked because the long operation only runs in the background.
-### Accumulate calls (buffer)
+### 2. Accumulate calls (using buffer)
This is a demo of how events can be accumulated using the "buffer" operation.
A button is provided and we accumulate the number of clicks on that button, over a span of time and then spit out the final results.
-If you hit the button once. you'll get message saying the button was hit once. If you hit it 5 times continuosly within a span of 2 seconds, then you get a single log, saying you hit that button 5 times (vs 5 individual logs saying "Button hit once").
+If you hit the button once, you'll get a message saying the button was hit once. If you hit it 5 times continuously within a span of 2 seconds, then you get a single log, saying you hit that button 5 times (vs 5 individual logs saying "Button hit once").
Note:
-If you're looking for a more foolproof solution that accumulates "continuous" taps vs just the number of taps within a time span, look at the [EventBus Demo](https://github.com/kaushikgopal/Android-RxJava/blob/master/app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java) where a combo of the `publish` and `buffer` operators is used. For a more detailed explanation you can also have a look at this [blog post](http://nerds.weddingpartyapp.com/tech/2015/01/05/debouncedbuffer-used-in-rxbus-example/).
+If you're looking for a more foolproof solution that accumulates "continuous" taps vs just the number of taps within a time span, look at the [EventBus Demo](https://github.com/kaushikgopal/Android-RxJava/blob/master/app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java) where a combo of the `publish` and `buffer` operators is used. For a more detailed explanation, you can also have a look at this [blog post](https://kau.sh/blog/debouncedbuffer-with-rxjava/).
-### Instant/Auto searching (subject + debounce)
+### 3. Instant/Auto searching text listeners (using Subjects & debounce)
-This is a demo of how events can be swallowed in a way that only the last one is respected. A typical example of this is instant search result boxes. As you type the word "Bruce Lee", you don't want to execute searches for B, Br, Bru, Bruce, Bruce , Bruce L ... etc. But rather intelligently wait for a couple of moments, make sure the user has finished typing the whole word, and then shoot out a single call for "Bruce Lee".
+This is a demo of how events can be swallowed in a way that only the last one is respected. A typical example of this is instant search result boxes. As you type the word "Bruce Lee", you don't want to execute searches for B, Br, Bru, Bruce, Bruce, Bruce L ... etc. But rather intelligently wait for a couple of moments, make sure the user has finished typing the whole word, and then shoot out a single call for "Bruce Lee".
As you type in the input box, it will not shoot out log messages at every single input character change, but rather only pick the lastly emitted event (i.e. input) and log that.
This is the debounce/throttleWithTimeout method in RxJava.
-### Retrofit and RxJava (zip, flatmap)
+### 4. Networking with Retrofit & RxJava (using zip, flatmap)
-[Retrofit from Square](http://square.github.io/retrofit/) is an amazing library that helps with easy networking (even if you haven't made the jump to RxJava just yet, you really should check it out). It works even better with RxJava and these are examples hitting the github api, taken straight up from the android demigod-developer Jake Wharton's talk at Netflix. You can [watch the talk](https://www.youtube.com/watch?v=aEuNBk1b5OE#t=2480) at this link. Incidentally, my motiviation to use RxJava was from attending this talk at Netflix.
+[Retrofit from Square](http://square.github.io/retrofit/) is an amazing library that helps with easy networking (even if you haven't made the jump to RxJava just yet, you really should check it out). It works even better with RxJava and these are examples hitting the GitHub API, taken straight up from the android demigod-developer Jake Wharton's talk at Netflix. You can [watch the talk](https://www.youtube.com/watch?v=aEuNBk1b5OE#t=2480) at this link. Incidentally, my motivation to use RxJava was from attending this talk at Netflix.
-Since it was a presentation, Jake only put up the most important code snippets in [his slides](https://speakerdeck.com/jakewharton/2014-1). Also he uses Java 8 in them, so I flushed those examples out in ~~good~~ old Java 6. (Note: you're most likely to hit the github api quota pretty fast so send in an oauth-token as a parameter if you want to keep running these examples often).
+(Note: you're most likely to hit the GitHub API quota pretty fast so send in an OAuth-token as a parameter if you want to keep running these examples often).
-### Orchestrating Observables. Make parallel network calls, then combine the result into a single data point (flatmap + zip)
+### 5. Two-way data binding for TextViews (using PublishSubject)
-The below ascii diagram expresses the intention of our next example with panache. f1,f2,3,f4,f5 are essentially network calls that when made, give back a result that's needed for a future calculation.
+Auto-updating views are a pretty cool thing. If you've dealt with Angular JS before, they have a pretty nifty concept called "two-way data binding", so when an HTML element is bound to a model/entity object, it constantly "listens" to changes on that entity and auto-updates its state based on the model. Using the technique in this example, you could potentially use a pattern like the [Presentation View Model pattern](http://martinfowler.com/eaaDev/PresentationModel.html) with great ease.
+While the example here is pretty rudimentary, the technique used to achieve the double binding using a `Publish Subject` is much more interesting.
- (flatmap)
- f1 ___________________ f3 _______
- (flatmap) | (zip)
- f2 ___________________ f4 _______| ___________ final output
- \ |
- \____________ f5 _______|
+### 6. Simple and Advanced polling (using interval and repeatWhen)
-The code for this example has already been written by one Mr.skehlet in the interwebs. Head over to [the gist](https://gist.github.com/skehlet/9418379) for the code. It's written in pure Java (6) so it's pretty comprehensible if you've understood the previous examples. I'll flush it out here again when time permits or I've run out of other compelling examples.
+This is an example of polling using RxJava Schedulers. This is useful in cases, where you want to constantly poll a server and possibly get new data. The network call is "simulated" so it forces a delay before return a resultant string.
-### Double binding with TextViews
+There are two variants for this:
-Auto-updating views are a pretty cool thing. If you've dealt with Angular JS before, they have a pretty nifty concept called "two way data binding", where when an HTML element is bound to a model/entity object, it constantly "listens" to changes on that entity and auto-updates its state based on the model. Using the technique in this example, you could potentially use a pattern like the [Presentation View Model pattern](http://martinfowler.com/eaaDev/PresentationModel.html) with great ease.
+1. Simple Polling: say when you want to execute a certain task every 5 seconds
+2. Increasing Delayed Polling: say when you want to execute a task first in 1 second, then in 2 seconds, then 3 and so on.
-While the example here is pretty rudimentary, the technique used to achieve the double binding using a `Publish Subject` is much more interesting.
+The second example is basically a variant of [Exponential Backoff](https://github.com/kaushikgopal/RxJava-Android-Samples#exponential-backoff).
-### Polling with Schedulers
+Instead of using a RetryWithDelay, we use a RepeatWithDelay here. To understand the difference between Retry(When) and Repeat(When) I wouuld suggest Dan's [fantastic post on the subject](http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/).
-This is an example of polling using RxJava Schedulers. This is useful in cases, where you want to constantly poll a server and possibly get new data. The network call is "simulated" so it forces a delay before return a resultant string.
+An alternative approach to delayed polling without the use of `repeatWhen` would be using chained nested delay observables. See [startExecutingWithExponentialBackoffDelay in the ExponentialBackOffFragment example](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/ExponentialBackoffFragment.java#L111).
+
+### 7. Simple and Advanced exponential backoff (using delay and retryWhen)
+
+[Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) is a strategy where based on feedback from a certain output, we alter the rate of a process (usually reducing the number of retries or increasing the wait time before retrying or re-executing a certain process).
+
+The concept makes more sense with examples. RxJava makes it (relatively) simple to implement such a strategy. My thanks to [Mike](https://twitter.com/m_evans10) for suggesting the idea.
+
+#### Retry (if error) with exponential backoff
+
+Say you have a network failure. A sensible strategy would be to NOT keep retrying your network call every 1 second. It would be smart instead (nay... elegant!) to retry with increasing delays. So you try at second 1 to execute the network call, no dice? try after 10 seconds... negatory? try after 20 seconds, no cookie? try after 1 minute. If this thing is still failing, you got to give up on the network yo!
+
+We simulate this behaviour using RxJava with the [`retryWhen` operator](http://reactivex.io/documentation/operators/retry.html).
-### RxBus - An event bus using RxJava + DebouncedBuffer
+`RetryWithDelay` code snippet courtesy:
-Have a look at the accompanying blog posts for details on this demo:
+* http://stackoverflow.com/a/25292833/159825
+* Another excellent implementation via @[sddamico](https://github.com/sddamico) : https://gist.github.com/sddamico/c45d7cdabc41e663bea1
+* This one includes support for jittering, by @[leandrofavarin](https://github.com/leandrofavarin) : http://leandrofavarin.com/exponential-backoff-rxjava-operator-with-jitter
-1. [Implementing an event bus with RxJava](http://nerds.weddingpartyapp.com/tech/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/)
-2. [DebouncedBuffer used for the fancier variant of the demo](http://nerds.weddingpartyapp.com/tech/2014/12/24/secret-bonus-part-debouncedbuffer-used-in-rxbus-example/)
-3. [share/publish/refcount](http://nerds.weddingpartyapp.com/tech/2014/12/24/rxjava-share-publish-refcount-and-all-that-jazz/)
+Also look at the [Polling example](https://github.com/kaushikgopal/RxJava-Android-Samples#polling-with-schedulers) where we use a very similar Exponential backoff mechanism.
-### Form validation - using [`.combineLatest`](http://reactivex.io/documentation/operators/combinelatest.html)
+#### "Repeat" with exponential backoff
-Thanks to Dan Lew for giving me this idea in the [fragmented podcast - episode #5](http://fragmentedpodcast.com/episodes/4/) (around the 4:30 mark).
+Another variant of the exponential backoff strategy is to execute an operation for a given number of times but with delayed intervals. So you execute a certain operation 1 second from now, then you execute it again 10 seconds from now, then you execute the operation 20 seconds from now. After a grand total of 3 times you stop executing.
+
+Simulating this behavior is actually way more simpler than the prevoius retry mechanism. You can use a variant of the `delay` operator to achieve this.
+
+
+### 8. Form validation (using [`.combineLatest`](http://reactivex.io/documentation/operators/combinelatest.html))
+
+Thanks to Dan Lew for giving me this idea in the [fragmented podcast - episode #4](http://fragmentedpodcast.com/episodes/4/) (around the 4:30 mark).
`.combineLatest` allows you to monitor the state of multiple observables at once compactly at a single location. The example demonstrated shows how you can use `.combineLatest` to validate a basic form. There are 3 primary inputs for this form to be considered "valid" (an email, a password and a number). The form will turn valid (the text below turns blue :P) once all the inputs are valid. If they are not, an error is shown against the invalid inputs.
@@ -83,13 +125,33 @@ Note that the `Func3` function that checks for validity, kicks in only after ALL
The value of this technique becomes more apparent when you have more number of input fields in a form. Handling it otherwise with a bunch of booleans makes the code cluttered and kind of difficult to follow. But using `.combineLatest` all that logic is concentrated in a nice compact block of code (I still use booleans but that was to make the example more readable).
-### Retrieve data first from a cache, then a network call - using [`.concat`](http://reactivex.io/documentation/operators/concat.html)
-Using concat, you can retrieve information from an observable first (presumably this one is fast like retrieveing from a disk cache) and show preliminary data to a user. Subsequently, when the longer running 2nd observable is complete (say a network call), you can update the results on the interface using the latest information.
+### 9. Pseudo caching : retrieve data first from a cache, then a network call (using concat, concatEager, merge or publish)
+
+We have two source Observables: a disk (fast) cache and a network (fresh) call. Typically the disk Observable is much faster than the network Observable. But in order to demonstrate the working, we've also used a fake "slower" disk cache just to see how the operators behave.
+
+This is demonstrated using 4 techniques:
+
+1. [`.concat`](http://reactivex.io/documentation/operators/concat.html)
+2. [`.concatEager`](http://reactivex.io/RxJava/javadoc/rx/Observable.html#concatEager(java.lang.Iterable))
+3. [`.merge`](http://reactivex.io/documentation/operators/merge.html)
+4. [`.publish`](http://reactivex.io/RxJava/javadoc/rx/Observable.html#publish(rx.functions.Func1)) selector + merge + takeUntil
+
+The 4th technique is probably what you want to use eventually but it's interesting to go through the progression of techniques, to understand why.
+
+`concat` is great. It retrieves information from the first Observable (disk cache in our case) and then the subsequent network Observable. Since the disk cache is presumably faster, all appears well and the disk cache is loaded up fast, and once the network call finishes we swap out the "fresh" results.
+
+The problem with `concat` is that the subsequent observable doesn't even start until the first Observable completes. That can be a problem. We want all observables to start simultaneously but produce the results in a way we expect. Thankfully RxJava introduced `concatEager` which does exactly that. It starts both observables but buffers the result from the latter one until the former Observable finishes. This is a completely viable option.
-For the purposes of illustration i use an in-memory `List` (not an actual disk cache), then shoot out a real network call to the github api so it gives you a feel of how this can really be applied in production apps.
+Sometimes though, you just want to start showing the results immediately. Assuming the first observable (for some strange reason) takes really long to run through all its items, even if the first few items from the second observable have come down the wire it will forcibly be queued. You don't necessarily want to "wait" on any Observable. In these situations, we could use the `merge` operator. It interleaves items as they are emitted. This works great and starts to spit out the results as soon as they're shown.
-### Simple Timing demos using timer/interval/delay
+Similar to the `concat` operator, if your first Observable is always faster than the second Observable you won't run into any problems. However the problem with `merge` is: if for some strange reason an item is emitted by the cache or slower observable *after* the newer/fresher observable, it will overwrite the newer content. Click the "MERGE (SLOWER DISK)" button in the example to see this problem in action. @JakeWharton and @swankjesse contributions go to 0! In the real world this could be bad, as it would mean the fresh data would get overridden by stale disk data.
+
+To solve this problem you can use merge in combination with the super nifty `publish` operator which takes in a "selector". I wrote about this usage in a [blog post](https://kau.sh/blog/rxjava-tip-for-the-day-share-publish-refcount-and-all-that-jazz/) but I have [Jedi JW](https://twitter.com/JakeWharton/status/786363146990649345) to thank for reminding of this technique. We `publish` the network observable and provide it a selector which starts emitting from the disk cache, up until the point that the network observable starts emitting. Once the network observable starts emitting, it ignores all results from the disk observable. This is perfect and handles any problems we might have.
+
+Previously, I was using the `merge` operator but overcoming the problem of results being overwritten by monitoring the "resultAge". See the old `PseudoCacheMergeFragment` example if you're curious to see this old implementation.
+
+### 10. Simple timing demos (using timer, interval and delay)
This is a super simple and straightforward example which shows you how to use RxJava's `timer`, `interval` and `delay` operators to handle a bunch of cases where you want to run a task at specific intervals. Basically say NO to Android `TimerTask`s.
@@ -99,17 +161,91 @@ Cases demonstrated here:
2. run a task constantly every 1s (there's a delay of 1s before the first task fires off)
3. run a task constantly every 1s (same as above but there's no delay before the first task fires off)
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)
+
+There are accompanying blog posts that do a much better job of explaining the details on this demo:
+
+1. [Implementing an event bus with RxJava](https://kau.sh/blog/implementing-an-event-bus-with-rxjava-rxbus/)
+2. [DebouncedBuffer used for the fancier variant of the demo](https://kau.sh/blog/debouncedbuffer-with-rxjava/)
+3. [share/publish/refcount](https://kau.sh/blog/rxjava-tip-for-the-day-share-publish-refcount-and-all-that-jazz/)
+
+### 12. Persist data on Activity rotations (using Subjects and retained Fragments)
+
+A common question that's asked when using RxJava in Android is, "how do i resume the work of an observable if a configuration change occurs (activity rotation, language locale change etc.)?".
+
+This example shows you one strategy viz. using retained Fragments. I started using retained fragments as "worker fragments" after reading this [fantastic post by Alex Lockwood](http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html) quite sometime back.
+
+Hit the start button and rotate the screen to your heart's content; you'll see the observable continue from where it left off.
-## Work in Progress:
+*There are certain quirks about the "hotness" of the source observable used in this example. Check [my blog post](https://kau.sh/blog/a-note-about-the-warmth-share-operator/) out where I explain the specifics.*
-Examples that I would like to have here, but haven't found the time yet to flush out.
+I have since rewritten this example using an alternative approach. While the [`ConnectedObservable` approach worked](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java#L20) it enters the lands of "multicasting" which can be tricky (thread-safety, .refcount etc.). Subjects on the other hand are far more simple. You can see it rewritten [using a `Subject` here](https://github.com/kaushikgopal/RxJava-Android-Samples/blob/master/app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2WorkerFragment.java#L22).
+I wrote [another blog post](https://tech.instacart.com/how-to-think-about-subjects-part-1/) on how to think about Subjects where I go into some specifics.
-### Pagination
-a. Simple pagination
-b. Optimized pagination
+### 13. Networking with Volley
+[Volley](http://developer.android.com/training/volley/index.html) is another networking library introduced by [Google at IO '13](https://www.youtube.com/watch?v=yhv8l9F44qo). A kind citizen of github contributed this example so we know how to integrate Volley with RxJava.
+
+### 14. Pagination with Rx (using Subjects)
+
+I leverage the simple use of a Subject here. Honestly, if you don't have your items coming down via an `Observable` already (like through Retrofit or a network request), there's no good reason to use Rx and complicate things.
+
+This example basically sends the page number to a Subject, and the subject handles adding the items. Notice the use of `concatMap` and the return of an `Observable` from `_itemsFromNetworkCall`.
+
+For kicks, I've also included a `PaginationAutoFragment` example, this "auto-paginates" without us requiring to hit a button. It should be simple to follow if you got how the previous example works.
+
+Here are some other fancy implementations (while i enjoyed reading them, i didn't land up using them for my real world app cause personally i don't think it's necessary):
+
+* [Matthias example of an Rx based pager](https://gist.github.com/mttkay/24881a0ce986f6ec4b4d)
+* [Eugene's very comprehensive Pagination sample](https://github.com/matzuk/PaginationSample)
+* [Recursive Paging example](http://stackoverflow.com/questions/28047272/handle-paging-with-rxjava)
+
+### 15. Orchestrating Observable: make parallel network calls, then combine the result into a single data point (using flatmap & zip)
+
+The below ascii diagram expresses the intention of our next example with panache. f1,f2,f3,f4,f5 are essentially network calls that when made, give back a result that's needed for a future calculation.
+
+
+ (flatmap)
+ f1 ___________________ f3 _______
+ (flatmap) | (zip)
+ f2 ___________________ f4 _______| ___________ final output
+ \ |
+ \____________ f5 _______|
+
+The code for this example has already been written by one Mr.skehlet in the interwebs. Head over to [the gist](https://gist.github.com/skehlet/9418379) for the code. It's written in pure Java (6) so it's pretty comprehensible if you've understood the previous examples. I'll flush it out here again when time permits or I've run out of other compelling examples.
+
+### 16. Simple Timeout example (using timeout)
+
+This is a simple example demonstrating the use of the `.timeout` operator. Button 1 will complete the task before the timeout constraint, while Button 2 will force a timeout error.
+
+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.
+
+* Have a look at [PR #83 to see the diff of changes between RxJava 1 and 2](https://github.com/kaushikgopal/RxJava-Android-Samples/pull/83/files)
+* [What's different in Rx 2.x](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0)
+
+We use [David Karnok's Interop library](https://github.com/akarnokd/RxJava2Interop) in some cases as certain libraries like RxBindings, RxRelays, RxJava-Math etc. have not been ported yet to 2.x.
## Contributing:
@@ -117,6 +253,18 @@ I try to ensure the examples are not overly contrived but reflect a real-world u
I'm wrapping my head around RxJava too so if you feel there's a better way of doing one of the examples mentioned above, open up an issue explaining how. Even better, send a pull request.
+
+## Sponsorship (Memory Management/Profiling)
+
+Rx threading is messy business. To help, this project uses YourKit tools for analysis.
+
+
+
+
+YourKit supports open source projects with innovative and intelligent tools
+for monitoring and profiling Java applications. YourKit is the creator of YourKit Java Profiler.
+
+
## License
Licensed under the Apache License, Version 2.0 (the "License").
@@ -130,4 +278,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-You agree that all contributions to this repository, in the form of fixes, pull-requests, new examples etc. follow the above mentioned license.
\ No newline at end of file
+You agree that all contributions to this repository, in the form of fixes, pull-requests, new examples etc. follow the above-mentioned license.
diff --git a/app/build.gradle b/app/build.gradle
index 7ff86bf2..edbcd31c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,33 +1,78 @@
+buildscript {
+ repositories {
+// mavenCentral()
+ jcenter()
+ }
+ dependencies {
+ classpath 'me.tatarka:gradle-retrolambda:3.6.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
+ }
+
+ // Exclude the lombok version that the android plugin depends on.
+ configurations.classpath.exclude group: 'com.android.tools.external.lombok'
+}
+
apply plugin: 'com.android.application'
+apply plugin: 'me.tatarka.retrolambda'
+apply plugin: 'com.f2prateek.javafmt'
+apply plugin: 'kotlin-android'
dependencies {
- compile 'com.android.support:support-v13:21.0.2'
+ 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}"
+
+ compile 'com.github.kaushikgopal:CoreTextUtils:c703fa12b6'
+ compile "com.jakewharton:butterknife:${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}"
+ compile "com.squareup.okhttp3:okhttp:${okhttpVersion}"
+ 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}"
- compile 'com.google.guava:guava:17.+'
+ compile "android.arch.lifecycle:runtime:${archComponentsVersion}"
+ compile "android.arch.lifecycle:extensions:${archComponentsVersion}"
+ kapt "android.arch.lifecycle:compiler:${archComponentsVersion}"
- compile 'com.jakewharton:butterknife:5.1.1'
- compile 'com.jakewharton.timber:timber:2.4.2'
- compile 'io.reactivex:rxandroid:0.24.0'
- //compile 'io.reactivex:rxjava-math:0.21.0'
+ // ----------------------------------
+ // Rx dependencies
- compile 'com.squareup.retrofit:retrofit:1.6.1'
- compile 'com.squareup.okhttp:okhttp:2.0.0'
- compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
+ compile 'io.reactivex.rxjava2:rxjava:2.0.7'
- debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
- releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
+ // Because RxAndroid releases are few and far between, it is recommended you also
+ // 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'
+ compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
+
+ // ----------------------------------
+
+ debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
+ releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
android {
- compileSdkVersion 21
- buildToolsVersion '21.1.2'
+ compileSdkVersion sdkVersion
+ buildToolsVersion buildToolsVrs
defaultConfig {
applicationId "com.morihacky.android.rxjava"
- minSdkVersion 14
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
+ minSdkVersion 15
+ targetSdkVersion sdkVersion
+ versionCode 2
+ versionName "1.2"
+ multiDexEnabled true
}
buildTypes {
release {
@@ -35,4 +80,14 @@ android {
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
+ }
+ packagingOptions {
+ pickFirst 'META-INF/rxjava.properties'
+ }
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f8779a75..3b469d7c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,23 +1,28 @@
+ package="com.morihacky.android.rxjava"
+ >
+ android:theme="@style/AppTheme"
+ >
+ android:label="@string/app_name"
+ >
-
+
-
+
-
+
+
+
diff --git a/app/src/main/java/com/morihacky/android/rxjava/BaseFragment.java b/app/src/main/java/com/morihacky/android/rxjava/BaseFragment.java
deleted file mode 100644
index 3d260adc..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/BaseFragment.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.support.v4.app.Fragment;
-import com.squareup.leakcanary.RefWatcher;
-
-public class BaseFragment
- extends Fragment {
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- RefWatcher refWatcher = MyApp.getRefWatcher();
- refWatcher.watch(this);
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java
deleted file mode 100644
index e0ee6152..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/BufferDemoFragment.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.morihacky.android.rxjava;
-
-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.Button;
-import android.widget.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import com.morihacky.android.rxjava.wiring.LogAdapter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import rx.Observer;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.android.view.OnClickEvent;
-import rx.android.view.ViewObservable;
-import rx.functions.Func1;
-import timber.log.Timber;
-
-/**
- * This is a demonstration of the `buffer` Observable.
- *
- * The buffer observable allows taps to be collected only within a time span. So taps outside the
- * 2s limit imposed by buffer will get accumulated in the next log statement.
- *
- * If you're looking for a more foolproof solution that accumulates "continuous" taps vs
- * a more dumb solution as show below (i.e. number of taps within a timespan)
- * look at {@link com.morihacky.android.rxjava.rxbus.RxBusDemo_Bottom3Fragment} where a combo
- * of `publish` and `buffer` is used.
- *
- * Also http://nerds.weddingpartyapp.com/tech/2015/01/05/debouncedbuffer-used-in-rxbus-example/
- * if you're looking for words instead of code
- */
-public class BufferDemoFragment
- extends BaseFragment {
-
- @InjectView(R.id.list_threading_log) ListView _logsList;
- @InjectView(R.id.btn_start_operation) Button _tapBtn;
-
- private LogAdapter _adapter;
- private List _logs;
-
- private Subscription _subscription;
-
- @Override
- public void onStart() {
- super.onStart();
- _subscription = _getBufferedSubscription();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- _subscription.unsubscribe();
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- _setupLogger();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_buffer, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- // -----------------------------------------------------------------------------------
- // Main Rx entities
-
- private Subscription _getBufferedSubscription() {
- return ViewObservable.clicks(_tapBtn)
- .map(new Func1() {
- @Override
- public Integer call(OnClickEvent onClickEvent) {
- Timber.d("--------- GOT A TAP");
- _log("GOT A TAP");
- return 1;
- }
- })
- .buffer(2, TimeUnit.SECONDS)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer>() {
-
- @Override
- public void onCompleted() {
- // fyi: you'll never reach here
- Timber.d("----- onCompleted");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "--------- Woops on error!");
- _log("Dang error! check your logs");
- }
-
- @Override
- public void onNext(List integers) {
- Timber.d("--------- onNext");
- if (integers.size() > 0) {
- _log(String.format("%d taps", integers.size()));
- } else {
- Timber.d("--------- No taps received ");
- }
- }
- });
- }
-
- // -----------------------------------------------------------------------------------
- // Methods that help wiring up the example (irrelevant to RxJava)
-
- private void _setupLogger() {
- _logs = new ArrayList<>();
- _adapter = new LogAdapter(getActivity(), new ArrayList());
- _logsList.setAdapter(_adapter);
- }
-
- 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(new Runnable() {
-
- @Override
- public void run() {
- _adapter.clear();
- _adapter.addAll(_logs);
- }
- });
- }
- }
-
- private boolean _isCurrentlyOnMainThread() {
- return Looper.myLooper() == Looper.getMainLooper();
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/ConcurrencyWithSchedulersDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/ConcurrencyWithSchedulersDemoFragment.java
deleted file mode 100644
index dac9749d..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/ConcurrencyWithSchedulersDemoFragment.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package com.morihacky.android.rxjava;
-
-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.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import java.util.ArrayList;
-import java.util.List;
-import rx.Observable;
-import rx.Observer;
-import rx.Subscription;
-import rx.android.app.AppObservable;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Func1;
-import rx.schedulers.Schedulers;
-import timber.log.Timber;
-
-public class ConcurrencyWithSchedulersDemoFragment
- extends BaseFragment {
-
- @InjectView(R.id.progress_operation_running) ProgressBar _progress;
- @InjectView(R.id.list_threading_log) ListView _logsList;
-
- private LogAdapter _adapter;
- private List _logs;
- private Subscription _subscription;
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (_subscription != null) {
- _subscription.unsubscribe();
- }
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- _setupLogger();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_concurrency_schedulers, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- @OnClick(R.id.btn_start_operation)
- public void startLongOperation() {
-
- _progress.setVisibility(View.VISIBLE);
- _log("Button Clicked");
-
- _subscription = AppObservable.bindFragment(this, _getObservable()) // Observable
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(_getObserver()); // Observer
- }
-
- private Observable _getObservable() {
- return Observable.just(true).map(new Func1() {
- @Override
- public Boolean call(Boolean aBoolean) {
- _log("Within Observable");
- _doSomeLongOperation_thatBlocksCurrentThread();
- return aBoolean;
- }
- });
- }
-
- /**
- * Observer that handles the result through the 3 important actions:
- *
- * 1. onCompleted
- * 2. onError
- * 3. onNext
- */
- private Observer _getObserver() {
- return new Observer() {
-
- @Override
- public void onCompleted() {
- _log("On complete");
- _progress.setVisibility(View.INVISIBLE);
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "Error in RxJava Demo concurrency");
- _log(String.format("Boo! Error %s", e.getMessage()));
- _progress.setVisibility(View.INVISIBLE);
- }
-
- @Override
- public void onNext(Boolean bool) {
- _log(String.format("onNext with return value \"%b\"", bool));
- }
- };
- }
-
- // -----------------------------------------------------------------------------------
- // 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(new Runnable() {
-
- @Override
- public void run() {
- _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/DebounceSearchEmitterFragment.java b/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java
deleted file mode 100644
index 6a71d07c..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/DebounceSearchEmitterFragment.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package com.morihacky.android.rxjava;
-
-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.EditText;
-import android.widget.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import rx.Observable;
-import rx.Observer;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.android.widget.OnTextChangeEvent;
-import rx.android.widget.WidgetObservable;
-import timber.log.Timber;
-
-import static java.lang.String.format;
-import static rx.android.app.AppObservable.bindFragment;
-
-public class DebounceSearchEmitterFragment
- extends BaseFragment {
-
- @InjectView(R.id.list_threading_log) ListView _logsList;
- @InjectView(R.id.input_txt_debounce) EditText _inputSearchText;
-
- private LogAdapter _adapter;
- private List _logs;
-
- private Subscription _subscription;
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (_subscription != null) {
- _subscription.unsubscribe();
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_debounce, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- @OnClick(R.id.clr_debounce)
- public void onClearLog() {
- _logs = new ArrayList<>();
- _adapter.clear();
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
-
- super.onActivityCreated(savedInstanceState);
- _setupLogger();
-
- Observable textChangeObservable = WidgetObservable.text(_inputSearchText);
-
- _subscription = bindFragment(this,//
- textChangeObservable//
- .debounce(400, TimeUnit.MILLISECONDS)// default Scheduler is Computation
- .observeOn(AndroidSchedulers.mainThread()))//
- .subscribe(_getSearchObserver());
- }
-
- // -----------------------------------------------------------------------------------
- // Main Rx entities
-
- private Observer _getSearchObserver() {
- return new Observer() {
- @Override
- public void onCompleted() {
- Timber.d("--------- onComplete");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "--------- Woops on error!");
- _log("Dang error. check your logs");
- }
-
- @Override
- public void onNext(OnTextChangeEvent onTextChangeEvent) {
- _log(format("Searching for %s", onTextChangeEvent.text().toString()));
- }
- };
- }
-
- // -----------------------------------------------------------------------------------
- // Method that help wiring up the example (irrelevant to RxJava)
-
- private void _setupLogger() {
- _logs = new ArrayList();
- _adapter = new LogAdapter(getActivity(), new ArrayList());
- _logsList.setAdapter(_adapter);
- }
-
- 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(new Runnable() {
-
- @Override
- public void run() {
- _adapter.clear();
- _adapter.addAll(_logs);
- }
- });
- }
- }
-
- 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/DoubleBindingTextViewFragment.java b/app/src/main/java/com/morihacky/android/rxjava/DoubleBindingTextViewFragment.java
deleted file mode 100644
index 7a7ce56e..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/DoubleBindingTextViewFragment.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.TextView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnTextChanged;
-import com.google.common.base.Strings;
-import com.morihacky.android.rxjava.R;
-import rx.Subscription;
-import rx.functions.Action1;
-import rx.subjects.PublishSubject;
-
-public class DoubleBindingTextViewFragment
- extends BaseFragment {
-
- @InjectView(R.id.double_binding_num1) EditText _number1;
- @InjectView(R.id.double_binding_num2) EditText _number2;
- @InjectView(R.id.double_binding_result) TextView _result;
-
- Subscription _subscription;
- PublishSubject _resultEmitterSubject;
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_double_binding_textview, container, false);
- ButterKnife.inject(this, layout);
-
- _resultEmitterSubject = PublishSubject.create();
- _subscription = _resultEmitterSubject.asObservable().subscribe(new Action1() {
- @Override
- public void call(Float aFloat) {
- _result.setText(String.valueOf(aFloat));
- }
- });
-
- onNumberChanged();
- _number2.requestFocus();
-
- return layout;
- }
-
- @OnTextChanged({ R.id.double_binding_num1, R.id.double_binding_num2 })
- public void onNumberChanged() {
- float num1 = 0;
- float num2 = 0;
-
- if (!Strings.isNullOrEmpty(_number1.getText().toString())) {
- num1 = Float.parseFloat(_number1.getText().toString());
- }
-
- if (!Strings.isNullOrEmpty(_number2.getText().toString())) {
- num2 = Float.parseFloat(_number2.getText().toString());
- }
-
- _resultEmitterSubject.onNext(num1 + num2);
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- if (_subscription != null) {
- _subscription.unsubscribe();
- }
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/FormValidationCombineLatestFragment.java b/app/src/main/java/com/morihacky/android/rxjava/FormValidationCombineLatestFragment.java
deleted file mode 100644
index 93bc7183..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/FormValidationCombineLatestFragment.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.TextView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import com.morihacky.android.rxjava.R;
-import rx.Observable;
-import rx.Observer;
-import rx.Subscription;
-import rx.android.widget.OnTextChangeEvent;
-import rx.android.widget.WidgetObservable;
-import rx.functions.Func3;
-import timber.log.Timber;
-
-import static android.util.Patterns.EMAIL_ADDRESS;
-import static com.google.common.base.Strings.isNullOrEmpty;
-
-public class FormValidationCombineLatestFragment
- extends BaseFragment {
-
- @InjectView(R.id.btn_demo_form_valid) TextView _btnValidIndicator;
- @InjectView(R.id.demo_combl_email) EditText _email;
- @InjectView(R.id.demo_combl_password) EditText _password;
- @InjectView(R.id.demo_combl_num) EditText _number;
-
- private Observable _emailChangeObservable;
- private Observable _passwordChangeObservable;
- private Observable _numberChangeObservable;
-
- private Subscription _subscription = null;
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_form_validation_comb_latest,
- container,
- false);
- ButterKnife.inject(this, layout);
-
- _emailChangeObservable = WidgetObservable.text(_email);
- _passwordChangeObservable = WidgetObservable.text(_password);
- _numberChangeObservable = WidgetObservable.text(_number);
-
- _combineLatestEvents();
-
- return layout;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (_subscription != null) {
- _subscription.unsubscribe();
- }
- }
-
- private void _combineLatestEvents() {
- _subscription = Observable.combineLatest(_emailChangeObservable,
- _passwordChangeObservable,
- _numberChangeObservable,
- new Func3() {
- @Override
- public Boolean call(OnTextChangeEvent onEmailChangeEvent,
- OnTextChangeEvent onPasswordChangeEvent,
- OnTextChangeEvent onNumberChangeEvent) {
-
- boolean emailValid = !isNullOrEmpty(onEmailChangeEvent.text().toString()) &&
- EMAIL_ADDRESS.matcher(onEmailChangeEvent.text())
- .matches();
- if (!emailValid) {
- _email.setError("Invalid Email!");
- }
-
- boolean passValid = !isNullOrEmpty(onPasswordChangeEvent.text().toString()) &&
- onPasswordChangeEvent.text().length() > 8;
- if (!passValid) {
- _password.setError("Invalid Password!");
- }
-
- boolean numValid = !isNullOrEmpty(onNumberChangeEvent.text().toString());
- if (numValid) {
- int num = Integer.parseInt(onNumberChangeEvent.text().toString());
- numValid = num > 0 && num <= 100;
- }
- if (!numValid) {
- _number.setError("Invalid Number!");
- }
-
- return emailValid && passValid && numValid;
-
- }
- })//
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- Timber.d("completed");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "there was an eroor");
- }
-
- @Override
- public void onNext(Boolean formValid) {
- if (formValid) {
- _btnValidIndicator.setBackgroundColor(getResources().getColor(R.color.blue));
- } else {
- _btnValidIndicator.setBackgroundColor(getResources().getColor(R.color.gray));
- }
- }
- });
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/MainActivity.java b/app/src/main/java/com/morihacky/android/rxjava/MainActivity.java
index 69e9c6a3..068a393d 100644
--- a/app/src/main/java/com/morihacky/android/rxjava/MainActivity.java
+++ b/app/src/main/java/com/morihacky/android/rxjava/MainActivity.java
@@ -1,36 +1,59 @@
package com.morihacky.android.rxjava;
import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
+import com.morihacky.android.rxjava.fragments.MainFragment;
+import com.morihacky.android.rxjava.fragments.RotationPersist1WorkerFragment;
+import com.morihacky.android.rxjava.fragments.RotationPersist2WorkerFragment;
import com.morihacky.android.rxjava.rxbus.RxBus;
-import timber.log.Timber;
-public class MainActivity
- extends FragmentActivity {
+public class MainActivity extends AppCompatActivity {
- private RxBus _rxBus = null;
+ private RxBus _rxBus = null;
- // This is better done with a DI Library like Dagger
- public RxBus getRxBusSingleton() {
- if (_rxBus == null) {
- _rxBus = new RxBus();
- }
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ _removeWorkerFragments();
+ }
- return _rxBus;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, new MainFragment(), this.toString())
+ .commit();
+ }
+ }
+
+ // This is better done with a DI Library like Dagger
+ public RxBus getRxBusSingleton() {
+ if (_rxBus == null) {
+ _rxBus = new RxBus();
}
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+ return _rxBus;
+ }
+
+ private void _removeWorkerFragments() {
+ Fragment frag =
+ getSupportFragmentManager()
+ .findFragmentByTag(RotationPersist1WorkerFragment.class.getName());
+
+ if (frag != null) {
+ getSupportFragmentManager().beginTransaction().remove(frag).commit();
+ }
- Timber.plant(new Timber.DebugTree());
+ frag =
+ getSupportFragmentManager()
+ .findFragmentByTag(RotationPersist2WorkerFragment.class.getName());
- if (savedInstanceState == null) {
- getSupportFragmentManager().beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new MainFragment(), this.toString())
- .commit();
- }
+ if (frag != null) {
+ getSupportFragmentManager().beginTransaction().remove(frag).commit();
}
-}
\ No newline at end of file
+ }
+}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java b/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java
deleted file mode 100644
index ce9b926f..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/MainFragment.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import butterknife.ButterKnife;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.rxbus.RxBusDemoFragment;
-
-public class MainFragment
- extends BaseFragment {
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_main, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- @OnClick(R.id.btn_demo_schedulers)
- public void demoConcurrencyWithSchedulers() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main,
- new ConcurrencyWithSchedulersDemoFragment(),
- this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_buffer)
- public void demoBuffer() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new BufferDemoFragment(), this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_debounce)
- public void demoThrottling() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main,
- new DebounceSearchEmitterFragment(),
- this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_retrofit)
- public void demoRetrofitCalls() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- //.replace(R.id.activity_main, new RetrofitAsyncTaskDeathFragment(), this.toString())
- .replace(R.id.activity_main, new RetrofitFragment(), this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_double_binding_textview)
- public void demoDoubleBindingWithPublishSubject() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new DoubleBindingTextViewFragment(), this.toString())
- .commit();
- }
-
- /*@OnClick(R.id.btn_demo_polling)
- public void demoPolling() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new PollingFragment(), this.toString())
- .commit();
- }*/
-
- @OnClick(R.id.btn_demo_rxbus)
- public void demoRxBus() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new RxBusDemoFragment(), this.toString())
- .commit();
- }
-
- //@OnClick(R.id.btn_demo_subject_timeout)
- public void demoTimeout() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new TimeoutDemoFragment(), this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_form_validation_combinel)
- public void formValidation() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main,
- new FormValidationCombineLatestFragment(),
- this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_pseudo_cache)
- public void pseudoCacheDemo() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new PseudoCacheConcatFragment(), this.toString())
- .commit();
- }
-
- @OnClick(R.id.btn_demo_timing)
- public void demoTimerIntervalDelays() {
- getActivity().getSupportFragmentManager()
- .beginTransaction()
- .addToBackStack(this.toString())
- .replace(R.id.activity_main, new TimingDemoFragment(), this.toString())
- .commit();
- }
-
-}
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 4dad4cc7..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,28 +1,43 @@
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;
+ private static MyApp _instance;
+ private RefWatcher _refWatcher;
- public static MyApp get() {
- return _instance;
- }
+ public static MyApp get() {
+ return _instance;
+ }
- public static RefWatcher getRefWatcher() {
- return MyApp.get()._refWatcher;
- }
+ public static RefWatcher getRefWatcher() {
+ return MyApp.get()._refWatcher;
+ }
- @Override
- public void onCreate() {
- super.onCreate();
+ @Override
+ public void onCreate() {
+ super.onCreate();
- _instance = (MyApp) getApplicationContext();
- _refWatcher = LeakCanary.install(this);
+ if (LeakCanary.isInAnalyzerProcess(this)) {
+ // This process is dedicated to LeakCanary for heap analysis.
+ // You should not init your app in this process.
+ return;
}
+
+ _instance = (MyApp) getApplicationContext();
+ _refWatcher = LeakCanary.install(this);
+
+ // for better RxJava debugging
+ //RxJavaHooks.enableAssemblyTracking();
+
+ // Initialize Volley
+ MyVolley.init(this);
+
+ Timber.plant(new Timber.DebugTree());
+ }
}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/PollingFragment.java b/app/src/main/java/com/morihacky/android/rxjava/PollingFragment.java
deleted file mode 100644
index 6f163e26..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/PollingFragment.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.morihacky.android.rxjava;
-
-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 butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.R;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import rx.Observable;
-import rx.Subscriber;
-import rx.functions.Action0;
-import rx.functions.Action1;
-import rx.schedulers.Schedulers;
-import rx.subscriptions.CompositeSubscription;
-import timber.log.Timber;
-
-public class PollingFragment
- extends BaseFragment {
-
- public static final int INITIAL_DELAY = 0;
- public static final int POLLING_INTERVAL = 1000;
- @InjectView(R.id.list_threading_log) ListView _logsList;
-
- private LogAdapter _adapter;
- private List _logs;
- private CompositeSubscription _subscriptions;
- private int _counter = 0;
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- _subscriptions.unsubscribe();
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- _subscriptions = new CompositeSubscription();
- _setupLogger();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_polling, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- @OnClick(R.id.btn_start_simple_polling)
- public void onStartSimplePollingClicked() {
- _subscriptions.add(Observable.create(new Observable.OnSubscribe() {
- @Override
- public void call(final Subscriber super String> observer) {
-
- Schedulers.newThread().createWorker() //
- .schedulePeriodically(new Action0() {
- @Override
- public void call() {
- observer.onNext(_doNetworkCallAndGetStringResult());
- }
- }, INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS);
- }
- }).take(10).subscribe(new Action1() {
- @Override
- public void call(String s) {
- _log(String.format("String polling - %s", s));
- }
- }));
- }
-
- // -----------------------------------------------------------------------------------
- // Method that help wiring up the example (irrelevant to RxJava)
-
- private String _doNetworkCallAndGetStringResult() {
-
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- Timber.d("Operation was interrupted");
- }
- _counter++;
-
- return String.valueOf(_counter);
- }
-
- 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(new Runnable() {
-
- @Override
- public void run() {
- _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/PseudoCacheConcatFragment.java b/app/src/main/java/com/morihacky/android/rxjava/PseudoCacheConcatFragment.java
deleted file mode 100644
index 623d9cdd..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/PseudoCacheConcatFragment.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-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 butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.R;
-import com.morihacky.android.rxjava.retrofit.Contributor;
-import com.morihacky.android.rxjava.retrofit.GithubApi;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import retrofit.RequestInterceptor;
-import retrofit.RestAdapter;
-import rx.Observable;
-import rx.Subscriber;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Func1;
-import timber.log.Timber;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.lang.String.format;
-
-public class PseudoCacheConcatFragment
- extends BaseFragment {
-
- @InjectView(R.id.log_list) ListView _resultList;
-
- private Subscription _subscription = null;
- private HashMap _contributionMap = null;
- private ArrayAdapter _adapter;
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_pseudo_cache_concat, container, false);
- ButterKnife.inject(this, layout);
- _initializeCache();
- return layout;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (_subscription != null) {
- _subscription.unsubscribe();
- }
- }
-
- @OnClick(R.id.btn_start_pseudo_cache)
- public void onDemoPseudoCacheClicked() {
- _adapter = new ArrayAdapter<>(getActivity(),
- R.layout.item_log,
- R.id.item_log,
- new ArrayList());
-
- _resultList.setAdapter(_adapter);
- _initializeCache();
-
- Observable.concat(_getCachedData(), _getFreshData())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Subscriber() {
- @Override
- public void onCompleted() {
- Timber.d("done loading all data");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "arr something went wrong");
- }
-
- @Override
- public void onNext(Contributor contributor) {
- _contributionMap.put(contributor.login, contributor.contributions);
- _adapter.clear();
- _adapter.addAll(getListStringFromMap());
- }
- });
- }
-
- private List getListStringFromMap() {
- List list = new ArrayList<>();
-
- for (String username : _contributionMap.keySet()) {
- String rowLog = String.format("%s [%d]", username, _contributionMap.get(username));
- list.add(rowLog);
- }
-
- return list;
- }
-
- private Observable _getCachedData() {
-
- List list = new ArrayList<>();
-
- for (String username : _contributionMap.keySet()) {
- Contributor c = new Contributor();
- c.login = username;
- c.contributions = _contributionMap.get(username);
- list.add(c);
- }
-
- return Observable.from(list);
- }
-
- private Observable _getFreshData() {
- return _createGithubApi().contributors("square", "retrofit")
- .flatMap(new Func1, Observable>() {
- @Override
- public Observable call(List contributors) {
- return Observable.from(contributors);
- }
- });
- }
-
- private GithubApi _createGithubApi() {
-
- RestAdapter.Builder builder = new RestAdapter.Builder().setEndpoint(
- "https://api.github.com/");
- //.setLogLevel(RestAdapter.LogLevel.FULL);
-
- final String githubToken = getResources().getString(R.string.github_oauth_token);
- if (!isNullOrEmpty(githubToken)) {
- builder.setRequestInterceptor(new RequestInterceptor() {
- @Override
- public void intercept(RequestFacade request) {
- request.addHeader("Authorization", format("token %s", githubToken));
- }
- });
- }
-
- return builder.build().create(GithubApi.class);
- }
-
- private void _initializeCache() {
- _contributionMap = new HashMap<>();
- _contributionMap.put("JakeWharton", 0l);
- _contributionMap.put("pforhan", 0l);
- _contributionMap.put("edenman", 0l);
- _contributionMap.put("swankjesse", 0l);
- _contributionMap.put("bruceLee", 0l);
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/RetrofitAsyncTaskDeathFragment.java b/app/src/main/java/com/morihacky/android/rxjava/RetrofitAsyncTaskDeathFragment.java
deleted file mode 100644
index a883cbe2..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/RetrofitAsyncTaskDeathFragment.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.retrofit.GithubApi;
-import com.morihacky.android.rxjava.retrofit.User;
-import java.util.ArrayList;
-import retrofit.RequestInterceptor;
-import retrofit.RestAdapter;
-import rx.Observable;
-import rx.Observer;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Func1;
-import rx.schedulers.Schedulers;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.lang.String.format;
-
-public class RetrofitAsyncTaskDeathFragment
- extends Fragment {
-
- @InjectView(R.id.btn_demo_retrofit_async_death_username) EditText _username;
- @InjectView(R.id.log_list) ListView _resultList;
-
- private GithubApi _api;
- private ArrayAdapter _adapter;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- _api = _createGithubApi();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
-
- View layout = inflater.inflate(R.layout.fragment_retrofit_async_task_death,
- container,
- false);
- ButterKnife.inject(this, layout);
-
- _adapter = new ArrayAdapter<>(getActivity(),
- R.layout.item_log,
- R.id.item_log,
- new ArrayList());
- //_adapter.setNotifyOnChange(true);
- _resultList.setAdapter(_adapter);
-
- return layout;
- }
-
- @OnClick(R.id.btn_demo_retrofit_async_death)
- public void onGetGithubUserClicked() {
- _adapter.clear();
-
- /*new AsyncTask() {
- @Override
- protected User doInBackground(String... params) {
- return _api.getUser(params[0]);
- }
-
- @Override
- protected void onPostExecute(User user) {
- _adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email));
- }
- }.execute(_username.getText().toString());*/
-
- _api.user(_username.getText().toString())
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- }
-
- @Override
- public void onError(Throwable e) {
- }
-
- @Override
- public void onNext(User user) {
- _adapter.add(format("%s = [%s: %s]",
- _username.getText(),
- user.name,
- user.email));
- }
- });
- }
-
- // -----------------------------------------------------------------------------------
-
- private GithubApi _createGithubApi() {
-
- RestAdapter.Builder builder = new RestAdapter.Builder().setEndpoint(
- "https://api.github.com/");
- //.setLogLevel(RestAdapter.LogLevel.FULL);
-
- final String githubToken = getResources().getString(R.string.github_oauth_token);
- if (!isNullOrEmpty(githubToken)) {
- builder.setRequestInterceptor(new RequestInterceptor() {
- @Override
- public void intercept(RequestFacade request) {
- request.addHeader("Authorization", format("token %s", githubToken));
- }
- });
- }
-
- return builder.build().create(GithubApi.class);
- }
-
- // -----------------------------------------------------------------------------------
-
- private class GetGithubUser
- extends AsyncTask {
-
- @Override
- protected User doInBackground(String... params) {
- return _api.getUser(params[0]);
- }
-
- @Override
- protected void onPostExecute(User user) {
- _adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email));
- }
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/RetrofitFragment.java b/app/src/main/java/com/morihacky/android/rxjava/RetrofitFragment.java
deleted file mode 100644
index 04195e3b..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/RetrofitFragment.java
+++ /dev/null
@@ -1,193 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.R;
-import com.morihacky.android.rxjava.retrofit.Contributor;
-import com.morihacky.android.rxjava.retrofit.GithubApi;
-import com.morihacky.android.rxjava.retrofit.User;
-import java.util.ArrayList;
-import java.util.List;
-import retrofit.RequestInterceptor;
-import retrofit.RestAdapter;
-import rx.Observable;
-import rx.Observer;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Func1;
-import rx.functions.Func2;
-import rx.schedulers.Schedulers;
-import timber.log.Timber;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.lang.String.format;
-
-public class RetrofitFragment
- extends Fragment {
-
- @InjectView(R.id.demo_retrofit_contributors_username) EditText _username;
- @InjectView(R.id.demo_retrofit_contributors_repository) EditText _repo;
- @InjectView(R.id.log_list) ListView _resultList;
-
- private GithubApi _api;
- private ArrayAdapter _adapter;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- _api = _createGithubApi();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
-
- View layout = inflater.inflate(R.layout.fragment_retrofit, container, false);
- ButterKnife.inject(this, layout);
-
- _adapter = new ArrayAdapter<>(getActivity(),
- R.layout.item_log,
- R.id.item_log,
- new ArrayList());
- //_adapter.setNotifyOnChange(true);
- _resultList.setAdapter(_adapter);
-
- return layout;
- }
-
- @OnClick(R.id.btn_demo_retrofit_contributors)
- public void onListContributorsClicked() {
- _adapter.clear();
-
- _api.contributors(_username.getText().toString(), _repo.getText().toString())
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer>() {
- @Override
- public void onCompleted() {
- Timber.d("Retrofit call 1 completed");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "woops we got an error while getting the list of contributors");
- }
-
- @Override
- public void onNext(List contributors) {
- for (Contributor c : contributors) {
- _adapter.add(format("%s has made %d contributions to %s",
- c.login,
- c.contributions,
- _repo.getText().toString()));
-
- Timber.d("%s has made %d contributions to %s",
- c.login,
- c.contributions,
- _repo.getText().toString());
- }
- }
- });
- }
-
- @OnClick(R.id.btn_demo_retrofit_contributors_with_user_info)
- public void onListContributorsWithFullUserInfoClicked() {
- _adapter.clear();
-
- _api.contributors(_username.getText().toString(), _repo.getText().toString())
- .flatMap(new Func1, Observable>() {
- @Override
- public Observable call(List contributors) {
- return Observable.from(contributors);
- }
- })
- .flatMap(new Func1>>() {
- @Override
- public Observable> call(Contributor contributor) {
- Observable _userObservable = _api.user(contributor.login)
- .filter(new Func1() {
- @Override
- public Boolean call(User user) {
- return !isNullOrEmpty(user.name) && !isNullOrEmpty(user.email);
- }
- });
-
- return Observable.zip(_userObservable,
- Observable.just(contributor),
- new Func2>() {
- @Override
- public Pair call(User user,
- Contributor contributor) {
- return new Pair<>(user, contributor);
- }
- });
- }
- })
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer>() {
- @Override
- public void onCompleted() {
- Timber.d("Retrofit call 2 completed ");
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e,
- "error while getting the list of contributors along with full names");
- }
-
- @Override
- public void onNext(Pair pair) {
- User user = pair.first;
- Contributor contributor = pair.second;
-
- _adapter.add(format("%s(%s) has made %d contributions to %s",
- user.name,
- user.email,
- contributor.contributions,
- _repo.getText().toString()));
-
- _adapter.notifyDataSetChanged();
-
- Timber.d("%s(%s) has made %d contributions to %s",
- user.name,
- user.email,
- contributor.contributions,
- _repo.getText().toString());
- }
- });
- }
-
- // -----------------------------------------------------------------------------------
-
- private GithubApi _createGithubApi() {
-
- RestAdapter.Builder builder = new RestAdapter.Builder().setEndpoint(
- "https://api.github.com/");
- //.setLogLevel(RestAdapter.LogLevel.FULL);
-
- final String githubToken = getResources().getString(R.string.github_oauth_token);
- if (!isNullOrEmpty(githubToken)) {
- builder.setRequestInterceptor(new RequestInterceptor() {
- @Override
- public void intercept(RequestFacade request) {
- request.addHeader("Authorization", format("token %s", githubToken));
- }
- });
- }
-
- return builder.build().create(GithubApi.class);
- }
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/TimeoutDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/TimeoutDemoFragment.java
deleted file mode 100644
index e70f6620..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/TimeoutDemoFragment.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package com.morihacky.android.rxjava;
-
-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.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.wiring.LogAdapter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import rx.Observable;
-import rx.Observer;
-import rx.Subscriber;
-import rx.Subscription;
-import rx.android.app.AppObservable;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-import timber.log.Timber;
-
-public class TimeoutDemoFragment
- extends BaseFragment {
-
- @InjectView(R.id.list_threading_log) ListView _logsList;
-
- private LogAdapter _adapter;
- private List _logs;
-
- private Subscription _subscription;
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- _subscription.unsubscribe();
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- _setupLogger();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_subject_timeout, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- @OnClick(R.id.btn_demo_timeout_1_2s)
- public void onStart2sTask() {
- _subscription = AppObservable.bindFragment(TimeoutDemoFragment.this,
- _getObservableTask_2sToComplete())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(_getEventCompletionObserver());
- }
-
- @OnClick(R.id.btn_demo_timeout_1_5s)
- public void onStart5sTask() {
- _subscription = AppObservable.bindFragment(TimeoutDemoFragment.this,
- _getObservableFor5sTask())
- .timeout(2, TimeUnit.SECONDS, _getTimeoutObservable())
- .subscribeOn(Schedulers.computation())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(_getEventCompletionObserver());
- }
-
- // -----------------------------------------------------------------------------------
- // Main Rx entities
-
- private Observable _getObservableFor5sTask() {
- return Observable.create(new Observable.OnSubscribe() {
-
- @Override
- public void call(Subscriber super String> subscriber) {
- _log(String.format("Starting a 5s task"));
- subscriber.onNext("5 s");
- try {
- Thread.sleep(1200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- subscriber.onCompleted();
- }
- });
- }
-
- private Observable _getObservableTask_2sToComplete() {
- return Observable.create(new Observable.OnSubscribe() {
-
- @Override
- public void call(Subscriber super String> subscriber) {
- _log(String.format("Starting a 2s task"));
- subscriber.onNext("2 s");
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- subscriber.onCompleted();
- }
- }).subscribeOn(Schedulers.computation()).timeout(3, TimeUnit.SECONDS);
- }
-
- private Observable extends String> _getTimeoutObservable() {
- return Observable.create(new Observable.OnSubscribe() {
-
- @Override
- public void call(Subscriber super String> subscriber) {
- _log("Timing out this task ...");
- subscriber.onCompleted();
- }
- });
- }
-
- private Observer _getEventCompletionObserver() {
- return new Observer() {
-
- @Override
- public void onCompleted() {
- _log(String.format("task was completed"));
- }
-
- @Override
- public void onError(Throwable e) {
- _log(String.format("Dang a task timeout"));
- onCompleted();
- Timber.e(e, "Timeout Demo exception");
- }
-
- @Override
- public void onNext(String taskType) {
- _log(String.format("onNext %s task", taskType));
- }
- };
- }
-
- // -----------------------------------------------------------------------------------
- // Method that help wiring up the example (irrelevant to RxJava)
-
- private void _setupLogger() {
- _logs = new ArrayList<>();
- _adapter = new LogAdapter(getActivity(), new ArrayList());
- _logsList.setAdapter(_adapter);
- }
-
- 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(new Runnable() {
-
- @Override
- public void run() {
- _adapter.clear();
- _adapter.addAll(_logs);
- }
- });
- }
- }
-
- private boolean _isCurrentlyOnMainThread() {
- return Looper.myLooper() == Looper.getMainLooper();
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/morihacky/android/rxjava/TimingDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/TimingDemoFragment.java
deleted file mode 100644
index 791684b0..00000000
--- a/app/src/main/java/com/morihacky/android/rxjava/TimingDemoFragment.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package com.morihacky.android.rxjava;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import butterknife.ButterKnife;
-import butterknife.InjectView;
-import butterknife.OnClick;
-import com.morihacky.android.rxjava.wiring.LogAdapter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import rx.Observable;
-import rx.Observer;
-import rx.Subscription;
-import timber.log.Timber;
-
-import static android.os.Looper.getMainLooper;
-import static android.os.Looper.myLooper;
-
-public class TimingDemoFragment
- extends BaseFragment {
-
- @InjectView(R.id.list_threading_log) ListView _logsList;
-
- private LogAdapter _adapter;
- private List _logs;
-
- private Subscription _subscription1 = null;
- private Subscription _subscription2 = null;
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- _setupLogger();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View layout = inflater.inflate(R.layout.fragment_demo_timing, container, false);
- ButterKnife.inject(this, layout);
- return layout;
- }
-
- // -----------------------------------------------------------------------------------
-
- @OnClick(R.id.btn_demo_timing_1)
- public void Btn1_RunSingleTaskAfter2s() {
- _log(String.format("A1 [%s] --- BTN click", _getCurrentTimestamp()));
-
- Observable.timer(2, TimeUnit.SECONDS)//
- //.just(1).delay(2, TimeUnit.SECONDS)//
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- _log(String.format("A1 [%s] XXX COMPLETE", _getCurrentTimestamp()));
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "something went wrong in TimingDemoFragment example");
- }
-
- @Override
- public void onNext(Long number) {
- _log(String.format("A1 [%s] NEXT", _getCurrentTimestamp()));
- }
- });
- }
-
- @OnClick(R.id.btn_demo_timing_2)
- public void Btn2_RunTask_IntervalOf1s() {
- if (_subscription1 != null && !_subscription1.isUnsubscribed()) {
- _subscription1.unsubscribe();
- _log(String.format("B2 [%s] XXX BTN KILLED", _getCurrentTimestamp()));
- return;
- }
-
- _log(String.format("B2 [%s] --- BTN click", _getCurrentTimestamp()));
-
- _subscription1 = Observable//
- .interval(1, TimeUnit.SECONDS)//
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- _log(String.format("B2 [%s] XXXX COMPLETE", _getCurrentTimestamp()));
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "something went wrong in TimingDemoFragment example");
- }
-
- @Override
- public void onNext(Long number) {
- _log(String.format("B2 [%s] NEXT", _getCurrentTimestamp()));
- }
- });
- }
-
- @OnClick(R.id.btn_demo_timing_3)
- public void Btn3_RunTask_IntervalOf1s_StartImmediately() {
- if (_subscription2 != null && !_subscription2.isUnsubscribed()) {
- _subscription2.unsubscribe();
- _log(String.format("C3 [%s] XXX BTN KILLED", _getCurrentTimestamp()));
- return;
- }
-
- _log(String.format("C3 [%s] --- BTN click", _getCurrentTimestamp()));
-
- _subscription2 = Observable//
- .timer(0, 1, TimeUnit.SECONDS)//
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- _log(String.format("C3 [%s] XXXX COMPLETE", _getCurrentTimestamp()));
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "something went wrong in TimingDemoFragment example");
- }
-
- @Override
- public void onNext(Long number) {
- _log(String.format("C3 [%s] NEXT", _getCurrentTimestamp()));
- }
- });
- }
-
- @OnClick(R.id.btn_demo_timing_4)
- public void Btn4_RunTask5Times_IntervalOf3s() {
- _log(String.format("D4 [%s] --- BTN click", _getCurrentTimestamp()));
-
- Observable//
- .interval(3, TimeUnit.SECONDS).take(5)//
- .subscribe(new Observer() {
- @Override
- public void onCompleted() {
- _log(String.format("D4 [%s] XXX COMPLETE", _getCurrentTimestamp()));
- }
-
- @Override
- public void onError(Throwable e) {
- Timber.e(e, "something went wrong in TimingDemoFragment example");
- }
-
- @Override
- public void onNext(Long number) {
- _log(String.format("D4 [%s] NEXT", _getCurrentTimestamp()));
- }
- });
- }
-
- // -----------------------------------------------------------------------------------
- // Method that help wiring up the example (irrelevant to RxJava)
-
- @OnClick(R.id.btn_clr)
- public void OnClearLog() {
- _logs = new ArrayList<>();
- _adapter.clear();
- }
-
- private void _setupLogger() {
- _logs = new ArrayList<>();
- _adapter = new LogAdapter(getActivity(), new ArrayList());
- _logsList.setAdapter(_adapter);
- }
-
- private void _log(String logMsg) {
- _logs.add(0, String.format(logMsg + " [MainThread: %b]", getMainLooper() == myLooper()));
-
- // You can only do below stuff on main thread.
- new Handler(getMainLooper()).post(new Runnable() {
-
- @Override
- public void run() {
- _adapter.clear();
- _adapter.addAll(_logs);
- }
- });
- }
-
- private String _getCurrentTimestamp() {
- return new SimpleDateFormat("k:m:s:S a").format(new Date());
- }
-
-}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java b/app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java
new file mode 100644
index 00000000..4ce242f3
--- /dev/null
+++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java
@@ -0,0 +1,15 @@
+package com.morihacky.android.rxjava.fragments;
+
+import android.support.v4.app.Fragment;
+import com.morihacky.android.rxjava.MyApp;
+import com.squareup.leakcanary.RefWatcher;
+
+public class BaseFragment extends Fragment {
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ RefWatcher refWatcher = MyApp.getRefWatcher();
+ refWatcher.watch(this);
+ }
+}
diff --git a/app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java b/app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java
new file mode 100644
index 00000000..bf51b85c
--- /dev/null
+++ b/app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java
@@ -0,0 +1,160 @@
+package com.morihacky.android.rxjava.fragments;
+
+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.Button;
+import android.widget.ListView;
+
+import com.jakewharton.rxbinding2.view.RxView;
+import com.morihacky.android.rxjava.R;
+import com.morihacky.android.rxjava.wiring.LogAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.observers.DisposableObserver;
+import timber.log.Timber;
+
+/**
+ * This is a demonstration of the `buffer` Observable.
+ *
+ *
The buffer observable allows taps to be collected only within a time span. So taps outside the
+ * 2s limit imposed by buffer will get accumulated in the next log statement.
+ *
+ *
If you're looking for a more foolproof solution that accumulates "continuous" taps vs a more
+ * dumb solution as show below (i.e. number of taps within a timespan) look at {@link
+ * com.morihacky.android.rxjava.rxbus.RxBusDemo_Bottom3Fragment} where a combo of `publish` and
+ * `buffer` is used.
+ *
+ *