├── .github ├── workflows │ ├── .java-version │ ├── build.yaml │ └── release.yaml └── renovate.json5 ├── replaying-share ├── gradle.properties ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── jakewharton │ │ └── rx3 │ │ └── ReplayingShare.java │ └── test │ └── java │ └── com │ └── jakewharton │ └── rx3 │ ├── ReplayingShareObservableTest.java │ └── ReplayingShareFlowableTest.java ├── marbles.key ├── marbles.png ├── replaying-share-kotlin ├── gradle.properties ├── build.gradle └── src │ ├── test │ └── kotlin │ │ └── com │ │ └── jakewharton │ │ └── rx3 │ │ └── ReplayingShareTest.kt │ └── main │ └── kotlin │ └── com │ └── jakewharton │ └── rx3 │ └── ReplayingShare.kt ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── settings.gradle ├── gradle.properties ├── CHANGELOG.md ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE.txt /.github/workflows/.java-version: -------------------------------------------------------------------------------- 1 | 25 2 | -------------------------------------------------------------------------------- /replaying-share/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=RxJava Replaying Share 2 | -------------------------------------------------------------------------------- /marbles.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/RxReplayingShare/HEAD/marbles.key -------------------------------------------------------------------------------- /marbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/RxReplayingShare/HEAD/marbles.png -------------------------------------------------------------------------------- /replaying-share-kotlin/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=RxJava Replaying Share (Kotlin Extensions) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle 3 | build 4 | 5 | # IntelliJ IDEA 6 | .idea 7 | 8 | # Kotlin 9 | .kotlin 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/RxReplayingShare/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'RxReplayingShare' 2 | 3 | include ':replaying-share' 4 | include ':replaying-share-kotlin' 5 | 6 | enableFeaturePreview('TYPESAFE_PROJECT_ACCESSORS') 7 | -------------------------------------------------------------------------------- /replaying-share/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'com.vanniktech.maven.publish' 3 | 4 | dependencies { 5 | api libs.rxjava3 6 | 7 | testImplementation libs.junit 8 | } 9 | -------------------------------------------------------------------------------- /replaying-share-kotlin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.jetbrains.kotlin.jvm' 2 | apply plugin: 'com.vanniktech.maven.publish' 3 | 4 | dependencies { 5 | api projects.replayingShare 6 | 7 | testImplementation libs.junit 8 | } 9 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [libraries] 2 | kotlinPlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0" 3 | mavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.35.0" 4 | 5 | rxjava3 = "io.reactivex.rxjava3:rxjava:3.1.12" 6 | 7 | junit = "junit:junit:4.13.2" 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:recommended', 5 | ], 6 | ignorePresets: [ 7 | // Ensure we get the latest version and are not pinned to old versions. 8 | 'workarounds:javaLTSVersions', 9 | ], 10 | customManagers: [ 11 | // Update .java-version file with the latest JDK version. 12 | { 13 | customType: 'regex', 14 | fileMatch: [ 15 | '\\.java-version$', 16 | ], 17 | matchStrings: [ 18 | '(?.*)\\n', 19 | ], 20 | datasourceTemplate: 'java-version', 21 | depNameTemplate: 'java', 22 | // Only write the major version. 23 | extractVersionTemplate: '^(?\\d+)', 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.jakewharton.rx3 2 | VERSION_NAME=3.0.1-SNAPSHOT 3 | 4 | mavenCentralPublishing=true 5 | mavenCentralAutomaticPublishing=true 6 | signAllPublications=true 7 | 8 | POM_DESCRIPTION=An RxJava transformer which combines replay(1), publish(), and refCount() operators. 9 | 10 | POM_URL=https://github.com/JakeWharton/RxReplayingShare/ 11 | POM_SCM_URL=https://github.com/JakeWharton/RxReplayingShare/ 12 | POM_SCM_CONNECTION=scm:git:git://github.com/JakeWharton/RxReplayingShare.git 13 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/JakeWharton/RxReplayingShare.git 14 | 15 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 16 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 17 | POM_LICENCE_DIST=repo 18 | 19 | POM_DEVELOPER_ID=jakewharton 20 | POM_DEVELOPER_NAME=Jake Wharton 21 | POM_DEVELOPER_URL=https://github.com/JakeWharton/ 22 | 23 | org.gradle.caching=true 24 | org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 25 | org.gradle.parallel=true 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | push: 7 | branches: 8 | - 'trunk' 9 | tags-ignore: 10 | - '**' 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v6 17 | - uses: actions/setup-java@v5 18 | with: 19 | distribution: 'zulu' 20 | java-version-file: .github/workflows/.java-version 21 | - uses: gradle/actions/setup-gradle@v5 22 | 23 | - run: ./gradlew build 24 | 25 | - run: ./gradlew publishToMavenCentral 26 | if: ${{ github.ref == 'refs/heads/trunk' && github.repository == 'JakeWharton/RxReplayingShare' }} 27 | env: 28 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} 29 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }} 30 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_PRIVATE_KEY }} 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - uses: actions/checkout@v6 15 | - uses: actions/setup-java@v5 16 | with: 17 | distribution: 'zulu' 18 | java-version-file: .github/workflows/.java-version 19 | - uses: gradle/actions/setup-gradle@v5 20 | 21 | - run: ./gradlew publishToMavenCentral 22 | env: 23 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} 24 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }} 25 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_PRIVATE_KEY }} 26 | 27 | - name: Extract release notes 28 | id: extract-release-notes 29 | uses: ffurrer2/extract-release-notes@v3 30 | with: 31 | release_notes_file: RELEASE_NOTES.md 32 | 33 | - name: Create GitHub release 34 | run: gh release create ${{ github.ref_name }} --notes-file RELEASE_NOTES.md 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /replaying-share-kotlin/src/test/kotlin/com/jakewharton/rx3/ReplayingShareTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.rx3 17 | 18 | import io.reactivex.rxjava3.core.Flowable 19 | import io.reactivex.rxjava3.core.Observable 20 | import org.junit.Assert.assertNotNull 21 | import org.junit.Test 22 | 23 | class ReplayingShareTest { 24 | @Test fun observableExtensionMethodWorks() { 25 | val o = Observable.never().replayingShare() 26 | assertNotNull(o) 27 | } 28 | 29 | @Test fun flowableExtensionMethodWorks() { 30 | val o = Flowable.never().replayingShare() 31 | assertNotNull(o) 32 | } 33 | 34 | @Test fun observableExtensionMethodWorksWithDefaultValue() { 35 | val strings = Observable.never().replayingShare("default").test() 36 | strings.assertValues("default") 37 | } 38 | 39 | @Test fun flowableExtensionMethodWorksWithDefaultValue() { 40 | val strings = Flowable.never().replayingShare("default").test() 41 | strings.assertValues("default") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | 5 | Minimum-supported Java version is now 1.8. 6 | 7 | 8 | ## Version 3.0.0 *(2020-05-19)* 9 | 10 | This version updates to support RxJava 3. No functional changes. 11 | 12 | The groupId and package name are now `com.jakewharton.rx3`. 13 | 14 | 15 | ## Version 2.2.0 *(2019-09-13)* 16 | 17 | * New: `createWithDefault` factory for supplying a default cached value. Currently, the first 18 | subscriber has to wait for the upstream source to emit. By specifying a default value you can 19 | deliver a value immediately upon subscribe in a way similar to that if an event from another 20 | subscriber had already been cached. 21 | * New: Annotate public API surface with nullability annotations. 22 | 23 | 24 | ## Version 2.1.1 *(2019-02-19)* 25 | 26 | * Fix: Do not emit cached value if the subscriber immediately disposes/cancel subscription. 27 | 28 | 29 | ## Version 2.1.0 *(2018-09-22)* 30 | 31 | * Fix: Clear cached value when upstream emits a terminal event. 32 | 33 | 34 | ## Version 2.0.1 *(2017-09-14)* 35 | 36 | * Fix: Kotlin extension functions now return non-nullable types instead of platform types. 37 | 38 | 39 | ## Version 2.0.0 *(2017-07-02)* 40 | 41 | This version only supports RxJava 2. 42 | 43 | * New: Support for both `Observable` and `Flowable`. 44 | 45 | Note: There is a 2.0.0 version which uses the `com.jakewharton.rx` group ID instead of `com.jakewharton.rx2`. 46 | You should use the latter to ensure 1.x and 2.x can be used side-by-side in the same module. 47 | 48 | 49 | ## Version 1.0.1 *(2016-03-31)* 50 | 51 | * Fix: Reduce allocations and memory overhead. 52 | 53 | 54 | ## Version 1.0.0 *(2016-03-01)* 55 | 56 | Initial release. 57 | -------------------------------------------------------------------------------- /replaying-share-kotlin/src/main/kotlin/com/jakewharton/rx3/ReplayingShare.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.rx3 17 | 18 | import io.reactivex.rxjava3.core.Flowable 19 | import io.reactivex.rxjava3.core.Observable 20 | 21 | /** 22 | * A transformer which combines `replay(1)`, `publish()`, and `refCount()` operators. 23 | * 24 | * Unlike traditional combinations of these operators, `ReplayingShare` caches the last emitted 25 | * value from the upstream observable *only* when one or more downstream observers are connected. 26 | * This allows expensive upstream observables to be shut down when no one is observing while also 27 | * replaying the last value seen by *any* observer to new ones. 28 | * 29 | * @param defaultValue the initial value delivered to new subscribers before any events are cached. 30 | * A null value means there will be no initial emission. 31 | */ 32 | @JvmOverloads 33 | fun Observable.replayingShare(defaultValue: T? = null): Observable { 34 | return compose( 35 | if (defaultValue != null) ReplayingShare.createWithDefault(defaultValue) 36 | else ReplayingShare.instance() 37 | ) 38 | } 39 | 40 | /** 41 | * A transformer which combines `replay(1)`, `publish()`, and `refCount()` operators. 42 | * 43 | * Unlike traditional combinations of these operators, `ReplayingShare` caches the last emitted 44 | * value from the upstream flowable *only* when one or more downstream subscribers are connected. 45 | * This allows expensive upstream flowables to be shut down when no one is subscribed while also 46 | * replaying the last value seen by *any* subscriber to new ones. 47 | * 48 | * @param defaultValue the initial value delivered to new subscribers before any events are cached. 49 | * A null value means there will be no initial emission. 50 | */ 51 | @JvmOverloads 52 | fun Flowable.replayingShare(defaultValue: T? = null): Flowable { 53 | return compose( 54 | if (defaultValue != null) ReplayingShare.createWithDefault(defaultValue) 55 | else ReplayingShare.instance() 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RxJava Replaying Share 2 | ====================== 3 | 4 | `ReplayingShare` is an RxJava 3 transformer which combines `replay(1)`, `publish()`, and 5 | `refCount()` operators. 6 | 7 | Unlike traditional combinations of these operators, `ReplayingShare` caches the last emitted 8 | value from the upstream observable or flowable *only* when one or more downstream subscribers are 9 | connected. This allows expensive upstream sources to be shut down when no one is listening while 10 | also replaying the last value seen by *any* subscriber to new ones. 11 | 12 | 13 | | | replayingShare() | replay(1).refCount() | publish().refCount() | replay(1).autoConnect(1) | 14 | |----------------------------------------------------------------------------------|------------------|----------------------|----------------------| -------------------------| 15 | | Disconnects from upstream when there are no subscribers | ✅ | ✅ | ✅ | ❌ | 16 | | Replays the latest value to new subscribers when other subscribers are active | ✅ | ✅ | ❌ | ✅ | 17 | | Replays the latest value to new subscribers when no other subscribers are active | ✅ | ❌ | ❌ | ✅ | 18 | 19 | 20 | 21 | ![marble diagram](marbles.png) 22 | 23 | 24 | Usage 25 | ----- 26 | 27 | Apply with `compose` to an upstream `Observable` or `Flowable` and cache the resulting instance for 28 | all new subscribers. 29 | 30 | ```java 31 | @Singleton class Chart { 32 | private final Observable chart; 33 | 34 | @Inject Chart(Observable> data) { 35 | chart = data.debounce(1, SECONDS) 36 | .map(list -> bigExpensiveRenderChartToBitmapFunction(list)) 37 | .compose(ReplayingShare.instance()); 38 | } 39 | 40 | Observable data() { 41 | return chart; 42 | } 43 | } 44 | ``` 45 | 46 | Kotlin users can use the operator via an extension function. 47 | 48 | ```kotlin 49 | @Singleton class Chart 50 | @Inject constructor(data: Observable>) { 51 | val chart: Observable = data.debounce(1, SECONDS) 52 | .map(list -> bigExpensiveRenderChartToBitmapFunction(list)) 53 | .replayingShare() 54 | } 55 | ``` 56 | 57 | Note: This operator is designed for composition with infinite or extremely long-lived streams. Any 58 | terminal event will clear the cached value. 59 | 60 | 61 | Download 62 | -------- 63 | 64 | Gradle: 65 | ```groovy 66 | implementation 'com.jakewharton.rx3:replaying-share:3.0.0' 67 | // Optional: 68 | implementation 'com.jakewharton.rx3:replaying-share-kotlin:3.0.0' 69 | ``` 70 | Maven: 71 | ```xml 72 | 73 | com.jakewharton.rx3 74 | replaying-share 75 | 3.0.0 76 | 77 | 78 | 79 | com.jakewharton.rx3 80 | replaying-share-kotlin 81 | 3.0.0 82 | 83 | ``` 84 | 85 | Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. 86 | 87 | ### RxJava 2.x 88 | 89 | Gradle: 90 | ```groovy 91 | implementation 'com.jakewharton.rx2:replaying-share:2.2.0' 92 | // Optional: 93 | implementation 'com.jakewharton.rx2:replaying-share-kotlin:2.2.0' 94 | ``` 95 | Maven: 96 | ```xml 97 | 98 | com.jakewharton.rx2 99 | replaying-share 100 | 2.2.0 101 | 102 | 103 | 104 | com.jakewharton.rx2 105 | replaying-share-kotlin 106 | 2.2.0 107 | 108 | ``` 109 | 110 | ### RxJava 1.x 111 | 112 | Gradle: 113 | ```groovy 114 | implementation 'com.jakewharton.rx:replaying-share:1.0.1' 115 | // Optional: 116 | implementation 'com.jakewharton.rx:replaying-share-kotlin:1.0.1' 117 | ``` 118 | Maven: 119 | ```xml 120 | 121 | com.jakewharton.rx 122 | replaying-share 123 | 1.0.1 124 | 125 | 126 | 127 | com.jakewharton.rx 128 | replaying-share-kotlin 129 | 1.0.1 130 | 131 | ``` 132 | 133 | 134 | License 135 | ------- 136 | 137 | Copyright 2016 Jake Wharton 138 | 139 | Licensed under the Apache License, Version 2.0 (the "License"); 140 | you may not use this file except in compliance with the License. 141 | You may obtain a copy of the License at 142 | 143 | http://www.apache.org/licenses/LICENSE-2.0 144 | 145 | Unless required by applicable law or agreed to in writing, software 146 | distributed under the License is distributed on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 148 | See the License for the specific language governing permissions and 149 | limitations under the License. 150 | 151 | 152 | 153 | [snap]: https://oss.sonatype.org/content/repositories/snapshots/ 154 | -------------------------------------------------------------------------------- /replaying-share/src/main/java/com/jakewharton/rx3/ReplayingShare.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.rx3; 17 | 18 | import io.reactivex.rxjava3.core.Flowable; 19 | import io.reactivex.rxjava3.core.FlowableTransformer; 20 | import io.reactivex.rxjava3.core.Observable; 21 | import io.reactivex.rxjava3.core.ObservableTransformer; 22 | import io.reactivex.rxjava3.core.Observer; 23 | import io.reactivex.rxjava3.annotations.NonNull; 24 | import io.reactivex.rxjava3.annotations.Nullable; 25 | import io.reactivex.rxjava3.disposables.Disposable; 26 | import org.reactivestreams.Subscriber; 27 | import org.reactivestreams.Subscription; 28 | 29 | /** 30 | * A transformer which combines the {@code replay(1)}, {@code publish()}, and {@code refCount()} 31 | * operators. 32 | *

