Showing preview only (271K chars total). Download the full file or copy to clipboard to get everything.
Repository: kaushikgopal/RxJava-Android-Samples
Branch: master
Commit: b84865252c20
Files: 93
Total size: 244.1 KB
Directory structure:
gitextract_4ja9hvpa/
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── morihacky/
│ │ └── android/
│ │ └── rxjava/
│ │ └── app/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── morihacky/
│ │ └── android/
│ │ └── rxjava/
│ │ ├── MainActivity.java
│ │ ├── MyApp.java
│ │ ├── fragments/
│ │ │ ├── BaseFragment.java
│ │ │ ├── BufferDemoFragment.java
│ │ │ ├── ConcurrencyWithSchedulersDemoFragment.java
│ │ │ ├── DebounceSearchEmitterFragment.java
│ │ │ ├── DoubleBindingTextViewFragment.java
│ │ │ ├── ExponentialBackoffFragment.java
│ │ │ ├── FormValidationCombineLatestFragment.java
│ │ │ ├── MainFragment.java
│ │ │ ├── NetworkDetectorFragment.java
│ │ │ ├── PollingFragment.java
│ │ │ ├── PseudoCacheFragment.java
│ │ │ ├── PseudoCacheMergeFragment.java
│ │ │ ├── RetrofitAsyncTaskDeathFragment.java
│ │ │ ├── RetrofitFragment.java
│ │ │ ├── RotationPersist1Fragment.java
│ │ │ ├── RotationPersist1WorkerFragment.java
│ │ │ ├── RotationPersist2Fragment.java
│ │ │ ├── RotationPersist2WorkerFragment.java
│ │ │ ├── RotationPersist3Fragment.kt
│ │ │ ├── TimeoutDemoFragment.java
│ │ │ └── TimingDemoFragment.java
│ │ ├── pagination/
│ │ │ ├── PaginationAdapter.java
│ │ │ ├── PaginationAutoAdapter.java
│ │ │ ├── PaginationAutoFragment.java
│ │ │ └── PaginationFragment.java
│ │ ├── retrofit/
│ │ │ ├── Contributor.java
│ │ │ ├── GithubApi.java
│ │ │ ├── GithubService.java
│ │ │ └── User.java
│ │ ├── rxbus/
│ │ │ ├── RxBus.java
│ │ │ ├── RxBusDemoFragment.java
│ │ │ ├── RxBusDemo_Bottom1Fragment.java
│ │ │ ├── RxBusDemo_Bottom2Fragment.java
│ │ │ ├── RxBusDemo_Bottom3Fragment.java
│ │ │ └── RxBusDemo_TopFragment.java
│ │ ├── volley/
│ │ │ ├── MyVolley.java
│ │ │ └── VolleyDemoFragment.java
│ │ └── wiring/
│ │ └── LogAdapter.java
│ ├── kotlin/
│ │ └── com/
│ │ └── morihacky/
│ │ └── android/
│ │ └── rxjava/
│ │ ├── ext/
│ │ │ └── RxExt.kt
│ │ └── fragments/
│ │ ├── MulticastPlaygroundFragment.kt
│ │ ├── PlaygroundFragment.kt
│ │ └── UsingFragment.kt
│ └── res/
│ ├── drawable/
│ │ └── btn_round.xml
│ ├── layout/
│ │ ├── fragment_buffer.xml
│ │ ├── fragment_concurrency_schedulers.xml
│ │ ├── fragment_debounce.xml
│ │ ├── fragment_demo_timing.xml
│ │ ├── fragment_double_binding_textview.xml
│ │ ├── fragment_exponential_backoff.xml
│ │ ├── fragment_form_validation_comb_latest.xml
│ │ ├── fragment_main.xml
│ │ ├── fragment_multicast_playground.xml
│ │ ├── fragment_network_detector.xml
│ │ ├── fragment_pagination.xml
│ │ ├── fragment_polling.xml
│ │ ├── fragment_pseudo_cache.xml
│ │ ├── fragment_pseudo_cache_concat.xml
│ │ ├── fragment_retrofit.xml
│ │ ├── fragment_retrofit_async_task_death.xml
│ │ ├── fragment_rotation_persist.xml
│ │ ├── fragment_rxbus_bottom.xml
│ │ ├── fragment_rxbus_demo.xml
│ │ ├── fragment_rxbus_frag3.xml
│ │ ├── fragment_rxbus_top.xml
│ │ ├── fragment_subject_timeout.xml
│ │ ├── fragment_timer_demo.xml
│ │ ├── fragment_volley.xml
│ │ ├── item_btn.xml
│ │ ├── item_log.xml
│ │ └── item_log_white.xml
│ ├── menu/
│ │ └── demo.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── values-w820dp/
│ └── dimens.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
build/
# Gradle files
.gradle/
subProjects/facebook/build
# Buck files
buck-out/
.buckd/
# Intellij project files
.idea/
.idea/libraries
.idea/.name
.idea/compiler.xml
.idea/gradle.xml
.idea/modules.xml
.idea/runConfigurations.xml
.idea/vcs.xml
.idea/workspace.xml
.idea/misc.xml
gen-external-apklibs/
*.iml
*.iws
# Local configuration file (sdk path, etc)
local.properties
# Mac-specific stuff
.DS_Store
#Maven
target
release.properties
pom.xml.*
#Ant
build.xml
ant.properties
profiles_settings.xml
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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.
================================================
FILE: README.md
================================================
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)](https://kau.sh/blog/learning-rxjava-with-android-by-example/).
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)
## Examples:
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.
### 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 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](https://kau.sh/blog/debouncedbuffer-with-rxjava/).
### 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".
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.
### 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 motivation to use RxJava was from attending this talk at Netflix.
(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).
### 5. Two-way data binding for TextViews (using PublishSubject)
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.
### 6. Simple and Advanced polling (using interval and repeatWhen)
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.
There are two variants for this:
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.
The second example is basically a variant of [Exponential Backoff](https://github.com/kaushikgopal/RxJava-Android-Samples#exponential-backoff).
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/).
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).
`RetryWithDelay` code snippet courtesy:
* 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
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.
#### "Repeat" with exponential backoff
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.
We have 3 independent observables that track the text/input changes for each of the form fields (RxAndroid's `WidgetObservable` comes in handy to monitor the text changes). After an event change is noticed from **all** 3 inputs, the result is "combined" and the form is evaluated for validity.
Note that the `Func3` function that checks for validity, kicks in only after ALL 3 inputs have received a text change event.
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).
### 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.
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.
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.
Cases demonstrated here:
1. run a single task after a delay of 2s, then complete
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.
*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.*
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.
### 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<List>` 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:
I try to ensure the examples are not overly contrived but reflect a real-world usecase. If you have similar useful examples demonstrating the use of RxJava, feel free to send in a pull request.
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 <a href="https://www.yourkit.com/java/profiler/">YourKit Java Profiler</a>.
## License
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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.
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
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: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 "android.arch.lifecycle:runtime:${archComponentsVersion}"
compile "android.arch.lifecycle:extensions:${archComponentsVersion}"
kapt "android.arch.lifecycle:compiler:${archComponentsVersion}"
// ----------------------------------
// Rx dependencies
compile 'io.reactivex.rxjava2:rxjava:2.0.7'
// 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 sdkVersion
buildToolsVersion buildToolsVrs
defaultConfig {
applicationId "com.morihacky.android.rxjava"
minSdkVersion 15
targetSdkVersion sdkVersion
versionCode 2
versionName "1.2"
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
pickFirst 'META-INF/rxjava.properties'
}
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/androidTest/java/com/morihacky/android/rxjava/app/ApplicationTest.java
================================================
package com.morihacky.android.rxjava.app;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.morihacky.android.rxjava"
>
<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
>
<activity
android:name="com.morihacky.android.rxjava.MainActivity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/MainActivity.java
================================================
package com.morihacky.android.rxjava;
import android.os.Bundle;
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;
public class MainActivity extends AppCompatActivity {
private RxBus _rxBus = null;
@Override
public void onBackPressed() {
super.onBackPressed();
_removeWorkerFragments();
}
@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();
}
return _rxBus;
}
private void _removeWorkerFragments() {
Fragment frag =
getSupportFragmentManager()
.findFragmentByTag(RotationPersist1WorkerFragment.class.getName());
if (frag != null) {
getSupportFragmentManager().beginTransaction().remove(frag).commit();
}
frag =
getSupportFragmentManager()
.findFragmentByTag(RotationPersist2WorkerFragment.class.getName());
if (frag != null) {
getSupportFragmentManager().beginTransaction().remove(frag).commit();
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/MyApp.java
================================================
package com.morihacky.android.rxjava;
import android.support.multidex.MultiDexApplication;
import com.morihacky.android.rxjava.volley.MyVolley;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import timber.log.Timber;
public class MyApp extends MultiDexApplication {
private static MyApp _instance;
private RefWatcher _refWatcher;
public static MyApp get() {
return _instance;
}
public static RefWatcher getRefWatcher() {
return MyApp.get()._refWatcher;
}
@Override
public void onCreate() {
super.onCreate();
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());
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java
================================================
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);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java
================================================
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.
*
* <p>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.
*
* <p>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.
*
* <p>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 {
@BindView(R.id.list_threading_log)
ListView _logsList;
@BindView(R.id.btn_start_operation)
Button _tapBtn;
private LogAdapter _adapter;
private List<String> _logs;
private Disposable _disposable;
private Unbinder unbinder;
@Override
public void onResume() {
super.onResume();
_disposable = _getBufferedDisposable();
}
@Override
public void onPause() {
super.onPause();
_disposable.dispose();
}
@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);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
// -----------------------------------------------------------------------------------
// Main Rx entities
private Disposable _getBufferedDisposable() {
return RxView.clicks(_tapBtn)
.map(
onClickEvent -> {
Timber.d("--------- GOT A TAP");
_log("GOT A TAP");
return 1;
})
.buffer(2, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(
new DisposableObserver<List<Integer>>() {
@Override
public void onComplete() {
// 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<Integer> 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(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
private boolean _isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/ConcurrencyWithSchedulersDemoFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class ConcurrencyWithSchedulersDemoFragment extends BaseFragment {
@BindView(R.id.progress_operation_running)
ProgressBar _progress;
@BindView(R.id.list_threading_log)
ListView _logsList;
private LogAdapter _adapter;
private List<String> _logs;
private CompositeDisposable _disposables = new CompositeDisposable();
private Unbinder unbinder;
@Override
public void onDestroy() {
super.onDestroy();
unbinder.unbind();
_disposables.clear();
}
@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);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@OnClick(R.id.btn_start_operation)
public void startLongOperation() {
_progress.setVisibility(View.VISIBLE);
_log("Button Clicked");
DisposableObserver<Boolean> d = _getDisposableObserver();
_getObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(d);
_disposables.add(d);
}
private Observable<Boolean> _getObservable() {
return Observable.just(true)
.map(
aBoolean -> {
_log("Within Observable");
_doSomeLongOperation_thatBlocksCurrentThread();
return aBoolean;
});
}
/**
* Observer that handles the result through the 3 important actions:
*
* <p>1. onCompleted 2. onError 3. onNext
*/
private DisposableObserver<Boolean> _getDisposableObserver() {
return new DisposableObserver<Boolean>() {
@Override
public void onComplete() {
_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(
() -> {
_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<String> {
public LogAdapter(Context context, List<String> logs) {
super(context, R.layout.item_log, R.id.item_log, logs);
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/DebounceSearchEmitterFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import com.jakewharton.rxbinding2.widget.RxTextView;
import com.jakewharton.rxbinding2.widget.TextViewTextChangeEvent;
import com.morihacky.android.rxjava.R;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableObserver;
import timber.log.Timber;
import static co.kaush.core.util.CoreNullnessUtils.isNotNullOrEmpty;
import static java.lang.String.format;
public class DebounceSearchEmitterFragment extends BaseFragment {
@BindView(R.id.list_threading_log)
ListView _logsList;
@BindView(R.id.input_txt_debounce)
EditText _inputSearchText;
private LogAdapter _adapter;
private List<String> _logs;
private Disposable _disposable;
private Unbinder unbinder;
@Override
public void onDestroy() {
super.onDestroy();
_disposable.dispose();
unbinder.unbind();
}
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_debounce, container, false);
unbinder = ButterKnife.bind(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();
_disposable =
RxTextView.textChangeEvents(_inputSearchText)
.debounce(400, TimeUnit.MILLISECONDS) // default Scheduler is Computation
.filter(changes -> isNotNullOrEmpty(changes.text().toString()))
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(_getSearchObserver());
}
// -----------------------------------------------------------------------------------
// Main Rx entities
private DisposableObserver<TextViewTextChangeEvent> _getSearchObserver() {
return new DisposableObserver<TextViewTextChangeEvent>() {
@Override
public void onComplete() {
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(TextViewTextChangeEvent 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(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
private boolean _isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
private class LogAdapter extends ArrayAdapter<String> {
public LogAdapter(Context context, List<String> logs) {
super(context, R.layout.item_log, R.id.item_log, logs);
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/DoubleBindingTextViewFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
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.BindView;
import butterknife.ButterKnife;
import butterknife.OnTextChanged;
import com.morihacky.android.rxjava.R;
import butterknife.Unbinder;
import io.reactivex.disposables.Disposable;
import io.reactivex.processors.PublishProcessor;
import static android.text.TextUtils.isEmpty;
public class DoubleBindingTextViewFragment extends BaseFragment {
@BindView(R.id.double_binding_num1)
EditText _number1;
@BindView(R.id.double_binding_num2)
EditText _number2;
@BindView(R.id.double_binding_result)
TextView _result;
Disposable _disposable;
PublishProcessor<Float> _resultEmitterSubject;
private Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_double_binding_textview, container, false);
unbinder = ButterKnife.bind(this, layout);
_resultEmitterSubject = PublishProcessor.create();
_disposable =
_resultEmitterSubject.subscribe(
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 (!isEmpty(_number1.getText().toString())) {
num1 = Float.parseFloat(_number1.getText().toString());
}
if (!isEmpty(_number2.getText().toString())) {
num2 = Float.parseFloat(_number2.getText().toString());
}
_resultEmitterSubject.onNext(num1 + num2);
}
@Override
public void onDestroyView() {
super.onDestroyView();
_disposable.dispose();
unbinder.unbind();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/ExponentialBackoffFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
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.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import butterknife.Unbinder;
import hu.akarnokd.rxjava2.math.MathFlowable;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Function;
import io.reactivex.subscribers.DisposableSubscriber;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.reactivestreams.Publisher;
import timber.log.Timber;
import static android.os.Looper.getMainLooper;
public class ExponentialBackoffFragment extends BaseFragment {
@BindView(R.id.list_threading_log)
ListView _logList;
private LogAdapter _adapter;
private CompositeDisposable _disposables = new CompositeDisposable();
private List<String> _logs;
Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_exponential_backoff, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_setupLogger();
}
@Override
public void onPause() {
super.onPause();
_disposables.clear();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
// -----------------------------------------------------------------------------------
@OnClick(R.id.btn_eb_retry)
public void startRetryingWithExponentialBackoffStrategy() {
_logs = new ArrayList<>();
_adapter.clear();
DisposableSubscriber<Object> disposableSubscriber =
new DisposableSubscriber<Object>() {
@Override
public void onNext(Object aVoid) {
Timber.d("on Next");
}
@Override
public void onComplete() {
Timber.d("on Completed");
}
@Override
public void onError(Throwable e) {
_log("Error: I give up!");
}
};
Flowable.error(new RuntimeException("testing")) // always fails
.retryWhen(new RetryWithDelay(5, 1000)) // notice this is called only onError (onNext
// values sent are ignored)
.doOnSubscribe(subscription -> _log("Attempting the impossible 5 times in intervals of 1s"))
.subscribe(disposableSubscriber);
_disposables.add(disposableSubscriber);
}
@OnClick(R.id.btn_eb_delay)
public void startExecutingWithExponentialBackoffDelay() {
_logs = new ArrayList<>();
_adapter.clear();
DisposableSubscriber<Integer> disposableSubscriber =
new DisposableSubscriber<Integer>() {
@Override
public void onNext(Integer integer) {
Timber.d("executing Task %d [xx:%02d]", integer, _getSecondHand());
_log(String.format("executing Task %d [xx:%02d]", integer, _getSecondHand()));
}
@Override
public void onError(Throwable e) {
Timber.d(e, "arrrr. Error");
_log("Error");
}
@Override
public void onComplete() {
Timber.d("onCompleted");
_log("Completed");
}
};
Flowable.range(1, 4)
.delay(
integer -> {
// Rx-y way of doing the Fibonnaci :P
return MathFlowable.sumInt(Flowable.range(1, integer))
.flatMap(
targetSecondDelay ->
Flowable.just(integer).delay(targetSecondDelay, TimeUnit.SECONDS));
})
.doOnSubscribe(
s ->
_log(
String.format(
"Execute 4 tasks with delay - time now: [xx:%02d]", _getSecondHand())))
.subscribe(disposableSubscriber);
_disposables.add(disposableSubscriber);
}
// -----------------------------------------------------------------------------------
private int _getSecondHand() {
long millis = System.currentTimeMillis();
return (int)
(TimeUnit.MILLISECONDS.toSeconds(millis)
- TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
// -----------------------------------------------------------------------------------
private void _setupLogger() {
_logs = new ArrayList<>();
_adapter = new LogAdapter(getActivity(), new ArrayList<>());
_logList.setAdapter(_adapter);
}
private void _log(String logMsg) {
_logs.add(logMsg);
// You can only do below stuff on main thread.
new Handler(getMainLooper())
.post(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
// -----------------------------------------------------------------------------------
// CAUTION:
// --------------------------------------
// THIS notificationHandler class HAS NO BUSINESS BEING non-static
// I ONLY did this cause i wanted access to the `_log` method from inside here
// for the purpose of demonstration. In the real world, make it static and LET IT BE!!
// It's 12am in the morning and i feel lazy dammit !!!
//public static class RetryWithDelay
public class RetryWithDelay implements Function<Flowable<? extends Throwable>, Publisher<?>> {
private final int _maxRetries;
private final int _retryDelayMillis;
private int _retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
_maxRetries = maxRetries;
_retryDelayMillis = retryDelayMillis;
_retryCount = 0;
}
// this is a notificationhandler, all that is cared about here
// is the emission "type" not emission "content"
// only onNext triggers a re-subscription (onError + onComplete kills it)
@Override
public Publisher<?> apply(Flowable<? extends Throwable> inputObservable) {
// it is critical to use inputObservable in the chain for the result
// ignoring it and doing your own thing will break the sequence
return inputObservable.flatMap(
new Function<Throwable, Publisher<?>>() {
@Override
public Publisher<?> apply(Throwable throwable) {
if (++_retryCount < _maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed)
Timber.d("Retrying in %d ms", _retryCount * _retryDelayMillis);
_log(String.format("Retrying in %d ms", _retryCount * _retryDelayMillis));
return Flowable.timer(_retryCount * _retryDelayMillis, TimeUnit.MILLISECONDS);
}
Timber.d("Argh! i give up");
// Max retries hit. Pass an error so the chain is forcibly completed
// only onNext triggers a re-subscription (onError + onComplete kills it)
return Flowable.error(throwable);
}
});
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import static android.text.TextUtils.isEmpty;
import static android.util.Patterns.EMAIL_ADDRESS;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import com.jakewharton.rxbinding2.widget.RxTextView;
import com.morihacky.android.rxjava.R;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.subscribers.DisposableSubscriber;
import timber.log.Timber;
public class FormValidationCombineLatestFragment extends BaseFragment {
@BindView(R.id.btn_demo_form_valid)
TextView _btnValidIndicator;
@BindView(R.id.demo_combl_email)
EditText _email;
@BindView(R.id.demo_combl_password)
EditText _password;
@BindView(R.id.demo_combl_num)
EditText _number;
private DisposableSubscriber<Boolean> _disposableObserver = null;
private Flowable<CharSequence> _emailChangeObservable;
private Flowable<CharSequence> _numberChangeObservable;
private Flowable<CharSequence> _passwordChangeObservable;
private Unbinder unbinder;
@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);
unbinder = ButterKnife.bind(this, layout);
_emailChangeObservable =
RxTextView.textChanges(_email).skip(1).toFlowable(BackpressureStrategy.LATEST);
_passwordChangeObservable =
RxTextView.textChanges(_password).skip(1).toFlowable(BackpressureStrategy.LATEST);
_numberChangeObservable =
RxTextView.textChanges(_number).skip(1).toFlowable(BackpressureStrategy.LATEST);
_combineLatestEvents();
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
_disposableObserver.dispose();
}
private void _combineLatestEvents() {
_disposableObserver =
new DisposableSubscriber<Boolean>() {
@Override
public void onNext(Boolean formValid) {
if (formValid) {
_btnValidIndicator.setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.blue));
} else {
_btnValidIndicator.setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.gray));
}
}
@Override
public void onError(Throwable e) {
Timber.e(e, "there was an error");
}
@Override
public void onComplete() {
Timber.d("completed");
}
};
Flowable.combineLatest(
_emailChangeObservable,
_passwordChangeObservable,
_numberChangeObservable,
(newEmail, newPassword, newNumber) -> {
boolean emailValid = !isEmpty(newEmail) && EMAIL_ADDRESS.matcher(newEmail).matches();
if (!emailValid) {
_email.setError("Invalid Email!");
}
boolean passValid = !isEmpty(newPassword) && newPassword.length() > 8;
if (!passValid) {
_password.setError("Invalid Password!");
}
boolean numValid = !isEmpty(newNumber);
if (numValid) {
int num = Integer.parseInt(newNumber.toString());
numValid = num > 0 && num <= 100;
}
if (!numValid) {
_number.setError("Invalid Number!");
}
return emailValid && passValid && numValid;
})
.subscribe(_disposableObserver);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.pagination.PaginationAutoFragment;
import com.morihacky.android.rxjava.rxbus.RxBusDemoFragment;
import com.morihacky.android.rxjava.volley.VolleyDemoFragment;
public class MainFragment extends BaseFragment {
private Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_main, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick(R.id.btn_demo_schedulers)
void demoConcurrencyWithSchedulers() {
clickedOn(new ConcurrencyWithSchedulersDemoFragment());
}
@OnClick(R.id.btn_demo_buffer)
void demoBuffer() {
clickedOn(new BufferDemoFragment());
}
@OnClick(R.id.btn_demo_debounce)
void demoThrottling() {
clickedOn(new DebounceSearchEmitterFragment());
}
@OnClick(R.id.btn_demo_retrofit)
void demoRetrofitCalls() {
clickedOn(new RetrofitFragment());
}
@OnClick(R.id.btn_demo_polling)
void demoPolling() {
clickedOn(new PollingFragment());
}
@OnClick(R.id.btn_demo_double_binding_textview)
void demoDoubleBindingWithPublishSubject() {
clickedOn(new DoubleBindingTextViewFragment());
}
@OnClick(R.id.btn_demo_rxbus)
void demoRxBus() {
clickedOn(new RxBusDemoFragment());
}
@OnClick(R.id.btn_demo_form_validation_combinel)
void formValidation() {
clickedOn(new FormValidationCombineLatestFragment());
}
@OnClick(R.id.btn_demo_pseudo_cache)
void pseudoCacheDemo() {
clickedOn(new PseudoCacheFragment());
}
@OnClick(R.id.btn_demo_timing)
void demoTimerIntervalDelays() {
clickedOn(new TimingDemoFragment());
}
@OnClick(R.id.btn_demo_timeout)
void demoTimeout() {
clickedOn(new TimeoutDemoFragment());
}
@OnClick(R.id.btn_demo_exponential_backoff)
void demoExponentialBackoff() {
clickedOn(new ExponentialBackoffFragment());
}
@OnClick(R.id.btn_demo_rotation_persist)
void demoRotationPersist() {
clickedOn(new RotationPersist3Fragment());
// clickedOn(new RotationPersist2Fragment());
// clickedOn(new RotationPersist1Fragment());
}
@OnClick(R.id.btn_demo_pagination)
void demoPaging() {
clickedOn(new PaginationAutoFragment());
//clickedOn(new PaginationFragment());
}
@OnClick(R.id.btn_demo_volley)
void demoVolleyRequest() {
clickedOn(new VolleyDemoFragment());
}
@OnClick(R.id.btn_demo_networkDetector)
void demoNetworkDetector() {
clickedOn(new NetworkDetectorFragment());
}
@OnClick(R.id.btn_demo_using)
void demoUsing() {
clickedOn(new UsingFragment());
}
@OnClick(R.id.btn_demo_multicastPlayground)
void demoMulticastPlayground() {
clickedOn(new MulticastPlaygroundFragment());
}
private void clickedOn(@NonNull Fragment fragment) {
final String tag = fragment.getClass().toString();
getActivity()
.getSupportFragmentManager()
.beginTransaction()
.addToBackStack(tag)
.replace(android.R.id.content, fragment, tag)
.commit();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/NetworkDetectorFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
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.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.R;
import butterknife.Unbinder;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.processors.PublishProcessor;
import java.util.ArrayList;
import java.util.List;
public class NetworkDetectorFragment extends BaseFragment {
@BindView(R.id.list_threading_log)
ListView logsList;
private LogAdapter adapter;
private BroadcastReceiver broadcastReceiver;
private List<String> logs;
private Disposable disposable;
private PublishProcessor<Boolean> publishProcessor;
private Unbinder unbinder;
@Override
public void onDestroy() {
super.onDestroy();
unbinder.unbind();
}
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_network_detector, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setupLogger();
}
@Override
public void onStart() {
super.onStart();
publishProcessor = PublishProcessor.create();
disposable =
publishProcessor
.startWith(getConnectivityStatus(getActivity()))
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
online -> {
if (online) {
log("You are online");
} else {
log("You are offline");
}
});
listenToNetworkConnectivity();
}
@Override
public void onStop() {
super.onStop();
disposable.dispose();
getActivity().unregisterReceiver(broadcastReceiver);
}
private void listenToNetworkConnectivity() {
broadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
publishProcessor.onNext(getConnectivityStatus(context));
}
};
final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(broadcastReceiver, intentFilter);
}
private boolean getConnectivityStatus(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
// -----------------------------------------------------------------------------------
// Method that help wiring up the example (irrelevant to RxJava)
private void log(String logMsg) {
if (isCurrentlyOnMainThread()) {
logs.add(0, logMsg + " (main thread) ");
adapter.clear();
adapter.addAll(logs);
} else {
logs.add(0, logMsg + " (NOT main thread) ");
// You can only do below stuff on main thread.
new Handler(Looper.getMainLooper())
.post(
() -> {
adapter.clear();
adapter.addAll(logs);
});
}
}
private void setupLogger() {
logs = new ArrayList<>();
adapter = new LogAdapter(getActivity(), new ArrayList<>());
logsList.setAdapter(adapter);
}
private boolean isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
private class LogAdapter extends ArrayAdapter<String> {
public LogAdapter(Context context, List<String> logs) {
super(context, R.layout.item_log, R.id.item_log, logs);
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PollingFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import butterknife.Unbinder;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.reactivestreams.Publisher;
import timber.log.Timber;
public class PollingFragment extends BaseFragment {
private static final int INITIAL_DELAY = 0;
private static final int POLLING_INTERVAL = 1000;
private static final int POLL_COUNT = 8;
@BindView(R.id.list_threading_log)
ListView _logsList;
private LogAdapter _adapter;
private int _counter = 0;
private CompositeDisposable _disposables;
private List<String> _logs;
private Unbinder unbinder;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_disposables = new CompositeDisposable();
}
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_polling, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_setupLogger();
}
@Override
public void onDestroy() {
super.onDestroy();
_disposables.clear();
unbinder.unbind();
}
@OnClick(R.id.btn_start_simple_polling)
public void onStartSimplePollingClicked() {
final int pollCount = POLL_COUNT;
Disposable d =
Flowable.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(this::_doNetworkCallAndGetStringResult)
.take(pollCount)
.doOnSubscribe(
subscription -> {
_log(String.format("Start simple polling - %s", _counter));
})
.subscribe(
taskName -> {
_log(
String.format(
Locale.US,
"Executing polled task [%s] now time : [xx:%02d]",
taskName,
_getSecondHand()));
});
_disposables.add(d);
}
@OnClick(R.id.btn_start_increasingly_delayed_polling)
public void onStartIncreasinglyDelayedPolling() {
_setupLogger();
final int pollingInterval = POLLING_INTERVAL;
final int pollCount = POLL_COUNT;
_log(
String.format(
Locale.US, "Start increasingly delayed polling now time: [xx:%02d]", _getSecondHand()));
_disposables.add(
Flowable.just(1L)
.repeatWhen(new RepeatWithDelay(pollCount, pollingInterval))
.subscribe(
o ->
_log(
String.format(
Locale.US,
"Executing polled task now time : [xx:%02d]",
_getSecondHand())),
e -> Timber.d(e, "arrrr. Error")));
}
// -----------------------------------------------------------------------------------
// CAUTION:
// --------------------------------------
// THIS notificationHandler class HAS NO BUSINESS BEING non-static
// I ONLY did this cause i wanted access to the `_log` method from inside here
// for the purpose of demonstration. In the real world, make it static and LET IT BE!!
// It's 12am in the morning and i feel lazy dammit !!!
private String _doNetworkCallAndGetStringResult(long attempt) {
try {
if (attempt == 4) {
// randomly make one event super long so we test that the repeat logic waits
// and accounts for this.
Thread.sleep(9000);
} else {
Thread.sleep(3000);
}
} catch (InterruptedException e) {
Timber.d("Operation was interrupted");
}
_counter++;
return String.valueOf(_counter);
}
// -----------------------------------------------------------------------------------
// Method that help wiring up the example (irrelevant to RxJava)
private int _getSecondHand() {
long millis = System.currentTimeMillis();
return (int)
(TimeUnit.MILLISECONDS.toSeconds(millis)
- TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
private void _log(String logMsg) {
if (_isCurrentlyOnMainThread()) {
_logs.add(0, logMsg + " (main thread) ");
_adapter.clear();
_adapter.addAll(_logs);
} else {
_logs.add(0, logMsg + " (NOT main thread) ");
// You can only do below stuff on main thread.
new Handler(Looper.getMainLooper())
.post(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
private void _setupLogger() {
_logs = new ArrayList<>();
_adapter = new LogAdapter(getActivity(), new ArrayList<>());
_logsList.setAdapter(_adapter);
_counter = 0;
}
private boolean _isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
//public static class RepeatWithDelay
public class RepeatWithDelay implements Function<Flowable<Object>, Publisher<Long>> {
private final int _repeatLimit;
private final int _pollingInterval;
private int _repeatCount = 1;
RepeatWithDelay(int repeatLimit, int pollingInterval) {
_pollingInterval = pollingInterval;
_repeatLimit = repeatLimit;
}
// this is a notificationhandler, all we care about is
// the emission "type" not emission "content"
// only onNext triggers a re-subscription
@Override
public Publisher<Long> apply(Flowable<Object> inputFlowable) throws Exception {
// it is critical to use inputObservable in the chain for the result
// ignoring it and doing your own thing will break the sequence
return inputFlowable.flatMap(
new Function<Object, Publisher<Long>>() {
@Override
public Publisher<Long> apply(Object o) throws Exception {
if (_repeatCount >= _repeatLimit) {
// terminate the sequence cause we reached the limit
_log("Completing sequence");
return Flowable.empty();
}
// since we don't get an input
// we store state in this handler to tell us the point of time we're firing
_repeatCount++;
return Flowable.timer(_repeatCount * _pollingInterval, TimeUnit.MILLISECONDS);
}
});
}
}
private class LogAdapter extends ArrayAdapter<String> {
public LogAdapter(Context context, List<String> logs) {
super(context, R.layout.item_log, R.id.item_log, logs);
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheFragment.java
================================================
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.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
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.GithubService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class PseudoCacheFragment extends BaseFragment {
@BindView(R.id.info_pseudoCache_demo)
TextView infoText;
@BindView(R.id.info_pseudoCache_listSubscription)
ListView listSubscriptionInfo;
@BindView(R.id.info_pseudoCache_listDtl)
ListView listDetail;
private ArrayAdapter<String> adapterDetail, adapterSubscriptionInfo;
private HashMap<String, Long> contributionMap = null;
private Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_pseudo_cache, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick(R.id.btn_pseudoCache_concat)
public void onConcatBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_concat);
wireupDemo();
Observable.concat(getSlowCachedDiskData(), getFreshNetworkData())
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
@OnClick(R.id.btn_pseudoCache_concatEager)
public void onConcatEagerBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_concatEager);
wireupDemo();
List<Observable<Contributor>> observables = new ArrayList<>(2);
observables.add(getSlowCachedDiskData());
observables.add(getFreshNetworkData());
Observable.concatEager(observables)
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
@OnClick(R.id.btn_pseudoCache_merge)
public void onMergeBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_merge);
wireupDemo();
Observable.merge(getCachedDiskData(), getFreshNetworkData())
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
@OnClick(R.id.btn_pseudoCache_mergeSlowDisk)
public void onMergeSlowBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_mergeSlowDisk);
wireupDemo();
Observable.merge(getSlowCachedDiskData(), getFreshNetworkData())
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
@OnClick(R.id.btn_pseudoCache_mergeOptimized)
public void onMergeOptimizedBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_mergeOptimized);
wireupDemo();
getFreshNetworkData() //
.publish(
network -> //
Observable.merge(
network, //
getCachedDiskData().takeUntil(network)))
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
@OnClick(R.id.btn_pseudoCache_mergeOptimizedSlowDisk)
public void onMergeOptimizedWithSlowDiskBtnClicked() {
infoText.setText(R.string.msg_pseudoCache_demoInfo_mergeOptimizedSlowDisk);
wireupDemo();
getFreshNetworkData() //
.publish(
network -> //
Observable.merge(
network, //
getSlowCachedDiskData().takeUntil(network)))
.subscribeOn(Schedulers.io()) // we want to add a list item at time of subscription
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Contributor>() {
@Override
public void onComplete() {
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);
adapterDetail.clear();
adapterDetail.addAll(mapAsList(contributionMap));
}
});
}
// -----------------------------------------------------------------------------------
// WIRING for example
private void wireupDemo() {
contributionMap = new HashMap<>();
adapterDetail =
new ArrayAdapter<>(
getActivity(), R.layout.item_log_white, R.id.item_log, new ArrayList<>());
listDetail.setAdapter(adapterDetail);
adapterSubscriptionInfo =
new ArrayAdapter<>(
getActivity(), R.layout.item_log_white, R.id.item_log, new ArrayList<>());
listSubscriptionInfo.setAdapter(adapterSubscriptionInfo);
}
private Observable<Contributor> getSlowCachedDiskData() {
return Observable.timer(1, TimeUnit.SECONDS).flatMap(dummy -> getCachedDiskData());
}
private Observable<Contributor> getCachedDiskData() {
List<Contributor> list = new ArrayList<>();
Map<String, Long> map = dummyDiskData();
for (String username : map.keySet()) {
Contributor c = new Contributor();
c.login = username;
c.contributions = map.get(username);
list.add(c);
}
return Observable.fromIterable(list) //
.doOnSubscribe(
(data) ->
new Handler(Looper.getMainLooper()) //
.post(() -> adapterSubscriptionInfo.add("(disk) cache subscribed"))) //
.doOnComplete(
() ->
new Handler(Looper.getMainLooper()) //
.post(() -> adapterSubscriptionInfo.add("(disk) cache completed")));
}
private Observable<Contributor> getFreshNetworkData() {
String githubToken = getResources().getString(R.string.github_oauth_token);
GithubApi githubService = GithubService.createGithubService(githubToken);
return githubService
.contributors("square", "retrofit")
.flatMap(Observable::fromIterable)
.doOnSubscribe(
(data) ->
new Handler(Looper.getMainLooper()) //
.post(() -> adapterSubscriptionInfo.add("(network) subscribed"))) //
.doOnComplete(
() ->
new Handler(Looper.getMainLooper()) //
.post(() -> adapterSubscriptionInfo.add("(network) completed")));
}
private List<String> mapAsList(HashMap<String, Long> map) {
List<String> list = new ArrayList<>();
for (String username : map.keySet()) {
String rowLog = String.format("%s [%d]", username, contributionMap.get(username));
list.add(rowLog);
}
return list;
}
private Map<String, Long> dummyDiskData() {
Map<String, Long> map = new HashMap<>();
map.put("JakeWharton", 0L);
map.put("pforhan", 0L);
map.put("edenman", 0L);
map.put("swankjesse", 0L);
map.put("bruceLee", 0L);
return map;
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheMergeFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
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.GithubService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class PseudoCacheMergeFragment extends BaseFragment {
@BindView(R.id.log_list)
ListView _resultList;
private ArrayAdapter<String> _adapter;
private HashMap<String, Long> _contributionMap = null;
private HashMap<Contributor, Long> _resultAgeMap = new HashMap<>();
private Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_pseudo_cache_concat, container, false);
unbinder = ButterKnife.bind(this, layout);
_initializeCache();
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@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.merge(_getCachedData(), _getFreshData())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<Pair<Contributor, Long>>() {
@Override
public void onComplete() {
Timber.d("done loading all data");
}
@Override
public void onError(Throwable e) {
Timber.e(e, "arr something went wrong");
}
@Override
public void onNext(Pair<Contributor, Long> contributorAgePair) {
Contributor contributor = contributorAgePair.first;
if (_resultAgeMap.containsKey(contributor)
&& _resultAgeMap.get(contributor) > contributorAgePair.second) {
return;
}
_contributionMap.put(contributor.login, contributor.contributions);
_resultAgeMap.put(contributor, contributorAgePair.second);
_adapter.clear();
_adapter.addAll(getListStringFromMap());
}
});
}
private List<String> getListStringFromMap() {
List<String> 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<Pair<Contributor, Long>> _getCachedData() {
List<Pair<Contributor, Long>> list = new ArrayList<>();
Pair<Contributor, Long> dataWithAgePair;
for (String username : _contributionMap.keySet()) {
Contributor c = new Contributor();
c.login = username;
c.contributions = _contributionMap.get(username);
dataWithAgePair = new Pair<>(c, System.currentTimeMillis());
list.add(dataWithAgePair);
}
return Observable.fromIterable(list);
}
private Observable<Pair<Contributor, Long>> _getFreshData() {
String githubToken = getResources().getString(R.string.github_oauth_token);
GithubApi githubService = GithubService.createGithubService(githubToken);
return githubService
.contributors("square", "retrofit")
.flatMap(Observable::fromIterable)
.map(contributor -> new Pair<>(contributor, System.currentTimeMillis()));
}
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);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitAsyncTaskDeathFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
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 com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.retrofit.GithubApi;
import com.morihacky.android.rxjava.retrofit.GithubService;
import com.morihacky.android.rxjava.retrofit.User;
import java.util.ArrayList;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import static java.lang.String.format;
public class RetrofitAsyncTaskDeathFragment extends Fragment {
@BindView(R.id.btn_demo_retrofit_async_death_username)
EditText _username;
@BindView(R.id.log_list)
ListView _resultList;
private GithubApi _githubService;
private ArrayAdapter<String> _adapter;
private Unbinder unbinder;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String githubToken = getResources().getString(R.string.github_oauth_token);
_githubService = GithubService.createGithubService(githubToken);
}
@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);
unbinder = ButterKnife.bind(this, layout);
_adapter =
new ArrayAdapter<>(getActivity(), R.layout.item_log, R.id.item_log, new ArrayList<>());
//_adapter.setNotifyOnChange(true);
_resultList.setAdapter(_adapter);
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@OnClick(R.id.btn_demo_retrofit_async_death)
public void onGetGithubUserClicked() {
_adapter.clear();
/*new AsyncTask<String, Void, User>() {
@Override
protected User doInBackground(String... params) {
return _githubService.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());*/
_githubService
.user(_username.getText().toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new DisposableObserver<User>() {
@Override
public void onComplete() {}
@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 class GetGithubUser extends AsyncTask<String, Void, User> {
@Override
protected User doInBackground(String... params) {
return _githubService.getUser(params[0]);
}
@Override
protected void onPostExecute(User user) {
_adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email));
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
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 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.GithubService;
import com.morihacky.android.rxjava.retrofit.User;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static android.text.TextUtils.isEmpty;
import static java.lang.String.format;
public class RetrofitFragment extends Fragment {
@BindView(R.id.demo_retrofit_contributors_username)
EditText _username;
@BindView(R.id.demo_retrofit_contributors_repository)
EditText _repo;
@BindView(R.id.log_list)
ListView _resultList;
private ArrayAdapter<String> _adapter;
private GithubApi _githubService;
private CompositeDisposable _disposables;
private Unbinder unbinder;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String githubToken = getResources().getString(R.string.github_oauth_token);
_githubService = GithubService.createGithubService(githubToken);
_disposables = new CompositeDisposable();
}
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_retrofit, container, false);
unbinder = ButterKnife.bind(this, layout);
_adapter =
new ArrayAdapter<>(getActivity(), R.layout.item_log, R.id.item_log, new ArrayList<>());
//_adapter.setNotifyOnChange(true);
_resultList.setAdapter(_adapter);
return layout;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override
public void onDestroy() {
super.onDestroy();
_disposables.dispose();
}
@OnClick(R.id.btn_demo_retrofit_contributors)
public void onListContributorsClicked() {
_adapter.clear();
_disposables.add( //
_githubService
.contributors(_username.getText().toString(), _repo.getText().toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(
new DisposableObserver<List<Contributor>>() {
@Override
public void onComplete() {
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<Contributor> 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();
_disposables.add(
_githubService
.contributors(_username.getText().toString(), _repo.getText().toString())
.flatMap(Observable::fromIterable)
.flatMap(
contributor -> {
Observable<User> _userObservable =
_githubService
.user(contributor.login)
.filter(user -> !isEmpty(user.name) && !isEmpty(user.email));
return Observable.zip(_userObservable, Observable.just(contributor), Pair::new);
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(
new DisposableObserver<Pair<User, Contributor>>() {
@Override
public void onComplete() {
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<User, Contributor> 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());
}
}));
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1Fragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import static android.os.Looper.getMainLooper;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subscribers.DisposableSubscriber;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class RotationPersist1Fragment extends BaseFragment
implements RotationPersist1WorkerFragment.IAmYourMaster {
public static final String TAG = RotationPersist1Fragment.class.toString();
@BindView(R.id.list_threading_log)
ListView _logList;
private LogAdapter _adapter;
private List<String> _logs;
private Unbinder unbinder;
private CompositeDisposable _disposables = new CompositeDisposable();
// -----------------------------------------------------------------------------------
@OnClick(R.id.btn_rotate_persist)
public void startOperationFromWorkerFrag() {
_logs = new ArrayList<>();
_adapter.clear();
FragmentManager fm = getActivity().getSupportFragmentManager();
RotationPersist1WorkerFragment frag =
(RotationPersist1WorkerFragment) fm.findFragmentByTag(RotationPersist1WorkerFragment.TAG);
if (frag == null) {
frag = new RotationPersist1WorkerFragment();
fm.beginTransaction().add(frag, RotationPersist1WorkerFragment.TAG).commit();
} else {
Timber.d("Worker frag already spawned");
}
}
@Override
public void observeResults(Flowable<Integer> intsFlowable) {
DisposableSubscriber<Integer> d =
new DisposableSubscriber<Integer>() {
@Override
public void onNext(Integer integer) {
_log(String.format("Worker frag spits out - %d", integer));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "Error in worker demo frag observable");
_log("Dang! something went wrong.");
}
@Override
public void onComplete() {
_log("Observable is complete");
}
};
intsFlowable
.doOnSubscribe(
subscription -> {
_log("Subscribing to intsObservable");
})
.subscribe(d);
_disposables.add(d);
}
// -----------------------------------------------------------------------------------
// Boilerplate
// -----------------------------------------------------------------------------------
@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_rotation_persist, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onPause() {
super.onPause();
_disposables.clear();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
private void _setupLogger() {
_logs = new ArrayList<>();
_adapter = new LogAdapter(getActivity(), new ArrayList<>());
_logList.setAdapter(_adapter);
}
private void _log(String logMsg) {
_logs.add(0, logMsg);
// You can only do below stuff on main thread.
new Handler(getMainLooper())
.post(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.morihacky.android.rxjava.MainActivity;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
import io.reactivex.flowables.ConnectableFlowable;
import java.util.concurrent.TimeUnit;
public class RotationPersist1WorkerFragment extends Fragment {
public static final String TAG = RotationPersist1WorkerFragment.class.toString();
private IAmYourMaster _masterFrag;
private ConnectableFlowable<Integer> _storedIntsFlowable;
private Disposable _storedIntsDisposable;
/**
* Hold a reference to the activity -> caller fragment this way when the worker frag kicks off we
* can talk back to the master and send results
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
_masterFrag =
(RotationPersist1Fragment)
((MainActivity) context)
.getSupportFragmentManager()
.findFragmentByTag(RotationPersist1Fragment.TAG);
if (_masterFrag == null) {
throw new ClassCastException("We did not find a master who can understand us :(");
}
}
/** This method will only be called once when the retained Fragment is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
if (_storedIntsFlowable != null) {
return;
}
Flowable<Integer> intsObservable =
Flowable.interval(1, TimeUnit.SECONDS).map(Long::intValue).take(20);
_storedIntsFlowable = intsObservable.publish();
_storedIntsDisposable = _storedIntsFlowable.connect();
}
/** The Worker fragment has started doing it's thing */
@Override
public void onResume() {
super.onResume();
_masterFrag.observeResults(_storedIntsFlowable);
}
@Override
public void onDestroy() {
super.onDestroy();
_storedIntsDisposable.dispose();
}
/** Set the callback to null so we don't accidentally leak the Activity instance. */
@Override
public void onDetach() {
super.onDetach();
_masterFrag = null;
}
public interface IAmYourMaster {
void observeResults(Flowable<Integer> intsObservable);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2Fragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import static android.os.Looper.getMainLooper;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import io.reactivex.Flowable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subscribers.DisposableSubscriber;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class RotationPersist2Fragment extends BaseFragment
implements RotationPersist2WorkerFragment.IAmYourMaster {
public static final String TAG = RotationPersist2Fragment.class.toString();
@BindView(R.id.list_threading_log)
ListView _logList;
private LogAdapter _adapter;
private List<String> _logs;
private CompositeDisposable _disposables = new CompositeDisposable();
// -----------------------------------------------------------------------------------
@OnClick(R.id.btn_rotate_persist)
public void startOperationFromWorkerFrag() {
_logs = new ArrayList<>();
_adapter.clear();
FragmentManager fm = getActivity().getSupportFragmentManager();
RotationPersist2WorkerFragment frag =
(RotationPersist2WorkerFragment) fm.findFragmentByTag(RotationPersist2WorkerFragment.TAG);
if (frag == null) {
frag = new RotationPersist2WorkerFragment();
fm.beginTransaction().add(frag, RotationPersist2WorkerFragment.TAG).commit();
} else {
Timber.d("Worker frag already spawned");
}
}
@Override
public void setStream(Flowable<Integer> intStream) {
DisposableSubscriber<Integer> d =
new DisposableSubscriber<Integer>() {
@Override
public void onNext(Integer integer) {
_log(String.format("Worker frag spits out - %d", integer));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "Error in worker demo frag observable");
_log("Dang! something went wrong.");
}
@Override
public void onComplete() {
_log("Observable is complete");
}
};
intStream.doOnSubscribe(subscription -> _log("Subscribing to intsObservable")).subscribe(d);
_disposables.add(d);
}
// -----------------------------------------------------------------------------------
// Boilerplate
// -----------------------------------------------------------------------------------
@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_rotation_persist, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onPause() {
super.onPause();
_disposables.clear();
}
private void _setupLogger() {
_logs = new ArrayList<>();
_adapter = new LogAdapter(getActivity(), new ArrayList<>());
_logList.setAdapter(_adapter);
}
private void _log(String logMsg) {
_logs.add(0, logMsg);
// You can only do below stuff on main thread.
new Handler(getMainLooper())
.post(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2WorkerFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.morihacky.android.rxjava.MainActivity;
import io.reactivex.Flowable;
import io.reactivex.processors.PublishProcessor;
import java.util.concurrent.TimeUnit;
public class RotationPersist2WorkerFragment extends Fragment {
public static final String TAG = RotationPersist2WorkerFragment.class.toString();
private PublishProcessor<Integer> _intStream;
private PublishProcessor<Boolean> _lifeCycleStream;
private IAmYourMaster _masterFrag;
/**
* Since we're holding a reference to the Master a.k.a Activity/Master Frag remember to explicitly
* remove the worker fragment or you'll have a mem leak in your hands.
*
* <p>See {@link MainActivity#onBackPressed()}
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
_masterFrag =
(RotationPersist2Fragment)
((MainActivity) context)
.getSupportFragmentManager()
.findFragmentByTag(RotationPersist2Fragment.TAG);
if (_masterFrag == null) {
throw new ClassCastException("We did not find a master who can understand us :(");
}
}
/** This method will only be called once when the retained Fragment is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_intStream = PublishProcessor.create();
_lifeCycleStream = PublishProcessor.create();
// Retain this fragment across configuration changes.
setRetainInstance(true);
_intStream.takeUntil(_lifeCycleStream);
Flowable.interval(1, TimeUnit.SECONDS).map(Long::intValue).take(20).subscribe(_intStream);
}
/** The Worker fragment has started doing it's thing */
@Override
public void onResume() {
super.onResume();
_masterFrag.setStream(_intStream);
}
@Override
public void onDestroy() {
super.onDestroy();
_lifeCycleStream.onComplete();
}
/** Set the callback to null so we don't accidentally leak the Activity instance. */
@Override
public void onDetach() {
super.onDetach();
_masterFrag = null;
}
public interface IAmYourMaster {
void setStream(Flowable<Integer> intStream);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist3Fragment.kt
================================================
package com.morihacky.android.rxjava.fragments
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.os.Handler
import android.os.Looper.getMainLooper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.morihacky.android.rxjava.MyApp
import com.morihacky.android.rxjava.R
import com.morihacky.android.rxjava.ext.plus
import com.morihacky.android.rxjava.wiring.LogAdapter
import io.reactivex.Flowable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
import java.util.concurrent.TimeUnit
class RotationPersist3Fragment : BaseFragment() {
@BindView(R.id.list_threading_log)
lateinit var logList: ListView
lateinit var adapter: LogAdapter
lateinit var sharedViewModel: SharedViewModel
private var logs: MutableList<String> = ArrayList()
private var disposables = CompositeDisposable()
// -----------------------------------------------------------------------------------
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val layout = inflater!!.inflate(R.layout.fragment_rotation_persist, container, false)
ButterKnife.bind(this, layout)
return layout
}
@OnClick(R.id.btn_rotate_persist)
fun startOperationFromWorkerFrag() {
logs = ArrayList<String>()
adapter.clear()
disposables +=
sharedViewModel
.sourceStream()
.subscribe({ l ->
_log("Received element $l")
})
}
// -----------------------------------------------------------------------------------
// Boilerplate
// -----------------------------------------------------------------------------------
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
_setupLogger()
}
override fun onPause() {
super.onPause()
disposables.clear()
}
private fun _setupLogger() {
logs = ArrayList<String>()
adapter = LogAdapter(activity, ArrayList<String>())
logList.adapter = adapter
}
private fun _log(logMsg: String) {
logs.add(0, logMsg)
// You can only do below stuff on main thread.
Handler(getMainLooper())
.post {
adapter.clear()
adapter.addAll(logs)
}
}
}
class SharedViewModel : ViewModel() {
var disposable: Disposable? = null
var sharedObservable: Flowable<Long> =
Flowable.interval(1, TimeUnit.SECONDS)
.take(20)
.doOnNext { l -> Timber.tag("KG").d("onNext $l") }
// .replayingShare()
.replay(1)
.autoConnect(1) { t -> disposable = t }
fun sourceStream(): Flowable<Long> {
return sharedObservable
}
override fun onCleared() {
super.onCleared()
Timber.tag("KG").d("Clearing ViewModel")
disposable?.dispose()
MyApp.getRefWatcher().watch(this)
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/TimeoutDemoFragment.java
================================================
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.ListView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import timber.log.Timber;
public class TimeoutDemoFragment extends BaseFragment {
@BindView(R.id.list_threading_log)
ListView _logsList;
private LogAdapter _adapter;
private DisposableObserver<String> _disposable;
private List<String> _logs;
@Override
public void onDestroy() {
super.onDestroy();
if (_disposable == null) {
return;
}
_disposable.dispose();
}
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_subject_timeout, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_setupLogger();
}
@OnClick(R.id.btn_demo_timeout_1_2s)
public void onStart2sTask() {
_disposable = _getEventCompletionObserver();
_getObservableTask_2sToComplete()
.timeout(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(_disposable);
}
@OnClick(R.id.btn_demo_timeout_1_5s)
public void onStart5sTask() {
_disposable = _getEventCompletionObserver();
_getObservableTask_5sToComplete()
.timeout(3, TimeUnit.SECONDS, _onTimeoutObservable())
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(_disposable);
}
// -----------------------------------------------------------------------------------
// Main Rx entities
private Observable<String> _getObservableTask_5sToComplete() {
return Observable.create(
new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> subscriber) throws Exception {
_log(String.format("Starting a 5s task"));
subscriber.onNext("5 s");
try {
Thread.sleep(5_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
subscriber.onComplete();
}
});
}
private Observable<String> _getObservableTask_2sToComplete() {
return Observable.create(
new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> subscriber) throws Exception {
_log(String.format("Starting a 2s task"));
subscriber.onNext("2 s");
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
subscriber.onComplete();
}
});
}
private Observable<? extends String> _onTimeoutObservable() {
return Observable.create(
new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> subscriber) throws Exception {
_log("Timing out this task ...");
subscriber.onError(new Throwable("Timeout Error"));
}
});
}
private DisposableObserver<String> _getEventCompletionObserver() {
return new DisposableObserver<String>() {
@Override
public void onNext(String taskType) {
_log(String.format("onNext %s task", taskType));
}
@Override
public void onError(Throwable e) {
_log(String.format("Dang a task timeout"));
Timber.e(e, "Timeout Demo exception");
}
@Override
public void onComplete() {
_log(String.format("task was completed"));
}
};
}
// -----------------------------------------------------------------------------------
// 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(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
private boolean _isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/TimingDemoFragment.java
================================================
package com.morihacky.android.rxjava.fragments;
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.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import butterknife.Unbinder;
import io.reactivex.Flowable;
import io.reactivex.subscribers.DefaultSubscriber;
import io.reactivex.subscribers.DisposableSubscriber;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import timber.log.Timber;
import static android.os.Looper.getMainLooper;
import static android.os.Looper.myLooper;
public class TimingDemoFragment extends BaseFragment {
@BindView(R.id.list_threading_log)
ListView _logsList;
private LogAdapter _adapter;
private List<String> _logs;
private DisposableSubscriber<Long> _subscriber1;
private DisposableSubscriber<Long> _subscriber2;
private Unbinder unbinder;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_demo_timing, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_setupLogger();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
// -----------------------------------------------------------------------------------
@OnClick(R.id.btn_demo_timing_1)
public void btn1_RunSingleTaskAfter2s() {
_log(String.format("A1 [%s] --- BTN click", _getCurrentTimestamp()));
Flowable.timer(2, TimeUnit.SECONDS) //
.subscribe(
new DefaultSubscriber<Long>() {
@Override
public void onNext(Long number) {
_log(String.format("A1 [%s] NEXT", _getCurrentTimestamp()));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "something went wrong in TimingDemoFragment example");
}
@Override
public void onComplete() {
_log(String.format("A1 [%s] XXX COMPLETE", _getCurrentTimestamp()));
}
});
}
@OnClick(R.id.btn_demo_timing_2)
public void btn2_RunTask_IntervalOf1s() {
if (_subscriber1 != null && !_subscriber1.isDisposed()) {
_subscriber1.dispose();
_log(String.format("B2 [%s] XXX BTN KILLED", _getCurrentTimestamp()));
return;
}
_log(String.format("B2 [%s] --- BTN click", _getCurrentTimestamp()));
_subscriber1 =
new DisposableSubscriber<Long>() {
@Override
public void onComplete() {
_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()));
}
};
Flowable.interval(1, TimeUnit.SECONDS).subscribe(_subscriber1);
}
@OnClick(R.id.btn_demo_timing_3)
public void btn3_RunTask_IntervalOf1s_StartImmediately() {
if (_subscriber2 != null && !_subscriber2.isDisposed()) {
_subscriber2.dispose();
_log(String.format("C3 [%s] XXX BTN KILLED", _getCurrentTimestamp()));
return;
}
_log(String.format("C3 [%s] --- BTN click", _getCurrentTimestamp()));
_subscriber2 =
new DisposableSubscriber<Long>() {
@Override
public void onNext(Long number) {
_log(String.format("C3 [%s] NEXT", _getCurrentTimestamp()));
}
@Override
public void onComplete() {
_log(String.format("C3 [%s] XXXX COMPLETE", _getCurrentTimestamp()));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "something went wrong in TimingDemoFragment example");
}
};
Flowable.interval(0, 1, TimeUnit.SECONDS).subscribe(_subscriber2);
}
@OnClick(R.id.btn_demo_timing_4)
public void btn4_RunTask5Times_IntervalOf3s() {
_log(String.format("D4 [%s] --- BTN click", _getCurrentTimestamp()));
Flowable.interval(3, TimeUnit.SECONDS)
.take(5)
.subscribe(
new DefaultSubscriber<Long>() {
@Override
public void onNext(Long number) {
_log(String.format("D4 [%s] NEXT", _getCurrentTimestamp()));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "something went wrong in TimingDemoFragment example");
}
@Override
public void onComplete() {
_log(String.format("D4 [%s] XXX COMPLETE", _getCurrentTimestamp()));
}
});
}
@OnClick(R.id.btn_demo_timing_5)
public void btn5_RunTask5Times_IntervalOf3s() {
_log(String.format("D5 [%s] --- BTN click", _getCurrentTimestamp()));
Flowable.just("Do task A right away")
.doOnNext(input -> _log(String.format("D5 %s [%s]", input, _getCurrentTimestamp())))
.delay(1, TimeUnit.SECONDS)
.doOnNext(
oldInput ->
_log(
String.format(
"D5 %s [%s]", "Doing Task B after a delay", _getCurrentTimestamp())))
.subscribe(
new DefaultSubscriber<String>() {
@Override
public void onComplete() {
_log(String.format("D5 [%s] XXX COMPLETE", _getCurrentTimestamp()));
}
@Override
public void onError(Throwable e) {
Timber.e(e, "something went wrong in TimingDemoFragment example");
}
@Override
public void onNext(String number) {
_log(String.format("D5 [%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(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
private String _getCurrentTimestamp() {
return new SimpleDateFormat("k:m:s:S a", Locale.getDefault()).format(new Date());
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAdapter.java
================================================
package com.morihacky.android.rxjava.pagination;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.rxbus.RxBus;
import java.util.ArrayList;
import java.util.List;
/** There isn't anything specific to Pagination here. Just wiring for the example */
class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_LOG = 0;
private static final int ITEM_BTN = 1;
private final List<String> _items = new ArrayList<>();
private final RxBus _bus;
PaginationAdapter(RxBus bus) {
_bus = bus;
}
void addItems(List<String> items) {
_items.addAll(items);
}
@Override
public int getItemViewType(int position) {
if (position == _items.size()) {
return ITEM_BTN;
}
return ITEM_LOG;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ITEM_BTN:
return ItemBtnViewHolder.create(parent);
default:
return ItemLogViewHolder.create(parent);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case ITEM_LOG:
((ItemLogViewHolder) holder).bindContent(_items.get(position));
return;
case ITEM_BTN:
((ItemBtnViewHolder) holder).bindContent(_bus);
}
}
@Override
public int getItemCount() {
return _items.size() + 1; // add 1 for paging button
}
private static class ItemLogViewHolder extends RecyclerView.ViewHolder {
ItemLogViewHolder(View itemView) {
super(itemView);
}
static ItemLogViewHolder create(ViewGroup parent) {
return new ItemLogViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log, parent, false));
}
void bindContent(String content) {
((TextView) itemView).setText(content);
}
}
static class ItemBtnViewHolder extends RecyclerView.ViewHolder {
ItemBtnViewHolder(View itemView) {
super(itemView);
}
static ItemBtnViewHolder create(ViewGroup parent) {
return new ItemBtnViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_btn, parent, false));
}
void bindContent(RxBus bus) {
((Button) itemView).setText(R.string.btn_demo_pagination_more);
itemView.setOnClickListener(v -> bus.send(new ItemBtnViewHolder.PageEvent()));
}
static class PageEvent {}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoAdapter.java
================================================
package com.morihacky.android.rxjava.pagination;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.rxbus.RxBus;
import java.util.ArrayList;
import java.util.List;
class PaginationAutoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_LOG = 0;
private final List<String> _items = new ArrayList<>();
private final RxBus _bus;
PaginationAutoAdapter(RxBus bus) {
_bus = bus;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return ItemLogViewHolder.create(parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((ItemLogViewHolder) holder).bindContent(_items.get(position));
boolean lastPositionReached = position == _items.size() - 1;
if (lastPositionReached) {
_bus.send(new PageEvent());
}
}
@Override
public int getItemViewType(int position) {
return ITEM_LOG;
}
@Override
public int getItemCount() {
return _items.size();
}
void addItems(List<String> items) {
_items.addAll(items);
}
private static class ItemLogViewHolder extends RecyclerView.ViewHolder {
ItemLogViewHolder(View itemView) {
super(itemView);
}
static ItemLogViewHolder create(ViewGroup parent) {
return new ItemLogViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log, parent, false));
}
void bindContent(String content) {
((TextView) itemView).setText(content);
}
}
static class PageEvent {}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoFragment.java
================================================
package com.morihacky.android.rxjava.pagination;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import com.morihacky.android.rxjava.rxbus.RxBus;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.processors.PublishProcessor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class PaginationAutoFragment extends BaseFragment {
@BindView(R.id.list_paging)
RecyclerView _pagingList;
@BindView(R.id.progress_paging)
ProgressBar _progressBar;
private PaginationAutoAdapter _adapter;
private RxBus _bus;
private CompositeDisposable _disposables;
private PublishProcessor<Integer> _paginator;
private boolean _requestUnderWay = false;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_pagination, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_bus = ((MainActivity) getActivity()).getRxBusSingleton();
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
_pagingList.setLayoutManager(layoutManager);
_adapter = new PaginationAutoAdapter(_bus);
_pagingList.setAdapter(_adapter);
_paginator = PublishProcessor.create();
}
@Override
public void onStart() {
super.onStart();
_disposables = new CompositeDisposable();
Disposable d2 =
_paginator
.onBackpressureDrop()
.doOnNext(
i -> {
_requestUnderWay = true;
_progressBar.setVisibility(View.VISIBLE);
})
.concatMap(this::_itemsFromNetworkCall)
.observeOn(AndroidSchedulers.mainThread())
.map(
items -> {
_adapter.addItems(items);
_adapter.notifyDataSetChanged();
return items;
})
.doOnNext(
i -> {
_requestUnderWay = false;
_progressBar.setVisibility(View.INVISIBLE);
})
.subscribe();
// I'm using an RxBus purely to hear from a nested button click
// we don't really need Rx for this part. it's just easy ¯\_(ツ)_/¯
Disposable d1 =
_bus.asFlowable()
.filter(o -> !_requestUnderWay)
.subscribe(
event -> {
if (event instanceof PaginationAutoAdapter.PageEvent) {
// trigger the paginator for the next event
int nextPage = _adapter.getItemCount();
_paginator.onNext(nextPage);
}
});
_disposables.add(d1);
_disposables.add(d2);
_paginator.onNext(0);
}
@Override
public void onStop() {
super.onStop();
_disposables.clear();
}
/** Fake Observable that simulates a network call and then sends down a list of items */
private Flowable<List<String>> _itemsFromNetworkCall(int pageStart) {
return Flowable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.delay(2, TimeUnit.SECONDS)
.map(
dummy -> {
List<String> items = new ArrayList<>();
for (int i = 0; i < 10; i++) {
items.add("Item " + (pageStart + i));
}
return items;
});
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationFragment.java
================================================
package com.morihacky.android.rxjava.pagination;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import com.morihacky.android.rxjava.rxbus.RxBus;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.processors.PublishProcessor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class PaginationFragment extends BaseFragment {
@BindView(R.id.list_paging)
RecyclerView _pagingList;
@BindView(R.id.progress_paging)
ProgressBar _progressBar;
private PaginationAdapter _adapter;
private RxBus _bus;
private CompositeDisposable _disposables;
private PublishProcessor<Integer> _paginator;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_bus = ((MainActivity) getActivity()).getRxBusSingleton();
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
_pagingList.setLayoutManager(layoutManager);
_adapter = new PaginationAdapter(_bus);
_pagingList.setAdapter(_adapter);
_paginator = PublishProcessor.create();
}
@Override
public void onStart() {
super.onStart();
_disposables = new CompositeDisposable();
Disposable d2 =
_paginator
.onBackpressureDrop()
.concatMap(nextPage -> _itemsFromNetworkCall(nextPage + 1, 10))
.observeOn(AndroidSchedulers.mainThread())
.map(
items -> {
int start = _adapter.getItemCount() - 1;
_adapter.addItems(items);
_adapter.notifyItemRangeInserted(start, 10);
_progressBar.setVisibility(View.INVISIBLE);
return items;
})
.subscribe();
// I'm using an Rxbus purely to hear from a nested button click
// we don't really need Rx for this part. it's just easy ¯\_(ツ)_/¯
Disposable d1 =
_bus.asFlowable()
.subscribe(
event -> {
if (event instanceof PaginationAdapter.ItemBtnViewHolder.PageEvent) {
// trigger the paginator for the next event
int nextPage = _adapter.getItemCount() - 1;
_paginator.onNext(nextPage);
}
});
_disposables.add(d1);
_disposables.add(d2);
}
@Override
public void onStop() {
super.onStop();
_disposables.clear();
}
/** Fake Observable that simulates a network call and then sends down a list of items */
private Flowable<List<String>> _itemsFromNetworkCall(int start, int count) {
return Flowable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(dummy -> _progressBar.setVisibility(View.VISIBLE))
.delay(2, TimeUnit.SECONDS)
.map(
dummy -> {
List<String> items = new ArrayList<>();
for (int i = 0; i < count; i++) {
items.add("Item " + (start + i));
}
return items;
});
}
// -----------------------------------------------------------------------------------
// WIRING up the views required for this example
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_pagination, container, false);
ButterKnife.bind(this, layout);
return layout;
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/Contributor.java
================================================
package com.morihacky.android.rxjava.retrofit;
public class Contributor {
public String login;
public long contributions;
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubApi.java
================================================
package com.morihacky.android.rxjava.retrofit;
import java.util.List;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface GithubApi {
/** See https://developer.github.com/v3/repos/#list-contributors */
@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributors(
@Path("owner") String owner, @Path("repo") String repo);
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> getContributors(@Path("owner") String owner, @Path("repo") String repo);
/** See https://developer.github.com/v3/users/ */
@GET("/users/{user}")
Observable<User> user(@Path("user") String user);
/** See https://developer.github.com/v3/users/ */
@GET("/users/{user}")
User getUser(@Path("user") String user);
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubService.java
================================================
package com.morihacky.android.rxjava.retrofit;
import android.text.TextUtils;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import static java.lang.String.format;
public class GithubService {
private GithubService() {}
public static GithubApi createGithubService(final String githubToken) {
Retrofit.Builder builder =
new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com");
if (!TextUtils.isEmpty(githubToken)) {
OkHttpClient client =
new OkHttpClient.Builder()
.addInterceptor(
chain -> {
Request request = chain.request();
Request newReq =
request
.newBuilder()
.addHeader("Authorization", format("token %s", githubToken))
.build();
return chain.proceed(newReq);
})
.build();
builder.client(client);
}
return builder.build().create(GithubApi.class);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/User.java
================================================
package com.morihacky.android.rxjava.retrofit;
public class User {
public String name;
public String email;
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBus.java
================================================
package com.morihacky.android.rxjava.rxbus;
import com.jakewharton.rxrelay2.PublishRelay;
import com.jakewharton.rxrelay2.Relay;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
/** courtesy: https://gist.github.com/benjchristensen/04eef9ca0851f3a5d7bf */
public class RxBus {
private final Relay<Object> _bus = PublishRelay.create().toSerialized();
public void send(Object o) {
_bus.accept(o);
}
public Flowable<Object> asFlowable() {
return _bus.toFlowable(BackpressureStrategy.LATEST);
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemoFragment.java
================================================
package com.morihacky.android.rxjava.rxbus;
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 com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
public class RxBusDemoFragment extends BaseFragment {
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_rxbus_demo, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity()
.getSupportFragmentManager()
.beginTransaction()
.replace(R.id.demo_rxbus_frag_1, new RxBusDemo_TopFragment())
.replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom3Fragment())
//.replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom2Fragment())
//.replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom1Fragment())
.commit();
}
public static class TapEvent {}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom1Fragment.java
================================================
package com.morihacky.android.rxjava.rxbus;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import io.reactivex.disposables.CompositeDisposable;
public class RxBusDemo_Bottom1Fragment extends BaseFragment {
@BindView(R.id.demo_rxbus_tap_txt)
TextView _tapEventTxtShow;
private CompositeDisposable _disposables;
private RxBus _rxBus;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_rxBus = ((MainActivity) getActivity()).getRxBusSingleton();
}
@Override
public void onStart() {
super.onStart();
_disposables = new CompositeDisposable();
_disposables.add(
_rxBus
.asFlowable()
.subscribe(
event -> {
if (event instanceof RxBusDemoFragment.TapEvent) {
_showTapText();
}
}));
}
@Override
public void onStop() {
super.onStop();
_disposables.clear();
}
private void _showTapText() {
_tapEventTxtShow.setVisibility(View.VISIBLE);
_tapEventTxtShow.setAlpha(1f);
ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom2Fragment.java
================================================
package com.morihacky.android.rxjava.rxbus;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class RxBusDemo_Bottom2Fragment extends BaseFragment {
@BindView(R.id.demo_rxbus_tap_txt)
TextView _tapEventTxtShow;
@BindView(R.id.demo_rxbus_tap_count)
TextView _tapEventCountShow;
private RxBus _rxBus;
private CompositeDisposable _disposables;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_rxBus = ((MainActivity) getActivity()).getRxBusSingleton();
}
@Override
public void onStart() {
super.onStart();
_disposables = new CompositeDisposable();
Flowable<Object> tapEventEmitter = _rxBus.asFlowable().share();
_disposables.add(
tapEventEmitter.subscribe(
event -> {
if (event instanceof RxBusDemoFragment.TapEvent) {
_showTapText();
}
}));
Flowable<Object> debouncedEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS);
Flowable<List<Object>> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEmitter);
_disposables.add(
debouncedBufferEmitter
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
taps -> {
_showTapCount(taps.size());
}));
}
@Override
public void onStop() {
super.onStop();
_disposables.clear();
}
// -----------------------------------------------------------------------------------
// Helper to show the text via an animation
private void _showTapText() {
_tapEventTxtShow.setVisibility(View.VISIBLE);
_tapEventTxtShow.setAlpha(1f);
ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400);
}
private void _showTapCount(int size) {
_tapEventCountShow.setText(String.valueOf(size));
_tapEventCountShow.setVisibility(View.VISIBLE);
_tapEventCountShow.setScaleX(1f);
_tapEventCountShow.setScaleY(1f);
ViewCompat.animate(_tapEventCountShow)
.scaleXBy(-1f)
.scaleYBy(-1f)
.setDuration(800)
.setStartDelay(100);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java
================================================
package com.morihacky.android.rxjava.rxbus;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.morihacky.android.rxjava.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.flowables.ConnectableFlowable;
import java.util.concurrent.TimeUnit;
public class RxBusDemo_Bottom3Fragment extends BaseFragment {
@BindView(R.id.demo_rxbus_tap_txt)
TextView _tapEventTxtShow;
@BindView(R.id.demo_rxbus_tap_count)
TextView _tapEventCountShow;
private RxBus _rxBus;
private CompositeDisposable _disposables;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_rxBus = ((MainActivity) getActivity()).getRxBusSingleton();
}
@Override
public void onStart() {
super.onStart();
_disposables = new CompositeDisposable();
ConnectableFlowable<Object> tapEventEmitter = _rxBus.asFlowable().publish();
_disposables //
.add(
tapEventEmitter.subscribe(
event -> {
if (event instanceof RxBusDemoFragment.TapEvent) {
_showTapText();
}
}));
_disposables.add(
tapEventEmitter
.publish(stream -> stream.buffer(stream.debounce(1, TimeUnit.SECONDS)))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
taps -> {
_showTapCount(taps.size());
}));
_disposables.add(tapEventEmitter.connect());
}
@Override
public void onStop() {
super.onStop();
_disposables.clear();
}
// -----------------------------------------------------------------------------------
// Helper to show the text via an animation
private void _showTapText() {
_tapEventTxtShow.setVisibility(View.VISIBLE);
_tapEventTxtShow.setAlpha(1f);
ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400);
}
private void _showTapCount(int size) {
_tapEventCountShow.setText(String.valueOf(size));
_tapEventCountShow.setVisibility(View.VISIBLE);
_tapEventCountShow.setScaleX(1f);
_tapEventCountShow.setScaleY(1f);
ViewCompat.animate(_tapEventCountShow)
.scaleXBy(-1f)
.scaleYBy(-1f)
.setDuration(800)
.setStartDelay(100);
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_TopFragment.java
================================================
package com.morihacky.android.rxjava.rxbus;
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.MainActivity;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
public class RxBusDemo_TopFragment extends BaseFragment {
private RxBus _rxBus;
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_rxbus_top, container, false);
ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_rxBus = ((MainActivity) getActivity()).getRxBusSingleton();
}
@OnClick(R.id.btn_demo_rxbus_tap)
public void onTapButtonClicked() {
if (_rxBus.hasObservers()) {
_rxBus.send(new RxBusDemoFragment.TapEvent());
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/volley/MyVolley.java
================================================
package com.morihacky.android.rxjava.volley;
import android.content.Context;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
/**
* Helper class that is used to provide references to initialized RequestQueue(s) and ImageLoader(s)
*/
public class MyVolley {
private static RequestQueue mRequestQueue;
private MyVolley() {
// no instances
}
public static void init(Context context) {
mRequestQueue = Volley.newRequestQueue(context);
}
static RequestQueue getRequestQueue() {
if (mRequestQueue != null) {
return mRequestQueue;
} else {
throw new IllegalStateException("RequestQueue not initialized");
}
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/volley/VolleyDemoFragment.java
================================================
package com.morihacky.android.rxjava.volley;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.android.volley.Request;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.RequestFuture;
import com.morihacky.android.rxjava.R;
import com.morihacky.android.rxjava.fragments.BaseFragment;
import com.morihacky.android.rxjava.wiring.LogAdapter;
import butterknife.Unbinder;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subscribers.DisposableSubscriber;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.json.JSONObject;
import timber.log.Timber;
public class VolleyDemoFragment extends BaseFragment {
public static final String TAG = "VolleyDemoFragment";
@BindView(R.id.list_threading_log)
ListView _logsList;
private List<String> _logs;
private LogAdapter _adapter;
private Unbinder unbinder;
private CompositeDisposable _disposables = new CompositeDisposable();
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_volley, container, false);
unbinder = ButterKnife.bind(this, layout);
return layout;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
_setupLogger();
}
@Override
public void onPause() {
super.onPause();
_disposables.clear();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
/**
* Creates and returns an observable generated from the Future returned from {@code
* getRouteData()}. The observable can then be subscribed to as shown in {@code
* startVolleyRequest()}
*
* @return Observable<JSONObject>
*/
public Flowable<JSONObject> newGetRouteData() {
return Flowable.defer(
() -> {
try {
return Flowable.just(getRouteData());
} catch (InterruptedException | ExecutionException e) {
Log.e("routes", e.getMessage());
return Flowable.error(e);
}
});
}
@OnClick(R.id.btn_start_operation)
void startRequest() {
startVolleyRequest();
}
private void startVolleyRequest() {
DisposableSubscriber<JSONObject> d =
new DisposableSubscriber<JSONObject>() {
@Override
public void onNext(JSONObject jsonObject) {
Log.e(TAG, "onNext " + jsonObject.toString());
_log("onNext " + jsonObject.toString());
}
@Override
public void onError(Throwable e) {
VolleyError cause = (VolleyError) e.getCause();
String s = new String(cause.networkResponse.data, Charset.forName("UTF-8"));
Log.e(TAG, s);
Log.e(TAG, cause.toString());
_log("onError " + s);
}
@Override
public void onComplete() {
Log.e(TAG, "onCompleted");
Timber.d("----- onCompleted");
_log("onCompleted ");
}
};
newGetRouteData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(d);
_disposables.add(d);
}
/**
* Converts the Asynchronous Request into a Synchronous Future that can be used to block via
* {@code Future.get()}. Observables require blocking/synchronous functions
*
* @return JSONObject
* @throws ExecutionException
* @throws InterruptedException
*/
private JSONObject getRouteData() throws ExecutionException, InterruptedException {
RequestFuture<JSONObject> future = RequestFuture.newFuture();
String url = "http://www.weather.com.cn/adat/sk/101010100.html";
JsonObjectRequest req = new JsonObjectRequest(Request.Method.GET, url, future, future);
MyVolley.getRequestQueue().add(req);
return future.get();
}
// -----------------------------------------------------------------------------------
// 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(
() -> {
_adapter.clear();
_adapter.addAll(_logs);
});
}
}
private boolean _isCurrentlyOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
================================================
FILE: app/src/main/java/com/morihacky/android/rxjava/wiring/LogAdapter.java
================================================
package com.morihacky.android.rxjava.wiring;
import android.content.Context;
import android.widget.ArrayAdapter;
import com.morihacky.android.rxjava.R;
import java.util.List;
public class LogAdapter extends ArrayAdapter<String> {
public LogAdapter(Context context, List<String> logs) {
super(context, R.layout.item_log, R.id.item_log, logs);
}
}
================================================
FILE: app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt
================================================
package com.morihacky.android.rxjava.ext
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
operator fun CompositeDisposable.plus(disposable: Disposable): CompositeDisposable {
add(disposable)
return this
}
================================================
FILE: app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt
================================================
package com.morihacky.android.rxjava.fragments
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.jakewharton.rx.replayingShare
import com.morihacky.android.rxjava.R
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import java.util.concurrent.TimeUnit
class MulticastPlaygroundFragment : BaseFragment() {
@BindView(R.id.list_threading_log) lateinit var logList: ListView
@BindView(R.id.dropdown) lateinit var pickOperatorDD: Spinner
@BindView(R.id.msg_text) lateinit var messageText: TextView
private lateinit var sharedObservable: Observable<Long>
private lateinit var adapter: LogAdapter
private var logs: MutableList<String> = ArrayList()
private var disposable1: Disposable? = null
private var disposable2: Disposable? = null
override fun onCreateView(inflater: LayoutInflater?,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val layout = inflater!!.inflate(R.layout.fragment_multicast_playground, container, false)
ButterKnife.bind(this, layout)
_setupLogger()
_setupDropdown()
return layout
}
@OnClick(R.id.btn_1)
fun onBtn1Click() {
disposable1?.let {
it.dispose()
_log("subscriber 1 disposed")
disposable1 = null
return
}
disposable1 =
sharedObservable
.doOnSubscribe { _log("subscriber 1 (subscribed)") }
.subscribe({ long -> _log("subscriber 1: onNext $long") })
}
@OnClick(R.id.btn_2)
fun onBtn2Click() {
disposable2?.let {
it.dispose()
_log("subscriber 2 disposed")
disposable2 = null
return
}
disposable2 =
sharedObservable
.doOnSubscribe { _log("subscriber 2 (subscribed)") }
.subscribe({ long -> _log("subscriber 2: onNext $long") })
}
@OnClick(R.id.btn_3)
fun onBtn3Click() {
logs = ArrayList<String>()
adapter.clear()
}
// -----------------------------------------------------------------------------------
// Method that help wiring up the example (irrelevant to RxJava)
private fun _log(logMsg: String) {
if (_isCurrentlyOnMainThread()) {
logs.add(0, logMsg + " (main thread) ")
adapter.clear()
adapter.addAll(logs)
} else {
logs.add(0, logMsg + " (NOT main thread) ")
// You can only do below stuff on main thread.
Handler(Looper.getMainLooper()).post {
adapter.clear()
adapter.addAll(logs)
}
}
}
private fun _setupLogger() {
logs = ArrayList<String>()
adapter = LogAdapter(activity, ArrayList<String>())
logList.adapter = adapter
}
private fun _setupDropdown() {
pickOperatorDD.adapter = ArrayAdapter<String>(context,
android.R.layout.simple_spinner_dropdown_item,
arrayOf(".publish().refCount()",
".publish().autoConnect(2)",
".replay(1).autoConnect(2)",
".replay(1).refCount()",
".replayingShare()"))
pickOperatorDD.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, index: Int, p3: Long) {
val sourceObservable = Observable.interval(0L, 3, TimeUnit.SECONDS)
.doOnSubscribe { _log("observer (subscribed)") }
.doOnDispose { _log("observer (disposed)") }
.doOnTerminate { _log("observer (terminated)") }
sharedObservable =
when (index) {
0 -> {
messageText.setText(R.string.msg_demo_multicast_publishRefCount)
sourceObservable.publish().refCount()
}
1 -> {
messageText.setText(R.string.msg_demo_multicast_publishAutoConnect)
sourceObservable.publish().autoConnect(2)
}
2 -> {
messageText.setText(R.string.msg_demo_multicast_replayAutoConnect)
sourceObservable.replay(1).autoConnect(2)
}
3 -> {
messageText.setText(R.string.msg_demo_multicast_replayRefCount)
sourceObservable.replay(1).refCount()
}
4 -> {
messageText.setText(R.string.msg_demo_multicast_replayingShare)
sourceObservable.replayingShare()
}
else -> throw RuntimeException("got to pick an op yo!")
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {}
}
}
private fun _isCurrentlyOnMainThread(): Boolean {
return Looper.myLooper() == Looper.getMainLooper()
}
private inner class LogAdapter(context: Context, logs: List<String>) :
ArrayAdapter<String>(context, R.layout.item_log, R.id.item_log, logs)
}
================================================
FILE: app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt
================================================
package com.morihacky.android.rxjava.fragments
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import com.morihacky.android.rxjava.R
class PlaygroundFragment : BaseFragment() {
private var _logsList: ListView? = null
private var _adapter: LogAdapter? = null
private var _logs: MutableList<String> = ArrayList()
override fun onCreateView(inflater: LayoutInflater?,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.fragment_concurrency_schedulers, container, false)
_logsList = view?.findViewById(R.id.list_threading_log) as ListView
_setupLogger()
view.findViewById(R.id.btn_start_operation).setOnClickListener { _ ->
_log("Button clicked")
}
return view
}
// -----------------------------------------------------------------------------------
// Method that help wiring up the example (irrelevant to RxJava)
private fun _log(logMsg: String) {
if (_isCurrentlyOnMainThread()) {
_logs.add(0, logMsg + " (main thread) ")
_adapter?.clear()
_adapter?.addAll(_logs)
} else {
_logs.add(0, logMsg + " (NOT main thread) ")
// You can only do below stuff on main thread.
Handler(Looper.getMainLooper()).post {
_adapter?.clear()
_adapter?.addAll(_logs)
}
}
}
private fun _setupLogger() {
_logs = ArrayList<String>()
_adapter = LogAdapter(activity, ArrayList<String>())
_logsList?.adapter = _adapter
}
private fun _isCurrentlyOnMainThread(): Boolean {
return Looper.myLooper() == Looper.getMainLooper()
}
private inner class LogAdapter(context: Context, logs: List<String>) : ArrayAdapter<String>(context, R.layout.item_log, R.id.item_log, logs)
}
================================================
FILE: app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt
================================================
package com.morihacky.android.rxjava.fragments
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import com.morihacky.android.rxjava.R
import io.reactivex.Flowable
import io.reactivex.functions.Consumer
import io.reactivex.functions.Function
import org.reactivestreams.Publisher
import java.util.*
import java.util.concurrent.Callable
class UsingFragment : BaseFragment() {
private lateinit var _logs: MutableList<String>
private lateinit var _logsList: ListView
private lateinit var _adapter: UsingFragment.LogAdapter
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.fragment_buffer, container, false)
_logsList = view?.findViewById(R.id.list_threading_log) as ListView
(view.findViewById(R.id.text_description) as TextView).setText(R.string.msg_demo_using)
_setupLogger()
view.findViewById(R.id.btn_start_operation).setOnClickListener { executeUsingOperation() }
return view
}
private fun executeUsingOperation() {
val resourceSupplier = Callable<Realm> { Realm() }
val sourceSupplier = Function<Realm, Publisher<Int>> { realm ->
Flowable.just(true)
.map {
realm.doSomething()
// i would use the copyFromRealm and change it to a POJO
Random().nextInt(50)
}
}
val disposer = Consumer<Realm> { realm ->
realm.clear()
}
Flowable.using(resourceSupplier, sourceSupplier, disposer)
.subscribe({ i ->
_log("got a value $i - (look at the logs)")
})
}
inner class Realm {
init {
_log("initializing Realm instance")
}
fun doSomething() {
_log("do something with Realm instance")
}
fun clear() {
// notice how this is called even before you manually "dispose"
_log("cleaning up the resources (happens before a manual 'dispose'")
}
}
// -----------------------------------------------------------------------------------
// Method that help wiring up the example (irrelevant to RxJava)
private fun _log(logMsg: String) {
_logs.add(0, logMsg)
// You can only do below stuff on main thread.
Handler(Looper.getMainLooper()).post {
_adapter.clear()
_adapter.addAll(_logs)
}
}
private fun _setupLogger() {
_logs = ArrayList<String>()
_adapter = LogAdapter(activity, ArrayList<String>())
_logsList.adapter = _adapter
}
private class LogAdapter(context: Context, logs: List<String>) : ArrayAdapter<String>(context, R.layout.item_log, R.id.item_log, logs)
}
================================================
FILE: app/src/main/res/drawable/btn_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<solid android:color="@color/red"/>
<corners
android:bottomRightRadius="100dp"
android:bottomLeftRadius="100dp"
android:topRightRadius="100dp"
android:topLeftRadius="100dp"
/>
</shape>
================================================
FILE: app/src/main/res/layout/fragment_buffer.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<TextView
android:id="@+id/text_description"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="10dp"
android:gravity="center"
android:text="@string/msg_demo_buffer"
/>
<Button
android:id="@+id/btn_start_operation"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginLeft="90dp"
android:layout_marginRight="90dp"
android:textSize="16sp"
android:text="@string/tap_me"
/>
<ListView
android:id="@+id/list_threading_log"
android:layout_height="match_parent"
android:layout_width="match_parent"
/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/fragment_concurrency_schedulers.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<TextView
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="10dp"
android:gravity="center"
android:text="@string/msg_demo_concurrency_schedulers"
/>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<Button
android:id="@+id/btn_start_operation"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dp"
android:textSize="16sp"
android:text="@string/start_long_operation"
/>
<ProgressBar
android:id="@+id/progress_operation_running"
android:visibility="invisible"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dp"
/>
</LinearLayout>
<ListView
android:id="@+id/list_threading_log"
android:layout_height="match_parent"
android:layout_width="match_parent"
/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/fragment_debounce.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<TextView
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="10dp"
android:gravity="center"
android:text="@string/msg_demo_debounce"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
>
<EditText
android:id="@+id/input_txt_debounce"
android:layout_weight="7"
android:layout_height="match_parent"
android:layout_width="0dp"
an
gitextract_4ja9hvpa/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── morihacky/ │ │ └── android/ │ │ └── rxjava/ │ │ └── app/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── morihacky/ │ │ └── android/ │ │ └── rxjava/ │ │ ├── MainActivity.java │ │ ├── MyApp.java │ │ ├── fragments/ │ │ │ ├── BaseFragment.java │ │ │ ├── BufferDemoFragment.java │ │ │ ├── ConcurrencyWithSchedulersDemoFragment.java │ │ │ ├── DebounceSearchEmitterFragment.java │ │ │ ├── DoubleBindingTextViewFragment.java │ │ │ ├── ExponentialBackoffFragment.java │ │ │ ├── FormValidationCombineLatestFragment.java │ │ │ ├── MainFragment.java │ │ │ ├── NetworkDetectorFragment.java │ │ │ ├── PollingFragment.java │ │ │ ├── PseudoCacheFragment.java │ │ │ ├── PseudoCacheMergeFragment.java │ │ │ ├── RetrofitAsyncTaskDeathFragment.java │ │ │ ├── RetrofitFragment.java │ │ │ ├── RotationPersist1Fragment.java │ │ │ ├── RotationPersist1WorkerFragment.java │ │ │ ├── RotationPersist2Fragment.java │ │ │ ├── RotationPersist2WorkerFragment.java │ │ │ ├── RotationPersist3Fragment.kt │ │ │ ├── TimeoutDemoFragment.java │ │ │ └── TimingDemoFragment.java │ │ ├── pagination/ │ │ │ ├── PaginationAdapter.java │ │ │ ├── PaginationAutoAdapter.java │ │ │ ├── PaginationAutoFragment.java │ │ │ └── PaginationFragment.java │ │ ├── retrofit/ │ │ │ ├── Contributor.java │ │ │ ├── GithubApi.java │ │ │ ├── GithubService.java │ │ │ └── User.java │ │ ├── rxbus/ │ │ │ ├── RxBus.java │ │ │ ├── RxBusDemoFragment.java │ │ │ ├── RxBusDemo_Bottom1Fragment.java │ │ │ ├── RxBusDemo_Bottom2Fragment.java │ │ │ ├── RxBusDemo_Bottom3Fragment.java │ │ │ └── RxBusDemo_TopFragment.java │ │ ├── volley/ │ │ │ ├── MyVolley.java │ │ │ └── VolleyDemoFragment.java │ │ └── wiring/ │ │ └── LogAdapter.java │ ├── kotlin/ │ │ └── com/ │ │ └── morihacky/ │ │ └── android/ │ │ └── rxjava/ │ │ ├── ext/ │ │ │ └── RxExt.kt │ │ └── fragments/ │ │ ├── MulticastPlaygroundFragment.kt │ │ ├── PlaygroundFragment.kt │ │ └── UsingFragment.kt │ └── res/ │ ├── drawable/ │ │ └── btn_round.xml │ ├── layout/ │ │ ├── fragment_buffer.xml │ │ ├── fragment_concurrency_schedulers.xml │ │ ├── fragment_debounce.xml │ │ ├── fragment_demo_timing.xml │ │ ├── fragment_double_binding_textview.xml │ │ ├── fragment_exponential_backoff.xml │ │ ├── fragment_form_validation_comb_latest.xml │ │ ├── fragment_main.xml │ │ ├── fragment_multicast_playground.xml │ │ ├── fragment_network_detector.xml │ │ ├── fragment_pagination.xml │ │ ├── fragment_polling.xml │ │ ├── fragment_pseudo_cache.xml │ │ ├── fragment_pseudo_cache_concat.xml │ │ ├── fragment_retrofit.xml │ │ ├── fragment_retrofit_async_task_death.xml │ │ ├── fragment_rotation_persist.xml │ │ ├── fragment_rxbus_bottom.xml │ │ ├── fragment_rxbus_demo.xml │ │ ├── fragment_rxbus_frag3.xml │ │ ├── fragment_rxbus_top.xml │ │ ├── fragment_subject_timeout.xml │ │ ├── fragment_timer_demo.xml │ │ ├── fragment_volley.xml │ │ ├── item_btn.xml │ │ ├── item_log.xml │ │ └── item_log_white.xml │ ├── menu/ │ │ └── demo.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle
SYMBOL INDEX (317 symbols across 40 files)
FILE: app/src/androidTest/java/com/morihacky/android/rxjava/app/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: app/src/main/java/com/morihacky/android/rxjava/MainActivity.java
class MainActivity (line 11) | public class MainActivity extends AppCompatActivity {
method onBackPressed (line 15) | @Override
method onCreate (line 21) | @Override
method getRxBusSingleton (line 34) | public RxBus getRxBusSingleton() {
method _removeWorkerFragments (line 42) | private void _removeWorkerFragments() {
FILE: app/src/main/java/com/morihacky/android/rxjava/MyApp.java
class MyApp (line 9) | public class MyApp extends MultiDexApplication {
method get (line 14) | public static MyApp get() {
method getRefWatcher (line 18) | public static RefWatcher getRefWatcher() {
method onCreate (line 22) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java
class BaseFragment (line 7) | public class BaseFragment extends Fragment {
method onDestroy (line 9) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java
class BufferDemoFragment (line 43) | public class BufferDemoFragment extends BaseFragment {
method onResume (line 57) | @Override
method onPause (line 63) | @Override
method onActivityCreated (line 69) | @Override
method onCreateView (line 75) | @Override
method onDestroyView (line 83) | @Override
method _getBufferedDisposable (line 92) | private Disposable _getBufferedDisposable() {
method _setupLogger (line 132) | private void _setupLogger() {
method _log (line 138) | private void _log(String logMsg) {
method _isCurrentlyOnMainThread (line 157) | private boolean _isCurrentlyOnMainThread() {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/ConcurrencyWithSchedulersDemoFragment.java
class ConcurrencyWithSchedulersDemoFragment (line 29) | public class ConcurrencyWithSchedulersDemoFragment extends BaseFragment {
method onDestroy (line 42) | @Override
method onActivityCreated (line 49) | @Override
method onCreateView (line 55) | @Override
method startLongOperation (line 63) | @OnClick(R.id.btn_start_operation)
method _getObservable (line 79) | private Observable<Boolean> _getObservable() {
method _getDisposableObserver (line 94) | private DisposableObserver<Boolean> _getDisposableObserver() {
method _doSomeLongOperation_thatBlocksCurrentThread (line 120) | private void _doSomeLongOperation_thatBlocksCurrentThread() {
method _log (line 130) | private void _log(String logMsg) {
method _setupLogger (line 149) | private void _setupLogger() {
method _isCurrentlyOnMainThread (line 155) | private boolean _isCurrentlyOnMainThread() {
class LogAdapter (line 159) | private class LogAdapter extends ArrayAdapter<String> {
method LogAdapter (line 161) | public LogAdapter(Context context, List<String> logs) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/DebounceSearchEmitterFragment.java
class DebounceSearchEmitterFragment (line 35) | public class DebounceSearchEmitterFragment extends BaseFragment {
method onDestroy (line 49) | @Override
method onCreateView (line 56) | @Override
method onClearLog (line 64) | @OnClick(R.id.clr_debounce)
method onActivityCreated (line 70) | @Override
method _getSearchObserver (line 87) | private DisposableObserver<TextViewTextChangeEvent> _getSearchObserver...
method _setupLogger (line 110) | private void _setupLogger() {
method _log (line 116) | private void _log(String logMsg) {
method _isCurrentlyOnMainThread (line 135) | private boolean _isCurrentlyOnMainThread() {
class LogAdapter (line 139) | private class LogAdapter extends ArrayAdapter<String> {
method LogAdapter (line 141) | public LogAdapter(Context context, List<String> logs) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/DoubleBindingTextViewFragment.java
class DoubleBindingTextViewFragment (line 21) | public class DoubleBindingTextViewFragment extends BaseFragment {
method onCreateView (line 36) | @Override
method onNumberChanged (line 56) | @OnTextChanged({R.id.double_binding_num1, R.id.double_binding_num2})
method onDestroyView (line 72) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/ExponentialBackoffFragment.java
class ExponentialBackoffFragment (line 31) | public class ExponentialBackoffFragment extends BaseFragment {
method onCreateView (line 41) | @Override
method onActivityCreated (line 49) | @Override
method onPause (line 55) | @Override
method onDestroyView (line 62) | @Override
method startRetryingWithExponentialBackoffStrategy (line 70) | @OnClick(R.id.btn_eb_retry)
method startExecutingWithExponentialBackoffDelay (line 102) | @OnClick(R.id.btn_eb_delay)
method _getSecondHand (line 150) | private int _getSecondHand() {
method _setupLogger (line 159) | private void _setupLogger() {
method _log (line 165) | private void _log(String logMsg) {
class RetryWithDelay (line 188) | public class RetryWithDelay implements Function<Flowable<? extends Thr...
method RetryWithDelay (line 194) | public RetryWithDelay(final int maxRetries, final int retryDelayMill...
method apply (line 204) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java
class FormValidationCombineLatestFragment (line 24) | public class FormValidationCombineLatestFragment extends BaseFragment {
method onCreateView (line 44) | @Override
method onDestroyView (line 62) | @Override
method _combineLatestEvents (line 69) | private void _combineLatestEvents() {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java
class MainFragment (line 19) | public class MainFragment extends BaseFragment {
method onCreateView (line 23) | @Override
method onDestroyView (line 31) | @Override
method demoConcurrencyWithSchedulers (line 37) | @OnClick(R.id.btn_demo_schedulers)
method demoBuffer (line 42) | @OnClick(R.id.btn_demo_buffer)
method demoThrottling (line 47) | @OnClick(R.id.btn_demo_debounce)
method demoRetrofitCalls (line 52) | @OnClick(R.id.btn_demo_retrofit)
method demoPolling (line 57) | @OnClick(R.id.btn_demo_polling)
method demoDoubleBindingWithPublishSubject (line 62) | @OnClick(R.id.btn_demo_double_binding_textview)
method demoRxBus (line 67) | @OnClick(R.id.btn_demo_rxbus)
method formValidation (line 72) | @OnClick(R.id.btn_demo_form_validation_combinel)
method pseudoCacheDemo (line 77) | @OnClick(R.id.btn_demo_pseudo_cache)
method demoTimerIntervalDelays (line 82) | @OnClick(R.id.btn_demo_timing)
method demoTimeout (line 87) | @OnClick(R.id.btn_demo_timeout)
method demoExponentialBackoff (line 92) | @OnClick(R.id.btn_demo_exponential_backoff)
method demoRotationPersist (line 97) | @OnClick(R.id.btn_demo_rotation_persist)
method demoPaging (line 104) | @OnClick(R.id.btn_demo_pagination)
method demoVolleyRequest (line 110) | @OnClick(R.id.btn_demo_volley)
method demoNetworkDetector (line 115) | @OnClick(R.id.btn_demo_networkDetector)
method demoUsing (line 120) | @OnClick(R.id.btn_demo_using)
method demoMulticastPlayground (line 125) | @OnClick(R.id.btn_demo_multicastPlayground)
method clickedOn (line 130) | private void clickedOn(@NonNull Fragment fragment) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/NetworkDetectorFragment.java
class NetworkDetectorFragment (line 29) | public class NetworkDetectorFragment extends BaseFragment {
method onDestroy (line 41) | @Override
method onCreateView (line 47) | @Override
method onActivityCreated (line 55) | @Override
method onStart (line 61) | @Override
method onStop (line 84) | @Override
method listenToNetworkConnectivity (line 92) | private void listenToNetworkConnectivity() {
method getConnectivityStatus (line 106) | private boolean getConnectivityStatus(Context context) {
method log (line 116) | private void log(String logMsg) {
method setupLogger (line 135) | private void setupLogger() {
method isCurrentlyOnMainThread (line 141) | private boolean isCurrentlyOnMainThread() {
class LogAdapter (line 145) | private class LogAdapter extends ArrayAdapter<String> {
method LogAdapter (line 147) | public LogAdapter(Context context, List<String> logs) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PollingFragment.java
class PollingFragment (line 30) | public class PollingFragment extends BaseFragment {
method onCreate (line 45) | @Override
method onCreateView (line 52) | @Override
method onActivityCreated (line 60) | @Override
method onDestroy (line 66) | @Override
method onStartSimplePollingClicked (line 73) | @OnClick(R.id.btn_start_simple_polling)
method onStartIncreasinglyDelayedPolling (line 99) | @OnClick(R.id.btn_start_increasingly_delayed_polling)
method _doNetworkCallAndGetStringResult (line 133) | private String _doNetworkCallAndGetStringResult(long attempt) {
method _getSecondHand (line 154) | private int _getSecondHand() {
method _log (line 161) | private void _log(String logMsg) {
method _setupLogger (line 179) | private void _setupLogger() {
method _isCurrentlyOnMainThread (line 186) | private boolean _isCurrentlyOnMainThread() {
class RepeatWithDelay (line 191) | public class RepeatWithDelay implements Function<Flowable<Object>, Pub...
method RepeatWithDelay (line 197) | RepeatWithDelay(int repeatLimit, int pollingInterval) {
method apply (line 206) | @Override
class LogAdapter (line 231) | private class LogAdapter extends ArrayAdapter<String> {
method LogAdapter (line 233) | public LogAdapter(Context context, List<String> logs) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheFragment.java
class PseudoCacheFragment (line 35) | public class PseudoCacheFragment extends BaseFragment {
method onCreateView (line 50) | @Override
method onDestroyView (line 58) | @Override
method onConcatBtnClicked (line 64) | @OnClick(R.id.btn_pseudoCache_concat)
method onConcatEagerBtnClicked (line 93) | @OnClick(R.id.btn_pseudoCache_concatEager)
method onMergeBtnClicked (line 126) | @OnClick(R.id.btn_pseudoCache_merge)
method onMergeSlowBtnClicked (line 155) | @OnClick(R.id.btn_pseudoCache_mergeSlowDisk)
method onMergeOptimizedBtnClicked (line 184) | @OnClick(R.id.btn_pseudoCache_mergeOptimized)
method onMergeOptimizedWithSlowDiskBtnClicked (line 218) | @OnClick(R.id.btn_pseudoCache_mergeOptimizedSlowDisk)
method wireupDemo (line 255) | private void wireupDemo() {
method getSlowCachedDiskData (line 269) | private Observable<Contributor> getSlowCachedDiskData() {
method getCachedDiskData (line 273) | private Observable<Contributor> getCachedDiskData() {
method getFreshNetworkData (line 295) | private Observable<Contributor> getFreshNetworkData() {
method mapAsList (line 312) | private List<String> mapAsList(HashMap<String, Long> map) {
method dummyDiskData (line 323) | private Map<String, Long> dummyDiskData() {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheMergeFragment.java
class PseudoCacheMergeFragment (line 31) | public class PseudoCacheMergeFragment extends BaseFragment {
method onCreateView (line 41) | @Override
method onDestroyView (line 50) | @Override
method onDemoPseudoCacheClicked (line 56) | @OnClick(R.id.btn_start_pseudo_cache)
method getListStringFromMap (line 97) | private List<String> getListStringFromMap() {
method _getCachedData (line 108) | private Observable<Pair<Contributor, Long>> _getCachedData() {
method _getFreshData (line 126) | private Observable<Pair<Contributor, Long>> _getFreshData() {
method _initializeCache (line 136) | private void _initializeCache() {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitAsyncTaskDeathFragment.java
class RetrofitAsyncTaskDeathFragment (line 31) | public class RetrofitAsyncTaskDeathFragment extends Fragment {
method onCreate (line 43) | @Override
method onCreateView (line 51) | @Override
method onDestroyView (line 66) | @Override
method onGetGithubUserClicked (line 72) | @OnClick(R.id.btn_demo_retrofit_async_death)
class GetGithubUser (line 109) | private class GetGithubUser extends AsyncTask<String, Void, User> {
method doInBackground (line 111) | @Override
method onPostExecute (line 116) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java
class RetrofitFragment (line 37) | public class RetrofitFragment extends Fragment {
method onCreate (line 53) | @Override
method onCreateView (line 62) | @Override
method onDestroyView (line 77) | @Override
method onDestroy (line 83) | @Override
method onListContributorsClicked (line 89) | @OnClick(R.id.btn_demo_retrofit_contributors)
method onListContributorsWithFullUserInfoClicked (line 127) | @OnClick(R.id.btn_demo_retrofit_contributors_with_user_info)
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1Fragment.java
class RotationPersist1Fragment (line 26) | public class RotationPersist1Fragment extends BaseFragment
method startOperationFromWorkerFrag (line 42) | @OnClick(R.id.btn_rotate_persist)
method observeResults (line 59) | @Override
method onActivityCreated (line 95) | @Override
method onCreateView (line 101) | @Override
method onPause (line 109) | @Override
method onDestroyView (line 115) | @Override
method _setupLogger (line 121) | private void _setupLogger() {
method _log (line 127) | private void _log(String logMsg) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java
class RotationPersist1WorkerFragment (line 12) | public class RotationPersist1WorkerFragment extends Fragment {
method onAttach (line 24) | @Override
method onCreate (line 40) | @Override
method onResume (line 59) | @Override
method onDestroy (line 65) | @Override
method onDetach (line 72) | @Override
type IAmYourMaster (line 78) | public interface IAmYourMaster {
method observeResults (line 79) | void observeResults(Flowable<Integer> intsObservable);
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2Fragment.java
class RotationPersist2Fragment (line 25) | public class RotationPersist2Fragment extends BaseFragment
method startOperationFromWorkerFrag (line 40) | @OnClick(R.id.btn_rotate_persist)
method setStream (line 57) | @Override
method onActivityCreated (line 87) | @Override
method onCreateView (line 93) | @Override
method onPause (line 101) | @Override
method _setupLogger (line 107) | private void _setupLogger() {
method _log (line 113) | private void _log(String logMsg) {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2WorkerFragment.java
class RotationPersist2WorkerFragment (line 11) | public class RotationPersist2WorkerFragment extends Fragment {
method onAttach (line 26) | @Override
method onCreate (line 42) | @Override
method onResume (line 58) | @Override
method onDestroy (line 64) | @Override
method onDetach (line 71) | @Override
type IAmYourMaster (line 77) | public interface IAmYourMaster {
method setStream (line 78) | void setStream(Flowable<Integer> intStream);
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/TimeoutDemoFragment.java
class TimeoutDemoFragment (line 27) | public class TimeoutDemoFragment extends BaseFragment {
method onDestroy (line 36) | @Override
method onCreateView (line 47) | @Override
method onActivityCreated (line 55) | @Override
method onStart2sTask (line 61) | @OnClick(R.id.btn_demo_timeout_1_2s)
method onStart5sTask (line 72) | @OnClick(R.id.btn_demo_timeout_1_5s)
method _getObservableTask_5sToComplete (line 86) | private Observable<String> _getObservableTask_5sToComplete() {
method _getObservableTask_2sToComplete (line 103) | private Observable<String> _getObservableTask_2sToComplete() {
method _onTimeoutObservable (line 120) | private Observable<? extends String> _onTimeoutObservable() {
method _getEventCompletionObserver (line 132) | private DisposableObserver<String> _getEventCompletionObserver() {
method _setupLogger (line 155) | private void _setupLogger() {
method _log (line 161) | private void _log(String logMsg) {
method _isCurrentlyOnMainThread (line 180) | private boolean _isCurrentlyOnMainThread() {
FILE: app/src/main/java/com/morihacky/android/rxjava/fragments/TimingDemoFragment.java
class TimingDemoFragment (line 31) | public class TimingDemoFragment extends BaseFragment {
method onCreateView (line 43) | @Override
method onActivityCreated (line 51) | @Override
method onDestroyView (line 57) | @Override
method btn1_RunSingleTaskAfter2s (line 64) | @OnClick(R.id.btn_demo_timing_1)
method btn2_RunTask_IntervalOf1s (line 88) | @OnClick(R.id.btn_demo_timing_2)
method btn3_RunTask_IntervalOf1s_StartImmediately (line 119) | @OnClick(R.id.btn_demo_timing_3)
method btn4_RunTask5Times_IntervalOf3s (line 150) | @OnClick(R.id.btn_demo_timing_4)
method btn5_RunTask5Times_IntervalOf3s (line 175) | @OnClick(R.id.btn_demo_timing_5)
method OnClearLog (line 209) | @OnClick(R.id.btn_clr)
method _setupLogger (line 215) | private void _setupLogger() {
method _log (line 221) | private void _log(String logMsg) {
method _getCurrentTimestamp (line 233) | private String _getCurrentTimestamp() {
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAdapter.java
class PaginationAdapter (line 17) | class PaginationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo...
method PaginationAdapter (line 25) | PaginationAdapter(RxBus bus) {
method addItems (line 29) | void addItems(List<String> items) {
method getItemViewType (line 33) | @Override
method onCreateViewHolder (line 42) | @Override
method onBindViewHolder (line 52) | @Override
method getItemCount (line 63) | @Override
class ItemLogViewHolder (line 68) | private static class ItemLogViewHolder extends RecyclerView.ViewHolder {
method ItemLogViewHolder (line 69) | ItemLogViewHolder(View itemView) {
method create (line 73) | static ItemLogViewHolder create(ViewGroup parent) {
method bindContent (line 78) | void bindContent(String content) {
class ItemBtnViewHolder (line 83) | static class ItemBtnViewHolder extends RecyclerView.ViewHolder {
method ItemBtnViewHolder (line 84) | ItemBtnViewHolder(View itemView) {
method create (line 88) | static ItemBtnViewHolder create(ViewGroup parent) {
method bindContent (line 93) | void bindContent(RxBus bus) {
class PageEvent (line 98) | static class PageEvent {}
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoAdapter.java
class PaginationAutoAdapter (line 13) | class PaginationAutoAdapter extends RecyclerView.Adapter<RecyclerView.Vi...
method PaginationAutoAdapter (line 20) | PaginationAutoAdapter(RxBus bus) {
method onCreateViewHolder (line 24) | @Override
method onBindViewHolder (line 29) | @Override
method getItemViewType (line 39) | @Override
method getItemCount (line 44) | @Override
method addItems (line 49) | void addItems(List<String> items) {
class ItemLogViewHolder (line 53) | private static class ItemLogViewHolder extends RecyclerView.ViewHolder {
method ItemLogViewHolder (line 54) | ItemLogViewHolder(View itemView) {
method create (line 58) | static ItemLogViewHolder create(ViewGroup parent) {
method bindContent (line 63) | void bindContent(String content) {
class PageEvent (line 68) | static class PageEvent {}
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoFragment.java
class PaginationAutoFragment (line 26) | public class PaginationAutoFragment extends BaseFragment {
method onCreateView (line 40) | @Override
method onActivityCreated (line 48) | @Override
method onStart (line 64) | @Override
method onStop (line 115) | @Override
method _itemsFromNetworkCall (line 122) | private Flowable<List<String>> _itemsFromNetworkCall(int pageStart) {
FILE: app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationFragment.java
class PaginationFragment (line 26) | public class PaginationFragment extends BaseFragment {
method onActivityCreated (line 39) | @Override
method onStart (line 55) | @Override
method onStop (line 96) | @Override
method _itemsFromNetworkCall (line 103) | private Flowable<List<String>> _itemsFromNetworkCall(int start, int co...
method onCreateView (line 121) | @Override
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/Contributor.java
class Contributor (line 3) | public class Contributor {
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubApi.java
type GithubApi (line 9) | public interface GithubApi {
method contributors (line 12) | @GET("/repos/{owner}/{repo}/contributors")
method getContributors (line 16) | @GET("/repos/{owner}/{repo}/contributors")
method user (line 20) | @GET("/users/{user}")
method getUser (line 24) | @GET("/users/{user}")
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubService.java
class GithubService (line 14) | public class GithubService {
method GithubService (line 16) | private GithubService() {}
method createGithubService (line 18) | public static GithubApi createGithubService(final String githubToken) {
FILE: app/src/main/java/com/morihacky/android/rxjava/retrofit/User.java
class User (line 3) | public class User {
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBus.java
class RxBus (line 10) | public class RxBus {
method send (line 14) | public void send(Object o) {
method asFlowable (line 18) | public Flowable<Object> asFlowable() {
method hasObservers (line 22) | public boolean hasObservers() {
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemoFragment.java
class RxBusDemoFragment (line 12) | public class RxBusDemoFragment extends BaseFragment {
method onCreateView (line 14) | @Override
method onActivityCreated (line 22) | @Override
class TapEvent (line 36) | public static class TapEvent {}
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom1Fragment.java
class RxBusDemo_Bottom1Fragment (line 17) | public class RxBusDemo_Bottom1Fragment extends BaseFragment {
method onCreateView (line 25) | @Override
method onActivityCreated (line 33) | @Override
method onStart (line 39) | @Override
method onStop (line 55) | @Override
method _showTapText (line 61) | private void _showTapText() {
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom2Fragment.java
class RxBusDemo_Bottom2Fragment (line 21) | public class RxBusDemo_Bottom2Fragment extends BaseFragment {
method onCreateView (line 32) | @Override
method onActivityCreated (line 40) | @Override
method onStart (line 46) | @Override
method onStop (line 73) | @Override
method _showTapText (line 82) | private void _showTapText() {
method _showTapCount (line 88) | private void _showTapCount(int size) {
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java
class RxBusDemo_Bottom3Fragment (line 20) | public class RxBusDemo_Bottom3Fragment extends BaseFragment {
method onCreateView (line 31) | @Override
method onActivityCreated (line 39) | @Override
method onStart (line 45) | @Override
method onStop (line 73) | @Override
method _showTapText (line 82) | private void _showTapText() {
method _showTapCount (line 88) | private void _showTapCount(int size) {
FILE: app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_TopFragment.java
class RxBusDemo_TopFragment (line 14) | public class RxBusDemo_TopFragment extends BaseFragment {
method onCreateView (line 18) | @Override
method onActivityCreated (line 26) | @Override
method onTapButtonClicked (line 32) | @OnClick(R.id.btn_demo_rxbus_tap)
FILE: app/src/main/java/com/morihacky/android/rxjava/volley/MyVolley.java
class MyVolley (line 10) | public class MyVolley {
method MyVolley (line 13) | private MyVolley() {
method init (line 17) | public static void init(Context context) {
method getRequestQueue (line 21) | static RequestQueue getRequestQueue() {
FILE: app/src/main/java/com/morihacky/android/rxjava/volley/VolleyDemoFragment.java
class VolleyDemoFragment (line 36) | public class VolleyDemoFragment extends BaseFragment {
method onCreateView (line 49) | @Override
method onActivityCreated (line 57) | @Override
method onPause (line 63) | @Override
method onDestroyView (line 69) | @Override
method newGetRouteData (line 82) | public Flowable<JSONObject> newGetRouteData() {
method startRequest (line 94) | @OnClick(R.id.btn_start_operation)
method startVolleyRequest (line 99) | private void startVolleyRequest() {
method getRouteData (line 141) | private JSONObject getRouteData() throws ExecutionException, Interrupt...
method _setupLogger (line 152) | private void _setupLogger() {
method _log (line 158) | private void _log(String logMsg) {
method _isCurrentlyOnMainThread (line 177) | private boolean _isCurrentlyOnMainThread() {
FILE: app/src/main/java/com/morihacky/android/rxjava/wiring/LogAdapter.java
class LogAdapter (line 8) | public class LogAdapter extends ArrayAdapter<String> {
method LogAdapter (line 10) | public LogAdapter(Context context, List<String> logs) {
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (268K chars).
[
{
"path": ".gitignore",
"chars": 632,
"preview": "# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\n"
},
{
"path": "LICENSE",
"chars": 11323,
"preview": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licens"
},
{
"path": "README.md",
"chars": 24943,
"preview": "Learning RxJava for Android by example\n==============\n\nThis is a repository with real-world useful examples of using RxJ"
},
{
"path": "app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "app/build.gradle",
"chars": 3313,
"preview": "buildscript {\n repositories {\n// mavenCentral()\n jcenter()\n }\n dependencies {\n classpath '"
},
{
"path": "app/proguard-rules.pro",
"chars": 667,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /A"
},
{
"path": "app/src/androidTest/java/com/morihacky/android/rxjava/app/ApplicationTest.java",
"chars": 363,
"preview": "package com.morihacky.android.rxjava.app;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 927,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n pa"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/MainActivity.java",
"chars": 1636,
"preview": "package com.morihacky.android.rxjava;\n\nimport android.os.Bundle;\nimport android.support.v4.app.Fragment;\nimport android."
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/MyApp.java",
"chars": 1047,
"preview": "package com.morihacky.android.rxjava;\n\nimport android.support.multidex.MultiDexApplication;\nimport com.morihacky.android"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java",
"chars": 370,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.support.v4.app.Fragment;\nimport com.morihacky.android.rx"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java",
"chars": 4773,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os."
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/ConcurrencyWithSchedulersDemoFragment.java",
"chars": 4579,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/DebounceSearchEmitterFragment.java",
"chars": 4206,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/DoubleBindingTextViewFragment.java",
"chars": 2073,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\ni"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/ExponentialBackoffFragment.java",
"chars": 7474,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.sup"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java",
"chars": 3951,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport static android.text.TextUtils.isEmpty;\nimport static android.uti"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java",
"chars": 3657,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nim"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/NetworkDetectorFragment.java",
"chars": 4347,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Contex"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/PollingFragment.java",
"chars": 7396,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheFragment.java",
"chars": 11301,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os."
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheMergeFragment.java",
"chars": 4598,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\ni"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitAsyncTaskDeathFragment.java",
"chars": 3626,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.s"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java",
"chars": 6211,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\ni"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1Fragment.java",
"chars": 3994,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport static android.os.Looper.getMainLooper;\n\nimport android.os.Bundl"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java",
"chars": 2367,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2Fragment.java",
"chars": 3752,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport static android.os.Looper.getMainLooper;\n\nimport android.os.Bundl"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2WorkerFragment.java",
"chars": 2319,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.content.Context;\nimport android.os.Bundle;\nimport androi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist3Fragment.kt",
"chars": 3635,
"preview": "package com.morihacky.android.rxjava.fragments\n\nimport android.arch.lifecycle.ViewModel\nimport android.arch.lifecycle.Vi"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/TimeoutDemoFragment.java",
"chars": 5434,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os."
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/fragments/TimingDemoFragment.java",
"chars": 7428,
"preview": "package com.morihacky.android.rxjava.fragments;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.sup"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAdapter.java",
"chars": 2698,
"preview": "package com.morihacky.android.rxjava.pagination;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.Lay"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoAdapter.java",
"chars": 1771,
"preview": "package com.morihacky.android.rxjava.pagination;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.Lay"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoFragment.java",
"chars": 4250,
"preview": "package com.morihacky.android.rxjava.pagination;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationFragment.java",
"chars": 4176,
"preview": "package com.morihacky.android.rxjava.pagination;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\n"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/retrofit/Contributor.java",
"chars": 129,
"preview": "package com.morihacky.android.rxjava.retrofit;\n\npublic class Contributor {\n public String login;\n public long contribu"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubApi.java",
"chars": 804,
"preview": "package com.morihacky.android.rxjava.retrofit;\n\nimport java.util.List;\n\nimport io.reactivex.Observable;\nimport retrofit2"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubService.java",
"chars": 1365,
"preview": "package com.morihacky.android.rxjava.retrofit;\n\nimport android.text.TextUtils;\n\nimport com.jakewharton.retrofit2.adapter"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/retrofit/User.java",
"chars": 115,
"preview": "package com.morihacky.android.rxjava.retrofit;\n\npublic class User {\n public String name;\n public String email;\n}\n"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBus.java",
"chars": 611,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport com.jakewharton.rxrelay2.PublishRelay;\nimport com.jakewharton.rxrela"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemoFragment.java",
"chars": 1252,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom1Fragment.java",
"chars": 1889,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom2Fragment.java",
"chars": 3044,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java",
"chars": 3012,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_TopFragment.java",
"chars": 1159,
"preview": "package com.morihacky.android.rxjava.rxbus;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimpor"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/volley/MyVolley.java",
"chars": 690,
"preview": "package com.morihacky.android.rxjava.volley;\n\nimport android.content.Context;\nimport com.android.volley.RequestQueue;\nim"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/volley/VolleyDemoFragment.java",
"chars": 5446,
"preview": "package com.morihacky.android.rxjava.volley;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Loo"
},
{
"path": "app/src/main/java/com/morihacky/android/rxjava/wiring/LogAdapter.java",
"chars": 357,
"preview": "package com.morihacky.android.rxjava.wiring;\n\nimport android.content.Context;\nimport android.widget.ArrayAdapter;\nimport"
},
{
"path": "app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt",
"chars": 263,
"preview": "package com.morihacky.android.rxjava.ext\n\nimport io.reactivex.disposables.CompositeDisposable\nimport io.reactivex.dispos"
},
{
"path": "app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt",
"chars": 5868,
"preview": "package com.morihacky.android.rxjava.fragments\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.o"
},
{
"path": "app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt",
"chars": 2163,
"preview": "package com.morihacky.android.rxjava.fragments\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.o"
},
{
"path": "app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt",
"chars": 3126,
"preview": "package com.morihacky.android.rxjava.fragments\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.o"
},
{
"path": "app/src/main/res/drawable/btn_round.xml",
"chars": 376,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:"
},
{
"path": "app/src/main/res/layout/fragment_buffer.xml",
"chars": 991,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_concurrency_schedulers.xml",
"chars": 1389,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_debounce.xml",
"chars": 1451,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_demo_timing.xml",
"chars": 3528,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_double_binding_textview.xml",
"chars": 1856,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_exponential_backoff.xml",
"chars": 1333,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_form_validation_comb_latest.xml",
"chars": 2853,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_main.xml",
"chars": 5056,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><ScrollView\n android:layout_height=\"match_parent\"\n android:layout_width=\"mat"
},
{
"path": "app/src/main/res/layout/fragment_multicast_playground.xml",
"chars": 1988,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/layout/fragment_network_detector.xml",
"chars": 649,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/fragment_pagination.xml",
"chars": 962,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/fragment_polling.xml",
"chars": 1398,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/fragment_pseudo_cache.xml",
"chars": 3766,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/fragment_pseudo_cache_concat.xml",
"chars": 735,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:baselineAligned=\"false\"\n android:orientation=\"vertic"
},
{
"path": "app/src/main/res/layout/fragment_retrofit.xml",
"chars": 2987,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_retrofit_async_task_death.xml",
"chars": 1447,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_rotation_persist.xml",
"chars": 854,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_rxbus_bottom.xml",
"chars": 1075,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n android:background=\"@color/blue\"\n android:layout_height=\"matc"
},
{
"path": "app/src/main/res/layout/fragment_rxbus_demo.xml",
"chars": 736,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:id=\"@+id/demo_rxbus_frag_12\"\n android:baselineAligne"
},
{
"path": "app/src/main/res/layout/fragment_rxbus_frag3.xml",
"chars": 377,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n android:layout_height=\"match_parent\"\n android:layout_width=\"m"
},
{
"path": "app/src/main/res/layout/fragment_rxbus_top.xml",
"chars": 855,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n android:background=\"@color/green\"\n android:layout_height=\"mat"
},
{
"path": "app/src/main/res/layout/fragment_subject_timeout.xml",
"chars": 1289,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/fragment_timer_demo.xml",
"chars": 408,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n android:layout_height=\"match_parent\"\n android:layout_width=\"m"
},
{
"path": "app/src/main/res/layout/fragment_volley.xml",
"chars": 953,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n android:orientation=\"vertical\"\n android:layout_height=\"match"
},
{
"path": "app/src/main/res/layout/item_btn.xml",
"chars": 329,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Button\n android:id=\"@+id/item_btn\"\n xmlns:android=\"http://schemas.android."
},
{
"path": "app/src/main/res/layout/item_log.xml",
"chars": 595,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/item_log_white.xml",
"chars": 637,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/menu/demo.xml",
"chars": 354,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"\n "
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 401,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#3F51B5</color>\n <color name=\"color"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 513,
"preview": "<resources>\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizontal"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 11006,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"app_name\">Android-RxJava</string>\n <string name"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 340,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
},
{
"path": "app/src/main/res/values-w820dp/dimens.xml",
"chars": 358,
"preview": "<resources>\n <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n (such as s"
},
{
"path": "build.gradle",
"chars": 963,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Wed May 24 09:29:38 PDT 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 853,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any "
},
{
"path": "gradlew",
"chars": 5299,
"preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n## Gradle start up"
},
{
"path": "gradlew.bat",
"chars": 2260,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "settings.gradle",
"chars": 15,
"preview": "include ':app'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the kaushikgopal/RxJava-Android-Samples GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (244.1 KB), approximately 58.6k tokens, and a symbol index with 317 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.