├── settings.gradle ├── lib ├── junit-4.12.jar ├── rxjava-1.0.7.jar ├── hamcrest-core-1.3.jar ├── rxjava-math-1.0.0.jar ├── assertj-core-1.7.1.jar └── rxjava-string-0.22.0.jar ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradlew.bat ├── src └── test │ └── java │ ├── util │ └── LessonResources.java │ ├── lessonD_AdvancedStreams.java │ ├── solutions │ ├── lessonD_Solutions.java │ ├── lessonA_Solutions.java │ ├── lessonB_Solutions.java │ └── lessonC_Solutions.java │ ├── lessonA_CreatingObservableStreams.java │ ├── lessonB_MapAndFlatMapAndBasicOperators.java │ └── lessonC_BooleanLogicAndErrorHandling.java └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rxjava-koans' -------------------------------------------------------------------------------- /lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/junit-4.12.jar -------------------------------------------------------------------------------- /lib/rxjava-1.0.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/rxjava-1.0.7.jar -------------------------------------------------------------------------------- /lib/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /lib/rxjava-math-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/rxjava-math-1.0.0.jar -------------------------------------------------------------------------------- /lib/assertj-core-1.7.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/assertj-core-1.7.1.jar -------------------------------------------------------------------------------- /lib/rxjava-string-0.22.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/lib/rxjava-string-0.22.0.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshskeen/rxjava-koans/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle 2 | /build 3 | /out 4 | /local.properties 5 | /.idea 6 | *.iml 7 | .DS_Store 8 | *.iws 9 | *~ 10 | *.swp -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 23 09:56:14 EST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##RxJava Koans 2 | ___ 3 | 4 | The Koans walk you along the [path to Rx enlightenment](https://pbs.twimg.com/media/B5oIZCXCMAI_vTn.jpg:large) in order to learn RxJava. The goal is to learn the functional reactive programming approach and how to work with RxJava to solve common problems. 5 | 6 | The koans are broken out into subjects by file. Each koan file builds up your knowledge of rx-java and builds upon itself. It will stop at the first place you need to correct. 7 | 8 | Some koans simply need to have the correct answer substituted for an incorrect one. Some, however, require you to supply your own answer. If you see the method __ (a double underscore) listed, it is a hint to you to supply your own code in order to make it work correctly. Your task is to make each test pass! 9 | 10 | 11 | ### Instructions for how to run the project 12 | 13 | 1. Java 8 is needed for the exercise. [Download from here](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) if you don't have it already. 14 | 2. [download IntelliJ Community Edition](https://www.jetbrains.com/idea/download/) 15 | 3. `git clone git@github.com:mutexkid/rxjava-koans.git` 16 | 4. In Intellij, select File > Import Project... and select the cloned directory 17 | 5. In the Import Project dialog, select Import Project from External Model, choose Gradle and click next. 18 | 6. On the next screen, make sure "use default gradle wrapper" is selected and click Finish 19 | 7. Last, under File > Project Structure, set Project SDK: to Java 1.8 and click ok! 20 | 21 | Run the test suite by right clicking on `src/test/java` and selecting `Run 'All Tests'`. 22 | 5. The test suite will fail - make each test pass! 23 | 24 | For more information about Functional Reactive Programming with RxJava, [check out my article on the topic](http://www.bignerdranch.com/blog/what-is-functional-reactive-programming/). 25 | 26 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/java/util/LessonResources.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import rx.Observable; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class LessonResources { 9 | 10 | public static class ElevatorPassenger { 11 | private String mName; 12 | 13 | public int getWeightInPounds() { 14 | return mWeightInPounds; 15 | } 16 | 17 | public String getName() { 18 | return mName; 19 | } 20 | 21 | public int mWeightInPounds; 22 | 23 | public ElevatorPassenger(String name, int weightInPounds) { 24 | mName = name; 25 | mWeightInPounds = weightInPounds; 26 | } 27 | 28 | public String toString() { 29 | return "ElevatorPassenger{" + 30 | "mName='" + mName + '\'' + 31 | ", mWeightInPounds=" + mWeightInPounds + 32 | '}'; 33 | } 34 | } 35 | 36 | 37 | public static class ComcastNetworkAdapter { 38 | private int mAttempts; 39 | 40 | public List getData() { 41 | if (mAttempts < 42) { 42 | mAttempts++; 43 | System.out.println("network issues!! please reboot your computer!"); 44 | return null; 45 | } 46 | ArrayList data = new ArrayList<>(); 47 | data.add("extremely important data"); 48 | System.out.println("transmitting data!"); 49 | return data; 50 | } 51 | } 52 | 53 | 54 | public static class Elevator { 55 | public static final int MAX_CAPACITY_POUNDS = 500; 56 | List mPassengers = new ArrayList<>(); 57 | 58 | public void addPassenger(ElevatorPassenger passenger) { 59 | mPassengers.add(passenger); 60 | } 61 | 62 | public int getTotalWeightInPounds() { 63 | return Observable.from(mPassengers).reduce(0, (accumulatedWeight, elevatorPassenger) -> 64 | elevatorPassenger.mWeightInPounds + accumulatedWeight) 65 | .toBlocking().last(); 66 | } 67 | 68 | public List getPassengers() { 69 | return mPassengers; 70 | } 71 | 72 | public int getPassengerCount() { 73 | return mPassengers.size(); 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "Elevator{" + 79 | "mPassengers=" + mPassengers + "\n" + 80 | "totalWeight=" + getTotalWeightInPounds() + 81 | '}'; 82 | } 83 | 84 | public void unload() { 85 | mPassengers = new ArrayList<>(); 86 | } 87 | } 88 | 89 | //A Carnival Food Object... 90 | public static class CarnivalFood { 91 | private String mName; 92 | public Double mPrice; 93 | 94 | public CarnivalFood(String name, Double price) { 95 | mName = name; 96 | mPrice = price; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "Food{" + 102 | "mName='" + mName + '\'' + 103 | ", mPrice=" + mPrice + 104 | "\n}"; 105 | } 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/lessonD_AdvancedStreams.java: -------------------------------------------------------------------------------- 1 | import org.junit.Test; 2 | import rx.Observable; 3 | import rx.functions.Func1; 4 | import rx.observables.GroupedObservable; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | 9 | public class lessonD_AdvancedStreams { 10 | 11 | public String mReceived = ""; 12 | public String _____; 13 | 14 | private String mEvenNums = ""; 15 | private String mOddNums = ""; 16 | 17 | /* 18 | So far everything has been pretty linear. Our pipelines all took the form: 19 | "do this, then do this, then do this, then end". In reality we can combine pipelines. We can take two streams 20 | and turn them into a single stream. 21 | 22 | Now its worth nothing this is different from what we did when we nested Observables. In that case we always had one stream. 23 | Lets take a stream of integers and a stream of strings and join them. 24 | */ 25 | @Test 26 | public void _1_merging() { 27 | Observable you = Observable.just(1, 2, 3); 28 | Observable me = Observable.just("A", "B", "C"); 29 | 30 | you.mergeWith(me).subscribe(string -> mReceived += string + " "); 31 | 32 | assertThat(mReceived).isEqualTo(_____); 33 | } 34 | 35 | /* 36 | We can also split up a single stream into two streams. We are going to to use the groupBy() action. 37 | This action can be a little tricky because it emits an observable of observables. So we need to subscribe to the 38 | "parent" observable and each emitted observable. 39 | 40 | We encourage you to read more from the wiki: http://reactivex.io/documentation/operators/groupby.html 41 | 42 | Lets split up a single stream of integers into two streams: even and odd numbers. 43 | */ 44 | @Test 45 | public void _2_splittingUp() { 46 | Observable.range(1, 9) 47 | .groupBy(integer -> { 48 | // ____ 49 | return _____; 50 | }) 51 | .subscribe(group -> group.subscribe(integer -> { 52 | String key = group.getKey(); 53 | if ("even".equals(key)) { 54 | mEvenNums = mEvenNums + integer; 55 | } else if ("odd".equals(key)) { 56 | mOddNums = mOddNums + integer; 57 | } 58 | })); 59 | 60 | assertThat(mEvenNums).isEqualTo("2468"); 61 | assertThat(mOddNums).isEqualTo("13579"); 62 | } 63 | 64 | 65 | /* 66 | Lets take what we know now and do some cool stuff. We've setup an observable and a function for you. Lets combine 67 | them together to average some numbers. 68 | 69 | Also see that we need to subscribe first to the "parent" observable but that the pipeline still cold until we 70 | subscribe to each subset observable. Don't forget to do that. 71 | */ 72 | @Test 73 | public void _3_challenge_needToSubscribeImmediatelyWhenSplitting() { 74 | final double[] averages = {0, 0}; 75 | Observable numbers = Observable.just(22, 22, 99, 22, 101, 22); 76 | Func1 keySelector = integer -> integer % 2; 77 | Observable> split = numbers.groupBy(keySelector); 78 | split.subscribe( 79 | group -> { 80 | Observable convertToDouble = group.map(integer -> (double) integer); 81 | Func1 insertIntoAveragesArray = aDouble -> averages[group.getKey()] = aDouble; 82 | // MathObservable.averageDouble(________).map(____________).______(); 83 | } 84 | ); 85 | 86 | assertThat(averages[0]).isEqualTo(22.0); 87 | assertThat(averages[1]).isEqualTo(100.0); 88 | } 89 | } -------------------------------------------------------------------------------- /src/test/java/solutions/lessonD_Solutions.java: -------------------------------------------------------------------------------- 1 | package solutions; 2 | 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | import rx.observables.GroupedObservable; 6 | 7 | import java.util.Objects; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | 12 | public class lessonD_Solutions { 13 | 14 | public String mReceived = ""; 15 | public String _____; 16 | public Integer _______; 17 | 18 | private String mEvenNums = ""; 19 | private String mOddNums = ""; 20 | private Observable ________; 21 | private Func1 ____________; 22 | 23 | /* 24 | So far everything has been pretty linear. Our pipelines all took the form: 25 | "do this, then do this, then do this, then end". In reality we can combine pipelines. We can take two streams 26 | and turn them into a single stream. 27 | 28 | Now its worth nothing this is different from what we did when we nested Observables. In that case we always had one stream. 29 | Lets take a stream of integers and a stream of strings and join them. 30 | */ 31 | public void merging() { 32 | Observable you = Observable.just(1, 2, 3); 33 | Observable me = Observable.just("A", "B", "C"); 34 | 35 | you.mergeWith(me).subscribe(string -> mReceived += string + " "); 36 | 37 | assertThat(mReceived).isEqualTo("1 2 3 A B C"); 38 | } 39 | 40 | /* 41 | We can also split up a single stream into two streams. We are going to to use the groupBy() action. 42 | This action can be a little tricky because it emits an observable of observables. So we need to subscribe to the 43 | "parent" observable and each emitted observable. 44 | 45 | We encourage you to read more from the wiki: http://reactivex.io/documentation/operators/groupby.html 46 | 47 | Lets split up a single stream of integers into two streams: even and odd numbers. 48 | */ 49 | public void splittingUp() { 50 | Observable.range(1, 9) 51 | .groupBy(integer -> { 52 | if (integer % 2 == 0) { 53 | return "even"; 54 | } else { 55 | return "odd"; 56 | } 57 | }) 58 | .subscribe(group -> group.subscribe(integer -> { 59 | String key = group.getKey(); 60 | if (Objects.equals(key, "even")) { 61 | mEvenNums = mEvenNums + integer; 62 | } else if (Objects.equals(key, "odd")) { 63 | mOddNums = mOddNums + integer; 64 | } 65 | })); 66 | 67 | assertThat(mEvenNums).isEqualTo("2468"); 68 | assertThat(mOddNums).isEqualTo("13579"); 69 | } 70 | 71 | 72 | /* 73 | Lets take what we know now and do some cool stuff. We've setup an observable and a function for you. Lets combine 74 | them together to average some numbers. 75 | 76 | Also see that we need to subscribe first to the "parent" observable but that the pipeline still cold until we 77 | subscribe to each subset observable. Don't forget to do that. 78 | */ 79 | public void challenge_needToSubscribeImmediatelyWhenSplitting() { 80 | final double[] averages = {0, 0}; 81 | Observable numbers = Observable.just(22, 22, 99, 22, 101, 22); 82 | Func1 keySelector = integer -> integer % 2; 83 | Observable> split = numbers.groupBy(keySelector); 84 | split.subscribe( 85 | group -> { 86 | Observable convertToDouble = group.map(integer -> (double) integer); 87 | Func1 insertIntoAveragesArray = aDouble -> averages[group.getKey()] = aDouble; 88 | // MathObservable.averageDouble(________).map(____________).______(); 89 | } 90 | ); 91 | 92 | assertThat(averages[0]).isEqualTo(22.0); 93 | assertThat(averages[1]).isEqualTo(100.0); 94 | } 95 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/test/java/solutions/lessonA_Solutions.java: -------------------------------------------------------------------------------- 1 | package solutions; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import rx.Observable; 6 | import rx.observers.TestSubscriber; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class lessonA_Solutions { 14 | 15 | private Object mReceived; 16 | private Integer mSum; 17 | private String _____; 18 | private int ____; 19 | private Object ______ = ""; 20 | private TestSubscriber mSubscriber; 21 | private int mCount1; 22 | private int mCount2; 23 | private int mCount3; 24 | 25 | 26 | public void setup() { 27 | mSubscriber = new TestSubscriber<>(); 28 | } 29 | 30 | /** 31 | * Observables are ultimately about handling "streams" of items (i.e. more than one item) in a "data pipeline". 32 | * Each item is called an "event" of "data". Here we have the creation of a new stream of data/events, 33 | * called an Observable. (http://reactivex.io/RxJava/javadoc/rx/Observable.html) 34 | * We also have a subscription, which finally takes the values from the pipeline and consumes them. 35 | *

36 | * For our RxJava tests, we will be working with an object called TestSubscriber which the framework includes. 37 | * It gives us an easy way to check what was emitted on the pipeline. 38 | */ 39 | public void anObservableStreamOfEventsEmitsEachItemInOrder() { 40 | Observable pipelineOfData = Observable.just("Foo", "Bar"); 41 | pipelineOfData.subscribe(mSubscriber); 42 | List dataEmitted = mSubscriber.getOnNextEvents(); 43 | assertThat(dataEmitted).hasSize(2); 44 | assertThat(dataEmitted).containsOnlyOnce("Foo"); 45 | assertThat(dataEmitted).containsOnlyOnce("Bar"); 46 | } 47 | 48 | 49 | 50 | /** 51 | * An observable stream calls 3 major lifecycle methods as it does it's work: 52 | * onNext(), onCompleted(), and onError(). 53 | *

54 | * onNext(): 55 | * An Observable calls this method whenever the Observable emits an item. 56 | * This method takes as a parameter the item emitted by the Observable. 57 | *

58 | * onError(): 59 | * An Observable calls this method to indicate that it has failed to generate the expected data 60 | * or has encountered some other error. 61 | * This stops the Observable and it will not make further calls to onNext or onCompleted. 62 | * The onError method takes as its parameter an indication of what caused the error. 63 | *

64 | * onCompleted(): 65 | * An Observable calls this method after it has called onNext for the final time, 66 | * if it has not encountered any errors. 67 | */ 68 | public void anObservableStreamEmitsThreeMajorEventTypes() { 69 | Observable pipelineOfData = Observable.just(1, 2, 3, 4, 5); 70 | pipelineOfData.doOnNext(integer -> mCount1++) 71 | .doOnCompleted(() -> mCount2++) 72 | .doOnError(throwable -> mCount3++) 73 | .subscribe(mSubscriber); 74 | mSubscriber.awaitTerminalEvent(); 75 | assertThat(mCount1).isEqualTo(5); 76 | assertThat(mCount2).isEqualTo(1); 77 | assertThat(mCount3).isEqualTo(0); 78 | } 79 | 80 | /** 81 | * In the test above, we saw Observable.just(), which takes one or several Java objects 82 | * and converts them into an Observable which emits those objects. (http://reactivex.io/RxJava/javadoc/rx/Observable.html#just(T)) 83 | * Let's build our own this time. 84 | */ 85 | public void justCreatesAnObservableEmittingItsArguments() { 86 | 87 | String stoogeOne = "Larry"; 88 | String stoogeTwo = "Moe"; 89 | String stoogeThree = "Curly"; 90 | Integer stoogeAge = 38; 91 | 92 | Observable stoogeDataObservable = Observable.just(stoogeOne, stoogeTwo, stoogeThree, stoogeAge); 93 | stoogeDataObservable.subscribe(mSubscriber); 94 | /** 95 | * As we've seen, the TestSubscriber's getOnNextEvents() method gives a list of all the events emitted by the observable stream in a blocking fashion. 96 | * This makes it possible for us to test what was emitted by the stream. 97 | * Without the TestSubscriber, the events would have been emitted asynchronously and our assertion would have failed. 98 | */ 99 | List events = mSubscriber.getOnNextEvents(); 100 | assertThat(events).containsOnlyOnce(stoogeOne); 101 | assertThat(events).containsOnlyOnce(stoogeTwo); 102 | assertThat(events).containsOnlyOnce(stoogeThree); 103 | assertThat(events).containsOnlyOnce(stoogeAge); 104 | assertThat(events).hasSize(4); 105 | } 106 | 107 | /** 108 | * Observable.from() is another way to create an Observable. It's different than .just() - it is specifically designed to work 109 | * with Collections. When just is given a collection, it converts it into an Observable that emits each item from the list. 110 | * Let's understand how the two are different more clearly. 111 | */ 112 | public void fromCreatesAnObservableThatEmitsEachElementFromAnIterable() { 113 | List sandwichIngredients = Arrays.asList("bread (one)", "bread (two)", "cheese", "mayo", "turkey", "lettuce", "pickles", "jalapenos", "Sriracha sauce"); 114 | 115 | Observable favoriteFoodsObservable = Observable.from(sandwichIngredients); 116 | TestSubscriber subscriber = new TestSubscriber<>(); 117 | favoriteFoodsObservable.subscribe(subscriber); 118 | assertThat(subscriber.getOnNextEvents()).hasSize(9); 119 | assertThat(subscriber.getOnNextEvents()).containsAll(sandwichIngredients); 120 | 121 | subscriber = new TestSubscriber<>(); 122 | Observable.just(sandwichIngredients).subscribe(subscriber); 123 | assertThat(subscriber.getOnNextEvents()).hasSize(1); 124 | assertThat(subscriber.getOnNextEvents()).contains(sandwichIngredients); 125 | /** 126 | * ^^ As you can see here, from() & just() do very different things! 127 | */ 128 | } 129 | 130 | /** 131 | * So far we've created observables and immediately "subscribed" to them. Its only when we subscribe to an 132 | * observable that it is fully wired up. This observable is now considered "hot". Until then it is "cold" 133 | * and doesn't really do anything, it won't emit any events. 134 | *

135 | * So if we are going to build an observable and not subscribe to it until later on, how can we include the all 136 | * of the functionality as before? Do we have to put all the work inside subscribe() ? No we don't! 137 | *

138 | * If we peek at the Observer interface we see it has three methods: 139 | *

140 | * public interface Observer { 141 | * void onCompleted(); 142 | * void onError(Throwable var1); 143 | * void onNext(T var1); 144 | *

145 | * When we subscribe to an Observable, the code we put inside subscribe() is getting handed off to the Observer's onNext() method. 146 | * However, we can manually pass code right to onNext() ourselves with Observable.doOnNext() 147 | *

148 | * Lets setup an Observable with all the functionality we need to sum a range of Integers. Then lets subscribe to it later on. 149 | */ 150 | public void nothingListensUntilYouSubscribe() { 151 | mSum = 0; 152 | /** 153 | * Observable.range() creates a sequential list from a starting number of a particular size. 154 | * (http://reactivex.io/RxJava/javadoc/rx/Observable.html#range(int,%20int)) 155 | * 156 | * We also haven't seen doOnNext() yet - its one way we can take action based on one of a series of Observable lifecycle events. 157 | * http://reactivex.io/documentation/operators/do.html 158 | */ 159 | Observable numbers = Observable.range(1, 10).doOnNext(integer -> mSum += integer); 160 | numbers.subscribe(); 161 | assertThat(mSum).isEqualTo(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10); 162 | //Hint: what would we need to do to get our Observable to start emitting things? 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/test/java/lessonA_CreatingObservableStreams.java: -------------------------------------------------------------------------------- 1 | import org.junit.Before; 2 | import org.junit.Test; 3 | import rx.Observable; 4 | import rx.observers.TestSubscriber; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | 12 | public class lessonA_CreatingObservableStreams { 13 | 14 | private Integer mSum; 15 | private String _____; 16 | private int ____; 17 | private TestSubscriber mSubscriber; 18 | private int mCount1; 19 | private int mCount2; 20 | private int mCount3; 21 | 22 | @Before 23 | public void setup() { 24 | mSubscriber = new TestSubscriber<>(); 25 | } 26 | 27 | /** 28 | * Observables are ultimately about handling "streams" of items (i.e. more than one item) in a "data pipeline". 29 | * Each item is called an "event" of "data". Here we have the creation of a new stream of data/events, 30 | * called an Observable. (http://reactivex.io/RxJava/javadoc/rx/Observable.html) 31 | * We also have a subscription, which finally takes the values from the pipeline and consumes them. 32 | *

33 | * For our RxJava tests, we will be working with an object called TestSubscriber which the framework includes. 34 | * It gives us an easy way to check what was emitted on the pipeline. 35 | */ 36 | @Test 37 | public void _1_anObservableStreamOfEventsAndDataEmitsEachItemInOrder() { 38 | Observable pipelineOfData = Observable.just("Foo", "Bar"); 39 | pipelineOfData.subscribe(mSubscriber); 40 | List dataEmitted = mSubscriber.getOnNextEvents(); 41 | assertThat(dataEmitted).hasSize(____); 42 | assertThat(dataEmitted).containsOnlyOnce(_____); 43 | assertThat(dataEmitted).containsOnlyOnce(_____); 44 | } 45 | 46 | /** 47 | * An observable stream calls 3 major lifecycle methods as it does it's work: 48 | * onNext(), onCompleted(), and onError(). 49 | *

50 | * onNext(): 51 | * An Observable calls this method whenever the Observable emits an item. 52 | * This method takes as a parameter the item emitted by the Observable. 53 | *

54 | * onError(): 55 | * An Observable calls this method to indicate that it has failed to generate the expected data 56 | * or has encountered some other error. 57 | * This stops the Observable and it will not make further calls to onNext or onCompleted. 58 | * The onError method takes as its parameter an indication of what caused the error. 59 | *

60 | * onCompleted(): 61 | * An Observable calls this method after it has called onNext for the final time, 62 | * if it has not encountered any errors. 63 | */ 64 | @Test 65 | public void _2_anObservableStreamEmitsThreeMajorEventTypes() { 66 | Observable pipelineOfData = Observable.just(1, 2, 3, 4, 5); 67 | pipelineOfData.doOnNext(integer -> mCount1++) 68 | .doOnCompleted(() -> mCount2++) 69 | .doOnError(throwable -> mCount3++) 70 | .subscribe(mSubscriber); 71 | mSubscriber.awaitTerminalEvent(); 72 | assertThat(mCount1).isEqualTo(____); 73 | assertThat(mCount2).isEqualTo(____); 74 | assertThat(mCount3).isEqualTo(____); 75 | } 76 | 77 | 78 | /** 79 | * In the test above, we saw Observable.just(), which takes one or several Java objects 80 | * and converts them into an Observable which emits those objects. (http://reactivex.io/RxJava/javadoc/rx/Observable.html#just(T)) 81 | * Let's build our own this time. 82 | */ 83 | @Test 84 | public void _3_justCreatesAnObservableEmittingItsArguments() { 85 | 86 | String stoogeOne = "Larry"; 87 | String stoogeTwo = "Moe"; 88 | String stoogeThree = "Curly"; 89 | Integer stoogeAge = 38; 90 | 91 | Observable stoogeDataObservable = Observable.just(_____, _____, _____, _____); 92 | stoogeDataObservable.subscribe(mSubscriber); 93 | /** 94 | * As we've seen, the TestSubscriber's getOnNextEvents() method gives a list of all the events emitted by the observable stream in a blocking fashion. 95 | * This makes it possible for us to test what was emitted by the stream. 96 | * Without the TestSubscriber, the events would have been emitted asynchronously and our assertion would have failed. 97 | */ 98 | List events = mSubscriber.getOnNextEvents(); 99 | assertThat(events).containsOnlyOnce(_____); 100 | assertThat(events).containsOnlyOnce(_____); 101 | assertThat(events).containsOnlyOnce(_____); 102 | assertThat(events).containsOnlyOnce(_____); 103 | assertThat(events).hasSize(____); 104 | } 105 | 106 | /** 107 | * Observable.from() is another way to create an Observable. It's different than .just() - it is specifically designed to work 108 | * with Collections. When just is given a collection, it converts it into an Observable that emits each item from the list. 109 | * Let's understand how the two are different more clearly. 110 | */ 111 | @Test 112 | public void _4_fromCreatesAnObservableThatEmitsEachElementFromAnIterable() { 113 | List sandwichIngredients = Arrays.asList("bread (one)", "bread (two)", "cheese", "mayo", "turkey", "lettuce", "pickles", "jalapenos", "Sriracha sauce"); 114 | Observable favoriteFoodsObservable = Observable.from(sandwichIngredients); 115 | TestSubscriber subscriber = new TestSubscriber<>(); 116 | favoriteFoodsObservable.subscribe(subscriber); 117 | assertThat(subscriber.getOnNextEvents()).hasSize(____); 118 | assertThat(subscriber.getOnNextEvents()).contains(_____); 119 | // Uncomment the following line and make it pass! 120 | //assertThat(subscriber.getOnNextEvents()).containsAll(_____); 121 | 122 | subscriber = new TestSubscriber<>(); 123 | Observable.just(sandwichIngredients).subscribe(subscriber); 124 | assertThat(subscriber.getOnNextEvents()).hasSize(____); 125 | assertThat(subscriber.getOnNextEvents()).contains(_____); 126 | /** 127 | * ^^ As you can see here, from() & just() do very different things! 128 | */ 129 | } 130 | 131 | /** 132 | * So far we've created observables and immediately "subscribed" to them. Its only when we subscribe to an 133 | * observable that it is fully wired up. This observable is now considered "hot". Until then it is "cold" 134 | * and doesn't really do anything, it won't emit any events. 135 | *

136 | * So if we are going to build an observable and not subscribe to it until later on, how can we include the all 137 | * of the functionality as before? Do we have to put all the work inside subscribe() ? No we don't! 138 | *

139 | * If we peek at the Observer interface we see it has three methods: 140 | *

141 | * public interface Observer { 142 | * void onCompleted(); 143 | * void onError(Throwable var1); 144 | * void onNext(T var1); 145 | *

146 | * When we subscribe to an Observable, the code we put inside subscribe() is getting handed off to the Observer's onNext() method. 147 | * However, we can manually pass code right to onNext() ourselves with Observable.doOnNext() 148 | *

149 | * Lets setup an Observable with all the functionality we need to sum a range of Integers. Then lets subscribe to it later on. 150 | */ 151 | @Test 152 | public void _5_nothingListensUntilYouSubscribe() { 153 | mSum = 0; 154 | /** 155 | * Observable.range() creates a sequential list from a starting number of a particular size. 156 | * (http://reactivex.io/RxJava/javadoc/rx/Observable.html#range(int,%20int)) 157 | * 158 | * We also haven't seen doOnNext() yet - its one way we can take action based on one of a series of Observable lifecycle events. 159 | * http://reactivex.io/documentation/operators/do.html 160 | */ 161 | Observable numbers = Observable.range(1, 10).doOnNext(integer -> mSum += integer); 162 | //Hint: what would we need to do to get our Observable to start emitting things? 163 | assertThat(mSum).isEqualTo(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/test/java/solutions/lessonB_Solutions.java: -------------------------------------------------------------------------------- 1 | package solutions; 2 | 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | import rx.observers.TestSubscriber; 6 | import util.LessonResources.CarnivalFood; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static util.LessonResources.ElevatorPassenger; 13 | 14 | public class lessonB_Solutions { 15 | 16 | public String mStringA; 17 | public String mStringB; 18 | private TestSubscriber mSubscriber; 19 | 20 | public void setup() { 21 | mSubscriber = new TestSubscriber<>(); 22 | } 23 | 24 | /** 25 | * the Map function transforms the items emitted by an Observable by applying a function to each, changing the content. 26 | */ 27 | public void mapAppliesAFunctionToEachItemAndEmitsDataOnTheOtherSide() { 28 | Observable.from(Arrays.asList("kewl", "leet", "speak")) 29 | .map(word -> word.replace("e", "3")) 30 | .map(word -> word.replace("l", "1")) 31 | .subscribe(mSubscriber); 32 | assertThat(mSubscriber.getOnNextEvents()).contains("k3w1"); 33 | assertThat(mSubscriber.getOnNextEvents()).contains("133t"); 34 | assertThat(mSubscriber.getOnNextEvents()).contains("sp3ak"); 35 | } 36 | 37 | /** 38 | * Understanding what flatMap() does is a major awakening on the seeker's path to rx enlightenment. 39 | * We will use non-lambda syntax here to help illustrate what the return types are in this use case for flatmap. 40 | * For this experiment, we will be going to the carnival. Because we spent our money unwisely at the 41 | * carnival ($25 dollars on the Dunk Tank), we are left only with 5$. 42 | * We still need to eat though. Our goal - check the available food options and get a filtered list of things under 5$. 43 | */ 44 | 45 | public void flatMapUnwrapsOneLevelOfNestingInAnObservableStream() { 46 | /** 47 | * The First Food cart's offerings: 48 | */ 49 | List funnelCakeCart = Arrays.asList(new CarnivalFood("Cheese Pizza", 5.95), 50 | new CarnivalFood("Funnel Cake", 3.95), 51 | new CarnivalFood("Candied Apple", 1.50), 52 | new CarnivalFood("Jumbo Corn Dog", 2.25), 53 | new CarnivalFood("Deluxe Corned Beef Hoagie with Swiss Cheese", 6.75), 54 | new CarnivalFood("Faygo", 1.95)); 55 | /** 56 | * The Second Food Cart's offerings: 57 | */ 58 | List chineseFoodCart = Arrays.asList(new CarnivalFood("Duck Teriyaki Kabobs", 12.95), 59 | new CarnivalFood("Vegetable Dumplings", 2.50), 60 | new CarnivalFood("Poor Quality Shrimp Lo Mein", 4.75), 61 | new CarnivalFood("Green Tea Ice Cream", 3.95), 62 | new CarnivalFood("Basic Mandarin Chicken", 5.25)); 63 | 64 | /** 65 | * Emit each foodCart list on a stream. 66 | */ 67 | Observable> foodCartItemsObservable = Observable.just(funnelCakeCart, chineseFoodCart); 68 | 69 | /** 70 | * what do you think calling .map() on the foodCartItemsObservable will do? 71 | */ 72 | Observable> map = foodCartItemsObservable.map(new Func1, Observable>() { 73 | @Override 74 | public Observable call(List foods) { 75 | Observable from = Observable.from(foods); 76 | return from; 77 | } 78 | }); 79 | map.subscribe(mSubscriber); 80 | 81 | assertThat(mSubscriber.getOnNextEvents()).hasSize(2); 82 | 83 | /** Was the result above what you expected? A bit strange huh? You'd think that you'd get 84 | * a value matching the number of items of foods in each list at first glance. 85 | * The reason we get a different result is because of the difference between map(), and flatmap(), which we will see next. 86 | * map() will always keep the SAME NUMBER OF events/ data as the previous segment in the pipeline. It can never change the number 87 | * of items on the previous piece of the pipeline. 88 | 89 | * Next, we would like to begin filtering the list to match what we can afford to eat. 90 | * The problem now is that rather than Observable items, we are emitting Observable>s instead. 91 | * We can't filter these, because Observable has no price (its content does, but we cant access that). 92 | * This is where flatMap comes in! 93 | */ 94 | 95 | mSubscriber = new TestSubscriber<>(); 96 | /** 97 | * flatMap() transform the items emitted by an Observable into Observables, then flattens the emissions from those into a single Observable 98 | * As martin fowler defines flatMap: 99 | * Map a function over a collection and flatten the result by one-level. In this case, we will map a function over the list of Lists 100 | * and then flatten them into one list. 101 | */ 102 | Observable individualItemsObservable = foodCartItemsObservable.flatMap(new Func1, Observable>() { 103 | @Override 104 | public Observable call(List foods) { 105 | return Observable.from(foods); 106 | } 107 | }); 108 | individualItemsObservable.subscribe(mSubscriber); 109 | assertThat(mSubscriber.getOnNextEvents()).hasSize(11); 110 | 111 | mSubscriber = new TestSubscriber<>(); 112 | 113 | /** 114 | * Now that the answer to the riddle of flatMap has been revealed to us, we may filter the stream of 115 | * individual carnival food items and eat what we can afford. to do that we can use the 116 | * filter() operator. 117 | * public final Observable filter(Func1 predicate) 118 | * if the predicate returns true, the data/event being evaluated in the predicate is passed on 119 | */ 120 | individualItemsObservable.filter(new Func1() { 121 | @Override 122 | public Boolean call(CarnivalFood food) { 123 | return food.mPrice < 5.00; 124 | } 125 | }).subscribe(mSubscriber); 126 | 127 | assertThat(mSubscriber.getOnNextEvents()).hasSize(7); 128 | 129 | System.out.println("With my 5 bucks I can buy: " + mSubscriber.getOnNextEvents()); 130 | } 131 | 132 | /** 133 | * Reduce is helpful for aggregating a set of data and emitting a final result 134 | */ 135 | public void theReduceOperatorAccumulatesValuesAndEmitsTheResult() { 136 | 137 | TestSubscriber testSubscriber = new TestSubscriber<>(); 138 | 139 | List elevatorPassengers = Arrays.asList( 140 | new ElevatorPassenger("Max", 168), 141 | new ElevatorPassenger("Mike", 234), 142 | new ElevatorPassenger("Ronald", 192), 143 | new ElevatorPassenger("William", 142), 144 | new ElevatorPassenger("Jacqueline", 114)); 145 | Observable elevatorPassengersObservable = Observable.from(elevatorPassengers); 146 | /** 147 | * http://reactivex.io/documentation/operators/reduce.html 148 | */ 149 | elevatorPassengersObservable.reduce(0, (accumulatedWeight, elevatorPassenger) -> 150 | elevatorPassenger.mWeightInPounds += accumulatedWeight) 151 | .subscribe(testSubscriber); 152 | assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(850); 153 | } 154 | 155 | /** 156 | * .repeat() creates an Observable that emits a particular item or sequence of items repeatedly 157 | */ 158 | public void repeatOperatorRepeatsThePreviousOperationANumberOfTimes() { 159 | String weapon = "A Boomerang made of Pure Gold"; 160 | TestSubscriber subscriber = new TestSubscriber<>(); 161 | 162 | Observable repeatingObservable = Observable.just(weapon).repeat(4); 163 | repeatingObservable.subscribe(subscriber); 164 | assertThat(subscriber.getOnNextEvents()).hasSize(4); 165 | 166 | subscriber = new TestSubscriber<>(); 167 | /** 168 | * Challenge - what about this one?? Remember, .repeat() repeats the previous step in the pipeline 169 | */ 170 | Observable challengeRepeatingObservable = repeatingObservable.repeat(4); 171 | challengeRepeatingObservable.subscribe(subscriber); 172 | assertThat(subscriber.getOnNextEvents()).hasSize(16); 173 | } 174 | 175 | /** 176 | * A great feature of RxJava is that we can chain actions together to achieve more functionality. 177 | * In this example we have one Observable and we perform two actions on the data it emits. 178 | * Lets build two Strings by concatenating some integers. 179 | */ 180 | public void composableFunctions() { 181 | mStringA = ""; 182 | mStringB = ""; 183 | Observable.range(1, 6) 184 | .doOnNext(integer -> mStringA += integer) 185 | .doOnNext(integer -> { 186 | if (integer % 2 == 0) { 187 | mStringB += integer; 188 | } 189 | }) 190 | .subscribe(); 191 | assertThat(mStringA).isEqualTo("123456"); 192 | assertThat(mStringB).isEqualTo("246"); 193 | } 194 | 195 | /** 196 | * Instead of just using events as input to actions (for example summing them), we can transform the events themselves. 197 | * We'll use the map() function for this. Lets take some text and map it to all lowercase. The key to making this work is to 198 | * return the same variable that comes into the action. 199 | */ 200 | public void convertingEvents() { 201 | mStringA = ""; 202 | Observable.just("wE", "hOpe", "yOU", "aRe", "eNjOyInG", "thIS") 203 | .map(String::toLowerCase) 204 | .subscribe(s -> mStringA += s + " "); 205 | 206 | assertThat(mStringA).isEqualTo("we hope you are enjoying this "); 207 | } 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/test/java/lessonB_MapAndFlatMapAndBasicOperators.java: -------------------------------------------------------------------------------- 1 | import org.junit.Before; 2 | import org.junit.Test; 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | import rx.observers.TestSubscriber; 6 | import util.LessonResources.CarnivalFood; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static util.LessonResources.ElevatorPassenger; 13 | 14 | public class lessonB_MapAndFlatMapAndBasicOperators { 15 | 16 | private String _____; 17 | private int ____; 18 | public String mStringA; 19 | public String mStringB; 20 | private String mStringC; 21 | private TestSubscriber mSubscriber; 22 | 23 | @Before 24 | public void setup() { 25 | mSubscriber = new TestSubscriber<>(); 26 | } 27 | 28 | /** 29 | * the Map function transforms the items emitted by an Observable by applying a function to each, changing the content. 30 | */ 31 | @Test 32 | public void _1_mapAppliesAFunctionToEachItemAndEmitsDataOnTheOtherSide() { 33 | Observable.from(Arrays.asList("kewl", "leet", "speak")) 34 | .map(word -> word.replace("e", "3")) 35 | .map(word -> word.replace("l", "1")) 36 | .subscribe(mSubscriber); 37 | assertThat(mSubscriber.getOnNextEvents()).contains(_____); 38 | assertThat(mSubscriber.getOnNextEvents()).contains(_____); 39 | assertThat(mSubscriber.getOnNextEvents()).contains(_____); 40 | } 41 | 42 | /** 43 | * Understanding what flatMap() does is a major awakening on the seeker's path to rx enlightenment. 44 | * We will use non-lambda syntax here to help illustrate what the return types are in this use case for flatmap. 45 | * For this experiment, we will be going to the carnival. Because we spent our money unwisely at the 46 | * carnival ($25 dollars on the Dunk Tank), we are left only with 5$. 47 | * We still need to eat though. Our goal - check the available food options and get a filtered list of things under 5$. 48 | */ 49 | 50 | @Test 51 | public void _2_flatMapUnwrapsOneLevelOfNestingInAnObservableStream() { 52 | 53 | /** 54 | * The First Food cart's offerings: 55 | */ 56 | List funnelCakeCart = Arrays.asList(new CarnivalFood("Cheese Pizza", 5.95), 57 | new CarnivalFood("Funnel Cake", 3.95), 58 | new CarnivalFood("Candied Apple", 1.50), 59 | new CarnivalFood("Jumbo Corn Dog", 2.25), 60 | new CarnivalFood("Deluxe Corned Beef Hoagie with Swiss Cheese", 6.75), 61 | new CarnivalFood("Faygo", 1.95)); 62 | /** 63 | * The Second Food Cart's offerings: 64 | */ 65 | List chineseFoodCart = Arrays.asList(new CarnivalFood("Duck Teriyaki Kabobs", 12.95), 66 | new CarnivalFood("Vegetable Dumplings", 2.50), 67 | new CarnivalFood("Poor Quality Shrimp Lo Mein", 4.75), 68 | new CarnivalFood("Green Tea Ice Cream", 3.95), 69 | new CarnivalFood("Basic Mandarin Chicken", 5.25)); 70 | 71 | /** 72 | * Emit each foodCart list on a stream. 73 | */ 74 | Observable> foodCartItemsObservable = Observable.just(funnelCakeCart, chineseFoodCart); 75 | 76 | /** 77 | * what do you think calling .map() on the foodCartItemsObservable will do? 78 | */ 79 | Observable> map = foodCartItemsObservable.map(new Func1, Observable>() { 80 | @Override 81 | public Observable call(List foods) { 82 | Observable from = Observable.from(foods); 83 | return from; 84 | } 85 | }); 86 | map.subscribe(mSubscriber); 87 | 88 | assertThat(mSubscriber.getOnNextEvents()).hasSize(____); 89 | 90 | /** Was the result above what you expected? A bit strange huh? You'd think that you'd get 91 | * a value matching the number of items of foods in each list at first glance. 92 | * The reason we get a different result is because of the difference between map(), and flatmap(), which we will see next. 93 | * map() will always keep the SAME NUMBER OF events/ data as the previous segment in the pipeline. It can never change the number 94 | * of items on the previous piece of the pipeline. 95 | 96 | * Next, we would like to begin filtering the list to match what we can afford to eat. 97 | * The problem now is that rather than Observable items, we are emitting Observable>s instead. 98 | * We can't filter these, because Observable has no price (its content does, but we cant access that). 99 | * This is where flatMap comes in! 100 | */ 101 | 102 | mSubscriber = new TestSubscriber<>(); 103 | /** 104 | * flatMap() transform the items emitted by an Observable into Observables, then flattens the emissions from those into a single Observable 105 | * As martin fowler defines flatMap: 106 | * Map a function over a collection and flatten the result by one-level. In this case, we will map a function over the list of Lists 107 | * and then flatten them into one list. 108 | */ 109 | Observable individualItemsObservable = foodCartItemsObservable.flatMap(new Func1, Observable>() { 110 | @Override 111 | public Observable call(List foods) { 112 | return Observable.from(foods); 113 | } 114 | }); 115 | individualItemsObservable.subscribe(mSubscriber); 116 | assertThat(mSubscriber.getOnNextEvents()).hasSize(____); 117 | 118 | mSubscriber = new TestSubscriber<>(); 119 | 120 | /** 121 | * Now that the answer to the riddle of flatMap has been revealed to us, we may filter the stream of 122 | * individual carnival food items and eat what we can afford. to do that we can use the 123 | * filter() operator. 124 | * public final Observable filter(Func1 predicate) 125 | * if the predicate returns true, the data/event being evaluated in the predicate is passed on 126 | */ 127 | individualItemsObservable.filter(new Func1() { 128 | @Override 129 | public Boolean call(CarnivalFood food) { 130 | return food.mPrice < 5.00; 131 | } 132 | }).subscribe(mSubscriber); 133 | 134 | assertThat(mSubscriber.getOnNextEvents()).hasSize(____); 135 | 136 | System.out.println("With my 5 bucks I can buy: " + mSubscriber.getOnNextEvents()); 137 | } 138 | 139 | /** 140 | * Reduce is helpful for aggregating a set of data and emitting a final result 141 | */ 142 | @Test 143 | public void _3_theReduceOperatorAccumulatesValuesAndEmitsTheResult() { 144 | 145 | TestSubscriber testSubscriber = new TestSubscriber<>(); 146 | 147 | List elevatorPassengers = Arrays.asList( 148 | new ElevatorPassenger("Max", 168), 149 | new ElevatorPassenger("Mike", 234), 150 | new ElevatorPassenger("Ronald", 192), 151 | new ElevatorPassenger("William", 142), 152 | new ElevatorPassenger("Jacqueline", 114)); 153 | Observable elevatorPassengersObservable = Observable.from(elevatorPassengers); 154 | /** 155 | * http://reactivex.io/documentation/operators/reduce.html 156 | */ 157 | elevatorPassengersObservable.reduce(0, (accumulatedWeight, elevatorPassenger) -> 158 | elevatorPassenger.mWeightInPounds + accumulatedWeight) 159 | .subscribe(testSubscriber); 160 | assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(____); 161 | } 162 | 163 | /** 164 | * .repeat() creates an Observable that emits a particular item or sequence of items repeatedly 165 | */ 166 | @Test 167 | public void _4_repeatOperatorRepeatsThePreviousOperationANumberOfTimes() { 168 | String weapon = "A Boomerang made of Pure Gold"; 169 | TestSubscriber subscriber = new TestSubscriber<>(); 170 | 171 | Observable repeatingObservable = Observable.just(weapon).repeat(4); 172 | repeatingObservable.subscribe(subscriber); 173 | assertThat(subscriber.getOnNextEvents()).hasSize(____); 174 | 175 | subscriber = new TestSubscriber<>(); 176 | /** 177 | * Challenge - what about this one?? Remember, .repeat() repeats the previous step in the pipeline 178 | */ 179 | Observable challengeRepeatingObservable = repeatingObservable.repeat(4); 180 | challengeRepeatingObservable.subscribe(subscriber); 181 | assertThat(subscriber.getOnNextEvents()).hasSize(____); 182 | } 183 | 184 | 185 | /** 186 | * A great feature of RxJava is that we can chain actions together to achieve more functionality. 187 | * In this example we have one Observable and we perform two actions on the data it emits. 188 | * Lets build two Strings by concatenating some integers. 189 | */ 190 | @Test 191 | public void _5_composableFunctions() { 192 | mStringA = ""; 193 | mStringB = ""; 194 | mStringC = ""; 195 | Observable.range(1, 6) 196 | .doOnNext(integer -> mStringA += integer) 197 | .doOnNext(integer -> { 198 | if (integer % 2 == 0) { 199 | mStringB += integer; 200 | } 201 | }) 202 | .doOnNext(integer -> mStringC += integer) 203 | .subscribe(integer -> mStringC += integer); 204 | assertThat(mStringA).isEqualTo("____"); 205 | assertThat(mStringB).isEqualTo("____"); 206 | assertThat(mStringC).isEqualTo("____"); 207 | } 208 | 209 | /** 210 | * Instead of just using events as input to actions (for example summing them), we can transform the events themselves. 211 | * We'll use the map() function for this. Lets take some text and map it to all lowercase. The key to making this work is to 212 | * return the same variable that comes into the action. 213 | */ 214 | @Test 215 | public void _6_convertingEvents() { 216 | mStringA = ""; 217 | Observable.just("wE", "hOpe", "yOU", "aRe", "eNjOyInG", "thIS") 218 | .map(s -> _____) 219 | .subscribe(s -> mStringA += s + " "); 220 | 221 | assertThat(mStringA).isEqualTo("we hope you are enjoying this "); 222 | } 223 | 224 | 225 | } 226 | -------------------------------------------------------------------------------- /src/test/java/lessonC_BooleanLogicAndErrorHandling.java: -------------------------------------------------------------------------------- 1 | import org.junit.Before; 2 | import org.junit.Test; 3 | import rx.Observable; 4 | import rx.functions.Action1; 5 | import rx.functions.Func1; 6 | import rx.functions.Func3; 7 | import rx.observables.MathObservable; 8 | import rx.observers.TestSubscriber; 9 | import util.LessonResources; 10 | import util.LessonResources.ComcastNetworkAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Random; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static util.LessonResources.Elevator; 20 | import static util.LessonResources.ElevatorPassenger; 21 | 22 | 23 | public class lessonC_BooleanLogicAndErrorHandling { 24 | 25 | private static final Observable ________ = null; 26 | public int mSum; 27 | public Boolean mBooleanValue; 28 | 29 | private int ____; 30 | private Object ______ = ""; 31 | private Boolean _________; 32 | private TestSubscriber mSubscriber; 33 | 34 | Func1 _______ = elevatorPassenger -> false; 35 | Observable __________; 36 | private Object mThrowable; 37 | 38 | @Before 39 | public void setup() { 40 | mSubscriber = new TestSubscriber<>(); 41 | } 42 | 43 | /** 44 | * In this section we will learn about boolean logic we can apply to our pipelines of data. 45 | * Our first stop on the tour is takeWhile(), similar in concept to the while loop you may already be familiar with. 46 | * http://reactivex.io/documentation/operators/takewhile.html 47 | *

48 | * In this experiment, we will load elevators with passengers eager to reach their destinations. One thing: 49 | * Our elevator has a maximum capacity. If we overload it, our passengers may be injured or even die! 50 | * We will use takeWhile to ensure no elevator is overloaded. 51 | */ 52 | @Test 53 | public void _1_takeWhileEvaluatesAnExpressionAndEmitsEventsUntilItReturnsFalse() { 54 | 55 | LessonResources.Elevator elevator = new LessonResources.Elevator(); 56 | 57 | Observable elevatorQueueOne = Observable.from(Arrays.asList( 58 | new ElevatorPassenger("Max", 168), 59 | new ElevatorPassenger("Mike", 234), 60 | new ElevatorPassenger("Ronald", 192), 61 | new ElevatorPassenger("William", 142), 62 | new ElevatorPassenger("Jacqueline", 114))); 63 | 64 | Observable elevatorQueueTwo = Observable.from(Arrays.asList( 65 | new ElevatorPassenger("Randy", 320), 66 | new ElevatorPassenger("Jerome", 125), 67 | new ElevatorPassenger("Sally-Joe", 349), 68 | new ElevatorPassenger("Little Eli", 54))); 69 | 70 | /** 71 | * the takeWhile operator evaluates an expression each time a new item is emitted in the stream. 72 | * As long as it returns true, the Observable stream of data takeWhile operates on continues to emit more data/events. 73 | * 74 | * Riddle: Lets define our elevator rule: the total weight of all passengers aboard an elevator may not be larger than 500 pounds. 75 | * How!?! 76 | * Hint: Check out the Public methods available on LessonResources.Elevator and passenger! 77 | */ 78 | 79 | Func1 elevatorRule = passenger -> ____ + ____ < ____; 80 | /** 81 | * Now all we need to do is to plug in the rule in takeWhile() 82 | */ 83 | elevatorQueueOne.takeWhile(_______).doOnNext(elevator::addPassenger).subscribe(mSubscriber); 84 | assertThat(elevator.getPassengerCount()).isGreaterThan(0); 85 | assertThat(elevator.getTotalWeightInPounds()).isLessThan(Elevator.MAX_CAPACITY_POUNDS); 86 | assertThat(elevator.getPassengerCount()).isEqualTo(2); 87 | System.out.println("elevator stats: " + elevator); 88 | /** 89 | * One of the great advantages of using RxJava is that functions become composable: 90 | * we can easily reuse existing pieces of the pipeline by plugging them into other pipelines. 91 | * takeWhile() accepts a predicate or rule for determining 92 | */ 93 | elevator.unload(); 94 | 95 | elevatorQueueTwo.takeWhile(elevatorRule).subscribe(elevator::addPassenger); 96 | assertThat(elevator.getPassengerCount()).isGreaterThan(0); 97 | assertThat(elevator.getTotalWeightInPounds()).isLessThan(Elevator.MAX_CAPACITY_POUNDS); 98 | assertThat(elevator.getPassengerCount()).isEqualTo(2); 99 | 100 | /** 101 | * a (secret) Extra Challenge! 102 | * Using what we've learned of rxJava so far, how could we get a list of passengers from elevatorQueueOne that didn't make it 103 | * into elevatorOne? 104 | */ 105 | mSubscriber = new TestSubscriber<>(); 106 | // 107 | // ??? 108 | // 109 | // assertThat(mSubscriber.getOnNextEvents()).hasSize(3); 110 | } 111 | 112 | /** 113 | * Next on our tour, we will see .amb(). Stands for Ambiguous - a somewhat mysterious name (traces its historical roots to the 60')! 114 | * What it does is it moves forward with the first of a set of Observables to emit an event. 115 | *

116 | * Useful in this situation below : 3 servers with the same data, but different response times. 117 | * Give us the fastest! 118 | */ 119 | 120 | @Test 121 | public void _2_AmbStandsForAmbiguousAndTakesTheFirstOfTwoObservablesToEmitData() { 122 | 123 | Integer randomInt = new Random().nextInt(100); 124 | Integer randomInt2 = new Random().nextInt(100); 125 | Integer randomInt3 = new Random().nextInt(100); 126 | 127 | // bonus - there's a MathObservable object that knows how to do math type things to numbers! 128 | // 129 | // here, we're getting the smallest of 3 numbers! 130 | Integer smallestNetworkLatency = MathObservable.from(Observable.just(randomInt, randomInt2, randomInt3)).min(Integer::compareTo).toBlocking().last(); 131 | 132 | Observable networkA = Observable.just("request took : " + randomInt + " millis").delay(randomInt, TimeUnit.MILLISECONDS); 133 | Observable networkB = Observable.just("request took : " + randomInt2 + " millis").delay(randomInt2, TimeUnit.MILLISECONDS); 134 | Observable networkC = Observable.just("request took : " + randomInt3 + " millis").delay(randomInt3, TimeUnit.MILLISECONDS); 135 | /** 136 | * Do we have several servers that give the same data and we want the fastest of the two? 137 | */ 138 | Observable.amb(________, ________, ________).subscribe(mSubscriber); 139 | mSubscriber.awaitTerminalEvent(); 140 | List onNextEvents = mSubscriber.getOnNextEvents(); 141 | assertThat(onNextEvents).contains("request took : " + ____ + " millis"); 142 | assertThat(onNextEvents).hasSize(1); 143 | 144 | // bonus! we can call .cache() on an operation that takes a while. It will save the pipeline's events 145 | // up to the point that .cache() was called, saving them for use again. 146 | // http://reactivex.io/RxJava/javadoc/rx/Observable.html#cache() 147 | // networkA.cache().first(); 148 | } 149 | 150 | /** 151 | * The all operator collects everything emitted in the Observable, and then evaluates a predicate, 152 | * which then emits true or false. 153 | */ 154 | 155 | @Test 156 | public void _3_checkingEverything() { 157 | Observable.just(2, 4, 6, 8, 9) 158 | .all(integer -> integer % 2 == 0) 159 | .subscribe(aBoolean -> mBooleanValue = aBoolean); 160 | assertThat(mBooleanValue).isEqualTo(____); 161 | } 162 | 163 | /** 164 | * OK, it's time for a challenge! 165 | * Given the range below and what we've learned of rxjava so far, how can we produce an mSum equal to 19?? 166 | * Hint: There are a couple ways you could do this, but the most readable will involve 2 operations. 167 | */ 168 | @Test 169 | public void _4_challenge_compositionMeansTheSumIsGreaterThanTheParts() { 170 | mSum = 0; 171 | Observable range = Observable.range(1, 10); 172 | //hmmmmmmmm.. how can we emit 1 value of 19 from the original range of numbers? 173 | assertThat(mSum).isEqualTo(19); 174 | //HINT: Could you use the MathObservable, with one of the operators you have learned about already to accomplish a result of 19? 175 | } 176 | 177 | /** 178 | * So far we've dealt with a perfect world. Unfortunately the real world involves exceptions! 179 | *

180 | * How do we respond to those exceptions in our program? Fortunately rxJava comes with many ways of handling these problems. 181 | * Our first means to do this is with the .onError() event we can implement in our pipeline. This will receive whatever 182 | * exception was emitted, so that we can log about it, take action, or notify the user for example. 183 | */ 184 | @Test 185 | public void _5_onErrorIsCalledWhenErrorsOccur() { 186 | List arrayOne = new ArrayList<>(); 187 | List arrayTwo = new ArrayList<>(); 188 | List arrayThree = null; 189 | Observable.just(arrayOne, arrayTwo, arrayThree).map(new Func1, List>() { 190 | @Override 191 | public List call(List strings) { 192 | strings.add("GOOD JOB!"); 193 | return strings; 194 | } 195 | }).doOnError(oops -> ______ = oops).subscribe(mSubscriber); 196 | assertThat(mThrowable).isInstanceOf(Throwable.class); 197 | } 198 | 199 | /** 200 | * In this test, our flaky comcast modem is on the blink again unfortunately. 201 | * .retry(long numberOfAttempts) can keep resubscribing to an Observable until a different non-error result occurs. 202 | * http://reactivex.io/documentation/operators/retry.html 203 | */ 204 | @Test 205 | public void _6_retryCanAttemptAnOperationWhichFailsMultipleTimesInTheHopesThatItMaySucceeed() { 206 | Observable networkRequestObservable = Observable.just(new ComcastNetworkAdapter()).map(new Func1() { 207 | @Override 208 | public String call(ComcastNetworkAdapter networkAdapter) { 209 | return networkAdapter.getData().get(0); 210 | } 211 | }).repeat(100); 212 | networkRequestObservable.retry(____).subscribe(mSubscriber); 213 | assertThat(mSubscriber.getOnNextEvents().get(0)).isEqualTo("extremely important data"); 214 | } 215 | 216 | /** 217 | * In this experiment, we will use RxJava to pick a lock. Our lock has three tumblers. We will need them all to be up to unlock the lock! 218 | */ 219 | 220 | @Test 221 | public void _7_combineLatestTakesTheLastEventsOfASetOfObservablesAndCombinesThem() { 222 | 223 | Observable tumbler1Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 224 | Observable tumbler2Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 225 | Observable tumbler3Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 226 | 227 | Func3 combineTumblerStatesFunction = (tumblerOneUp, tumblerTwoUp, tumblerThreeUp) -> { 228 | Boolean allTumblersUnlocked = _________ && _________ && _________; 229 | return allTumblersUnlocked; 230 | }; 231 | 232 | Observable lockIsPickedObservable = Observable.combineLatest(__________, __________, __________, combineTumblerStatesFunction).takeUntil(unlocked -> unlocked == true).last(); 233 | lockIsPickedObservable.subscribe(mSubscriber); 234 | mSubscriber.awaitTerminalEvent(); 235 | List onNextEvents = mSubscriber.getOnNextEvents(); 236 | assertThat(onNextEvents.size()).isEqualTo(1); 237 | assertThat(onNextEvents.get(0)).isEqualTo(true); 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/test/java/solutions/lessonC_Solutions.java: -------------------------------------------------------------------------------- 1 | package solutions; 2 | 3 | import org.junit.Test; 4 | import rx.Observable; 5 | import rx.functions.Func1; 6 | import rx.functions.Func3; 7 | import rx.observables.MathObservable; 8 | import rx.observers.TestSubscriber; 9 | import util.LessonResources; 10 | import util.LessonResources.ComcastNetworkAdapter; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Random; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static util.LessonResources.Elevator; 20 | import static util.LessonResources.ElevatorPassenger; 21 | 22 | 23 | public class lessonC_Solutions { 24 | 25 | private static final Observable ________ = null; 26 | public int mSum; 27 | public String mStringA; 28 | public String mStringB; 29 | public Boolean mBooleanValue; 30 | 31 | private String _____; 32 | private int ____; 33 | private Object ______ = ""; 34 | private TestSubscriber mSubscriber; 35 | private List> mField; 36 | Func1 _______ = elevatorPassenger -> false; 37 | private Object mThrowable; 38 | 39 | public void setup() { 40 | mSubscriber = new TestSubscriber<>(); 41 | } 42 | 43 | /** 44 | * In this section we will learn about boolean logic we can apply to our pipelines of data. 45 | * Our first stop on the tour is takeWhile(), similar in concept to the while loop you may already be familiar with. 46 | * http://reactivex.io/documentation/operators/takewhile.html 47 | *

48 | * In this experiment, we will load elevators with passengers eager to reach their destinations. One thing: 49 | * Our elevator has a maximum capacity. If we overload it, our passengers may be injured or even die! 50 | * We will use takeWhile to ensure no elevator is overloaded. 51 | */ 52 | public void takeWhileEvaluatesAnExpressionAndEmitsEventsUntilItReturnsFalse() { 53 | 54 | LessonResources.Elevator elevator = new LessonResources.Elevator(); 55 | 56 | Observable elevatorQueueOne = Observable.from(Arrays.asList( 57 | new ElevatorPassenger("Max", 168), 58 | new ElevatorPassenger("Mike", 234), 59 | new ElevatorPassenger("Ronald", 192), 60 | new ElevatorPassenger("William", 142), 61 | new ElevatorPassenger("Jacqueline", 114))); 62 | 63 | Observable elevatorQueueTwo = Observable.from(Arrays.asList( 64 | new ElevatorPassenger("Randy", 320), 65 | new ElevatorPassenger("Jerome", 125), 66 | new ElevatorPassenger("Sally-Joe", 349), 67 | new ElevatorPassenger("Little Eli", 54))); 68 | 69 | /** 70 | * the takeWhile operator evaluates an expression each time a new item is emitted in the stream. 71 | * As long as it returns true, the Observable stream of data takeWhile operates on continues to emit more data/events. 72 | * 73 | * Lets define our elevator rule: the total weight of all passengers aboard an elevator may not be larger than 500 pounds 74 | */ 75 | 76 | Func1 elevatorRule = passenger -> elevator.getTotalWeightInPounds() + passenger.mWeightInPounds < Elevator.MAX_CAPACITY_POUNDS; 77 | /** 78 | * Now all we need to do is to plug in the rule in takeWhile() 79 | */ 80 | elevatorQueueOne.takeWhile(elevatorRule).doOnNext(elevator::addPassenger).subscribe(mSubscriber); 81 | assertThat(elevator.getPassengerCount()).isGreaterThan(0); 82 | assertThat(elevator.getTotalWeightInPounds()).isLessThan(Elevator.MAX_CAPACITY_POUNDS); 83 | assertThat(elevator.getPassengerCount()).isEqualTo(2); 84 | System.out.println("elevator stats: " + elevator); 85 | /** 86 | * One of the great advantages of using RxJava is that functions become composable: 87 | * we can easily reuse existing pieces of the pipeline by plugging them into other pipelines. 88 | * takeWhile() accepts a predicate or rule for determining 89 | */ 90 | elevator.unload(); 91 | 92 | elevatorQueueTwo.takeWhile(elevatorRule).subscribe(elevator::addPassenger); 93 | assertThat(elevator.getPassengerCount()).isGreaterThan(0); 94 | assertThat(elevator.getTotalWeightInPounds()).isLessThan(Elevator.MAX_CAPACITY_POUNDS); 95 | assertThat(elevator.getPassengerCount()).isEqualTo(2); 96 | 97 | mSubscriber = new TestSubscriber<>(); 98 | /** 99 | * an Extra Challenge! 100 | * Using what we've learned of rxJava so far, how could we get a list of passengers from the elevator that didn't make it 101 | * into the elevator in the last queue? 102 | */ 103 | elevatorQueueTwo.filter((ElevatorPassenger passenger) -> !elevator.getPassengers().contains(passenger)).subscribe(mSubscriber); 104 | System.out.println("left behind: " + mSubscriber.getOnNextEvents()); 105 | } 106 | 107 | /** 108 | * Next on our tour, we will see .amb(). Stands for Ambiguous - a somewhat mysterious name (traces its historical roots to the 60's)! 109 | * What it does is it moves forward with the first of a set of Observables to emit an event. 110 | *

111 | * Useful in this situation below : 3 servers with the same data, but different response times. 112 | * Give us the fastest! 113 | */ 114 | 115 | @Test 116 | public void test(){ 117 | TestSubscriber stringTestSubscriber = new TestSubscriber<>(); 118 | Observable.amb( 119 | Observable.just("FOO").delay(100l, TimeUnit.MILLISECONDS), 120 | Observable.just("BAR").delay(200l, TimeUnit.MILLISECONDS) 121 | ).subscribe(stringTestSubscriber); 122 | stringTestSubscriber.awaitTerminalEvent(); 123 | System.out.println(stringTestSubscriber.getOnNextEvents()); 124 | } 125 | 126 | public void AmbStandsForAmbiguousAndTakesTheFirstOfTwoObservablesToEmitData() { 127 | 128 | Integer randomInt = new Random().nextInt(100); 129 | Integer randomInt2 = new Random().nextInt(100); 130 | Integer randomInt3 = new Random().nextInt(100); 131 | 132 | // bonus - there's a MathObservable object that knows how to do math type things to numbers! 133 | // 134 | // here, we're getting the smallest of 3 numbers! 135 | Integer smallestNetworkLatency = MathObservable.from(Observable.just(randomInt, randomInt2, randomInt3)).min(Integer::compareTo).toBlocking().last(); 136 | 137 | Observable networkA = Observable.just("request took : " + randomInt + " millis").delay(randomInt, TimeUnit.MILLISECONDS); 138 | Observable networkB = Observable.just("request took : " + randomInt2 + " millis").delay(randomInt2, TimeUnit.MILLISECONDS); 139 | Observable networkC = Observable.just("request took : " + randomInt3 + " millis").delay(randomInt3, TimeUnit.MILLISECONDS); 140 | 141 | /** 142 | * Do we have several servers that give the same data and we want the fastest of the three? 143 | */ 144 | 145 | 146 | 147 | Observable.amb(networkA, networkB, networkC).subscribe(mSubscriber); 148 | mSubscriber.awaitTerminalEvent(); //needed, since the delay causes 149 | System.out.println(mSubscriber.getOnNextEvents()); 150 | 151 | List onNextEvents = mSubscriber.getOnNextEvents(); 152 | assertThat(onNextEvents).contains("request took : " + smallestNetworkLatency + " millis"); 153 | assertThat(onNextEvents).hasSize(1); 154 | 155 | // bonus! we can call .cache() on an operation that takes a while. It will save the pipeline's events 156 | // up to the point that .cache() was called, saving them for use again. 157 | // http://reactivex.io/RxJava/javadoc/rx/Observable.html#cache() 158 | // networkA.cache().first(); 159 | } 160 | 161 | /** 162 | * The all operator collects everything emitted in the Observable, and then evaluates a predicate, 163 | * which then emits true or false. 164 | */ 165 | 166 | public void checkingEverything() { 167 | Observable.just(2, 4, 6, 8, 9) 168 | .all(integer -> integer % 2 == 0) 169 | .subscribe(aBoolean -> mBooleanValue = aBoolean); 170 | assertThat(mBooleanValue).isEqualTo(false); 171 | } 172 | 173 | /** 174 | * OK, it's time for a challenge! 175 | * Given the range below and what we've learned of rxjava so far, how can we produce an mSum equal to 19?? 176 | * Hint: There are a couple ways you could do this, but the most readable will involve 2 operations. 177 | */ 178 | public void challenge_compositionMeansTheSumIsGreaterThanTheParts() { 179 | //one way to do it! another might be using filter & reduce 180 | Observable filter = Observable.range(1, 10).filter(integer -> integer >= 9); 181 | Integer sum = MathObservable.sumInteger(filter).toBlocking().last(); 182 | assertThat(sum).isEqualTo(19); 183 | } 184 | 185 | /** 186 | * So far we've dealt with a perfect world. Unfortunately the real world involves exceptions! 187 | *

188 | * How do we respond to those exceptions in our program? Fortunately rxJava comes with many ways of handling these problems. 189 | * Our first means to do this is with the .onError() event we can implement in our pipeline. This will receive whatever 190 | * exception was emitted, so that we can log about it, take action, or notify the user for example. 191 | */ 192 | public void onErrorIsCalledWhenErrorsOccur() { 193 | List arrayOne = new ArrayList<>(); 194 | List arrayTwo = new ArrayList<>(); 195 | List arrayThree = null; 196 | Observable.just(arrayOne, arrayTwo, arrayThree).map(new Func1, List>() { 197 | @Override 198 | public List call(List strings) { 199 | strings.add("GOOD JOB!"); 200 | return strings; 201 | } 202 | }).doOnError(oops -> mThrowable = oops).subscribe(mSubscriber); 203 | assertThat(mThrowable).isInstanceOf(Throwable.class); 204 | } 205 | 206 | /** 207 | * In this test, our flaky comcast modem is on the blink again unfortunately. 208 | * .retry(long numberOfAttempts) can keep resubscribing to an Observable until a different non-error result occurs. 209 | * http://reactivex.io/documentation/operators/retry.html 210 | */ 211 | public void retryCanAttemptAnOperationWhichFailsMultipleTimesInTheHopesThatItMaySucceeed() { 212 | Observable networkRequestObservable = Observable.just(new ComcastNetworkAdapter()).map(new Func1() { 213 | @Override 214 | public String call(ComcastNetworkAdapter networkAdapter) { 215 | return networkAdapter.getData().get(0); 216 | } 217 | }).repeat(100); 218 | networkRequestObservable.retry(43).subscribe(mSubscriber); 219 | assertThat(mSubscriber.getOnNextEvents().get(0)).isEqualTo("extremely important data"); 220 | } 221 | 222 | /** 223 | * In this experiment, we will use RxJava to pick a lock. Our lock has three tumblers. We will need them all to be up to unlock the lock! 224 | */ 225 | 226 | public void combineLatestTakesTheLastEventsOfASetOfObservablesAndCombinesThem() { 227 | 228 | Observable tumbler1Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 229 | Observable tumbler2Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 230 | Observable tumbler3Observable = Observable.just(20).map(integer -> new Random().nextInt(20) > 15).delay(new Random().nextInt(20), TimeUnit.MILLISECONDS).repeat(1000); 231 | 232 | Func3 combineTumblerStatesFunction = (tumblerOneUp, tumblerTwoUp, tumblerThreeUp) -> { 233 | Boolean allTumblersUnlocked = tumblerOneUp && tumblerTwoUp && tumblerThreeUp; 234 | return allTumblersUnlocked; 235 | }; 236 | 237 | Observable lockIsPickedObservable = Observable.combineLatest(tumbler1Observable, tumbler2Observable, tumbler3Observable, combineTumblerStatesFunction).takeUntil(unlocked -> unlocked).last(); 238 | lockIsPickedObservable.subscribe(mSubscriber); 239 | mSubscriber.awaitTerminalEvent(); 240 | List onNextEvents = mSubscriber.getOnNextEvents(); 241 | assertThat(onNextEvents.size()).isEqualTo(1); 242 | assertThat(onNextEvents.get(0)).isEqualTo(true); 243 | } 244 | } 245 | --------------------------------------------------------------------------------