33 | * Unlike traditional combinations of these operators, `ReplayingShare` caches the last emitted 34 | * value from the upstream observable or flowable *only* when one or more downstream subscribers 35 | * are connected. This allows expensive upstream sources to be shut down when no one is listening 36 | * while also replaying the last value seen by *any* subscriber to new ones. 37 | */ 38 | public final class ReplayingShare 39 | implements ObservableTransformer, FlowableTransformer { 40 | private static final ReplayingShare INSTANCE = new ReplayingShare<>(null); 41 | 42 | /** The singleton instance of this transformer. */ 43 | @NonNull 44 | @SuppressWarnings("unchecked") // Safe because of erasure. 45 | public static ReplayingShare instance() { 46 | return (ReplayingShare) INSTANCE; 47 | } 48 | 49 | /** 50 | * Creates a `ReplayingShare` transformer with a default value which will be emitted downstream 51 | * on subscription if there is not any cached value yet. 52 | * 53 | * @param defaultValue the initial value delivered to new subscribers before any events are 54 | * cached. 55 | */ 56 | @NonNull 57 | public static ReplayingShare createWithDefault(@NonNull T defaultValue) { 58 | if (defaultValue == null) throw new NullPointerException("defaultValue == null"); 59 | return new ReplayingShare<>(defaultValue); 60 | } 61 | 62 | private final @Nullable T defaultValue; 63 | 64 | private ReplayingShare(@Nullable T defaultValue) { 65 | this.defaultValue = defaultValue; 66 | } 67 | 68 | @Override public Observable apply(Observable upstream) { 69 | LastSeen lastSeen = new LastSeen<>(defaultValue); 70 | return new LastSeenObservable<>(upstream.doOnEach(lastSeen).share(), lastSeen); 71 | } 72 | 73 | @Override public Flowable apply(Flowable upstream) { 74 | LastSeen lastSeen = new LastSeen<>(defaultValue); 75 | return new LastSeenFlowable<>(upstream.doOnEach(lastSeen).share(), lastSeen); 76 | } 77 | 78 | static final class LastSeen implements Observer, Subscriber { 79 | private final @Nullable T defaultValue; 80 | volatile @Nullable T value; 81 | 82 | LastSeen(@Nullable T defaultValue) { 83 | this.defaultValue = defaultValue; 84 | value = defaultValue; 85 | } 86 | 87 | @Override public void onNext(T value) { 88 | this.value = value; 89 | } 90 | 91 | @Override public void onError(Throwable e) { 92 | value = defaultValue; 93 | } 94 | 95 | @Override public void onComplete() { 96 | value = defaultValue; 97 | } 98 | 99 | @Override public void onSubscribe(Subscription ignored) {} 100 | @Override public void onSubscribe(Disposable ignored) {} 101 | } 102 | 103 | static final class LastSeenObservable extends Observable { 104 | private final Observable upstream; 105 | private final LastSeen lastSeen; 106 | 107 | LastSeenObservable(Observable upstream, LastSeen lastSeen) { 108 | this.upstream = upstream; 109 | this.lastSeen = lastSeen; 110 | } 111 | 112 | @Override protected void subscribeActual(Observer observer) { 113 | upstream.subscribe(new LastSeenObserver<>(observer, lastSeen)); 114 | } 115 | } 116 | 117 | static final class LastSeenObserver implements Observer { 118 | private final Observer downstream; 119 | private final LastSeen lastSeen; 120 | 121 | LastSeenObserver(Observer downstream, LastSeen lastSeen) { 122 | this.downstream = downstream; 123 | this.lastSeen = lastSeen; 124 | } 125 | 126 | @Override public void onSubscribe(Disposable d) { 127 | downstream.onSubscribe(d); 128 | 129 | T value = lastSeen.value; 130 | if (value != null && !d.isDisposed()) { 131 | downstream.onNext(value); 132 | } 133 | } 134 | 135 | @Override public void onNext(T value) { 136 | downstream.onNext(value); 137 | } 138 | 139 | @Override public void onComplete() { 140 | downstream.onComplete(); 141 | } 142 | 143 | @Override public void onError(Throwable e) { 144 | downstream.onError(e); 145 | } 146 | } 147 | 148 | static final class LastSeenFlowable extends Flowable { 149 | private final Flowable upstream; 150 | private final LastSeen lastSeen; 151 | 152 | LastSeenFlowable(Flowable upstream, LastSeen lastSeen) { 153 | this.upstream = upstream; 154 | this.lastSeen = lastSeen; 155 | } 156 | 157 | @Override protected void subscribeActual(Subscriber subscriber) { 158 | upstream.subscribe(new LastSeenSubscriber<>(subscriber, lastSeen)); 159 | } 160 | } 161 | 162 | static final class LastSeenSubscriber implements Subscriber, Subscription { 163 | private final Subscriber downstream; 164 | private final LastSeen lastSeen; 165 | 166 | private @Nullable Subscription subscription; 167 | private volatile boolean cancelled; 168 | private boolean first = true; 169 | 170 | LastSeenSubscriber(Subscriber downstream, LastSeen lastSeen) { 171 | this.downstream = downstream; 172 | this.lastSeen = lastSeen; 173 | } 174 | 175 | @Override public void onSubscribe(Subscription subscription) { 176 | this.subscription = subscription; 177 | downstream.onSubscribe(this); 178 | } 179 | 180 | @Override public void request(long amount) { 181 | if (amount == 0) return; 182 | 183 | if (first) { 184 | first = false; 185 | 186 | T value = lastSeen.value; 187 | if (value != null && !cancelled) { 188 | downstream.onNext(value); 189 | 190 | if (amount != Long.MAX_VALUE && --amount == 0) { 191 | return; 192 | } 193 | } 194 | } 195 | Subscription subscription = this.subscription; 196 | assert subscription != null; 197 | subscription.request(amount); 198 | } 199 | 200 | @Override public void cancel() { 201 | Subscription subscription = this.subscription; 202 | assert subscription != null; 203 | cancelled = true; 204 | subscription.cancel(); 205 | } 206 | 207 | @Override public void onNext(T value) { 208 | downstream.onNext(value); 209 | } 210 | 211 | @Override public void onComplete() { 212 | downstream.onComplete(); 213 | } 214 | 215 | @Override public void onError(Throwable t) { 216 | downstream.onError(t); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /replaying-share/src/test/java/com/jakewharton/rx3/ReplayingShareObservableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.rx3; 17 | 18 | import io.reactivex.rxjava3.core.Observable; 19 | import io.reactivex.rxjava3.disposables.Disposable; 20 | import io.reactivex.rxjava3.functions.Action; 21 | import io.reactivex.rxjava3.functions.Consumer; 22 | import io.reactivex.rxjava3.observers.TestObserver; 23 | import io.reactivex.rxjava3.subjects.PublishSubject; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | import org.junit.Ignore; 28 | import org.junit.Test; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.junit.Assert.fail; 32 | 33 | public final class ReplayingShareObservableTest { 34 | @Test public void noInitialValue() { 35 | PublishSubject subject = PublishSubject.create(); 36 | Observable observable = subject.compose(ReplayingShare.instance()); 37 | 38 | TestObserver observer = new TestObserver<>(); 39 | observable.subscribe(observer); 40 | observer.assertNoValues(); 41 | } 42 | 43 | @Test public void initialValueToNewSubscriber() { 44 | PublishSubject subject = PublishSubject.create(); 45 | Observable observable = subject.compose(ReplayingShare.instance()); 46 | 47 | TestObserver observer1 = new TestObserver<>(); 48 | observable.subscribe(observer1); 49 | observer1.assertNoValues(); 50 | 51 | subject.onNext("Foo"); 52 | observer1.assertValues("Foo"); 53 | 54 | TestObserver observer2 = new TestObserver<>(); 55 | observable.subscribe(observer2); 56 | observer2.assertValues("Foo"); 57 | } 58 | 59 | @Test public void initialValueToNewSubscriberAfterUnsubscribe() { 60 | PublishSubject subject = PublishSubject.create(); 61 | Observable observable = subject.compose(ReplayingShare.instance()); 62 | 63 | TestObserver observer1 = new TestObserver<>(); 64 | observable.subscribe(observer1); 65 | observer1.assertNoValues(); 66 | 67 | subject.onNext("Foo"); 68 | observer1.assertValues("Foo"); 69 | observer1.dispose(); 70 | 71 | TestObserver observer2 = new TestObserver<>(); 72 | observable.subscribe(observer2); 73 | observer2.assertValues("Foo"); 74 | } 75 | 76 | @Test public void valueMissedWhenNoSubscribers() { 77 | PublishSubject subject = PublishSubject.create(); 78 | Observable observable = subject.compose(ReplayingShare.instance()); 79 | 80 | TestObserver observer1 = new TestObserver<>(); 81 | observable.subscribe(observer1); 82 | observer1.assertNoValues(); 83 | observer1.dispose(); 84 | 85 | subject.onNext("Foo"); 86 | observer1.assertNoValues(); 87 | 88 | TestObserver observer2 = new TestObserver<>(); 89 | observable.subscribe(observer2); 90 | observer2.assertNoValues(); 91 | } 92 | 93 | @SuppressWarnings("CheckReturnValue") 94 | @Test public void fatalExceptionDuringReplayThrown() { 95 | PublishSubject subject = PublishSubject.create(); 96 | Observable observable = subject.compose(ReplayingShare.instance()); 97 | 98 | observable.subscribe(); 99 | subject.onNext("Foo"); 100 | 101 | Consumer brokenAction = new Consumer() { 102 | @Override public void accept(String s) { 103 | throw new OutOfMemoryError("broken!"); 104 | } 105 | }; 106 | try { 107 | observable.subscribe(brokenAction); 108 | fail(); 109 | } catch (OutOfMemoryError e) { 110 | assertEquals("broken!", e.getMessage()); 111 | } 112 | } 113 | 114 | @Test public void refCountToUpstream() { 115 | PublishSubject subject = PublishSubject.create(); 116 | 117 | final AtomicInteger count = new AtomicInteger(); 118 | Observable observable = subject // 119 | .doOnSubscribe(new Consumer() { 120 | @Override public void accept(Disposable disposable) throws Exception { 121 | count.incrementAndGet(); 122 | } 123 | }) // 124 | .doOnDispose(new Action() { 125 | @Override public void run() throws Exception { 126 | count.decrementAndGet(); 127 | } 128 | }) // 129 | .compose(ReplayingShare.instance()); 130 | 131 | Disposable disposable1 = observable.subscribeWith(new TestObserver()); 132 | assertEquals(1, count.get()); 133 | 134 | Disposable disposable2 = observable.subscribeWith(new TestObserver()); 135 | assertEquals(1, count.get()); 136 | 137 | Disposable disposable3 = observable.subscribeWith(new TestObserver()); 138 | assertEquals(1, count.get()); 139 | 140 | disposable1.dispose(); 141 | assertEquals(1, count.get()); 142 | 143 | disposable3.dispose(); 144 | assertEquals(1, count.get()); 145 | 146 | disposable2.dispose(); 147 | assertEquals(0, count.get()); 148 | } 149 | 150 | @Ignore("No backpressure in Observable") 151 | @Test public void backpressureHonoredWhenCached() { 152 | PublishSubject subject = PublishSubject.create(); 153 | Observable observable = subject.compose(ReplayingShare.instance()); 154 | 155 | TestObserver observer1 = new TestObserver<>(); 156 | observable.subscribe(observer1); 157 | observer1.assertNoValues(); 158 | 159 | subject.onNext("Foo"); 160 | observer1.assertValues("Foo"); 161 | 162 | TestObserver observer2 = new TestObserver<>(/*0*/); 163 | observable.subscribe(observer2); 164 | observer2.assertNoValues(); 165 | 166 | subject.onNext("Bar"); // Replace the cached value... 167 | observer1.assertValues("Foo", "Bar"); 168 | 169 | //observer2.requestMore(1); // ...and ensure new requests see it. 170 | observer2.assertValues("Bar"); 171 | } 172 | 173 | @Test public void streamsDoNotShareInstances() { 174 | PublishSubject subjectA = PublishSubject.create(); 175 | Observable observableA = subjectA.compose(ReplayingShare.instance()); 176 | TestObserver observerA1 = new TestObserver<>(); 177 | observableA.subscribe(observerA1); 178 | 179 | PublishSubject subjectB = PublishSubject.create(); 180 | Observable observableB = subjectB.compose(ReplayingShare.instance()); 181 | TestObserver observerB1 = new TestObserver<>(); 182 | observableB.subscribe(observerB1); 183 | 184 | subjectA.onNext("Foo"); 185 | observerA1.assertValues("Foo"); 186 | subjectB.onNext("Bar"); 187 | observerB1.assertValues("Bar"); 188 | 189 | TestObserver observerA2 = new TestObserver<>(); 190 | observableA.subscribe(observerA2); 191 | observerA2.assertValues("Foo"); 192 | 193 | TestObserver observerB2 = new TestObserver<>(); 194 | observableB.subscribe(observerB2); 195 | observerB2.assertValues("Bar"); 196 | } 197 | 198 | @Test public void completeClearsCacheAndResubscribes() { 199 | List start = new ArrayList<>(); 200 | start.add("initA"); 201 | 202 | PublishSubject upstream = PublishSubject.create(); 203 | Observable replayed = upstream.startWithIterable(start).compose(ReplayingShare.instance()); 204 | 205 | TestObserver observer1 = new TestObserver<>(); 206 | replayed.subscribe(observer1); 207 | observer1.assertValues("initA"); 208 | 209 | TestObserver observer2 = new TestObserver<>(); 210 | replayed.subscribe(observer2); 211 | observer1.assertValues("initA"); 212 | 213 | upstream.onComplete(); 214 | observer1.assertComplete(); 215 | observer2.assertComplete(); 216 | 217 | start.set(0, "initB"); 218 | 219 | TestObserver observer3 = new TestObserver<>(); 220 | replayed.subscribe(observer3); 221 | observer3.assertValues("initB"); 222 | } 223 | 224 | @Test public void errorClearsCacheAndResubscribes() { 225 | List start = new ArrayList<>(); 226 | start.add("initA"); 227 | 228 | PublishSubject upstream = PublishSubject.create(); 229 | Observable replayed = upstream.startWithIterable(start).compose(ReplayingShare.instance()); 230 | 231 | TestObserver observer1 = new TestObserver<>(); 232 | replayed.subscribe(observer1); 233 | observer1.assertValues("initA"); 234 | 235 | TestObserver observer2 = new TestObserver<>(); 236 | replayed.subscribe(observer2); 237 | observer1.assertValues("initA"); 238 | 239 | RuntimeException r = new RuntimeException(); 240 | upstream.onError(r); 241 | observer1.assertError(r); 242 | observer2.assertError(r); 243 | 244 | start.set(0, "initB"); 245 | 246 | TestObserver observer3 = new TestObserver<>(); 247 | replayed.subscribe(observer3); 248 | observer3.assertValues("initB"); 249 | } 250 | 251 | @Test public void unsubscribeBeforeSubscribePreventsCacheEmission() { 252 | PublishSubject upstream = PublishSubject.create(); 253 | Observable replayed = upstream.compose(ReplayingShare.instance()); 254 | replayed.subscribe(); 255 | upstream.onNext("something to cache"); 256 | 257 | TestObserver testObserver = new TestObserver<>(); 258 | testObserver.dispose(); 259 | replayed.subscribe(testObserver); 260 | testObserver.assertNoValues(); 261 | } 262 | 263 | @Test public void defaultValueOnSubscribe() { 264 | PublishSubject subject = PublishSubject.create(); 265 | Observable observable = subject.compose(ReplayingShare.createWithDefault("default")); 266 | 267 | TestObserver observer1 = new TestObserver<>(); 268 | observable.subscribe(observer1); 269 | observer1.assertValues("default"); 270 | 271 | subject.onNext("Foo"); 272 | observer1.assertValues("default", "Foo"); 273 | } 274 | 275 | @Test public void defaultValueIsOverriddenByLatestEmissionForNewSubscriber() { 276 | PublishSubject subject = PublishSubject.create(); 277 | Observable observable = subject.compose(ReplayingShare.createWithDefault("default")); 278 | 279 | TestObserver observer1 = new TestObserver<>(); 280 | observable.subscribe(observer1); 281 | observer1.assertValues("default"); 282 | 283 | subject.onNext("Foo"); 284 | observer1.assertValues("default", "Foo"); 285 | 286 | TestObserver observer2 = new TestObserver<>(); 287 | observable.subscribe(observer2); 288 | observer2.assertValues("Foo"); 289 | } 290 | 291 | @Test public void completeClearsCacheAndResubscribesStartingWithDefault() { 292 | List start = new ArrayList<>(); 293 | start.add("initA"); 294 | 295 | PublishSubject upstream = PublishSubject.create(); 296 | Observable replayed = 297 | upstream.startWithIterable(start).compose(ReplayingShare.createWithDefault("default")); 298 | 299 | TestObserver observer1 = new TestObserver<>(); 300 | replayed.subscribe(observer1); 301 | observer1.assertValues("default", "initA"); 302 | 303 | TestObserver observer2 = new TestObserver<>(); 304 | replayed.subscribe(observer2); 305 | observer1.assertValues("default", "initA"); 306 | 307 | upstream.onComplete(); 308 | observer1.assertComplete(); 309 | observer2.assertComplete(); 310 | 311 | start.set(0, "initB"); 312 | 313 | TestObserver observer3 = new TestObserver<>(); 314 | replayed.subscribe(observer3); 315 | observer3.assertValues("default", "initB"); 316 | } 317 | 318 | @Test public void errorClearsCacheAndResubscribesStartingWithDefault() { 319 | List start = new ArrayList<>(); 320 | start.add("initA"); 321 | 322 | PublishSubject upstream = PublishSubject.create(); 323 | Observable replayed = 324 | upstream.startWithIterable(start).compose(ReplayingShare.createWithDefault("default")); 325 | 326 | TestObserver observer1 = new TestObserver<>(); 327 | replayed.subscribe(observer1); 328 | observer1.assertValues("default", "initA"); 329 | 330 | TestObserver observer2 = new TestObserver<>(); 331 | replayed.subscribe(observer2); 332 | observer1.assertValues("default", "initA"); 333 | 334 | RuntimeException r = new RuntimeException(); 335 | upstream.onError(r); 336 | observer1.assertError(r); 337 | observer2.assertError(r); 338 | 339 | start.set(0, "initB"); 340 | 341 | TestObserver observer3 = new TestObserver<>(); 342 | replayed.subscribe(observer3); 343 | observer3.assertValues("default", "initB"); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /replaying-share/src/test/java/com/jakewharton/rx3/ReplayingShareFlowableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.rx3; 17 | 18 | import io.reactivex.rxjava3.core.Flowable; 19 | import io.reactivex.rxjava3.functions.Action; 20 | import io.reactivex.rxjava3.functions.Consumer; 21 | import io.reactivex.rxjava3.processors.PublishProcessor; 22 | import io.reactivex.rxjava3.subscribers.TestSubscriber; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | import org.junit.Test; 27 | import org.reactivestreams.Subscription; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.fail; 31 | 32 | public final class ReplayingShareFlowableTest { 33 | @Test public void noInitialValue() { 34 | PublishProcessor subject = PublishProcessor.create(); 35 | Flowable flowable = subject.compose(ReplayingShare.instance()); 36 | 37 | TestSubscriber subscriber = new TestSubscriber<>(); 38 | flowable.subscribe(subscriber); 39 | subscriber.assertNoValues(); 40 | } 41 | 42 | @Test public void initialValueToNewSubscriber() { 43 | PublishProcessor subject = PublishProcessor.create(); 44 | Flowable flowable = subject.compose(ReplayingShare.instance()); 45 | 46 | TestSubscriber subscriber1 = new TestSubscriber<>(); 47 | flowable.subscribe(subscriber1); 48 | subscriber1.assertNoValues(); 49 | 50 | subject.onNext("Foo"); 51 | subscriber1.assertValues("Foo"); 52 | 53 | TestSubscriber subscriber2 = new TestSubscriber<>(); 54 | flowable.subscribe(subscriber2); 55 | subscriber2.assertValues("Foo"); 56 | } 57 | 58 | @Test public void initialValueToNewSubscriberAfterUnsubscribe() { 59 | PublishProcessor subject = PublishProcessor.create(); 60 | Flowable flowable = subject.compose(ReplayingShare.instance()); 61 | 62 | TestSubscriber subscriber1 = new TestSubscriber<>(); 63 | flowable.subscribe(subscriber1); 64 | subscriber1.assertNoValues(); 65 | 66 | subject.onNext("Foo"); 67 | subscriber1.assertValues("Foo"); 68 | subscriber1.cancel(); 69 | 70 | TestSubscriber subscriber2 = new TestSubscriber<>(); 71 | flowable.subscribe(subscriber2); 72 | subscriber2.assertValues("Foo"); 73 | } 74 | 75 | @Test public void valueMissedWhenNoSubscribers() { 76 | PublishProcessor subject = PublishProcessor.create(); 77 | Flowable flowable = subject.compose(ReplayingShare.instance()); 78 | 79 | TestSubscriber subscriber1 = new TestSubscriber<>(); 80 | flowable.subscribe(subscriber1); 81 | subscriber1.assertNoValues(); 82 | subscriber1.cancel(); 83 | 84 | subject.onNext("Foo"); 85 | subscriber1.assertNoValues(); 86 | 87 | TestSubscriber subscriber2 = new TestSubscriber<>(); 88 | flowable.subscribe(subscriber2); 89 | subscriber2.assertNoValues(); 90 | } 91 | 92 | @SuppressWarnings("CheckReturnValue") 93 | @Test public void fatalExceptionDuringReplayThrown() { 94 | PublishProcessor subject = PublishProcessor.create(); 95 | Flowable flowable = subject.compose(ReplayingShare.instance()); 96 | 97 | flowable.subscribe(); 98 | subject.onNext("Foo"); 99 | 100 | Consumer brokenAction = new Consumer() { 101 | @Override public void accept(String s) { 102 | throw new OutOfMemoryError("broken!"); 103 | } 104 | }; 105 | try { 106 | flowable.subscribe(brokenAction); 107 | fail(); 108 | } catch (OutOfMemoryError e) { 109 | assertEquals("broken!", e.getMessage()); 110 | } 111 | } 112 | 113 | @Test public void refCountToUpstream() { 114 | PublishProcessor subject = PublishProcessor.create(); 115 | 116 | final AtomicInteger count = new AtomicInteger(); 117 | Flowable flowable = subject // 118 | .doOnSubscribe(new Consumer() { 119 | @Override public void accept(Subscription subscription) { 120 | count.incrementAndGet(); 121 | } 122 | }) // 123 | .doOnCancel(new Action() { 124 | @Override public void run() { 125 | count.decrementAndGet(); 126 | } 127 | }) // 128 | .compose(ReplayingShare.instance()); 129 | 130 | TestSubscriber disposable1 = flowable.subscribeWith(new TestSubscriber()); 131 | assertEquals(1, count.get()); 132 | 133 | TestSubscriber disposable2 = flowable.subscribeWith(new TestSubscriber()); 134 | assertEquals(1, count.get()); 135 | 136 | TestSubscriber disposable3 = flowable.subscribeWith(new TestSubscriber()); 137 | assertEquals(1, count.get()); 138 | 139 | disposable1.cancel(); 140 | assertEquals(1, count.get()); 141 | 142 | disposable3.cancel(); 143 | assertEquals(1, count.get()); 144 | 145 | disposable2.cancel(); 146 | assertEquals(0, count.get()); 147 | } 148 | 149 | @Test public void backpressureHonoredWhenCached() { 150 | PublishProcessor subject = PublishProcessor.create(); 151 | Flowable flowable = subject.compose(ReplayingShare.instance()); 152 | 153 | TestSubscriber subscriber1 = new TestSubscriber<>(); 154 | flowable.subscribe(subscriber1); 155 | subscriber1.assertNoValues(); 156 | 157 | subject.onNext("Foo"); 158 | subscriber1.assertValues("Foo"); 159 | 160 | TestSubscriber subscriber2 = new TestSubscriber<>(0); 161 | flowable.subscribe(subscriber2); 162 | subscriber2.assertNoValues(); 163 | 164 | subject.onNext("Bar"); // Replace the cached value... 165 | subscriber2.request(1); // ...and ensure new requests see it. 166 | subscriber2.assertValues("Bar"); 167 | } 168 | 169 | @Test public void streamsDoNotShareInstances() { 170 | PublishProcessor subjectA = PublishProcessor.create(); 171 | Flowable flowableA = subjectA.compose(ReplayingShare.instance()); 172 | TestSubscriber subscriberA1 = new TestSubscriber<>(); 173 | flowableA.subscribe(subscriberA1); 174 | 175 | PublishProcessor subjectB = PublishProcessor.create(); 176 | Flowable flowableB = subjectB.compose(ReplayingShare.instance()); 177 | TestSubscriber subscriberB1 = new TestSubscriber<>(); 178 | flowableB.subscribe(subscriberB1); 179 | 180 | subjectA.onNext("Foo"); 181 | subscriberA1.assertValues("Foo"); 182 | subjectB.onNext("Bar"); 183 | subscriberB1.assertValues("Bar"); 184 | 185 | TestSubscriber subscriberA2 = new TestSubscriber<>(); 186 | flowableA.subscribe(subscriberA2); 187 | subscriberA2.assertValues("Foo"); 188 | 189 | TestSubscriber subscriberB2 = new TestSubscriber<>(); 190 | flowableB.subscribe(subscriberB2); 191 | subscriberB2.assertValues("Bar"); 192 | } 193 | 194 | @Test public void completeClearsCacheAndResubscribes() { 195 | List start = new ArrayList<>(); 196 | start.add("initA"); 197 | 198 | PublishProcessor upstream = PublishProcessor.create(); 199 | Flowable replayed = upstream.startWithIterable(start).compose(ReplayingShare.instance()); 200 | 201 | TestSubscriber subscriber1 = new TestSubscriber<>(); 202 | replayed.subscribe(subscriber1); 203 | subscriber1.assertValues("initA"); 204 | 205 | TestSubscriber observer2 = new TestSubscriber<>(); 206 | replayed.subscribe(observer2); 207 | subscriber1.assertValues("initA"); 208 | 209 | upstream.onComplete(); 210 | subscriber1.assertComplete(); 211 | observer2.assertComplete(); 212 | 213 | start.set(0, "initB"); 214 | 215 | TestSubscriber observer3 = new TestSubscriber<>(); 216 | replayed.subscribe(observer3); 217 | observer3.assertValues("initB"); 218 | } 219 | 220 | @Test public void errorClearsCacheAndResubscribes() { 221 | List start = new ArrayList<>(); 222 | start.add("initA"); 223 | 224 | PublishProcessor upstream = PublishProcessor.create(); 225 | Flowable replayed = upstream.startWithIterable(start).compose(ReplayingShare.instance()); 226 | 227 | TestSubscriber subscriber1 = new TestSubscriber<>(); 228 | replayed.subscribe(subscriber1); 229 | subscriber1.assertValues("initA"); 230 | 231 | TestSubscriber observer2 = new TestSubscriber<>(); 232 | replayed.subscribe(observer2); 233 | subscriber1.assertValues("initA"); 234 | 235 | RuntimeException r = new RuntimeException(); 236 | upstream.onError(r); 237 | subscriber1.assertError(r); 238 | observer2.assertError(r); 239 | 240 | start.set(0, "initB"); 241 | 242 | TestSubscriber observer3 = new TestSubscriber<>(); 243 | replayed.subscribe(observer3); 244 | observer3.assertValues("initB"); 245 | } 246 | 247 | @Test public void unsubscribeBeforeSubscribePreventsCacheEmission() { 248 | PublishProcessor upstream = PublishProcessor.create(); 249 | Flowable replayed = upstream.compose(ReplayingShare.instance()); 250 | replayed.subscribe(); 251 | upstream.onNext("something to cache"); 252 | 253 | TestSubscriber testSubscriber = new TestSubscriber<>(); 254 | testSubscriber.cancel(); 255 | replayed.subscribe(testSubscriber); 256 | testSubscriber.assertNoValues(); 257 | } 258 | 259 | @Test public void defaultValueOnSubscribe() { 260 | PublishProcessor subject = PublishProcessor.create(); 261 | Flowable flowable = subject.compose(ReplayingShare.createWithDefault("default")); 262 | 263 | TestSubscriber subscriber1 = new TestSubscriber<>(); 264 | flowable.subscribe(subscriber1); 265 | subscriber1.assertValues("default"); 266 | 267 | subject.onNext("Foo"); 268 | subscriber1.assertValues("default", "Foo"); 269 | } 270 | 271 | @Test public void defaultValueIsOverriddenByLatestEmissionForNewSubscriber() { 272 | PublishProcessor subject = PublishProcessor.create(); 273 | Flowable flowable = subject.compose(ReplayingShare.createWithDefault("default")); 274 | 275 | TestSubscriber subscriber1 = new TestSubscriber<>(); 276 | flowable.subscribe(subscriber1); 277 | subscriber1.assertValues("default"); 278 | 279 | subject.onNext("Foo"); 280 | subscriber1.assertValues("default", "Foo"); 281 | 282 | TestSubscriber observer2 = new TestSubscriber<>(); 283 | flowable.subscribe(observer2); 284 | observer2.assertValues("Foo"); 285 | } 286 | 287 | @Test public void completeClearsCacheAndResubscribesStartingWithDefault() { 288 | List start = new ArrayList<>(); 289 | start.add("initA"); 290 | 291 | PublishProcessor upstream = PublishProcessor.create(); 292 | Flowable replayed = 293 | upstream.startWithIterable(start).compose(ReplayingShare.createWithDefault("default")); 294 | 295 | TestSubscriber subscriber1 = new TestSubscriber<>(); 296 | replayed.subscribe(subscriber1); 297 | subscriber1.assertValues("default", "initA"); 298 | 299 | TestSubscriber observer2 = new TestSubscriber<>(); 300 | replayed.subscribe(observer2); 301 | subscriber1.assertValues("default", "initA"); 302 | 303 | upstream.onComplete(); 304 | subscriber1.assertComplete(); 305 | observer2.assertComplete(); 306 | 307 | start.set(0, "initB"); 308 | 309 | TestSubscriber observer3 = new TestSubscriber<>(); 310 | replayed.subscribe(observer3); 311 | observer3.assertValues("default", "initB"); 312 | } 313 | 314 | @Test public void errorClearsCacheAndResubscribesStartingWithDefault() { 315 | List start = new ArrayList<>(); 316 | start.add("initA"); 317 | 318 | PublishProcessor upstream = PublishProcessor.create(); 319 | Flowable replayed = 320 | upstream.startWithIterable(start).compose(ReplayingShare.createWithDefault("default")); 321 | 322 | TestSubscriber subscriber1 = new TestSubscriber<>(); 323 | replayed.subscribe(subscriber1); 324 | subscriber1.assertValues("default", "initA"); 325 | 326 | TestSubscriber observer2 = new TestSubscriber<>(); 327 | replayed.subscribe(observer2); 328 | subscriber1.assertValues("default", "initA"); 329 | 330 | RuntimeException r = new RuntimeException(); 331 | upstream.onError(r); 332 | subscriber1.assertError(r); 333 | observer2.assertError(r); 334 | 335 | start.set(0, "initB"); 336 | 337 | TestSubscriber observer3 = new TestSubscriber<>(); 338 | replayed.subscribe(observer3); 339 | observer3.assertValues("default", "initB"); 340 | } 341 | } 342 | --------------------------------------------------------------------------------