├── LICENSE ├── README.md ├── item-ideas.md └── items ├── convert-callbacks-to-observable.md ├── convert-functions-to-observable.md ├── emit-immutable-values.md ├── images ├── download-video-notification.gif ├── observable-chain.png ├── observer-chain-stacktrace.png ├── observer-chain-subscribe-o1.png ├── observer-chain-subscribe-o2.png ├── observer-chain-subscribe-o3.png ├── playing-time-millis-observable-marble-diagram.png ├── switch-map-marble-diagram.png ├── throttle-first-marble-diagram.png ├── video-player-transcript-playing.gif └── video-player-transcript-seeking.gif ├── optimize-your-subscribers.md ├── test-emitted-values-using-testobserver.md ├── test-work-scheduling-using-testscheduler.md ├── understand-observable-and-observer-chains.md ├── understand-subscribeon-and-observeon.md ├── understand-switch-map.md ├── use-compose-for-operator-sequences.md └── use-retrolambda.md /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the “Licensor.” The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. 396 | 397 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Effective RxJava 2 | 3 | This is a collection of items, each specifying one general rule, to help you write [RxJava](https://github.com/ReactiveX/RxJava) code more effectively. It is modeled after two of my favorite technical books, _Effective C++_ and _Effective Java_. I appreciate not only the soundness of their contents, but the brevity of their writing style. I hope that Effective RxJava has the same value proposition. 4 | 5 | For each rule I've attempted to provide relevant example code -- most of which is currently in production in the [Khan Academy Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android). 6 | 7 | ### Items 8 | 9 | * [Use Retrolambda](items/use-retrolambda.md) 10 | * [Emit immutable values](items/emit-immutable-values.md) 11 | * [Understand `Observable` and observer chains](items/understand-observable-and-observer-chains.md) 12 | * [Convert functions to `Observable`](items/convert-functions-to-observable.md) 13 | * [Convert callbacks to `Observable`](items/convert-callbacks-to-observable.md) 14 | * [Understand `subscribeOn` and `observeOn`](items/understand-subscribeon-and-observeon.md) 15 | * [Understand `switchMap`](items/understand-switch-map.md) 16 | * [Test emitted values using `TestObserver`](items/test-emitted-values-using-testobserver.md) 17 | * [Test work scheduling using `TestScheduler`](items/test-work-scheduling-using-testscheduler.md) 18 | * [Use `compose` for operator sequences](items/use-compose-for-operator-sequences.md) 19 | * [Optimize your subscribers](items/optimize-your-subscribers.md) 20 | 21 | -------------------------------------------------------------------------------- /item-ideas.md: -------------------------------------------------------------------------------- 1 | * Strive for a single source of truth 2 | * Use Schedulers.io for independent I/O only 3 | * Understand hot vs cold observables 4 | -------------------------------------------------------------------------------- /items/convert-callbacks-to-observable.md: -------------------------------------------------------------------------------- 1 | ### Convert callbacks to `Observable` 2 | 3 | Many Java libraries define callback interfaces for operations that are very `Observer`-like: 4 | 5 | * If the operation succeeded, an `onSuccess` or similar method provides a value. 6 | * If the operation failed, an `onError` or similar method provides a `Throwable` specifying the error. 7 | 8 | By converting such callbacks to `Observable` instances, we can apply operators to the emitted value. 9 | 10 | As an example of such an `Observer`-like interface consider the [`ManifestCallback`](http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/util/ManifestFetcher.ManifestCallback.html) interface of class `ManifestFetcher` in [ExoPlayer](http://google.github.io/ExoPlayer/): 11 | 12 | ```java 13 | /** 14 | * @param The type of manifest. 15 | */ 16 | public interface ManifestCallback { 17 | /** 18 | * @param manifest the successfully loaded manifest 19 | */ 20 | void onSingleManifest(T manifest); 21 | 22 | /** 23 | * Invoked when the load has failed. 24 | * 25 | * @param e the cause of the failure for loading the manifest 26 | */ 27 | void onSingleManifestError(IOException e); 28 | } 29 | ``` 30 | 31 | In the [Khan Academy Android application](https://play.google.com/store/apps/details?id=org.khanacademy.android), we use ExoPlayer to play videos. When the client has an Internet connection, the video player streams videos using [HLS](https://en.wikipedia.org/wiki/HTTP_Live_Streaming). This allows the video player to stream the video at a quality that is commensurate with its available bandwidth. The URLs for the video streams of different qualities are specified by a `HlsPlaylist` instance. The client must therefore first download this `HlsPlaylist` from the server. 32 | 33 | Given the `Uri` instance of the playlist, we can retrieve it by constructing and invoking `singleLoad` on a `ManifestFetcher` instance parameterized with type `HlsPlaylist`: 34 | 35 | ```java 36 | final UriDataSource dataSource = new DefaultUriDataSource(mContext, mUserAgent); 37 | final HlsPlaylistParser parser = new HlsPlaylistParser(); 38 | final ManifestFetcher playlistFetcher = new ManifestFetcher<>( 39 | uri.toString(), dataSource, parser); 40 | 41 | playlistFetcher.singleLoad(mMainHandler.getLooper(), new ManifestCallback() { 42 | @Override 43 | public void onSingleManifest(final HlsPlaylist manifest) { 44 | // code to configure ExoPlayer with the given playlist goes here ... 45 | } 46 | 47 | @Override 48 | public void onSingleManifestError(final IOException e) { 49 | // code to recover from the error goes here ... 50 | } 51 | }); 52 | ``` 53 | 54 | When `singleLoad` either succeeds loading or fails to load the `HlsPlaylist` from the server, it invokes the corresponding method of its `ManifestCallback` parameter, which is run on a `Looper` instance associated with the main thread. 55 | 56 | To convert this to an `Observable`, we use the `create` static factory method: 57 | 58 | ```java 59 | final Observable playlistObservable = Observable.create(subscriber -> { 60 | final UriDataSource dataSource = new DefaultUriDataSource(mContext, mUserAgent); 61 | final HlsPlaylistParser parser = new HlsPlaylistParser(); 62 | final ManifestFetcher playlistFetcher = new ManifestFetcher<>( 63 | uri.toString(), dataSource, parser); 64 | 65 | playlistFetcher.singleLoad(mMainHandler.getLooper(), new ManifestCallback() { 66 | @Override 67 | public void onSingleManifest(final HlsPlaylist manifest) { 68 | if (!subscriber.isUnsubscribed()) { 69 | subscriber.onNext(manifest); 70 | subscriber.onCompleted(); 71 | } 72 | } 73 | 74 | @Override 75 | public void onSingleManifestError(final IOException e) { 76 | if (!subscriber.isUnsubscribed()) { 77 | subscriber.onError(e); 78 | } 79 | } 80 | }); 81 | }); 82 | // subscribing to playlistObservable and configuring ExoPlayer follows here ... 83 | ``` 84 | 85 | Whenever a new subscription to the `Observable` is created, the code that was passed to the `create` method is invoked. This is a method that accepts as a parameter a [`Subscriber`](http://reactivex.io/RxJava/javadoc/rx/Subscriber.html) instance belonging to the subscription. 86 | 87 | When the method passed to `create` returns, no methods belonging to the `Subscriber` parameter have yet been invoked. The fetching of the `HlsPlaylist` has begun, however, and when the `ManifestFetcher` succeeds or fails to load the `HlsPlaylist`, it again invokes the corresponding method on its handler: 88 | 89 | * If the operation succeeded, `onSingleManifest` is invoked with the playlist. This emits the `HlsPlaylist` instance as a single value to the subscriber using `onNext`, and then completes the subscription using `onCompleted`. 90 | * If the operation failed, an `onSingleManifestError` is invoked with the exception describing the failure. This forwards the `IOException` to the subscriber using `onError`, thereby terminating the subscription. 91 | 92 | Note that if the client retains its `Subscription` instance, then it may call `unsubscribe` while the `HlsPlaylist` is still being fetched, and consequently before either method `onSingleManifest` or `onSingleManifestError` is invoked. Therefore both methods guard against the client unsubscribing. Note that unsubscribing does not cancel the work of fetching the `HlsPlaylist`, but instead, simply ignores the outcome. 93 | 94 | -------------------------------------------------------------------------------- /items/convert-functions-to-observable.md: -------------------------------------------------------------------------------- 1 | ### Convert functions to `Observable` 2 | 3 | From the Javadoc for `Observable`, it is not immediately clear how to create an `Observable` that invokes a given `Func0` upon subscription. We can accomplish this by combining its `defer` and `just` operators: 4 | 5 | ```java 6 | /** 7 | * @return an {@link Observable} that emits invokes {@code function} upon subscription and emits 8 | * its value 9 | */ 10 | public static Observable makeObservable(final Func0 function) { 11 | checkNotNull(function); 12 | 13 | return Observable.defer(() -> Observable.just(function.call())); 14 | } 15 | ``` 16 | 17 | Sometimes this method is useful by itself: 18 | 19 | ```java 20 | mVideoDownloadManager.getDownloadedJsonTranscriptUri(videoItemId) 21 | .flatMap(downloadedJsonTranscriptUriOptional -> { 22 | if (downloadedJsonTranscriptUriOptional.isPresent()) { 23 | // Read the transcript from the downloaded file. 24 | return ObservableUtils.makeObservable(() -> { 25 | final URI uri = downloadedJsonTranscriptUriOptional.get(); 26 | final BufferedReader reader = FileUtil.newUtf8Reader(new File(uri)); 27 | return VideoSubtitleSequenceJsonDecoder.read(new JsonReader(reader)); 28 | }) 29 | .subscribeOn(Schedulers.io()); 30 | } else { 31 | // Return the transcript from the network using Retrofit. 32 | return mContentApi.downloadVideoSubtitles(youTubeId); 33 | } 34 | }) 35 | // more operators follow here ... 36 | ``` 37 | 38 | In many cases, we have found this method useful when defining a class that wraps another class and converts its public API to expose an `Observable` for each `public` method. For example, we have a `ContentDatabase` in the [Khan Academy Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android) through which the client can load content, such as videos and articles and their parent topics (e.g. "math" and "3rd grade math"), from a database on the device. Its methods are synchronous: 39 | 40 | ```java 41 | public interface ContentDatabase extends Closeable { 42 | Optional fetchTopic(String topicSlug); 43 | 44 | Optional fetchContentItem(ContentItemIdentifier identifier); 45 | 46 | // more methods follow here ... 47 | } 48 | ``` 49 | 50 | The synchronous API of `ContentDatabase` makes testing an implementation easy: 51 | 52 | ```java 53 | @Test 54 | public void testFetchMissingContentItem() { 55 | final ContentItemIdentifier missingContentItemId = TestUtil.randomContentItemId(); 56 | final Optional contentItemOptional = 57 | mContentDatabase.fetchContentItem(missingContentItemId); 58 | assertEquals(Optional.absent(), contentItemOptional); 59 | } 60 | ``` 61 | 62 | But our application should not invoke methods on `ContentDatabase` directly, as the main thread will block upon reading from the database. We therefore introduce an `ObservableContentDatabase` class that converts the return type of each method of `ContentDatabase` to an `Observable`. A `ObservableContentDatabase` instance delegates to its underlying `ContentDatabase` instance, but invokes each method on a given `Scheduler` so that all reads from the database happen off the main thread: 63 | 64 | ```java 65 | public class ObservableContentDatabase implements Closeable { 66 | private final ContentDatabase mContentDatabase; 67 | private final Scheduler mScheduler; 68 | 69 | public ObservableContentDatabase(final ContentDatabase contentDatabase) { 70 | this(contentDatabase, SchedulerUtils.newSingleThreadIoScheduler()); 71 | } 72 | public ObservableContentDatabase(final ContentDatabase contentDatabase, 73 | final Scheduler scheduler) { 74 | mContentDatabase = checkNotNull(contentDatabase); 75 | mScheduler = checkNotNull(scheduler); 76 | } 77 | 78 | public Observable> fetchTopic(final String topicSlug) { 79 | return subscribeOnScheduler(() -> mContentDatabase.fetchTopic(topicSlug)); 80 | } 81 | public Observable> fetchContentItem( 82 | final ContentItemIdentifier contentItemId) { 83 | return subscribeOnScheduler(() -> mContentDatabase.fetchContentItem(contentItemId)); 84 | } 85 | // more delegating methods follow here ... 86 | 87 | private Observable subscribeOnScheduler(final Func0 function) { 88 | return ObservableUtils.makeObservable(function) 89 | .subscribeOn(mScheduler); 90 | } 91 | } 92 | ``` 93 | 94 | Each of the `public` methods that return an `Observable` uses the `private` method `subscribeOnScheduler` to create that `Observable`. That method accepts a `Func0` parameter specifying the work to execute on the `Scheduler` instance `mScheduler`. It uses our `makeObservable` method to convert that work into an `Observable`, and uses the `subscribeOn` operator to ensure that it is executed on the `Scheduler` instance `mScheduler`. 95 | 96 | It is up to a client, upon subscribing to the `Observable` instances from `ObservableContentDatabase`, to invoke `observeOn(AndroidSchedulers.mainThread())` if necessary. 97 | 98 | -------------------------------------------------------------------------------- /items/emit-immutable-values.md: -------------------------------------------------------------------------------- 1 | ### Emit immutable values 2 | 3 | The [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa/) project, which is an Functional Reactive Programming implementation for iOS and OS X, touts itself as "streams of values over time." We propose that you go one step further, and to emit "streams of *immutable values* over time" whenever possible. To quote from the [Android Development Best Practices guide](https://github.com/Khan/style-guides/blob/master/style/android-development-best-practices.md) for [Khan Academy](https://www.khanacademy.org/): 4 | 5 | > Our principal goal as programmers is to reduce complexity. At any point in a program, we can reason about the state of a constant trivially -- its state is the state upon assignment. By contrast, the state of a variable can change endlessly. 6 | > 7 | > With judicious use, immutable objects can lead to quicker execution speed and lower memory usage. The hash value of a `String`, the query parameters of an immutable URL, and the distance traced by an immutable sequence of points are all immutable as well. We can cache their values for later use instead of recompute them on every access. 8 | > 9 | > An immutable value is safe in many ways. We can share an immutable value with clients without worry that the client will silently mutate the value. Similarly, a client can accept an immutable value as a parameter without needing to defensively copy it. An immutable value is also thread-safe, because a client must acquire a lock only to read data that another client can concurrently modify. An immutable value renders that moot. 10 | 11 | To summarize: Immutable values help tame complexity. In RxJava, a data source with an `Observable` should emit immutable values that reflect its current state. When the state of the data source is updated, it should simply emit a new immutable value that reflects the updated state. 12 | 13 | If you are developing an Android application, and you are using [ProGuard](http://proguard.sourceforge.net/) to eliminate dead code from your application, then consider using AutoValue and Guava from Google to help construct immutable values. (AutoValue includes Guava as a dependency, and Guava is a large library. So dead code elimination via ProGuard is ideal.) 14 | 15 | #### Use AutoValue 16 | 17 | [AutoValue](https://github.com/google/auto/tree/master/value) creates immutable value types with very little effort required on your part. From its documentation: 18 | 19 | > Classes with value semantics are extremely common in Java. These are classes for which object identity is irrelevant, and instances are considered interchangeable based only on the equality of their field values... AutoValue provides an easier way to create immutable value types, with less code and less room for error. 20 | 21 | Instances of immutable value types defined by AutoValue are simply data. They have no behavior. This is ideal because such added behavior will seek to mutate the associated value type instance, and we want to avoid this. 22 | 23 | To use AutoValue, create an abstract class with an abstract getter for each desired field. For example, our `ArticleViewFragment` for viewing articles defines a static inner class named `ToolbarViewData` that is annotated with `@AutoValue`: 24 | 25 | ```java 26 | @AutoValue 27 | static abstract class ToolbarViewData { 28 | abstract Article article(); 29 | abstract ContentItemThumbnailData thumbnailData(); 30 | } 31 | ``` 32 | 33 | Upon compiling, the annotation processor for AutoValue creates a concrete subclass of `ToolbarViewData` named `AutoValue_ArticleViewFragment_ToolbarViewData`. This class has a constructor that is initialized with an `Article` and `ContentItemThumbnailData` instance. These values are assigned to private and `final` instance fields, which the implemented `article` and `thumbnailData` getters return: 34 | 35 | ```java 36 | @Override 37 | Article article() { 38 | return article; 39 | } 40 | 41 | @Override 42 | ContentItemThumbnailData thumbnailData() { 43 | return thumbnailData; 44 | } 45 | ``` 46 | 47 | The class also implements `equals` and `hashCode`, making such values suitable for use in sets, or as keys in maps: 48 | 49 | ```java 50 | @Override 51 | public boolean equals(Object o) { 52 | if (o == this) { 53 | return true; 54 | } 55 | if (o instanceof ArticleViewFragment.ToolbarViewData) { 56 | ArticleViewFragment.ToolbarViewData that = (ArticleViewFragment.ToolbarViewData) o; 57 | return (this.article.equals(that.article())) 58 | && (this.thumbnailData.equals(that.thumbnailData())); 59 | } 60 | return false; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | int h = 1; 66 | h *= 1000003; 67 | h ^= article.hashCode(); 68 | h *= 1000003; 69 | h ^= thumbnailData.hashCode(); 70 | return h; 71 | } 72 | ``` 73 | 74 | Moreover, it also implements the `toString` method, which facilitates with logging such values: 75 | 76 | ```java 77 | @Override 78 | public String toString() { 79 | return "ToolbarViewData{" 80 | + "article=" + article + ", " 81 | + "thumbnailData=" + thumbnailData 82 | + "}"; 83 | } 84 | ``` 85 | 86 | Because `AutoValue_ArticleViewFragment_ToolbarViewData` is not a convenient name, convention is to add a static factory method named `create` to each class annotated with `@AutoValue`. This method instantiates and returns the corresponding AutoValue implementation. For example, `ToolbarViewData` defines: 87 | 88 | ```java 89 | public static ToolbarViewData create(final Article article, 90 | final ContentItemThumbnailData thumbnailData) { 91 | return new AutoValue_ArticleViewFragment_ToolbarViewData(article, thumbnailData); 92 | } 93 | ``` 94 | 95 | A client can now simply call static factory method `create` of `ToolbarViewData` to create such a `AutoValue_ArticleViewFragment_ToolbarViewData` instance. 96 | 97 | Other conveniences from AutoValue include: 98 | 99 | * The generated class ensures that each constructor parameter is not `null`, unless the corresponding getter in the abstract base class has the `@Nullable` annotation. 100 | * A builder is automatically generated through the use of the `@AutoValue.Builder` annotation. 101 | * The generated class is serializable if the abstract base class implements `Serializable`. 102 | 103 | See the [AutoValue documentation](https://github.com/google/auto/tree/master/value) for more details. 104 | 105 | If you are developing for Android, consider integrating the [`Parcelable` extension for AutoValue](https://github.com/rharter/auto-value-parcel). As its name suggests, it generates value types that implement the `Parcelable` interface. This allows you to easily persist instances in a `Bundle`, which may be useful when specifying `Intent` parameters or saving the state of an activity or fragment. 106 | 107 | #### Use Guava 108 | 109 | [Guava](https://github.com/google/guava) contains several of Google's core libraries for its Java based projects. Most notably it includes [immutable collection implementations](https://github.com/google/guava/wiki/ImmutableCollectionsExplained), thereby allowing you to construct immutable `List`, `Map`, `Set`, `SortedMap`, and `SortedSet` instances. This is possible because the mutating methods of each collection interface, such as `add`, `set`, or `remove`, are defined as optional operations. The underlying implementation can choose to implement the method as specified by the interface, or throw an `UnsupportedOperationException`. Guava chooses the latter. 110 | 111 | If an `Observable` must emit a collection, use Guava to construct and then emit an immutable copy of the backing collection. For example: 112 | 113 | ```java 114 | private void emitUpdatedFiles() { 115 | final Set updatedFiles = ImmutableSet.copyOf(mDownloadsByFile.keySet()); 116 | mDownloadsSubject.onNext(updatedDownloads); 117 | } 118 | ``` 119 | 120 | If entries are later added to or removed from `mDownloadsByFile`, those changes are not reflected in the emitted `Set` because an immutable copy was made. 121 | 122 | These classes are also useful for ensuring that AutoValue instances are constructed with immutable collections, and thus the AutoValue instance itself is also immutable. For example: 123 | 124 | ```java 125 | @AutoValue 126 | public abstract class ProcessedConversionsError { 127 | public abstract List failedConversionNames(); 128 | public abstract List invalidConversionNames(); 129 | 130 | public static ProcessedConversionsError create( 131 | List failedConversionNames, 132 | List invalidConversionNames) { 133 | return new AutoValue_ProcessedConversionsError( 134 | ImmutableList.copyOf(failedConversionNames), 135 | ImmutableList.copyOf(invalidConversionNames) 136 | ); 137 | } 138 | } 139 | ``` 140 | 141 | If the caller mutates the `parameters` argument that was passed into the constructor, those changes are not reflected in the `ProcessedConversionsError` instance, again because an immutable copy was made. 142 | 143 | Finally, note that if the value passed into a `copyOf` method is itself an instance of the immutable collection, then the `copyOf` method simply returns its parameter. See the [Guava documentation on immutable collections](https://github.com/google/guava/wiki/ImmutableCollectionsExplained) for more details. 144 | 145 | #### Other sources of immutability 146 | 147 | Well written third party libraries often have immutable abstractions that are ripe for reuse. For example, if you are using [OkHttp](http://square.github.io/okhttp/), then you can use its `HttpUrl` class to represent an immutable URL. Moreover, OkHttp includes [Okio](https://github.com/square/okio) as a dependency, which has a `ByteString` class that you can use to represent an immutable sequence of bytes. 148 | 149 | Peruse the API documentation of third party libraries that you use, and identify immutable value types that you can leverage. 150 | 151 | -------------------------------------------------------------------------------- /items/images/download-video-notification.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/download-video-notification.gif -------------------------------------------------------------------------------- /items/images/observable-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/observable-chain.png -------------------------------------------------------------------------------- /items/images/observer-chain-stacktrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/observer-chain-stacktrace.png -------------------------------------------------------------------------------- /items/images/observer-chain-subscribe-o1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/observer-chain-subscribe-o1.png -------------------------------------------------------------------------------- /items/images/observer-chain-subscribe-o2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/observer-chain-subscribe-o2.png -------------------------------------------------------------------------------- /items/images/observer-chain-subscribe-o3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/observer-chain-subscribe-o3.png -------------------------------------------------------------------------------- /items/images/playing-time-millis-observable-marble-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/playing-time-millis-observable-marble-diagram.png -------------------------------------------------------------------------------- /items/images/switch-map-marble-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/switch-map-marble-diagram.png -------------------------------------------------------------------------------- /items/images/throttle-first-marble-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/throttle-first-marble-diagram.png -------------------------------------------------------------------------------- /items/images/video-player-transcript-playing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/video-player-transcript-playing.gif -------------------------------------------------------------------------------- /items/images/video-player-transcript-seeking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgp/effective-rxjava/ef182557eb23c32cd662ebef2bbecd1f8fa11192/items/images/video-player-transcript-seeking.gif -------------------------------------------------------------------------------- /items/optimize-your-subscribers.md: -------------------------------------------------------------------------------- 1 | ### Optimize your subscribers 2 | 3 | Below are just a few ways to optimize your subscriptions to `Observable` instances. 4 | 5 | #### Make the side-effect obvious 6 | 7 | When defining a subscriber to an observable, attempt to move any filtering or transformations of the input to upstream operators. This makes any side-effect of the subscription more obvious. 8 | 9 | For example, consider the following code from the video player of the [Khan Academy Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android): 10 | 11 | ```java 12 | getStateObservable().subscribe(state -> { 13 | switch (state) { 14 | case UNINITIALIZED: 15 | case PREPARING: 16 | case PAUSED: 17 | case ENDED: 18 | case ERROR: 19 | mVideoSurfaceHolder.setKeepScreenOn(false); 20 | return; 21 | 22 | case BUFFERING: 23 | case PLAYING: 24 | mVideoSurfaceHolder.setKeepScreenOn(true); 25 | return; 26 | } 27 | throw new IllegalArgumentException("Unknown player state: " + state); 28 | }); 29 | ``` 30 | 31 | The `getStateObservable()` method returns an `Observable` that emits the state of the video player. When that state is either the `BUFFERING` or `PLAYING` state, then we call the `setKeepSreenOn` method of `mVideoSurfaceHolder` with `true`. This keeps the screen turned on while the video is playing or is preparing to play. But when the video player is in any other state, then we call the `setKeepScreenOn` method with a value of `false` since playback is not and will not happen. 32 | 33 | The side-effect is invoking `setKeepScreenOn`, but that constitutes only two lines of this 15 line method. Moreover, it requires close inspection to conclude that `setKeepScreenOn` is called for every state. Alternatively, we can rewrite this to use the `map` operator: 34 | 35 | ```java 36 | getStateObservable() 37 | .map(state -> { 38 | switch (state) { 39 | case UNINITIALIZED: 40 | case PREPARING: 41 | case PAUSED: 42 | case ENDED: 43 | case ERROR: 44 | return false; 45 | 46 | case BUFFERING: 47 | case PLAYING: 48 | return true; 49 | } 50 | throw new IllegalArgumentException("Unknown player state: " + state); 51 | }) 52 | .distinctUntilChanged() 53 | .subscribe(shouldKeepOnScreen -> { 54 | mVideoSurfaceHolder.setKeepScreenOn(shouldKeepOnScreen); 55 | }); 56 | ``` 57 | 58 | The `map` operator returns whether the screen should be kept on for its state parameter. It is obvious that it returns a `boolean` value for every state, and consequently that `setKeepScreenOn` can be called for any state. And as the only line of the subscription body, the side-effect of invoking `setKeepScreenOn` is also obvious. Moreover, this allows us to precede creating the subscription with a call to `distinctUntilChanged`, which will ensure that we don't invoke the `setKeepScreenOn` method needlessly. (In practice, this is unlikely to matter; we only mean to show how creating thin subscribers can increase flexibility.) 59 | 60 | #### Merge like subscribers 61 | 62 | If you have multiple subscriptions to multiple `Observable` instances, and each of those subscriptions consumes the emitted values or notifications in the same manner, then use the `merge` static factory method to create a single `Observable` instance and subscribe to only that. 63 | 64 | For example, in the [item for understanding `switchMap`](understand-switch-map.md), we created an `Observable` that emitted the video playback time at regular intervals as a video plays. When that observable emits a new time, we scroll to and highlight the part of the transcript that spans that time: 65 | 66 | ```java 67 | mVideoPlayer.getPlayingTimeMillisObservable() 68 | .subscribe(playingTime -> { 69 | mTranscriptPlayer.highlightPartForTime(playingTime); 70 | }); 71 | ``` 72 | 73 | Alternatively, the user can drag the "thumb" of the seek bar and seek through the video. As the user seeks through the video, we also scroll to and highlight the part of the video that spans the time that the user sought: 74 | 75 | ![Highlighting of the transcript as the user seeks](images/video-player-transcript-seeking.gif) 76 | 77 | As the user seeks, the time sought is not emitted by the `Observable` from `getPlayingTimeMillisObservable`, because the video is not playing. Instead, as the user seeks, the `ExoPlayer` instance passes the sought time to the `seekTo` method of its registered `MediaPlayerControl`. We implement this method such that it emits on a `PublishSubject` named `mSeekingSubject`: 78 | 79 | ```java 80 | @Override 81 | public void seekTo(final int timeMillis) { 82 | mSeekingSubject.onNext(timeMillis); 83 | } 84 | ``` 85 | 86 | The video player implementation exposes this subject as another `Observable`, available through a method named `getSeekingTimeMillisObservable`. Again, when that observable emits a new time, we scroll to and highlight the part of the transcript that spans that time: 87 | 88 | ```java 89 | mVideoPlayer.getSeekingTimeMillisObservable() 90 | .subscribe(seekingTime -> { 91 | mTranscriptPlayer.highlightPartForTime(seekingTime); 92 | }); 93 | ``` 94 | 95 | We consolidate these two subscribers using the `merge` method of `Observable`: 96 | 97 | ```java 98 | Observable.merge( 99 | mVideoPlayer.getPlayingTimeMillisObservable(), 100 | mVideoPlayer.getSeekingTimeMillisObservable() 101 | ) 102 | .subscribe(highlightTime -> { 103 | mTranscriptPlayer.highlightPartForTime(highlightTime); 104 | }); 105 | ``` 106 | 107 | Now it is far easier to intuit that as the video plays or as the user seeks through the video, we always highlight the part of the transcript that spans that time. 108 | 109 | -------------------------------------------------------------------------------- /items/test-emitted-values-using-testobserver.md: -------------------------------------------------------------------------------- 1 | ### Test emitted values using `TestObserver` 2 | 3 | To test a chain of `Observable` instances within a component, attempt to: 4 | 5 | * inject the root `Observable` instance into the component 6 | * observe the emitted values through a public or package-private `Observable` instance that is downstream 7 | 8 | You can then inject a `PublishSubject` into the component, thereby allowing you to control the emission of events by the root `Observable`. Subscribing to the public or package-private `Observable` instance using a `TestObserver` lets you assert that it emits the expected values. 9 | 10 | If you cannot inject the root `Observable` instance into the component, or if the component is too difficult to get under test, then consider two options: 11 | 12 | * Attempt to create a static, package-private method that encapsulates creating the chain of `Observable` instances. Your component can simply delegate to this method to create the chain and subscribe to the returned `Observable`. Similarly, your test can invoke this method to create the chain, and then use a `TestObserver` to assert the behavior of the returned `Observable`. 13 | * Attempt to move the creation of the `Observable` instance and any related functionality into a smaller component that allows injecting the root `Observable` and is easier to get under test. Again, your component can simply delegate to this smaller component, and your test can depend only on this smaller component instead of the larger one. 14 | 15 | Below we demonstrate an example of the second strategy from the [Khan Academy Android application](https://play.google.com/store/apps/details?id=org.khanacademy.android). 16 | 17 | #### Using `TestObserver` 18 | 19 | A user of the Khan Academy Android application can download videos for offline viewing. As a video downloads, an `Observable` emits `DownloadEvent` instances that reflect the progress of the download. We want to observe these events so that we can display and update a notification for the download: 20 | 21 | ![Notification updating as a video downloads](images/download-video-notification.gif) 22 | 23 | This notification contains the title of the downloading video. The title is a property of the `ContentItem` that represents the video. However, the `DownloadEvent` that we receive has only a `ContentItemIdentifier` property, which is only a unique identifier for a `ContentItem` instance. We can pass the `ContentItemIdentifier` to the `fetchContentItem` method of a `ContentDatabase` implementation: 24 | 25 | ```java 26 | interface ContentDatabase { 27 | Observable fetchContentItem(ContentItemIdentifier contentItemId); 28 | } 29 | ``` 30 | 31 | Subscribing to the returned `Observable` will fetch the corresponding `ContentItem` from the database and emit it. We now define a `ContentDownloadEvent` class that pairs a `DownloadEvent` with its corresponding `ContentItem`: 32 | 33 | ```java 34 | @AutoValue 35 | public abstract class ContentDownloadEvent { 36 | public abstract DownloadEvent downloadEvent(); 37 | public abstract ContentItem contentItem(); 38 | 39 | public static ContentDownloadEvent create(final DownloadEvent downloadEvent, 40 | final ContentItem contentItem) { 41 | return new AutoValue_ContentDownloadEvent(downloadEvent, contentItem); 42 | } 43 | } 44 | ``` 45 | 46 | And now, by combining these elements, we can create an `Observable` that emits a `ContentDownloadEvent` for each emitted `DownloadEvent`: 47 | 48 | ```java 49 | public Observable getContentDownloadEventObservable() { 50 | return mDownloadEventObservable.flatMap(downloadEvent -> { 51 | final ContentItemIdentifier contentItemId = downloadEvent.contentItemIdentifier(); 52 | return mContentDatabase.fetchContentItem(contentItemId) 53 | .map(fetchedContentItem -> { 54 | return ContentDownloadEvent.create(downloadEvent, fetchedContentItem) 55 | }); 56 | }); 57 | } 58 | ``` 59 | 60 | Above, `mDownloadEventObservable` is the `Observable` that emits `DownloadEvent` instances. And `mContentDatabase` is the `ContentDatabase` implementation. 61 | 62 | However, the above method is inefficient: Over the course of downloading a large video, thousands of `DownloadEvent` instances may be emitted with the same `ContentItemIdentifier`. Fetching the same `ContentItem` thousands of times from the `ContentDatabase` will both drain the user's battery and cause poor performance. 63 | 64 | To remedy this, we create a new class named `ContentDownloadEventCache` and move method `getContentDownloadEventObservable` into it. It is constructed with the same `Observable` and `ContentDatabase` instances. But internally, it does not necessarily need to fetch from the `ContentDatabase` the `ContentItem` associated with every `DownloadEvent`. Instead, it maintains a `Map` of cached `ContentItem` instances. This map has a maximum size and an LRU eviction policy: 65 | 66 | ```java 67 | public Observable getContentDownloadEventObservable() { 68 | return mDownloadEventObservable.flatMap(downloadEvent -> { 69 | final ContentItemIdentifier contentItemId = downloadEvent.contentItemIdentifier(); 70 | final @Nullable ContentItem cachedContentItem = mCachedContentItemsMap.get(contentItemId)); 71 | if (cachedContentItem != null) { 72 | return Observable.just(ContentDownloadEvent.create(downloadEvent, cachedContentItem)); 73 | } else { 74 | return mContentDatabase.fetchContentItem(contentItemId) 75 | .observeOn(mScheduler) 76 | .doOnNext(fetchedContentItem -> { 77 | mCachedContentItemsMap.put(contentItemId, fetchedContentItem); 78 | }) 79 | .map(fetchedContentItem -> { 80 | return ContentDownloadEvent.create(downloadEvent, fetchedContentItem) 81 | }); 82 | } 83 | }); 84 | } 85 | ``` 86 | 87 | Note that the `Observable` returned by method `fetchContentItem` may perform its work on another `Scheduler` instance like `Schedulers.io()`. To keep this code thread-safe, we must transition back to the main thread before adding the `ContentItem` to the `Map` in the `doOnNext` action. We do this by calling `observeOn(mScheduler)`, where `mScheduler` is a `Scheduler` that is backed by the main thread and passed into the constructor. (In the Android application, we specify it as `AndroidSchedulers.mainThread()`.) See the item [Understand `subscribeOn` and `observeOn`](understand-subscribeon-and-observeon.md) for more details on the use of `observeOn` and its related method `subscribeOn`. 88 | 89 | With this in place, only the first `DownloadEvent` for a download will fetch the `ContentItem` from the `ContentDatabase`. All subsequent `DownloadEvent` instances will use the cached `ContentItem` to construct the corresponding `ContentDownloadEvent`. We now want to test this behavior. 90 | 91 | ##### Test setup 92 | 93 | In the setup for our test, we create a `PublishSubject` on which we can manually emit `DownloadEvent` instances. Also, using [Mockito](http://mockito.org/), we create a `ContentDatabase` implementation whose `fetchContentItem` method will return the `ContentItem` for a video, provided its `ContentItemIdentifier` is specified as a parameter: 94 | 95 | ```java 96 | final ContentItem videoItem = TestUtil.randomVideoItem(); 97 | final ContentItemIdentifier videoItemId = videoItem.contentItemIdentifier(); 98 | 99 | final PublishSubject downloadEventSubject = PublishSubject.create(); 100 | final ContentDatabase contentDatabase = mock(ContentDatabase.class); 101 | when(contentDatabase.fetchContentItem(eq(videoItemId))).thenReturn(Observable.just(videoItem)); 102 | ``` 103 | 104 | From these values we create the `ContentDownloadEventCache` instance. We also create a `TestObserver` that subscribes to the `Observable` returned by its `getContentDownloadEventObservable` method. This will allow us to assert that we observe the expected values: 105 | 106 | ```java 107 | final ContentDownloadEventCache contentDownloadEventCache = new ContentDownloadEventCache( 108 | downloadEventSubject, contentDatabase, Schedulers.immediate() 109 | ); 110 | 111 | final TestObserver testObserver = new TestObserver<>(); 112 | testObserver.subscribe(contentDownloadEventCache.getContentDownloadEventObservable()); 113 | ``` 114 | 115 | Now that our setup is complete, we begin making assertions. 116 | 117 | ##### Test execution 118 | 119 | We can emit a `DownloadEvent` instance by passing it to the `onNext` method of the `PublishSubject`. Then, using the `TestObserver` that is subscribed to the `Observable` returned by method `getContentDownloadEventObservable`, we can assert that the expected `ContentDownloadEvent` instances are emitted: 120 | 121 | ```java 122 | final DownloadEvent addedDownloadEvent = createAddedDownloadEvent(videoItemId); 123 | final DownloadEvent receivedDataDownloadEvent = createReceivedDataDownloadEvent(videoItemId); 124 | downloadEventSubject.onNext(addedDownloadEvent); 125 | downloadEventSubject.onNext(receivedDataDownloadEvent); 126 | 127 | final List expectedContentDownloadEvents = ImmutableList.of( 128 | ContentDownloadEvent.create(addedDownloadEvent, videoItem), 129 | ContentDownloadEvent.create(receivedDataDownloadEvent, videoItem) 130 | ); 131 | testObserver.assertReceivedOnNext(expectedContentDownloadEvents); 132 | ``` 133 | 134 | Above, method `assertReceivedOnNext` of `TestObserver` asserts that it observed the given sequence of `ContentDownloadEvent` values. Because `ContentDownloadEvent` is [defined as a value type using AutoValue](https://github.com/mgp/effective-rxjava/blob/master/items/emit-immutable-values.md#use-autovalue), we can rely on this method to test the equality of the expected and actual sequences. 135 | 136 | Moreover, using Mockito we can ensure that the `fetchContentItem` method of `ContentDatabase` was invoked only once. This implies that when `receivedDataDownloadEvent` was emitted, the `ContentItem` was read from the cache instead of fetched from the `ContentDatabase` again: 137 | 138 | ```java 139 | final InOrder inOrder = inOrder(contentDatabase); 140 | inOrder.verify(contentDatabase).fetchContentItem(videoItemId); 141 | inOrder.verifyNoMoreInteractions(); 142 | ``` 143 | 144 | Other tests can verify the eviction of the eldest entries in the `Map` when it reaches capacity, and so on. 145 | 146 | -------------------------------------------------------------------------------- /items/test-work-scheduling-using-testscheduler.md: -------------------------------------------------------------------------------- 1 | ### Test work scheduling using `TestScheduler` 2 | 3 | Operators like `debounce`, `delay`, `interval`, `repeat`, and `throttleFirst` accept a `Scheduler` parameter on which they schedule their work. If you are testing an `Observable` that uses one of these operators, have your test specify a `TestScheduler` instance as a parameter. This allows you to simulate the passing of time and execute work immediately. Do not, instead, add calls to `Thread.sleep` in your test so that it waits for the work to execute. Such calls make your tests brittle and extremely slow to run. 4 | 5 | #### Using `TestScheduler` 6 | 7 | Let us return to the example of [downloading a video for offline viewing](test-emitted-values-using-testobserver.md). In this case, there is an `Observable` that emits a `ByteStreamProgress` instance whenever a new sequence of bytes is received and written to the corresponding file. Such an instance specifies the number of bytes written so far and whether the file is now complete: 8 | 9 | ```java 10 | @AutoValue 11 | public abstract class ByteStreamProgress { 12 | public abstract long bytesWritten(); 13 | public abstract boolean isComplete(); 14 | 15 | public static ByteStreamProgress create(final long bytesWritten, final boolean isComplete) { 16 | checkArgument(bytesWritten >= 0, "Invalid bytesWritten: " + bytesWritten); 17 | 18 | return new AutoValue_ByteStreamProgress(bytesWritten, isComplete); 19 | } 20 | } 21 | ``` 22 | 23 | Downstream, a series of `map` operators transform each `ByteStreamProgress` into a `DownloadEvent` for the corresponding file. But if the user has a fast Internet connection, dozens of `ByteStreamProgress` instances may be generated per second. If unchecked, this will create dozens of `DownloadEvent` instances per second, which will lead to updating the corresponding notification dozens of times per second. 24 | 25 | To remedy this, we define a static factory method named `throttledObservable` on the `ByteStreamProgressUtils` class. On a given `Observable`, it uses `throttleFirst` to throttle the rate at which incomplete `ByteStreamProgress` instances are emitted. But it does not throttle the last `ByteStreamProgress` specifying that the file is complete. Using this throttled `Observable` will throttle the rate at which `DownloadEvent` instances are created and observed downstream: 26 | 27 | ```java 28 | @VisibleForTesting 29 | static Observable throttledObservable( 30 | final Observable observable, 31 | final long windowDuration, 32 | final TimeUnit timeUnit, 33 | final Scheduler scheduler) { 34 | final Observable cachedObservable = 35 | observable.compose(ObservableUtils.cacheTransformer(1)); 36 | return Observable.merge( 37 | cachedObservable 38 | .filter(byteStreamProgress -> byteStreamProgress.isComplete()), 39 | cachedObservable 40 | .filter(byteStreamProgress -> !byteStreamProgress.isComplete()) 41 | .throttleFirst(windowDuration, timeUnit, scheduler) 42 | ); 43 | } 44 | ``` 45 | 46 | Note that the marble diagram for `throttleFirst` is: 47 | 48 | ![Marble diagram of throttleFirst](images/throttle-first-marble-diagram.png) 49 | 50 | To test `throttledObservable`, we create a `PublishSubject` that will emit `ByteStreamProgress` instances without throttling, as well as a `TestScheduler` on which the throttling will occur. We pass both, along with a window duration, to method `throttledObservable`: 51 | 52 | ```java 53 | final PublishSubject progressSubject = PublishSubject.create(); 54 | final long windowDuration = 500; 55 | final TestScheduler testScheduler = new TestScheduler(); 56 | final Observable throttledObservable = 57 | ByteStreamProgressUtils.throttledObservable( 58 | progressSubject, windowDuration, TimeUnit.MILLISECONDS, testScheduler 59 | ); 60 | ``` 61 | 62 | After we have created the throttled `Observable` instance, we first subscribe to it with a `TestObserver`. We then emit a sequence of `ByteSteamProgress` instances on `progressSubject`: 63 | 64 | ```java 65 | // Emit incomplete progress with 10 and 20 bytes consumed in the first window. 66 | testScheduler.advanceTimeTo(1, TimeUnit.MILLISECONDS); 67 | progressSubject.onNext(ByteStreamProgress.create(10, false)); 68 | testScheduler.advanceTimeTo(2, TimeUnit.MILLISECONDS); 69 | progressSubject.onNext(ByteStreamProgress.create(20, false)); 70 | 71 | // Emit incomplete progress with 30 and 40 bytes consumed in the second window. 72 | testScheduler.advanceTimeTo(windowDuration + 1, TimeUnit.MILLISECONDS); 73 | progressSubject.onNext(ByteStreamProgress.create(30, false)); 74 | testScheduler.advanceTimeTo(windowDuration + 2, TimeUnit.MILLISECONDS); 75 | progressSubject.onNext(ByteStreamProgress.create(40, false)); 76 | 77 | // Emit complete progress with 50 bytes consumed in the second window. 78 | testScheduler.advanceTimeTo(windowDuration + 3, TimeUnit.MILLISECONDS); 79 | progressSubject.onNext(ByteStreamProgress.create(50, true)); 80 | ``` 81 | 82 | We emit incomplete `ByteStreamProgress` instances with `10` and then `20` bytes in the first window. The `Observable` returned by `throttledObservable` should emit only the first instance in this pair. Similarly, we emit incomplete `ByteStreamProgress` instances with `30` and then `40` bytes in the second window. Again, `throttledObservable` should emit only the first instance in this pair. 83 | 84 | Finally, we emit a complete `ByteStreamProgress` instance with `50` bytes in the second window. The `Observable` returned by `throttledObservable` should not throttling it, and consequently emit it. 85 | 86 | To assert this behavior, we create the expected sequence of `ContentDownloadEvent` values and pass it to method `assertReceivedOnNext` of `TestObserver`: 87 | 88 | ```java 89 | final List expectedByteStreamProgress = ImmutableList.of( 90 | ByteStreamProgress.create(10, false), 91 | ByteStreamProgress.create(30, false), 92 | ByteStreamProgress.create(50, true) 93 | ); 94 | testObserver.assertReceivedOnNext(expectedByteStreamProgress); 95 | ``` 96 | 97 | Note that the test above takes less than a millisecond to run on my computer, even though it simulates over 500 milliseconds passing. Use `TestScheduler` to keep your tests fast. 98 | 99 | -------------------------------------------------------------------------------- /items/understand-observable-and-observer-chains.md: -------------------------------------------------------------------------------- 1 | ### Understand `Observable` and observer chains 2 | 3 | Chains of `Observable` instances and observers are important. Understanding them is the key to understanding how your RxJava code executes. 4 | 5 | To begin our understanding, let us start with the most banal of examples: 6 | 7 | ```java 8 | Observable.just(1, 2, 3, 4, 5) 9 | .filter(x -> (x % 2) == 1) 10 | .map(x -> x * x) 11 | .subscribe(integer -> System.out.println("Received value: " + integer)); 12 | ``` 13 | 14 | When run, this prints: 15 | 16 | ```text 17 | Received value: 1 18 | Received value: 9 19 | Received value: 25 20 | ``` 21 | 22 | Now most newcomers to RxJava intuitively understand why these values are printed. They have seen `filter` and `map` in other contexts and in other languages, and so they can understand how the integer values are filtered and then transformed. 23 | 24 | However, just as I did, they may fail to understand why code like the following does nothing: 25 | 26 | ```java 27 | mBookmarkDatabase.addBookmark(contentItemId); 28 | ``` 29 | 30 | The `addBookmark` method returns an `Observable` that emits `true` if a new bookmark was added for the content item with the given identifier, or emits `false` if such a bookmark already existed. (This is similar to the `add` method of `Collection` returning `true` if the collection was changed.) But in this case, we do not care about which value is emitted, and so the returned `Observable` is not assigned to a value. 31 | 32 | But the statement above does not write the bookmark to the database if it does not already exist. This is because the returned `Observable` is not *subscribed to*, either directly or through a chain of other `Observable` instances. 33 | 34 | Again, to understand how RxJava code executes, you must understand this chain. 35 | 36 | #### Creating an `Observable` chain 37 | 38 | Factory methods like `just` and `filter` and `map` above return `Observable` instances. Whereas `just` is a static factory method invoked on the `Observable` class, `filter` and `map` are instance factory methods invoked on an existing `Observable` instance. These two categories of factory methods have different state, different behavior, and a different purpose: 39 | 40 | * Typically, an `Observable` created from a static factory has no upstream `Observable` instance. It is the root of any chain. It is constructed with some "work" to perform, which we discuss below. 41 | * An `Observable` created from an instance factory method has an upstream `Observable` instance. This upstream `Observable` instance is the instance on which the factory method was invoked. It is an intermediate or end `Observable` of any chain. It is constructed with some logic to filter or transform emitted values, which we discuss below. 42 | 43 | Let us rewrite our most banal of examples to include some intermediate variables for the `Observable` instances: 44 | 45 | ```java 46 | Observable o1 = Observable.just(1, 2, 3, 4, 5); 47 | Observable o2 = o1.filter(x -> (x % 2) == 1); 48 | Observable o3 = o2.map(x -> x * x); 49 | ``` 50 | 51 | We can diagram this as: 52 | 53 | ![Chain of Observable instances](images/observable-chain.png) 54 | 55 | And now let us dissect what happens when the client invokes `subscribe` on `o3`. 56 | 57 | #### Creating an observer chain 58 | 59 | We often talk about subscribing to `Observable` instances, and we talk about `Observable` instances filtering or transforming values. But to increase our understanding at the risk of being completely pedantic, a client that calls `subscribe` on an `Observable` isn't really subscribing to the `Observable` instance itself. As we discuss below, calling `subscribe` on an `Observable` creates a parallel chain of observers. It is along that chain of observers that values are emitted, and then filtered or transformed. 60 | 61 | Above, when the client invokes `subscribe` on `o3`, it registers itself as the most downstream observer on a chain of observers that will be implicitly created: 62 | 63 | ![Observer subscribing to o3](images/observer-chain-subscribe-o3.png) 64 | 65 | `o3` then implicitly calls `subscribe` on its upstream `Observable` instance, which is `o2`. To this `subscribe` method it passes an observer implementation that propagates all events to the observer that the client passed to the `subscribe` method of `o3`. But the observer implementation provided by `o3` also implements the behavior that `o3` was constructed with. Namely, the observer squares a value before propagating it. You can think of its `onNext` method as similar to: 66 | 67 | ```java 68 | void onNext(T value) { 69 | // func is x -> x * x, provided on construction of o3 70 | final T transformedValue = func.call(value); 71 | downstreamObserver.onNext(transformedValue); 72 | } 73 | ``` 74 | 75 | We can diagram this as: 76 | 77 | ![Observer subscribing to o2](images/observer-chain-subscribe-o2.png) 78 | 79 | Again, `o3` implicitly calls `subscribe` on `o2`. This repeats a similar process, where `o2` implicitly calls `subscribe` on `o1`. `o2` passes to this method an observer implementation that only propagates values that are odd, which again is the behavior that it was constructed with. You can think of the `onNext` method of this observer as similar to: 80 | 81 | ```java 82 | void onNext(T value) { 83 | // func is x -> (x % 2) == 1, provided on construction of o2 84 | final boolean isSatisfied = func.call(value); 85 | if (isSatisfied) { 86 | downstreamObserver.onNext(value); 87 | } 88 | } 89 | ``` 90 | 91 | We can diagram this as: 92 | 93 | ![Observer subscribing to o1](images/observer-chain-subscribe-o1.png) 94 | 95 | Again, `o2` implicitly calls `subscribe` on `o1`. But note that `o1` has no upstream `Observable` instance that it can invoke `subscribe` on. Instead, it is this call that completes the observer chain and begins the emission of events. 96 | 97 | #### Emitting values on an observer chain 98 | 99 | While `o3` and `o2` are configured to transform and filter values, respectively, `o1` is configured to emit values. When its `subscribe` method is invoked with an observer implementation, it emits the values that it is constructed with to that observer. More generally, the `Observable` that is the root in a chain of `Observable` instances is configured to perform some work upon its `subscribe` method being invoked. This work typically constitutes creating a side-effect that leads to the emission of zero or more values, followed by the delivery of the completion event. 100 | 101 | Now we understand why the previous use of `addBookmark` did not actually write to the database: The side-effect of writing to the database only happens when `subscribe` is invoked on the returned `Observable`. By calling `subscribe`, this work is performed: 102 | 103 | ```java 104 | mBookmarkDatabase.addBookmark(contentItemId) 105 | .subscribe(didNotExist -> { /* success */ }); 106 | ``` 107 | 108 | Returning to our banal example, the "work" of `o1` is to emit all values to its observer, followed by a completion event. You can think of its `subscribe` method as executing the following: 109 | 110 | ```java 111 | for (T element : list) { 112 | observer.onNext(element); 113 | } 114 | observer.onCompleted(); 115 | ``` 116 | 117 | These events propagate down the observer chain, where the observer created by `o2` filters the values, and then the observer created by `o3` squares the values. Those values are then passed to the method passed to the `subscribe` method of `o3`, which prints them. 118 | 119 | #### Unsubscribing 120 | 121 | Note that in our banal example above, the `subscribe` method of `o1` immediately emitted all values to its observer and then it completed. That is, the work of the `Observable` instance happened immediately. 122 | 123 | But other `Observable` instances might define work that takes time to complete or is scheduled for later execution, such as making a network request and emitting the response, or writing data to disk on another thread. In such cases, the time for the `Observable` to complete its work might exceed the lifetime of the client that invoked `subscribe`. In many cases, we do not want to extend the lifetime of the client so that it observes the completion of the work. Instead, we want to *unsubscribe* our observer from the chain of observers. 124 | 125 | To do this, we retain the `Subscripton` instance returned by the `subscribe` method of `Observable`: 126 | 127 | ```java 128 | Subscription s = o3.subscribe(integer -> System.out.println("Received value: " + integer)); 129 | ``` 130 | 131 | If the client later invokes `unsunscribe` on this `Subscription` instance, then superficially the observer passed to the `subscribe` method of `o3` no longer receives any events. 132 | 133 | To understand what happens implicitly, note that not only does each observer in the observer chain track the downstream observer that it will propagate events to, but each observer refers to the upstream observer that it will receive events from. Each implicitly created observer can unsubscribe from its upstream observer in turn: 134 | 135 | * The client invokes `s.unsubscribe()`, which unsubscribes the client from the squaring observer implicitly created by calling `subscribe` on `o3`. 136 | * This squaring observer then implicitly unsubscribes from the filtering observer implicitly created by calling `subscribe` on `o2`. 137 | * This filtering observer then implicitly unsubscribes from the source `o1`. 138 | 139 | The call to `unsubscribe` therefore destroys the chain of observers that was implicitly created by the call to `subscribe`. 140 | 141 | #### Going forward 142 | 143 | You should now have a mental model for how `Observable` instances form chains, and how observer chains are formed by the `Observable` instances and propagate events. Later, using this model, we will explore the difference between `subscribeOn` and `observeOn`, and discuss the difference between hot and cold observables. 144 | 145 | -------------------------------------------------------------------------------- /items/understand-subscribeon-and-observeon.md: -------------------------------------------------------------------------------- 1 | ### Understand `subscribeOn` and `observeOn` 2 | 3 | By default, `Observable` instances specify an execution policy of "immediate." That is, if a thread has an action to perform, then it is executed immediately by that thread. We can observe this in our banal example from the item [Understand `Observable` and observer chains](understand-observable-and-observer-chains.md). To refresh our memory, that example is: 4 | 5 | ```java 6 | Observable o1 = Observable.just(1, 2, 3, 4, 5); 7 | Observable o2 = o1.filter(x -> (x % 2) == 1); 8 | Observable o3 = o2.map(x -> x * x); 9 | o3.subscribe(integer -> System.out.println("Received value: " + integer)); 10 | ``` 11 | 12 | We can observe the effects of the immediate scheduling policy by setting a breakpoint on the line with `subscribe` and then running the program. When this breakpoint is reached the stack trace shows: 13 | 14 | ![Stack trace for the observed value](images/observer-chain-stacktrace.png) 15 | 16 | Note that at the bottom of the stacktrace we find the call to `subscribe` on `o3`. The item [Understand `Observable` and observer chains](understand-observable-and-observer-chains.md) explains in detail why this happens, but to summarize: 17 | 18 | * The client explicitly calls `subscribe` on `o3`. 19 | * `o3` then implicitly calls `subscribe` on `o2`. 20 | * `o2` then implicitly calls `subscribe` on `o1`. 21 | * The `subscribe` method of `o1` then performs its work, by emitting each value that it was constructed with. 22 | * The first value of `1` passes the `filter` operator, and then its value is squared. We observe this value at the breakpoint. 23 | 24 | This stacktrace gives a complete picture. We can follow the thread of execution from its call to `subscribe` on `o3` to each observed value. 25 | 26 | But while this immediate execution policy is easy to reason about, it is not ideal when the work performed by the root `Observable` instance is CPU-intensive or performs I/O. In such cases, the thread of execution can spend significant time computing the result or blocking on a read or write operation. And if the application is an Android application, then the thread of execution is likely the main thread. Consequently the UI will freeze entirely and the user might have no choice but to force-quit your unresponsive application. 27 | 28 | To remedy these problems, we can introduce `Scheduler` instances via methods `subscribeOn` or `observeOn`. 29 | 30 | #### Choosing a scheduler 31 | 32 | A `Scheduler` instance defines the thread on which actions should happen. If you are familiar with the concurrency libraries from Java, you can think of a `Scheduler` as resembling an [`Executor`](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html). (In fact, the [`Schedulers` class from RxJava](http://reactivex.io/RxJava/javadoc/rx/schedulers/Schedulers.html) has a `from` method that adapts an `Executor` instance to the `Scheduler` interface.) 33 | 34 | The `Schedulers` class has static factory methods for creating common `Scheduler` implementations: 35 | 36 | * The `computation` method returns a `Scheduler` for CPU-intensive work. The work is typically performed by a backing [thread pool](https://en.wikipedia.org/wiki/Thread_pool), where the number of threads equals the number of CPU cores. If new work is scheduled but all thread in the pool are busy, then that work is enqueued for later execution. 37 | * The `io` method returns a `Scheduler` for I/O-bound work. The work is performed by a backing thread pool that grows as needed. This growth strategy is chosen because most threads in this pool will be blocked on a read or on a write operation. These threads do not contend for the CPU. 38 | * The `immediate` method returns a `Scheduler` that executes work immediately on the current thread. It resembles the default execution policy explored at the beginning of this item. 39 | 40 | Finally, if your application has a default [run loop](https://en.wikipedia.org/wiki/Event_loop), then consider a `Scheduler` implementation that adapts it. For example, the [RxAndroid](https://github.com/ReactiveX/RxAndroid) project provides an `AndroidSchedulers` class. Its static method `mainThread` returns a `Scheduler` for the main UI thread. 41 | 42 | Some methods of `Observable` that perform work take a `Scheduler` parameter specifying the thread on which the work is performed. But for the most part, you specify a `Scheduler` in your chain of `Observable` instances using the `subscribeOn` and `observeOn` methods. Let's consider `subscribeOn` first. 43 | 44 | #### Using `subscribeOn` 45 | 46 | The `subscribeOn` method applies to upstream `Observable` instances. Its `Scheduler` parameter specifies the thread on which the upstream `subscribe` method is invoked. Consequently, if the root `Observable` instance performs work, a downstream `subscribeOn` specifies the thread on which the work is performed. This allows us to offload I/O or CPU-intensive tasks to another thread. 47 | 48 | Let's redefine `o1` so that it reads the integers that it emits from a file. Moreover, we will print the name of the thread of execution: 49 | 50 | ```java 51 | Observable o1 = Observable.create(subscriber -> { 52 | System.out.println("Reading file on thread " + Thread.currentThread().getName()); 53 | 54 | final List integers = readIntegersFromFile(); 55 | for (Integer integer : integers) { 56 | subscriber.onNext(integer); 57 | } 58 | subscriber.onCompleted(); 59 | }); 60 | ``` 61 | 62 | We do not want method `readIntegersFromFile` to execute on the main thread, and so we pass the `Schedulers.io()` instance to the `subscribeOn` method downstream: 63 | 64 | ``` 65 | Observable o2 = o1.filter(x -> (x % 2) == 1); 66 | Observable o3 = o2.map(x -> x * x); 67 | Observable o4 = o3.subscribeOn(Schedulers.io()) 68 | o4.subscribe(integer -> System.out.println("Received value: " + integer)); 69 | ``` 70 | 71 | This revises our summary of how the observer chain is created: 72 | 73 | * The client explicitly calls `subscribe` on `o4`. 74 | * `o4` then enqueues on the `Schedulers.io()` instance the work of calling `subscribe` on `o3`. 75 | * A thread belonging to the thread pool backing `Schedulers.io()` executes this work, thereby calling `subscribe` on `o3`. 76 | * `o3` then implicitly calls `subscribe` on `o2`, which implicitly calls `subscribe` on `o1`, which then performs the work of calling method `readIntegersFromFile` and emitting each returned value. 77 | 78 | Consequently, `o1` performs its work on a thread belonging to the thread pool backing the `Schedulers.io()` instance. We validate this by running the program: 79 | 80 | ```text 81 | Reading file on thread RxCachedThreadScheduler-1 82 | Received value: 1 83 | Received value: 9 84 | Received value: 25 85 | ``` 86 | 87 | `RxCachedThreadScheduler-1` is the name of the thread in the thread pool backing the `Schedulers.io()` instance. As desired, we have offloaded the work of reading the file to another thread. 88 | 89 | But there is one consequence that is not obvious: Because a thread belonging to `Schedulers.io()` is executing the work of `o1`, then it is that thread that invokes the downstream operators and calls our observer passed to the `subscribe` method of `o4`. We can verify this by printing the thread name at that point: 90 | 91 | ```java 92 | o4.subscribe(integer -> { 93 | System.out.println("Received value " + integer + " on thread " + Thread.currentThread().getName())); 94 | }); 95 | ``` 96 | 97 | When run, this prints: 98 | 99 | ```text 100 | Reading file on thread RxCachedThreadScheduler-1 101 | Received value 1 on thread RxCachedThreadScheduler-1 102 | Received value 9 on thread RxCachedThreadScheduler-1 103 | Received value 25 on thread RxCachedThreadScheduler-1 104 | ``` 105 | 106 | Above the observer passed to the `subscribe` method of `o4` simply prints the emitted values to the screen. But we can easily imagine cases where the provided observer must execute on the main thread. (Such as an Android application where the upstream `Observable` makes a network request, and the downstream observer consumes this response by updating the application state.) 107 | 108 | To ensure that the emitted events are observed on the desired thread, we use method `observeOn`. 109 | 110 | #### Using `observeOn` 111 | 112 | The `observeOn` method applies to downstream `Observable` instances. Its `Scheduler` parameter specifies the thread on which events, such as the next emitted value or the stream terminating normally or with an error, are observed downstream. 113 | 114 | As we have seen, if you use `subscribeOn` so that the upstream `Observable` instance performs work on another thread, and if that work emits events that is consumed downstream, then you should typically also use `observeOn`. This ensures that the consumption of the events happen on the proper thread, which is typically the main thread that called `subscribe`. 115 | 116 | Assuming we are writing an Android application, we can observe the events on the main thread like so: 117 | 118 | ```java 119 | Observable o4 = o3 120 | .subscribeOn(Schedulers.io()) 121 | .observeOn(AndroidSchedulers.mainThread()); 122 | o4.subscribe(integer -> { 123 | System.out.println("Received value " + integer + " on thread " + Thread.currentThread().getName())); 124 | }); 125 | ``` 126 | 127 | When run, this prints: 128 | 129 | ```text 130 | Reading file on thread RxCachedThreadScheduler-1 131 | Received value 1 on thread main 132 | Received value 9 on thread main 133 | Received value 25 on thread main 134 | ``` 135 | 136 | Note that by placing the `observeOn` method immediately before the call to `subscribe` on `o4`, it is the `RxCachedThreadScheduler-1` thread that executes the logic of the `filter` and `map` operators. This is ideal, as you should not schedule more work than necessary on your main thread. 137 | 138 | Finally, as mentioned earlier, near every call to `subscribeOn` should be a call to `observeOn`. As [Dan Lew](http://blog.danlew.net/) suggests in his excellent [Don't break the chain](http://blog.danlew.net/2015/03/02/dont-break-the-chain/) article, consider defining a `Transformer` that calls both of these methods. 139 | 140 | -------------------------------------------------------------------------------- /items/understand-switch-map.md: -------------------------------------------------------------------------------- 1 | ### Understand `switchMap` 2 | 3 | This has absolutely no scientific basis, but I will contend this: Understanding `switchMap` is an essential step on the path to using RxJava effectively. Upon understanding this operator, you will find use cases for it everywhere. Furthermore, using it opens the door to using a wide variety of other helpful operators. 4 | 5 | The Javadoc for `switchMap` says: 6 | 7 | > Returns a new `Observable` by applying a function that you supply to each item emitted by the source `Observable` that returns an `Observable`, and then emitting the items emitted by the most recently emitted of these `Observables`. 8 | 9 | Its marble diagram from the Javadoc is: 10 | 11 | ![Marble diagram of switchMap](images/switch-map-marble-diagram.png) 12 | 13 | And its method signature is: 14 | 15 | ```java 16 | public final Observable switchMap(Func1> func) 17 | ``` 18 | 19 | Let's ignore the bounding by the `? super` and `? extends` clauses, and break this down so that it is hopefully more understandable. You have some upstream observable that is emitting values of type `T`, and you have downstream subscribers that are receiving values of type `R`. 20 | 21 | Upon the upstream observable emitting the *first value* of type `T`, that value is passed to the provided function, which returns a value of type `Observable`. When the returned `Observable` emits a value of type `R`, downstream subscribers will receive it. 22 | 23 | The interesting part is when the upstream observable emits the *next value* of type `T`. Again, this value is passed to the provided function, which returns another value of type `Observable`. At this point, downstream subscribers will stop receiving events emitted by the previously created `Observable`, and will start receiving events emitted by this `Observable`. These downstream subscribers are unaware of this upstream change. 24 | 25 | If you have read the [item on understanding subscriber chains](understand-observable-and-observer-chains.md), you can intuit how `switchMap` works: Whenever the function returns a new `Observable`, the `Observable` returned by the `switchMap` operator implicitly unsubscribes from the last `Observable` returned, and then implicitly subscribes to this new `Observable` returned. 26 | 27 | To demonstrate this power, let's look at an example from the [Khan Academy Android application](https://play.google.com/store/apps/details?id=org.khanacademy.android). 28 | 29 | #### An example: Transcript highlighting 30 | 31 | [Khan Academy](https://www.khanacademy.org/) has over 6,500 videos on many topics in many languages. The video player is therefore an important component of the application. The video plays above a transcript. As it plays, the corresponding text of the transcript is highlighted. 32 | 33 | ![Highlighting of the transcript as the video plays](images/video-player-transcript-playing.gif) 34 | 35 | The transcript is represented as a `List` of `TranscriptPart` instances, sorted by ascending `timeMillis` values: 36 | 37 | ```java 38 | @AutoValue 39 | public abstract class TranscriptPart { 40 | /** The time at which this part of the transcript starts, in milliseconds. */ 41 | public abstract long timeMillis(); 42 | 43 | public abstract String text(); 44 | 45 | public static TranscriptPart create(long timeMillis, String text) { 46 | checkArgument(timeMillis >= 0, "Invalid timeMillis: " + timeMillis); 47 | return new AutoValue_TranscriptPart(timeMillis, checkNotEmpty(text)); 48 | } 49 | } 50 | ``` 51 | 52 | Given a time, we can easily find the `TranscriptPart` that spans that time, and highlight it on screen. The real task is to create an `Observable` that, as the video plays, emits the playback time at regular intervals. 53 | 54 | To start, we provide an `ExoPlayer.Listener` implementation to the `ExoPlayer` instance. [ExoPlayer](http://google.github.io/ExoPlayer/) is the library from Google we use to play videos. As the `ExoPlayer` instance changes state, the `onPlayerStateChanged` method of its listener is invoked. This method converts each ExoPlayer state to a state belonging to our own `VideoPlayerState` enumeration, and emits that enumeration value on a subject named `mPlayerStateSubject`: 55 | 56 | ```java 57 | private final ExoPlayer.Listener mExoPlayerListener = new ExoPlayer.Listener() { 58 | @Override 59 | public void onPlayerStateChanged(boolean playWhenReady, int state) { 60 | switch (state) { 61 | case ExoPlayer.STATE_BUFFERING: 62 | mPlayerStateSubject.onNext(VideoPlayerState.BUFFERING); 63 | return; 64 | case ExoPlayer.STATE_READY: 65 | mPlayerStateSubject.onNext( 66 | playWhenReady ? VideoPlayerState.PLAYING : VideoPlayerState.PAUSED 67 | ); 68 | return; 69 | // remaining states follow here ... 70 | } 71 | } 72 | // remaining methods follow here ... 73 | } 74 | ``` 75 | 76 | If the `ExoPlayer` instance is playing, then the `case` statement for `STATE_READY` above emits a `VideoPlayerState` value of `PLAYING`. Other `case` statements emit other `VideoPlayerState` values that are consumed by subscribers of `mPlayerStateSubject`. 77 | 78 | We can now transform the `mPlayerStateSubject` into the desired `Observable` that emits the video playback time at regular intervals as it plays: 79 | 80 | ```java 81 | public Observable getPlayingTimeMillisObservable() { 82 | return mPlayerStateSubject 83 | .map(state -> state == VideoPlayerState.PLAYING) 84 | .distinctUntilChanged() 85 | .switchMap(isPlaying -> { 86 | if (isPlaying) { 87 | return Observable.timer( 88 | 0, PLAYING_VIDEO_POLLING_RATE_MS, TimeUnit.MILLISECONDS 89 | ); 90 | } else { 91 | return Observable.never(); 92 | } 93 | }) 94 | // Transition from the computation scheduler of timer back to the main thread. 95 | .observeOn(AndroidSchedulers.mainThread()) 96 | .map(ignored -> mExoPlayer.getCurrentPosition()); 97 | } 98 | ``` 99 | 100 | Let's step through this from top to bottom. 101 | 102 | The combination of the `map` and `distinctUntilChanged` operators emit to the downstream `switchMap` operator a value of `true` every time playbacks starts, and a value of `false` every time playback ends. 103 | 104 | That `switchMap` operator is where the magic happens: 105 | 106 | * If the `isPlaying` parameter is `true`, meaning that the video is now playing, then the subscriber to `switchMap` will observe the values emitted by the `Observable` returned by the `timer` static factory method. The call above to `timer` returns an `Observable` that emits `0` after an initial delay of `0` milliseconds (the first parameter) and ever incrementing numbers every `PLAYING_VIDEO_POLLING_RATE_MS` milliseconds (the second parameter). The static constant `PLAYING_VIDEO_POLLING_RATE_MS` is defined as `300` in our application. 107 | * If the `isPlaying` parameter is `false`, meaning that the video is no longer playing, then the subscriber to `switchMap` will observe the values emitted by the `Observable` returned by the `never` static factory method. As its name implies, that `Observable` never emits any values. 108 | 109 | In sum, the subscriber of `switchMap` will observe the sequence of values `0`, `1`, `2`, and so on every 300 milliseconds while the video is playing, and no values while the video is not playing. 110 | 111 | To transform this `Observable` to emit the current playback time every 300 milliseconds when the video is playing, we apply the `map` operator. We can ignore the input parameter entirely and simply return the current playback time in milliseconds by invoking method `getCurrentPosition` on the `ExoPlayer` instance. Note that the `timer` method emits its values on the `computation` `Scheduler` instance, and so we must call `observeOn(AndroidSchedulers.mainThread())` first to ensure that this happens on the main thread. 112 | 113 | The following marble diagram illustrates the full sequence: 114 | 115 | ![Marble diagram for getPlayingTimeMillisObservable](images/playing-time-millis-observable-marble-diagram.png) 116 | 117 | Finally, a parent UI component subscribes to the `Observable` returned by method `getPlayingTimeMillisObservable`. When a new time is emitted, it highlights the `TranscriptPart` that spans that time. 118 | 119 | -------------------------------------------------------------------------------- /items/use-compose-for-operator-sequences.md: -------------------------------------------------------------------------------- 1 | ### Use `compose` for operator sequences 2 | 3 | > Note that the definitive article on this subject is [Don't break the chain: use RxJava's `compose()` operator](http://blog.danlew.net/2015/03/02/dont-break-the-chain/) by Android programmer extraordinaire [Dan Lew](http://blog.danlew.net/). You're better of reading that article instead of this one, but I am including this item for completeness. 4 | 5 | In the [Khan Academy Android app](https://play.google.com/store/apps/details?id=org.khanacademy.android), we make extensive use of the [`Optional` class](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html) from [Google's Guava library](https://github.com/google/guava). Whereas we typically represent "absent values" by using `null`, the `Optional` class encodes the optionality of such values in the type system. This helps ensure that clients deal with such optional values in a direct manner. For example: 6 | 7 | ```java 8 | final Optional optional = Optional.of("abc"); 9 | if (optional.isPresent()) { 10 | final String s = optional.get(); 11 | } 12 | ``` 13 | 14 | The code above first creates an `Optional` instance with the string `"abc"`. Because that instance contains a value, we say that the value is *present*, not *absent*. Its `isPresent` method therefore returns `true`, and the call to `get` assigns the value `"abc"` to `s`. 15 | 16 | Our application has many cases where an `Observable` emits `Optional` for some type `T`, and downstream subscribers want to receive only the present values of type `T`. 17 | 18 | For example, a `BookmarkEvent` instance represents any update to the user's bookmarks, such as adding a bookmark, removing a bookmark, or updating the download progress associated with a bookmark. Every `BookmarkEvent` instance has an `Optional` value of type `DownloadEvent` that is accessed through its `downloadEventOptional()` method. If the `BookmarkEvent` updates the download progress of a bookmark, then that `Optional` contains a `DownloadEvent` instance with more details, such as how many bytes have been downloaded so far, what the estimated total byte count is, and so on. We can use this `DownloadEvent` to [display a notification](http://developer.android.com/guide/topics/ui/notifiers/notifications.html) with the download progress: 19 | 20 | ```java 21 | mBookmarkManager.getBookmarkEventObservable() 22 | .map(bookmarkEvent -> bookmarkEvent.downloadEventOptional()) 23 | .filter(downloadEventOptional -> downloadEventOptional.isPresent()) 24 | .map(downloadEventOptional -> downloadEventOptional.get()); 25 | .subscribe(downloadEvent -> { 26 | mNotificationManager.displayNotificationForDownloadEvent(downloadEvent); 27 | }); 28 | } 29 | ``` 30 | 31 | The `filter` operator and its following `map` operator consume an `Optional`, and together emit only present `DownloadEvent` instances. Again, this is a common occurrence, but for different types. And so we might define a utility method like so: 32 | 33 | ```java 34 | /** 35 | * @return an observable emitting the present values of {@link Optional} instances emitted by 36 | * the observable parameter 37 | */ 38 | public static Observable observePresentValues(Observable> observable) { 39 | return observable. 40 | .filter(optional -> optional.isPresent()) 41 | .map(optional -> optional.get()); 42 | } 43 | ``` 44 | 45 | And then call it like so: 46 | 47 | ```java 48 | final Observable> downloadEventOptionalObservable = 49 | mBookmarkManager.getBookmarkEventObservable() 50 | .map(bookmarkEvent -> bookmarkEvent.downloadEventOptional()) 51 | ObservableUtils.observePresentValues(downloadEventOptionalObservable) 52 | .subscribe(downloadEvent -> { 53 | mNotificationManager.displayNotificationForDownloadEvent(downloadEvent); 54 | }); 55 | ``` 56 | 57 | There are other ways to structure this, but none of them are pleasing. Reading RxJava is arguably most straightforward when there are no temporary variables like `downloadEventOptionalObservable`, and following the left margin from top to bottom yields all operators that constitute the subscriber chain. 58 | 59 | To achieve these goals, we can use the `compose` method of `Observable`. This method accepts an instance that implements `Transformer`, which transforms an instance of `Observable` to an instance of `Observable`. The above calls to `filter` and then `map` specify a transformation from `Observable>` to `Observable`. We can instead define a utility method that returns a `Transformer`: 60 | 61 | 62 | ```java 63 | /** 64 | * @return a transformer for emitting the present values of emitted {@link Optional} instances 65 | */ 66 | public static Transformer, T> presentValuesTransformer() { 67 | return observable -> observable 68 | .filter(optional -> optional.isPresent()) 69 | .map(optional -> optional.get()); 70 | } 71 | ``` 72 | 73 | And then pass it to `compose`: 74 | 75 | ```java 76 | mBookmarkManager.getBookmarkEventObservable() 77 | .map(bookmarkEvent -> bookmarkEvent.downloadEventOptional()) 78 | .compose(ObservableUtils.presentValuesTransformer()) 79 | .subscribe(downloadEvent -> { 80 | mNotificationManager.displayNotificationForDownloadEvent(downloadEvent); 81 | }); 82 | } 83 | ``` 84 | 85 | This is much more readable than using the `observePresentValues` utility method. 86 | 87 | Note that the `Transformer, T>` instance returned has no state, and is therefore trivially immutable and safe for sharing. Moreover its sequence of `filter` and `map` operators does not rely on the parameterized type `T`. The same instance, therefore, can transform a `Observable>` for any type `T`. 88 | 89 | To implement this, we create a `Transformer, Object>` instance that is both private and static, and then modify `presentValuesTransformer` to return it: 90 | 91 | ```java 92 | private static Transformer PRESENT_VALUES_TRANSFORMER = 93 | new Transformer, Object>() { 94 | @Override 95 | public Observable call(final Observable> optionalObservable) { 96 | return optionalObservable 97 | .filter(optional -> optional.isPresent()) 98 | .map(optional -> optional.get()); 99 | } 100 | }; 101 | 102 | @SuppressWarnings("unchecked") 103 | public static Transformer, T> presentValuesTransformer() { 104 | return PRESENT_VALUES_TRANSFORMER; 105 | } 106 | ``` 107 | 108 | The compiler cannot conclude that the cast from `Transformer, Object>` to `Transformer, T>` is safe, and so it generates a compilation warning. The `SuppressWarnings("unchecked")` annotation suppresses that warning. 109 | 110 | -------------------------------------------------------------------------------- /items/use-retrolambda.md: -------------------------------------------------------------------------------- 1 | ### Use Retrolambda 2 | 3 | [Retrolambda](https://github.com/orfjackal/retrolambda) enables lambda expressions and method references on Android by transforming Java 8 bytecode to bytecode for Java 7 and lower. While this may sound scary, lambda expressions and method references remove the syntactic boilerplate from the anonymous classes you must define when providing `Func` and `Action` implementations, thereby greatly improving your code's readability. Additionally, the [Gradle plugin](https://github.com/evant/gradle-retrolambda) makes integrating Retrolambda with your build seamless. 4 | 5 | Without Retrolambda, your RxJava code will look like: 6 | 7 | ```java 8 | mContentDatabase 9 | .fetchContentItems(ImmutableSet.of(contentItemId)) 10 | .map(new Func1, ContentItem>() { 11 | @Override 12 | public ContentItem call(final Map resultSet) { 13 | return checkNotNull(resultSet.get(contentItemId)); 14 | } 15 | }) 16 | .map(new Func1() { 17 | @Override 18 | public TopicPath call(final ContentItem contentItem) { 19 | return contentItem.topicPath(); 20 | } 21 | }) 22 | .subscribe(new Action1() { 23 | @Override 24 | public void call(final TopicPath topicPath) { 25 | openContentViewActivity(contentItemId, topicPath); 26 | } 27 | }); 28 | ``` 29 | 30 | With Retrolambda, your RxJava code will look like: 31 | 32 | ```java 33 | mContentDatabase 34 | .fetchContentItems(ImmutableSet.of(contentItemId)) 35 | .map(resultSet -> checkNotNull(resultSet.get(contentItemId))) 36 | .map(ContentItem::topicPath) 37 | .subscribe(topicPath -> { 38 | openContentViewActivity(contentItemId, topicPath); 39 | }); 40 | ``` 41 | 42 | We at [Khan Academy](https://www.khanacademy.org/) have been using it in our [Android application](https://play.google.com/store/apps/details?id=org.khanacademy.android) with no issues. Many other companies use it with their own applications. Use it with your own application if you are using RxJava. 43 | 44 | --------------------------------------------------------------------------------