├── .editorconfig ├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rx-codelab ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── jraska │ │ └── rx │ │ └── codelab │ │ ├── Rx.kt │ │ ├── RxLogging.kt │ │ ├── SchedulerProvider.kt │ │ ├── http │ │ ├── GitHubApi.kt │ │ ├── GitHubConverter.kt │ │ ├── GitHubRepo.java │ │ ├── GitHubUser.java │ │ ├── GitHubUserDetail.java │ │ ├── HttpBinApi.kt │ │ ├── IpViewModel.kt │ │ ├── Repo.kt │ │ ├── RequestInfo.java │ │ ├── RequestInfoCache.kt │ │ ├── User.kt │ │ ├── UserCache.kt │ │ └── UserWithRepos.kt │ │ └── server │ │ ├── ChattyServer.kt │ │ ├── Log.kt │ │ ├── RxServer.kt │ │ └── RxServerFactory.kt │ └── test │ └── java │ └── com │ └── jraska │ └── rx │ └── codelab │ ├── RolePlaying.kt │ ├── SetupTest.kt │ ├── Task10Backpressure.kt │ ├── Task11OtherLibrariesInteroperability.kt │ ├── Task1Basics.kt │ ├── Task2Transformations.kt │ ├── Task3Combining.kt │ ├── Task4ErrorHandling.kt │ ├── Task5Threading.kt │ ├── Task6SingleCompletableMaybe.kt │ ├── Task7HotObservables.kt │ ├── Task8Testing.kt │ ├── Task9WhereWeCanFindRxJavaHandy.kt │ ├── http │ ├── HttpModule.kt │ ├── MockResponsesInterceptor.kt │ └── MockedResponses.java │ └── solution │ ├── SolutionTask10Backpressure.kt │ ├── SolutionTask11OtherLibrariesInteroperability.kt │ ├── SolutionTask1Basics.kt │ ├── SolutionTask2Transformations.kt │ ├── SolutionTask3Combining.kt │ ├── SolutionTask4ErrorHandling.kt │ ├── SolutionTask5Threading.kt │ ├── SolutionTask6SingleCompletableMaybe.kt │ ├── SolutionTask7HotObservables.kt │ ├── SolutionTask8Testing.kt │ └── SolutionTask9WhereWeCanFindRxJavaHandy.kt └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | 4 | # Files for the Dalvik VM 5 | *.dex 6 | 7 | # Generated files 8 | bin/ 9 | gen/ 10 | 11 | # Gradle files 12 | .gradle/ 13 | build/ 14 | /*/build/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Proguard folder generated by Eclipse 20 | proguard/ 21 | 22 | # Log Files 23 | *.log 24 | 25 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 26 | 27 | *.iml 28 | 29 | # Directory-based project format: 30 | .idea/ 31 | # if you remove the above rule, at least ignore the following: 32 | 33 | # User-specific stuff: 34 | # .idea/workspace.xml 35 | # .idea/tasks.xml 36 | # .idea/dictionaries 37 | 38 | # Sensitive or high-churn files: 39 | # .idea/dataSources.ids 40 | # .idea/dataSources.xml 41 | # .idea/sqlDataSources.xml 42 | # .idea/dynamic.xml 43 | # .idea/uiDesigner.xml 44 | 45 | # Gradle: 46 | # .idea/gradle.xml 47 | # .idea/libraries 48 | 49 | # Mongo Explorer plugin: 50 | # .idea/mongoSettings.xml 51 | 52 | ## File-based project format: 53 | *.ipr 54 | *.iws 55 | 56 | ## Plugin-specific files: 57 | 58 | # IntelliJ 59 | /out/ 60 | 61 | # mpeltonen/sbt-idea plugin 62 | .idea_modules/ 63 | 64 | # JIRA plugin 65 | atlassian-ide-plugin.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | jdk: 4 | - oraclejdk8 5 | notifications: 6 | email: false 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxJava Codelabs: Basic + Advanced 2 | 3 | [![Build Status](https://travis-ci.org/jraska/RxJava-Codelab.svg?branch=master)](https://travis-ci.org/jraska/RxJava-Codelab) 4 | 5 | ## Introduction 6 | Reactive programming is mentioned everywhere these days. It solves many common issues of imperative programming 7 | in an elegant way. Dozens of libraries are springing up, giving developers access to powerful tools that make their everyday development life easier. However, it can take time to learn how to wield these tools effectively as they are often difficult to understand and if used improperly they can sometimes do more harm than good. 8 | 9 | These codelabs will first go through RxJava basics, in order to demonstrate the main concepts and common use-cases of the ReactiveX framework, as well as reactive programming in general. It should make RxJava and reactive programming much clearer to you and give you lots of ideas of when and where you might want to use it. Once you have seen what it can do, you won't want to go back to how you were programming before! 10 | 11 | The advanced codelab will take you more into deep of reactive world, we will go into detail and we will discover more of its beauty, because its endless. 12 | 13 | 14 | ## Prerequisites 15 | 0. Java 8 SDK 16 | 0. IntelliJ (any edition) or any other IDE you like 17 | 18 | 19 | ## Project setup 20 | 0. Clone this repo (GitHub Desktop, SourceTree or `git clone https://github.com/jraska/RxJava-Codelab.git`) 21 | 0. Open it in IntelliJ or other IDE 22 | 0. Try to run `SetupTest` from your IDE 23 | 0. Call for help if it doesn't work 24 | 25 | 26 | ## RxJava Basics Codelab covers ([slides](https://docs.google.com/presentation/d/1W7AZm5t1PRIxttFtxROPV4wowUn6gh7lNsp_y0aNvgs/edit?usp=sharing)) 27 | - What is a stream? Observable contract 28 | - How to create Observables 29 | - Transformations 30 | - Composing 31 | - Error handling 32 | - Threading - Schedulers 33 | 34 | 35 | ## RxJava Advanced Codelab covers ([slides](https://docs.google.com/presentation/d/1jxQA4uN61aZAmvnsw10-wTVD_f1rcRP2zIhzPNC7NYE/edit?usp=sharing)) 36 | - Observable feat. Single, Maybe, Completable 37 | - Hot/Cold Observables 38 | - Testing 39 | - Use cases - cache/refresh, polling, caching, ... 40 | - Backpressure - Observable vs. Flowable 41 | - Other reactive libraries interoperability 42 | 43 | ## How to use the Code Lab 44 | - There are tasks for different areas formed as unit tests and having a format `Task{Order}{AreaOfFocus}` (e.g. `Task2Transformations`). 45 | - Each of these tasks has a method with a `TODO` explaining what to do and giving hint which operator might be useful. Very often there are multiple solutions to given problem. 46 | - Each Task has a solution with format `SolutionTask{Order}{AreaOfFocus}` (e.g. `SolutionTask2Transformations`). 47 | - Recommended order is doing the tasks by order of their numbers, however jumping based on area of interest is encouraged as well. 48 | 49 | ## Links 50 | - ReactiveX http://reactivex.io/intro.html I consider understanding the intro part the most essential thing to understand Rx. 51 | - RxJava https://github.com/ReactiveX/RxJava 52 | - All other relevant resources are linked on [ReactiveX website](http://reactivex.io/tutorials.html). 53 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50' 7 | } 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | jcenter() 13 | } 14 | } 15 | 16 | task clean(type: Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m 2 | org.gradle.parallel=true 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jraska/RxJava-Codelab/d1648c20fe94a02df0a89ae5ea4d14a9dc64862f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 20 13:20:07 GMT 2017 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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rx-codelab/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /rx-codelab/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'kotlin' 3 | 4 | dependencies { 5 | implementation 'io.reactivex.rxjava2:rxjava:2.2.4' 6 | 7 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50' 8 | 9 | implementation 'com.squareup.retrofit2:retrofit:2.6.1' 10 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.1' 11 | implementation 'com.squareup.retrofit2:converter-gson:2.6.1' 12 | implementation 'com.google.code.gson:gson:2.8.5' 13 | implementation 'com.squareup.okhttp3:okhttp:3.14.2' 14 | implementation 'com.squareup.okhttp3:logging-interceptor:3.14.2' 15 | implementation 'com.squareup.okio:okio:1.17.3' 16 | 17 | //libs for interop showcase 18 | // implementation 'io.reactivex:rxjava:1.3.8' 19 | // implementation 'com.github.akarnokd:rxjava2-interop:0.13.7' 20 | // implementation 'io.projectreactor:reactor-core:3.2.12.RELEASE' 21 | 22 | testImplementation 'junit:junit:4.12' 23 | testImplementation 'org.assertj:assertj-core:3.13.2' 24 | testImplementation 'org.mockito:mockito-core:2.28.2' 25 | } 26 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/Rx.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.functions.BiFunction 5 | 6 | fun Observable.combineLatest( 7 | other: Observable, 8 | combiner: (T1, T2) -> R 9 | ): Observable { 10 | return Observable.combineLatest(this, other, BiFunction(combiner)) 11 | } 12 | 13 | fun Observable.zipWith( 14 | other: Observable, 15 | combiner: (T1, T2) -> R 16 | ): Observable { 17 | return this.zipWith(other, BiFunction(combiner)) 18 | } 19 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/RxLogging.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import io.reactivex.plugins.RxJavaPlugins 4 | 5 | object RxLogging { 6 | 7 | fun enableObservableSubscribeLogging() { 8 | RxJavaPlugins.setOnObservableSubscribe { observable, observer -> 9 | println(observable.javaClass.getName() + ".onSubscribe(" + observer.javaClass.getSimpleName() + ")") 10 | return@setOnObservableSubscribe observer 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/SchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import io.reactivex.Scheduler 4 | import io.reactivex.schedulers.Schedulers 5 | 6 | class SchedulerProvider internal constructor( 7 | val main: Scheduler, 8 | val io: Scheduler, 9 | val computation: Scheduler 10 | ) { 11 | companion object { 12 | fun testSchedulers(): SchedulerProvider { 13 | return SchedulerProvider(Schedulers.trampoline(), Schedulers.trampoline(), Schedulers.trampoline()) 14 | } 15 | 16 | fun realSchedulers(): SchedulerProvider { 17 | return SchedulerProvider(Schedulers.trampoline(), Schedulers.io(), Schedulers.computation()) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import io.reactivex.Observable 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | 7 | interface GitHubApi { 8 | 9 | @GET("/users?since=0") 10 | fun getFirstUsers(): Observable> 11 | 12 | @GET("/users/{login}") 13 | fun getUser(@Path("login") login: String): Observable 14 | 15 | @GET("/users/{login}/repos") 16 | fun getRepos(@Path("login") login: String): Observable> 17 | } 18 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/GitHubConverter.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | /** 4 | * Many combination here to avoid pain with finding proper order of observables. 5 | */ 6 | object GitHubConverter { 7 | fun convert(gitHubUser: GitHubUser): User { 8 | val isAdmin = gitHubUser.siteAdmin ?: false 9 | return User(gitHubUser.login, gitHubUser.avatarUrl, isAdmin, gitHubUser.htmlUrl) 10 | } 11 | 12 | fun convert(gitHubRepo: GitHubRepo): Repo { 13 | return Repo(gitHubRepo.name, gitHubRepo.description ?: "", 14 | gitHubRepo.stargazersCount, gitHubRepo.forks, gitHubRepo.size) 15 | } 16 | 17 | fun convert(gitHubRepos: List): List { 18 | return gitHubRepos.map { convert(it) } 19 | } 20 | 21 | fun convert(gitHubUser: GitHubUser, gitHubRepos: List): UserWithRepos { 22 | return UserWithRepos(convert(gitHubUser), convert(gitHubRepos)) 23 | } 24 | 25 | fun convert(gitHubRepos: List, user: User): UserWithRepos { 26 | return UserWithRepos(user, gitHubRepos) 27 | } 28 | 29 | fun convert(user: User, repos: List): UserWithRepos { 30 | return UserWithRepos(user, repos) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/GitHubRepo.java: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public final class GitHubRepo { 7 | 8 | @SerializedName("id") 9 | @Expose 10 | public Integer id; 11 | @SerializedName("name") 12 | @Expose 13 | public String name; 14 | @SerializedName("full_name") 15 | @Expose 16 | public String fullName; 17 | @SerializedName("owner") 18 | @Expose 19 | public GitHubUser owner; 20 | @SerializedName("private") 21 | @Expose 22 | public Boolean _private; 23 | @SerializedName("html_url") 24 | @Expose 25 | public String htmlUrl; 26 | @SerializedName("description") 27 | @Expose 28 | public String description; 29 | @SerializedName("fork") 30 | @Expose 31 | public Boolean fork; 32 | @SerializedName("url") 33 | @Expose 34 | public String url; 35 | @SerializedName("forks_url") 36 | @Expose 37 | public String forksUrl; 38 | @SerializedName("keys_url") 39 | @Expose 40 | public String keysUrl; 41 | @SerializedName("collaborators_url") 42 | @Expose 43 | public String collaboratorsUrl; 44 | @SerializedName("teams_url") 45 | @Expose 46 | public String teamsUrl; 47 | @SerializedName("hooks_url") 48 | @Expose 49 | public String hooksUrl; 50 | @SerializedName("issue_events_url") 51 | @Expose 52 | public String issueEventsUrl; 53 | @SerializedName("events_url") 54 | @Expose 55 | public String eventsUrl; 56 | @SerializedName("assignees_url") 57 | @Expose 58 | public String assigneesUrl; 59 | @SerializedName("branches_url") 60 | @Expose 61 | public String branchesUrl; 62 | @SerializedName("tags_url") 63 | @Expose 64 | public String tagsUrl; 65 | @SerializedName("blobs_url") 66 | @Expose 67 | public String blobsUrl; 68 | @SerializedName("git_tags_url") 69 | @Expose 70 | public String gitTagsUrl; 71 | @SerializedName("git_refs_url") 72 | @Expose 73 | public String gitRefsUrl; 74 | @SerializedName("trees_url") 75 | @Expose 76 | public String treesUrl; 77 | @SerializedName("statuses_url") 78 | @Expose 79 | public String statusesUrl; 80 | @SerializedName("languages_url") 81 | @Expose 82 | public String languagesUrl; 83 | @SerializedName("stargazers_url") 84 | @Expose 85 | public String stargazersUrl; 86 | @SerializedName("contributors_url") 87 | @Expose 88 | public String contributorsUrl; 89 | @SerializedName("subscribers_url") 90 | @Expose 91 | public String subscribersUrl; 92 | @SerializedName("subscription_url") 93 | @Expose 94 | public String subscriptionUrl; 95 | @SerializedName("commits_url") 96 | @Expose 97 | public String commitsUrl; 98 | @SerializedName("git_commits_url") 99 | @Expose 100 | public String gitCommitsUrl; 101 | @SerializedName("comments_url") 102 | @Expose 103 | public String commentsUrl; 104 | @SerializedName("issue_comment_url") 105 | @Expose 106 | public String issueCommentUrl; 107 | @SerializedName("contents_url") 108 | @Expose 109 | public String contentsUrl; 110 | @SerializedName("compare_url") 111 | @Expose 112 | public String compareUrl; 113 | @SerializedName("merges_url") 114 | @Expose 115 | public String mergesUrl; 116 | @SerializedName("archive_url") 117 | @Expose 118 | public String archiveUrl; 119 | @SerializedName("downloads_url") 120 | @Expose 121 | public String downloadsUrl; 122 | @SerializedName("issues_url") 123 | @Expose 124 | public String issuesUrl; 125 | @SerializedName("pulls_url") 126 | @Expose 127 | public String pullsUrl; 128 | @SerializedName("milestones_url") 129 | @Expose 130 | public String milestonesUrl; 131 | @SerializedName("notifications_url") 132 | @Expose 133 | public String notificationsUrl; 134 | @SerializedName("labels_url") 135 | @Expose 136 | public String labelsUrl; 137 | @SerializedName("releases_url") 138 | @Expose 139 | public String releasesUrl; 140 | @SerializedName("deployments_url") 141 | @Expose 142 | public String deploymentsUrl; 143 | @SerializedName("created_at") 144 | @Expose 145 | public String createdAt; 146 | @SerializedName("updated_at") 147 | @Expose 148 | public String updatedAt; 149 | @SerializedName("pushed_at") 150 | @Expose 151 | public String pushedAt; 152 | @SerializedName("git_url") 153 | @Expose 154 | public String gitUrl; 155 | @SerializedName("ssh_url") 156 | @Expose 157 | public String sshUrl; 158 | @SerializedName("clone_url") 159 | @Expose 160 | public String cloneUrl; 161 | @SerializedName("svn_url") 162 | @Expose 163 | public String svnUrl; 164 | @SerializedName("homepage") 165 | @Expose 166 | public String homepage; 167 | @SerializedName("size") 168 | @Expose 169 | public Integer size; 170 | @SerializedName("stargazers_count") 171 | @Expose 172 | public Integer stargazersCount; 173 | @SerializedName("watchers_count") 174 | @Expose 175 | public Integer watchersCount; 176 | @SerializedName("language") 177 | @Expose 178 | public String language; 179 | @SerializedName("has_issues") 180 | @Expose 181 | public Boolean hasIssues; 182 | @SerializedName("has_downloads") 183 | @Expose 184 | public Boolean hasDownloads; 185 | @SerializedName("has_wiki") 186 | @Expose 187 | public Boolean hasWiki; 188 | @SerializedName("has_pages") 189 | @Expose 190 | public Boolean hasPages; 191 | @SerializedName("forks_count") 192 | @Expose 193 | public Integer forksCount; 194 | @SerializedName("mirror_url") 195 | @Expose 196 | public Object mirrorUrl; 197 | @SerializedName("open_issues_count") 198 | @Expose 199 | public Integer openIssuesCount; 200 | @SerializedName("forks") 201 | @Expose 202 | public Integer forks; 203 | @SerializedName("open_issues") 204 | @Expose 205 | public Integer openIssues; 206 | @SerializedName("watchers") 207 | @Expose 208 | public Integer watchers; 209 | @SerializedName("default_branch") 210 | @Expose 211 | public String defaultBranch; 212 | } 213 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/GitHubUser.java: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public final class GitHubUser { 7 | 8 | @SerializedName("login") 9 | @Expose 10 | public String login; 11 | @SerializedName("id") 12 | @Expose 13 | public Integer id; 14 | @SerializedName("avatar_url") 15 | @Expose 16 | public String avatarUrl; 17 | @SerializedName("gravatar_id") 18 | @Expose 19 | public String gravatarId; 20 | @SerializedName("url") 21 | @Expose 22 | public String url; 23 | @SerializedName("html_url") 24 | @Expose 25 | public String htmlUrl; 26 | @SerializedName("followers_url") 27 | @Expose 28 | public String followersUrl; 29 | @SerializedName("following_url") 30 | @Expose 31 | public String followingUrl; 32 | @SerializedName("gists_url") 33 | @Expose 34 | public String gistsUrl; 35 | @SerializedName("starred_url") 36 | @Expose 37 | public String starredUrl; 38 | @SerializedName("subscriptions_url") 39 | @Expose 40 | public String subscriptionsUrl; 41 | @SerializedName("organizations_url") 42 | @Expose 43 | public String organizationsUrl; 44 | @SerializedName("repos_url") 45 | @Expose 46 | public String reposUrl; 47 | @SerializedName("events_url") 48 | @Expose 49 | public String eventsUrl; 50 | @SerializedName("received_events_url") 51 | @Expose 52 | public String receivedEventsUrl; 53 | @SerializedName("type") 54 | @Expose 55 | public String type; 56 | @SerializedName("site_admin") 57 | @Expose 58 | public Boolean siteAdmin; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/GitHubUserDetail.java: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public final class GitHubUserDetail { 7 | 8 | @SerializedName("login") 9 | @Expose 10 | public String login; 11 | @SerializedName("id") 12 | @Expose 13 | public Integer id; 14 | @SerializedName("avatar_url") 15 | @Expose 16 | public String avatarUrl; 17 | @SerializedName("gravatar_id") 18 | @Expose 19 | public String gravatarId; 20 | @SerializedName("url") 21 | @Expose 22 | public String url; 23 | @SerializedName("html_url") 24 | @Expose 25 | public String htmlUrl; 26 | @SerializedName("followers_url") 27 | @Expose 28 | public String followersUrl; 29 | @SerializedName("following_url") 30 | @Expose 31 | public String followingUrl; 32 | @SerializedName("gists_url") 33 | @Expose 34 | public String gistsUrl; 35 | @SerializedName("starred_url") 36 | @Expose 37 | public String starredUrl; 38 | @SerializedName("subscriptions_url") 39 | @Expose 40 | public String subscriptionsUrl; 41 | @SerializedName("organizations_url") 42 | @Expose 43 | public String organizationsUrl; 44 | @SerializedName("repos_url") 45 | @Expose 46 | public String reposUrl; 47 | @SerializedName("events_url") 48 | @Expose 49 | public String eventsUrl; 50 | @SerializedName("received_events_url") 51 | @Expose 52 | public String receivedEventsUrl; 53 | @SerializedName("type") 54 | @Expose 55 | public String type; 56 | @SerializedName("site_admin") 57 | @Expose 58 | public Boolean siteAdmin; 59 | @SerializedName("name") 60 | @Expose 61 | public String name; 62 | @SerializedName("company") 63 | @Expose 64 | public Object company; 65 | @SerializedName("blog") 66 | @Expose 67 | public String blog; 68 | @SerializedName("location") 69 | @Expose 70 | public String location; 71 | @SerializedName("email") 72 | @Expose 73 | public String email; 74 | @SerializedName("hireable") 75 | @Expose 76 | public Object hireable; 77 | @SerializedName("bio") 78 | @Expose 79 | public Object bio; 80 | @SerializedName("public_repos") 81 | @Expose 82 | public Integer publicRepos; 83 | @SerializedName("public_gists") 84 | @Expose 85 | public Integer publicGists; 86 | @SerializedName("followers") 87 | @Expose 88 | public Integer followers; 89 | @SerializedName("following") 90 | @Expose 91 | public Integer following; 92 | @SerializedName("created_at") 93 | @Expose 94 | public String createdAt; 95 | @SerializedName("updated_at") 96 | @Expose 97 | public String updatedAt; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/HttpBinApi.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Observable 5 | import io.reactivex.Single 6 | import okhttp3.ResponseBody 7 | import retrofit2.http.Body 8 | import retrofit2.http.GET 9 | import retrofit2.http.POST 10 | import retrofit2.http.PUT 11 | 12 | interface HttpBinApi { 13 | @GET("/get?show_env=1") 14 | fun getRequest(): Observable 15 | 16 | @GET("/status/404") 17 | fun failingGet(): Observable 18 | 19 | @GET("/status/200") 20 | fun backupGet(): Observable 21 | 22 | @GET("/status/200,400,401,402,403,404,410,412") 23 | fun flakyGet(): Observable 24 | 25 | @POST("/post") 26 | fun postRequest(@Body input: String): Single 27 | 28 | @PUT("/put") 29 | fun putRequest(@Body input: String): Completable 30 | } 31 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/IpViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import com.jraska.rx.codelab.SchedulerProvider 4 | import io.reactivex.Observable 5 | 6 | class IpViewModel(private val httpBinApi: HttpBinApi, private val schedulerProvider: SchedulerProvider) { 7 | 8 | fun ip(): Observable { 9 | return httpBinApi.getRequest() 10 | .subscribeOn(schedulerProvider.io) 11 | .observeOn(schedulerProvider.main) 12 | .map { requestInfo -> requestInfo.origin } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/Repo.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | data class Repo( 4 | val name: String, 5 | val description: String, 6 | val stars: Int, 7 | val forks: Int, 8 | val size: Int 9 | ) 10 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/RequestInfo.java: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.Map; 7 | 8 | public final class RequestInfo { 9 | @SerializedName("args") 10 | @Expose 11 | public Map args; 12 | @SerializedName("headers") 13 | @Expose 14 | public Map headers; 15 | @SerializedName("origin") 16 | @Expose 17 | public String origin; 18 | @SerializedName("url") 19 | @Expose 20 | public String url; 21 | @SerializedName("data") 22 | @Expose 23 | public String data; 24 | @SerializedName("json") 25 | @Expose 26 | public String json; 27 | 28 | @Override 29 | public String toString() { 30 | return "RequestInfo{" + 31 | "args=" + args + 32 | ",\nheaders=" + headers + 33 | ",\norigin='" + origin + '\'' + 34 | ",\nurl='" + url + '\'' + 35 | ",\ndata='" + data + '\'' + 36 | ",\njson='" + json + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/RequestInfoCache.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import io.reactivex.Observable 4 | 5 | import java.util.concurrent.TimeUnit 6 | 7 | /** 8 | * Showcase purpose - not a cache at all :P 9 | */ 10 | object RequestInfoCache { 11 | val requestInfo: Observable 12 | get() = Observable.fromCallable { requestSync } 13 | .delay(400, TimeUnit.MILLISECONDS) 14 | 15 | private val requestSync: RequestInfo 16 | get() { 17 | val requestInfo = RequestInfo() 18 | requestInfo.origin = "cache" 19 | 20 | return requestInfo 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/User.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | data class User( 4 | val login: String, 5 | val avatarUrl: String, 6 | val isAdmin: Boolean, 7 | val gitHubUrl: String 8 | ) 9 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/UserCache.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import io.reactivex.Observable 4 | 5 | /** 6 | * Showcase purpose - not a cache at all :P 7 | */ 8 | object UserCache { 9 | fun getUser(login: String): Observable { 10 | return Observable.fromCallable { getUserSync(login) } 11 | } 12 | 13 | private fun getUserSync(login: String): User { 14 | return User(login, "", true, "") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/http/UserWithRepos.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | data class UserWithRepos( 4 | val user: User, 5 | val repos: List 6 | ) 7 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/server/ChattyServer.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.server 2 | 3 | import io.reactivex.Flowable 4 | import io.reactivex.Observable 5 | import io.reactivex.processors.PublishProcessor 6 | import java.util.concurrent.TimeUnit 7 | 8 | internal class ChattyServer : RxServer { 9 | private val logs = PublishProcessor.create() 10 | 11 | override fun allLogsHot(): Flowable { 12 | return logs 13 | } 14 | 15 | override fun debugLogsHot(): Observable { 16 | return logs.filter { log -> log.level.value >= Log.Level.DEBUG.value }.toObservable() 17 | } 18 | 19 | companion object { 20 | 21 | fun create(): ChattyServer { 22 | val chattyServer = ChattyServer() 23 | Observable.interval(5, TimeUnit.MILLISECONDS) 24 | .map { Log(Log.Level.VERBOSE, "I'm too chatty") } 25 | .subscribe { chattyServer.logs.onNext(it) } 26 | 27 | Observable.interval(100, TimeUnit.MILLISECONDS) 28 | .map { Log(Log.Level.DEBUG, "I'm happily running") } 29 | .subscribe { chattyServer.logs.onNext(it) } 30 | 31 | return chattyServer 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/server/Log.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.server 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | data class Log( 6 | val level: Level, 7 | val message: String, 8 | val id: Int = nextId.incrementAndGet()) { 9 | 10 | enum class Level constructor(internal var value: Int) { 11 | VERBOSE(0), DEBUG(1), INFO(2), WARNING(3), ERROR(4) 12 | } 13 | 14 | companion object { 15 | private val nextId = AtomicInteger() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/server/RxServer.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.server 2 | 3 | import io.reactivex.Flowable 4 | import io.reactivex.Observable 5 | 6 | interface RxServer { 7 | 8 | fun debugLogsHot(): Observable 9 | 10 | fun allLogsHot(): Flowable 11 | } 12 | -------------------------------------------------------------------------------- /rx-codelab/src/main/java/com/jraska/rx/codelab/server/RxServerFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.server 2 | 3 | object RxServerFactory { 4 | fun create(): RxServer { 5 | return ChattyServer.create() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/RolePlaying.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Scheduler 5 | import io.reactivex.schedulers.Schedulers 6 | import org.junit.Test 7 | 8 | class RolePlaying { 9 | private val schedulerProvider = LegoSchedulersProvider(Schedulers.trampoline(), Schedulers.trampoline()) 10 | 11 | @Test 12 | fun equipYourLegoMan() { 13 | val legoRepository = Observable.just(LegoBody(), LegoBody()) 14 | 15 | val armedLegoMans = legoRepository 16 | .map { LegoMan(it, LegoHat()) } 17 | .map { ArmedLegoMan(it, LegoWeapon()) } 18 | 19 | armedLegoMans.subscribe { println(it) } 20 | } 21 | 22 | @Test 23 | fun equipYourLegoManFromDifferentThreads() { 24 | val legoRepository = Observable.just(LegoBody(), LegoBody()) 25 | 26 | val armedLegoMans = legoRepository 27 | .subscribeOn(schedulerProvider.leftHand) 28 | .map { LegoMan(it, LegoHat()) } 29 | .observeOn(schedulerProvider.rightHand) 30 | .map { ArmedLegoMan(it, LegoWeapon()) } 31 | .observeOn(schedulerProvider.leftHand) 32 | 33 | armedLegoMans.subscribe { println(it) } 34 | } 35 | } 36 | 37 | data class ArmedLegoMan(val legoMan: LegoMan, val legoWeapon: LegoWeapon) 38 | 39 | class LegoMan(val legoBody: LegoBody, val legoMan: LegoHat) 40 | 41 | class LegoWeapon 42 | 43 | class LegoHat 44 | 45 | class LegoBody 46 | 47 | class LegoSchedulersProvider( 48 | val leftHand: Scheduler, 49 | val rightHand: Scheduler 50 | ) 51 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/SetupTest.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import org.assertj.core.api.Assertions.assertThat 5 | import org.junit.Test 6 | import java.io.IOException 7 | 8 | class SetupTest { 9 | @Test 10 | @Throws(IOException::class) 11 | fun everythingSetUp() { 12 | val requestInfo = HttpModule.httpBinApi() 13 | .getRequest() 14 | .blockingFirst() 15 | 16 | assertThat(requestInfo.url).isNotNull() 17 | assertThat(requestInfo.origin).isNotNull() 18 | assertThat(requestInfo.headers).isNotEmpty 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task10Backpressure.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.server.Log 4 | import com.jraska.rx.codelab.server.RxServer 5 | import com.jraska.rx.codelab.server.RxServerFactory 6 | import io.reactivex.functions.Consumer 7 | import org.junit.After 8 | import org.junit.Test 9 | 10 | class Task10Backpressure { 11 | private val rxServer: RxServer = RxServerFactory.create() 12 | 13 | @Test 14 | fun backpressureFail() { 15 | // TODO: Subscribe to rxServer.allLogsHot on different thread (observeOn), use reallySlowLogConsumer 16 | } 17 | 18 | @Test 19 | fun noBackpressure() { 20 | // TODO: Modify example above to ignore backpressure and continue forever (toObservable()) 21 | } 22 | 23 | @Test 24 | fun onBackpressureDrop() { 25 | // TODO: Drop values on backpressure with logging which values are dropped (onBackpressureDrop), use slowLogConsumer 26 | } 27 | 28 | @Test 29 | fun buffer_backpressureBatching() { 30 | // TODO: batch values and process them with batchLogsConsumer() 31 | // TODO: Experiment with different sizes of buffer 32 | } 33 | 34 | @Test 35 | fun onBackpressureBuffer() { 36 | // TODO: Try different sizes of backpressure buffer to better understand how internal buffers work 37 | } 38 | 39 | private fun slowLogConsumer(): Consumer { 40 | return Consumer { log -> 41 | Thread.sleep(25) 42 | println(log) 43 | } 44 | } 45 | 46 | private fun reallySlowLogConsumer(): Consumer { 47 | return Consumer { log -> 48 | Thread.sleep(100) 49 | println(log) 50 | } 51 | } 52 | 53 | private fun batchLogsConsumer(): Consumer> { 54 | return Consumer { logs -> 55 | Thread.sleep(100) 56 | println(logs) 57 | } 58 | } 59 | 60 | @After 61 | fun after() { 62 | Thread.sleep(3000) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task11OtherLibrariesInteroperability.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import org.junit.Test 4 | 5 | class Task11OtherLibrariesInteroperability { 6 | 7 | // TODO: Uncomment Gradle dependencies for this test 8 | 9 | @Test 10 | fun givenRxJava1and2() { 11 | // TODO: Create RxJava 2 observable and convert it to RxJava 1 osbervable and vice versa 12 | // TODO: Create RxJava 2 PublishProcessor and convert it to RxJava 1 Subject and back again 13 | } 14 | 15 | @Test 16 | fun reactiveStreams_reactor_rxjava() { 17 | // TODO: Create RxJava 2 Observable and convert it to Reactor Flux, subscribe 18 | // TODO: Create RxJava 2 Single and convert it to Reactor Mono, subscribe 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task1Basics.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import org.junit.Test 4 | 5 | class Task1Basics { 6 | @Test 7 | fun dummyObservable() { 8 | // TODO: Create Observable with single String value, subscribe to it and print it to console (Observable.just) 9 | } 10 | 11 | @Test 12 | fun methodIntoObservable() { 13 | // TODO: Create Observable getting current time, subscribe to it and print value to console (Observable.fromCallable) 14 | } 15 | 16 | @Test 17 | fun helloOperator() { 18 | // TODO: Create Observable with ints 1 .. 10 subscribe to it and print only odd values (Observable.range, observable.filter) 19 | } 20 | 21 | @Test 22 | fun receivingError() { 23 | // TODO: Create Observable which emits an error and print the console (Observable.error), subscribe with onError handling 24 | } 25 | 26 | companion object { 27 | fun isOdd(value: Int): Boolean { 28 | return value % 2 == 1 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task2Transformations.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import org.junit.Before 5 | import org.junit.Test 6 | 7 | class Task2Transformations { 8 | private val gitHubApi = HttpModule.mockedGitHubApi() 9 | 10 | @Before 11 | fun setUp() { 12 | RxLogging.enableObservableSubscribeLogging() 13 | } 14 | 15 | @Test 16 | fun map_convertUserDto() { 17 | // TODO: Use gitHubApi to get and print string representation of user with `LOGIN`. Use `User` and `GitHubConverter` 18 | } 19 | 20 | @Test 21 | fun flatMap_getFirstUserDetailAfterGettingList() { 22 | // TODO: Use gitHubApi to first get list of users and subsequently get its first user by other request 23 | } 24 | 25 | @Test 26 | fun replayAutoConnect_oneRequestForTwoSubscriptions() { 27 | // TODO: Get again the user with `LOGIN`, but subscribe twice and print only with 1 network request 28 | } 29 | 30 | companion object { 31 | private val LOGIN = "defunkt" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task3Combining.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import org.junit.Before 5 | import org.junit.Test 6 | 7 | class Task3Combining { 8 | private val gitHubApi = HttpModule.mockedGitHubApi() 9 | 10 | @Before 11 | fun setUp() { 12 | RxLogging.enableObservableSubscribeLogging() 13 | } 14 | 15 | @Test 16 | fun zipWith_userWithRepos() { 17 | // TODO: Use gitHubApi to get user with `LOGIN` and his repos and print them. Use `GitHubConverter::convert` as zipper 18 | } 19 | 20 | @Test 21 | fun startWith_userInCache() { 22 | // TODO: Get user with `LOGIN` and startWith a `UserCache.getUserSync(LOGIN)`, Subscribe and print both values 23 | } 24 | 25 | @Test 26 | fun merge_userInCache() { 27 | // TODO: Get user `UserCache.getUserSync(LOGIN)` and mergeWith user with `LOGIN`, Subscribe and print both values 28 | } 29 | 30 | @Test 31 | fun combineLatest_cachedUserWithRepos() { 32 | // TODO: Create observable of `UserWithRepos` with `LOGIN` and use observable with cache from previous example - use Observable.combineLatest, GithubConverter::convert 33 | // TODO: Print the results - there should be two emission. Try to change order of passing into Observable.combineLatest - what happens? 34 | } 35 | 36 | companion object { 37 | private const val LOGIN = "defunkt" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task4ErrorHandling.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import okhttp3.MediaType 5 | import okhttp3.ResponseBody 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | class Task4ErrorHandling { 10 | private val httpBinApi = HttpModule.httpBinApi() 11 | 12 | @Before 13 | fun before() { 14 | RxLogging.enableObservableSubscribeLogging() 15 | } 16 | 17 | @Test 18 | fun printErrorMessage() { 19 | // TODO: Subscribe and incoming error message - httpBinApi.failingGet(), subscribe() with 2 parameters 20 | } 21 | 22 | @Test 23 | fun onErrorReturnItem_emitCustomItemOnError() { 24 | // TODO: When an error happens, emit syntheticBody(), httpBinApi.failingGet() 25 | } 26 | 27 | @Test 28 | fun onErrorResumeNext_subscribeToExtraObservableOnError() { 29 | // TODO: When an error happens, subscribe to extra observable - httpBinApi.backupGet() 30 | } 31 | 32 | @Test 33 | fun retry_retryOnError() { 34 | // TODO: httpBinApi.flakyGet is a bit flakey and often fails, use retry to make it always complete 35 | } 36 | 37 | companion object { 38 | 39 | internal fun syntheticBody(): ResponseBody { 40 | return ResponseBody.create(MediaType.get("application/json"), "{}") 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task5Threading.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.GitHubConverter 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import org.junit.After 6 | import org.junit.Test 7 | 8 | class Task5Threading { 9 | 10 | private val gitHubApi = HttpModule.mockedGitHubApi() 11 | 12 | @After 13 | fun after() { 14 | HttpModule.awaitNetworkRequests() 15 | } 16 | 17 | @Test 18 | fun zip_subscribeOn_getUserAndHisReposInParallel() { 19 | val userObservable = gitHubApi.getUser(LOGIN) 20 | val reposObservable = gitHubApi.getRepos(LOGIN) 21 | 22 | // TODO: Get and print Observable whilst running both requests in parallel 23 | } 24 | 25 | @Test 26 | fun zip_subscribeOn_twoUserAndReposInSerialExplicitly() { 27 | val userObservable = gitHubApi.getUser(LOGIN) 28 | val reposObservable = gitHubApi.getRepos(LOGIN) 29 | 30 | // TODO: Get and print Observable whilst running both requests in serial order using `Schedulers.single()` 31 | } 32 | 33 | @Test 34 | fun observeOn_receivingResultsOnDifferentThreads() { 35 | val userObservable = gitHubApi.getUser(LOGIN).map { GitHubConverter.convert(it) } 36 | printWithThreadId("Test thread") 37 | 38 | // TODO: Get user and print him on different threads, use `observeOn`, `doOnNext` and `printWithThreadId` methods 39 | } 40 | 41 | private fun printWithThreadId(value: Any) { 42 | println("Thread id: " + Thread.currentThread().id + ", " + value) 43 | } 44 | 45 | companion object { 46 | private const val LOGIN = "defunkt" // One of GitHub founders. <3 GitHub <3 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task6SingleCompletableMaybe.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import org.junit.After 4 | import org.junit.Test 5 | 6 | class Task6SingleCompletableMaybe { 7 | 8 | @Test 9 | fun helloSingle() { 10 | // TODO: Create Single emitting one item and subscribe to it printing onSuccess value, 11 | // TODO: Convert Single to completable and print message about completion. 12 | } 13 | 14 | @Test 15 | fun maybe() { 16 | // TODO: Create a Single with one value to emit and convert it to maybe 17 | } 18 | 19 | @Test 20 | fun transformObservableToCompletable() { 21 | // TODO: Create Observable emitting values 1 .. 10 and make it completable (ignoreElements), subscribe and print 22 | } 23 | 24 | @Test 25 | fun intervalRange_firstOrError_observableToSingle() { 26 | // TODO: Create Observable emitting 5 items each 10 ms (intervalRange) 27 | // TODO: Get first element (firstOrError) 28 | // TODO: Play around with skip operator, implement error handling for skip(5) 29 | } 30 | 31 | @After 32 | fun after() { 33 | // to see easily time dependent operations, because we are in unit tests 34 | Thread.sleep(100) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task7HotObservables.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpBinApi 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import com.jraska.rx.codelab.server.RxServer 6 | import com.jraska.rx.codelab.server.RxServerFactory 7 | import io.reactivex.schedulers.Schedulers 8 | import org.junit.After 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class Task7HotObservables { 13 | 14 | private val rxServer: RxServer = RxServerFactory.create() 15 | private val httpBinApi: HttpBinApi = HttpModule.httpBinApi() 16 | 17 | @Before 18 | fun before() { 19 | RxLogging.enableObservableSubscribeLogging() 20 | } 21 | 22 | @Test 23 | fun coldObservable() { 24 | val getRequest = httpBinApi.getRequest() 25 | .subscribeOn(Schedulers.io()) 26 | 27 | // TODO: Subscribe twice to getRequest and print its values, how many http requests it triggers? 28 | // TODO: Delay first subscription by 250 ms - delaySubscription() 29 | // TODO: Modify getRequest to be able to perform only one http request - share() 30 | } 31 | 32 | @Test 33 | fun hotObservable() { 34 | // TODO: Subscribe twice to rxServer.debugLogsHot and print the logs 35 | // TODO: Delay first subscription by 250ms - delaySubscription(), how is this different than cold observable 36 | } 37 | 38 | @Test 39 | fun createHotObservableThroughSubject() { 40 | val getRequest = httpBinApi.getRequest() 41 | 42 | // TODO: Create a PublishSubject and subscribe twice to it with printing the result 43 | // TODO: Subscribe to getRequest and publish its values to subject 44 | } 45 | 46 | @After 47 | fun after() { 48 | Thread.sleep(500) 49 | HttpModule.awaitNetworkRequests() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task8Testing.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import com.jraska.rx.codelab.server.RxServerFactory 5 | import io.reactivex.schedulers.TestScheduler 6 | import io.reactivex.subjects.PublishSubject 7 | import org.junit.Test 8 | import java.util.concurrent.TimeUnit 9 | 10 | class Task8Testing { 11 | private val rxServer = RxServerFactory.create() 12 | private val httpBinApi = HttpModule.httpBinApi() 13 | 14 | @Test 15 | fun testObserver_onColdObservable() { 16 | val request = httpBinApi.getRequest() 17 | 18 | // TODO: Subscribe with test() method to request and assert values count, value has "show_env" in url and no errors were thrown 19 | } 20 | 21 | @Test 22 | fun testSubscriber_onHotFlowable() { 23 | val logObservable = rxServer.debugLogsHot() 24 | 25 | // TODO: Subscribe with test() method to rxServer.debugLogsHot, wait for 5 values(awaitCount), assert no errors and stream not completed 26 | } 27 | 28 | @Test 29 | fun testScheduler_advancingTime() { 30 | val testScheduler = TestScheduler() 31 | 32 | val subject = PublishSubject.create() 33 | val bufferedObservable = subject.buffer(100, TimeUnit.MILLISECONDS, testScheduler) 34 | bufferedObservable.subscribe { println(it) } 35 | 36 | subject.onNext("First") 37 | subject.onNext("Batch") 38 | subject.onNext("Second") 39 | subject.onNext("Longer") 40 | subject.onNext("Batch") 41 | 42 | // TODO: Move time of test scheduler so the [First, Batch] and [Second, Longer, Batch] are printed together 43 | } 44 | 45 | @Test 46 | fun schedulerProvider_runSynchronouslyInTest() { 47 | // TODO: Create an instance of IpViewModel and get ip synchronously. Use SchedulerProvider.testSchedulers() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/Task9WhereWeCanFindRxJavaHandy.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import com.jraska.rx.codelab.http.RequestInfoCache 5 | import io.reactivex.schedulers.Schedulers 6 | import io.reactivex.subjects.PublishSubject 7 | import org.junit.After 8 | import org.junit.Test 9 | 10 | class Task9WhereWeCanFindRxJavaHandy { 11 | private val httpBinApi = HttpModule.httpBinApi() 12 | 13 | @Test 14 | fun repeatWhen_refreshFunctionality() { 15 | val refreshSignal = PublishSubject.create() 16 | 17 | val request = httpBinApi.getRequest() 18 | .subscribeOn(Schedulers.io()) 19 | 20 | // TODO: Perform only one request for both following subscribes - share() 21 | request.subscribe() 22 | request.subscribe() 23 | 24 | HttpModule.awaitNetworkRequests() 25 | 26 | // TODO: Make this subscribe receive cached value = cache() 27 | request.subscribe() 28 | 29 | // TODO: Now refresh the existing observable - use repeatWhen(subject) before calling cache() 30 | refreshSignal.onNext(Any()) 31 | } 32 | 33 | @Test 34 | fun repeat_pollingNetwork() { 35 | val request = httpBinApi.getRequest() 36 | val endOfPolling = System.currentTimeMillis() + 1000 37 | 38 | // TODO: Implement polling on the request for one second - repeat(), takeUntil() 39 | // TODO: Add 100ms delay between requests - repeatWhen(), delay() 40 | } 41 | 42 | @Test 43 | fun ambWith_effectiveCache() { 44 | val request = httpBinApi.getRequest() 45 | .subscribeOn(Schedulers.io()) 46 | .share() 47 | 48 | val requestWithCache = RequestInfoCache.requestInfo.mergeWith(request) 49 | 50 | // TODO: requestWithCache has a bug! In rare case when the network request happens to be faster than the cache, ... 51 | // TODO: ...the last emission will be cached value. Fix this with ambWith 52 | 53 | requestWithCache.subscribe { println(it) } 54 | } 55 | 56 | @After 57 | fun after() { 58 | Thread.sleep(1000) 59 | HttpModule.awaitNetworkRequests() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/http/HttpModule.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import okhttp3.Dispatcher 4 | import okhttp3.OkHttpClient 5 | import okhttp3.logging.HttpLoggingInterceptor 6 | import okhttp3.logging.HttpLoggingInterceptor.Level 7 | import retrofit2.Retrofit 8 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 9 | import retrofit2.converter.gson.GsonConverterFactory 10 | import java.text.SimpleDateFormat 11 | import java.util.* 12 | 13 | abstract class HttpModule private constructor() { 14 | 15 | init { 16 | throw AssertionError("No instances") 17 | } 18 | 19 | companion object { 20 | private val dispatcher = Dispatcher() 21 | 22 | fun mockedGitHubApi(): GitHubApi { 23 | return gitHubRetrofitBuilder() 24 | .client(okClientBuilder() 25 | .addInterceptor(MockResponsesInterceptor()) 26 | .build()) 27 | .build() 28 | .create(GitHubApi::class.java) 29 | } 30 | 31 | fun gitHubApi(): GitHubApi { 32 | return gitHubRetrofitBuilder() 33 | .client(okClientBuilder().build()) 34 | .build() 35 | .create(GitHubApi::class.java) 36 | } 37 | 38 | fun httpBinApi(): HttpBinApi { 39 | return httpBinRetrofit().create(HttpBinApi::class.java) 40 | } 41 | 42 | private fun gitHubRetrofitBuilder(): Retrofit.Builder { 43 | return Retrofit.Builder() 44 | .baseUrl("https://api.github.com") 45 | .validateEagerly(true) 46 | .addConverterFactory(GsonConverterFactory.create()) 47 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 48 | } 49 | 50 | private fun httpBinRetrofit(): Retrofit { 51 | return Retrofit.Builder() 52 | .baseUrl("http://httpbin.org") 53 | .validateEagerly(true) 54 | .client(okClientBuilder().build()) 55 | .addConverterFactory(GsonConverterFactory.create()) 56 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 57 | .build() 58 | } 59 | 60 | private fun okClientBuilder(): OkHttpClient.Builder { 61 | val timeFormat = SimpleDateFormat("HH:mm:ss.SSS") 62 | val loggingInterceptor = HttpLoggingInterceptor { message -> println(timeFormat.format(Date()) + ": " + message) } 63 | .setLevel(Level.BASIC) 64 | 65 | return OkHttpClient.Builder() 66 | .dispatcher(dispatcher) 67 | .addInterceptor(loggingInterceptor) 68 | } 69 | 70 | fun awaitNetworkRequests() { 71 | Thread.sleep(25) 72 | // There is a small delay during thread hopping in Rx 73 | while (dispatcher.runningCallsCount() > 0) { 74 | Thread.sleep(25) 75 | } 76 | 77 | Thread.sleep(25) 78 | // To allow consuming results, There is a small delay during thread hopping in Rx 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/http/MockResponsesInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import java.util.* 6 | 7 | /** 8 | * We have to do this for the code lab to not exceed rate limit from one ip 9 | */ 10 | internal class MockResponsesInterceptor : Interceptor { 11 | 12 | private val random = Random() 13 | private val mockedResponses = MockedResponses() 14 | 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | val request = chain.request() 17 | 18 | val response = mockedResponses.get(request) 19 | if (response != null) { 20 | sleepToSimulateLatency() 21 | return response 22 | } else { 23 | return chain.proceed(request) 24 | } 25 | } 26 | 27 | private fun sleepToSimulateLatency() { 28 | Thread.sleep((200 + random.nextInt(600)).toLong()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/http/MockedResponses.java: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.http; 2 | 3 | import okhttp3.HttpUrl; 4 | import okhttp3.Protocol; 5 | import okhttp3.Request; 6 | import okhttp3.Response; 7 | import okhttp3.internal.http.RealResponseBody; 8 | import okio.BufferedSource; 9 | import okio.Okio; 10 | import okio.Source; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | final class MockedResponses { 17 | private final Map responseUrlMap; 18 | 19 | MockedResponses() { 20 | responseUrlMap = initMap(); 21 | } 22 | 23 | Response get(Request request) { 24 | String json = responseUrlMap.get(request.url()); 25 | if (json == null) { 26 | return null; 27 | } 28 | 29 | RealResponseBody responseBody = realJsonBody(json); 30 | 31 | return new Response.Builder() 32 | .request(request) 33 | .code(200) 34 | .protocol(Protocol.HTTP_1_1) 35 | .message("OK Mock Response") 36 | .body(responseBody) 37 | .build(); 38 | } 39 | 40 | private RealResponseBody realJsonBody(String rawResponse) { 41 | Source source = Okio.source(new ByteArrayInputStream(rawResponse.getBytes())); 42 | BufferedSource buffer = Okio.buffer(source); 43 | return new RealResponseBody("application/json", 0, buffer); 44 | } 45 | 46 | private static Map initMap() { 47 | HashMap map = new HashMap<>(); 48 | map.put(HttpUrl.parse("https://api.github.com/users?since=0"), JsonResponses.FIRST_USERS); 49 | map.put(HttpUrl.parse("https://api.github.com/users/mojombo"), JsonResponses.USER_MOJOMBO); 50 | map.put(HttpUrl.parse("https://api.github.com/users/defunkt"), JsonResponses.USER_DEFUNKT); 51 | map.put(HttpUrl.parse("https://api.github.com/users/defunkt/repos"), JsonResponses.REPOS_DEFUNKT); 52 | 53 | return map; 54 | } 55 | 56 | static class JsonResponses { 57 | static final String REPOS_DEFUNKT = "[\n" + 58 | " {\n" + 59 | " \"id\": 1861402,\n" + 60 | " \"name\": \"ace\",\n" + 61 | " \"full_name\": \"defunkt/ace\",\n" + 62 | " \"owner\": {\n" + 63 | " \"login\": \"defunkt\",\n" + 64 | " \"id\": 2,\n" + 65 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 66 | " \"gravatar_id\": \"\",\n" + 67 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 68 | " \"html_url\": \"https://github.com/defunkt\",\n" + 69 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 70 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 71 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 72 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 73 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 74 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 75 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 76 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 77 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 78 | " \"type\": \"User\",\n" + 79 | " \"site_admin\": true\n" + 80 | " },\n" + 81 | " \"private\": false,\n" + 82 | " \"html_url\": \"https://github.com/defunkt/ace\",\n" + 83 | " \"description\": \"Ajax.org Cloud9 Editor\",\n" + 84 | " \"fork\": true,\n" + 85 | " \"url\": \"https://api.github.com/repos/defunkt/ace\",\n" + 86 | " \"forks_url\": \"https://api.github.com/repos/defunkt/ace/forks\",\n" + 87 | " \"keys_url\": \"https://api.github.com/repos/defunkt/ace/keys{/key_id}\",\n" + 88 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/ace/collaborators{/collaborator}\",\n" + 89 | " \"teams_url\": \"https://api.github.com/repos/defunkt/ace/teams\",\n" + 90 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/ace/hooks\",\n" + 91 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/ace/issues/events{/number}\",\n" + 92 | " \"events_url\": \"https://api.github.com/repos/defunkt/ace/events\",\n" + 93 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/ace/assignees{/user}\",\n" + 94 | " \"branches_url\": \"https://api.github.com/repos/defunkt/ace/branches{/branch}\",\n" + 95 | " \"tags_url\": \"https://api.github.com/repos/defunkt/ace/tags\",\n" + 96 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/ace/git/blobs{/sha}\",\n" + 97 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/ace/git/tags{/sha}\",\n" + 98 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/ace/git/refs{/sha}\",\n" + 99 | " \"trees_url\": \"https://api.github.com/repos/defunkt/ace/git/trees{/sha}\",\n" + 100 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/ace/statuses/{sha}\",\n" + 101 | " \"languages_url\": \"https://api.github.com/repos/defunkt/ace/languages\",\n" + 102 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/ace/stargazers\",\n" + 103 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/ace/contributors\",\n" + 104 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/ace/subscribers\",\n" + 105 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/ace/subscription\",\n" + 106 | " \"commits_url\": \"https://api.github.com/repos/defunkt/ace/commits{/sha}\",\n" + 107 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/ace/git/commits{/sha}\",\n" + 108 | " \"comments_url\": \"https://api.github.com/repos/defunkt/ace/comments{/number}\",\n" + 109 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/ace/issues/comments{/number}\",\n" + 110 | " \"contents_url\": \"https://api.github.com/repos/defunkt/ace/contents/{+path}\",\n" + 111 | " \"compare_url\": \"https://api.github.com/repos/defunkt/ace/compare/{base}...{head}\",\n" + 112 | " \"merges_url\": \"https://api.github.com/repos/defunkt/ace/merges\",\n" + 113 | " \"archive_url\": \"https://api.github.com/repos/defunkt/ace/{archive_format}{/ref}\",\n" + 114 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/ace/downloads\",\n" + 115 | " \"issues_url\": \"https://api.github.com/repos/defunkt/ace/issues{/number}\",\n" + 116 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/ace/pulls{/number}\",\n" + 117 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/ace/milestones{/number}\",\n" + 118 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/ace/notifications{?since,all,participating}\",\n" + 119 | " \"labels_url\": \"https://api.github.com/repos/defunkt/ace/labels{/name}\",\n" + 120 | " \"releases_url\": \"https://api.github.com/repos/defunkt/ace/releases{/id}\",\n" + 121 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/ace/deployments\",\n" + 122 | " \"created_at\": \"2011-06-07T18:41:40Z\",\n" + 123 | " \"updated_at\": \"2016-12-27T18:48:28Z\",\n" + 124 | " \"pushed_at\": \"2011-11-16T18:37:42Z\",\n" + 125 | " \"git_url\": \"git://github.com/defunkt/ace.git\",\n" + 126 | " \"ssh_url\": \"git@github.com:defunkt/ace.git\",\n" + 127 | " \"clone_url\": \"https://github.com/defunkt/ace.git\",\n" + 128 | " \"svn_url\": \"https://github.com/defunkt/ace\",\n" + 129 | " \"homepage\": \"http://ace.ajax.org\",\n" + 130 | " \"size\": 4405,\n" + 131 | " \"stargazers_count\": 11,\n" + 132 | " \"watchers_count\": 11,\n" + 133 | " \"language\": \"JavaScript\",\n" + 134 | " \"has_issues\": false,\n" + 135 | " \"has_projects\": true,\n" + 136 | " \"has_downloads\": true,\n" + 137 | " \"has_wiki\": true,\n" + 138 | " \"has_pages\": false,\n" + 139 | " \"forks_count\": 5,\n" + 140 | " \"mirror_url\": null,\n" + 141 | " \"open_issues_count\": 0,\n" + 142 | " \"forks\": 5,\n" + 143 | " \"open_issues\": 0,\n" + 144 | " \"watchers\": 11,\n" + 145 | " \"default_branch\": \"master\"\n" + 146 | " },\n" + 147 | " {\n" + 148 | " \"id\": 388149,\n" + 149 | " \"name\": \"colored\",\n" + 150 | " \"full_name\": \"defunkt/colored\",\n" + 151 | " \"owner\": {\n" + 152 | " \"login\": \"defunkt\",\n" + 153 | " \"id\": 2,\n" + 154 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 155 | " \"gravatar_id\": \"\",\n" + 156 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 157 | " \"html_url\": \"https://github.com/defunkt\",\n" + 158 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 159 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 160 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 161 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 162 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 163 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 164 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 165 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 166 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 167 | " \"type\": \"User\",\n" + 168 | " \"site_admin\": true\n" + 169 | " },\n" + 170 | " \"private\": false,\n" + 171 | " \"html_url\": \"https://github.com/defunkt/colored\",\n" + 172 | " \"description\": \"Colors in your terminal. Unmaintained.\",\n" + 173 | " \"fork\": false,\n" + 174 | " \"url\": \"https://api.github.com/repos/defunkt/colored\",\n" + 175 | " \"forks_url\": \"https://api.github.com/repos/defunkt/colored/forks\",\n" + 176 | " \"keys_url\": \"https://api.github.com/repos/defunkt/colored/keys{/key_id}\",\n" + 177 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/colored/collaborators{/collaborator}\",\n" + 178 | " \"teams_url\": \"https://api.github.com/repos/defunkt/colored/teams\",\n" + 179 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/colored/hooks\",\n" + 180 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/colored/issues/events{/number}\",\n" + 181 | " \"events_url\": \"https://api.github.com/repos/defunkt/colored/events\",\n" + 182 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/colored/assignees{/user}\",\n" + 183 | " \"branches_url\": \"https://api.github.com/repos/defunkt/colored/branches{/branch}\",\n" + 184 | " \"tags_url\": \"https://api.github.com/repos/defunkt/colored/tags\",\n" + 185 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/colored/git/blobs{/sha}\",\n" + 186 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/colored/git/tags{/sha}\",\n" + 187 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/colored/git/refs{/sha}\",\n" + 188 | " \"trees_url\": \"https://api.github.com/repos/defunkt/colored/git/trees{/sha}\",\n" + 189 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/colored/statuses/{sha}\",\n" + 190 | " \"languages_url\": \"https://api.github.com/repos/defunkt/colored/languages\",\n" + 191 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/colored/stargazers\",\n" + 192 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/colored/contributors\",\n" + 193 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/colored/subscribers\",\n" + 194 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/colored/subscription\",\n" + 195 | " \"commits_url\": \"https://api.github.com/repos/defunkt/colored/commits{/sha}\",\n" + 196 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/colored/git/commits{/sha}\",\n" + 197 | " \"comments_url\": \"https://api.github.com/repos/defunkt/colored/comments{/number}\",\n" + 198 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/colored/issues/comments{/number}\",\n" + 199 | " \"contents_url\": \"https://api.github.com/repos/defunkt/colored/contents/{+path}\",\n" + 200 | " \"compare_url\": \"https://api.github.com/repos/defunkt/colored/compare/{base}...{head}\",\n" + 201 | " \"merges_url\": \"https://api.github.com/repos/defunkt/colored/merges\",\n" + 202 | " \"archive_url\": \"https://api.github.com/repos/defunkt/colored/{archive_format}{/ref}\",\n" + 203 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/colored/downloads\",\n" + 204 | " \"issues_url\": \"https://api.github.com/repos/defunkt/colored/issues{/number}\",\n" + 205 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/colored/pulls{/number}\",\n" + 206 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/colored/milestones{/number}\",\n" + 207 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/colored/notifications{?since,all,participating}\",\n" + 208 | " \"labels_url\": \"https://api.github.com/repos/defunkt/colored/labels{/name}\",\n" + 209 | " \"releases_url\": \"https://api.github.com/repos/defunkt/colored/releases{/id}\",\n" + 210 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/colored/deployments\",\n" + 211 | " \"created_at\": \"2009-11-28T06:16:20Z\",\n" + 212 | " \"updated_at\": \"2017-04-12T15:33:09Z\",\n" + 213 | " \"pushed_at\": \"2017-02-14T12:49:33Z\",\n" + 214 | " \"git_url\": \"git://github.com/defunkt/colored.git\",\n" + 215 | " \"ssh_url\": \"git@github.com:defunkt/colored.git\",\n" + 216 | " \"clone_url\": \"https://github.com/defunkt/colored.git\",\n" + 217 | " \"svn_url\": \"https://github.com/defunkt/colored\",\n" + 218 | " \"homepage\": \"\",\n" + 219 | " \"size\": 120,\n" + 220 | " \"stargazers_count\": 255,\n" + 221 | " \"watchers_count\": 255,\n" + 222 | " \"language\": \"Ruby\",\n" + 223 | " \"has_issues\": false,\n" + 224 | " \"has_projects\": true,\n" + 225 | " \"has_downloads\": false,\n" + 226 | " \"has_wiki\": false,\n" + 227 | " \"has_pages\": false,\n" + 228 | " \"forks_count\": 41,\n" + 229 | " \"mirror_url\": null,\n" + 230 | " \"open_issues_count\": 8,\n" + 231 | " \"forks\": 41,\n" + 232 | " \"open_issues\": 8,\n" + 233 | " \"watchers\": 255,\n" + 234 | " \"default_branch\": \"master\"\n" + 235 | " },\n" + 236 | " {\n" + 237 | " \"id\": 12220,\n" + 238 | " \"name\": \"currency_converter\",\n" + 239 | " \"full_name\": \"defunkt/currency_converter\",\n" + 240 | " \"owner\": {\n" + 241 | " \"login\": \"defunkt\",\n" + 242 | " \"id\": 2,\n" + 243 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 244 | " \"gravatar_id\": \"\",\n" + 245 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 246 | " \"html_url\": \"https://github.com/defunkt\",\n" + 247 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 248 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 249 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 250 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 251 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 252 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 253 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 254 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 255 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 256 | " \"type\": \"User\",\n" + 257 | " \"site_admin\": true\n" + 258 | " },\n" + 259 | " \"private\": false,\n" + 260 | " \"html_url\": \"https://github.com/defunkt/currency_converter\",\n" + 261 | " \"description\": null,\n" + 262 | " \"fork\": false,\n" + 263 | " \"url\": \"https://api.github.com/repos/defunkt/currency_converter\",\n" + 264 | " \"forks_url\": \"https://api.github.com/repos/defunkt/currency_converter/forks\",\n" + 265 | " \"keys_url\": \"https://api.github.com/repos/defunkt/currency_converter/keys{/key_id}\",\n" + 266 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/currency_converter/collaborators{/collaborator}\",\n" + 267 | " \"teams_url\": \"https://api.github.com/repos/defunkt/currency_converter/teams\",\n" + 268 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/currency_converter/hooks\",\n" + 269 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/currency_converter/issues/events{/number}\",\n" + 270 | " \"events_url\": \"https://api.github.com/repos/defunkt/currency_converter/events\",\n" + 271 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/currency_converter/assignees{/user}\",\n" + 272 | " \"branches_url\": \"https://api.github.com/repos/defunkt/currency_converter/branches{/branch}\",\n" + 273 | " \"tags_url\": \"https://api.github.com/repos/defunkt/currency_converter/tags\",\n" + 274 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/currency_converter/git/blobs{/sha}\",\n" + 275 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/currency_converter/git/tags{/sha}\",\n" + 276 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/currency_converter/git/refs{/sha}\",\n" + 277 | " \"trees_url\": \"https://api.github.com/repos/defunkt/currency_converter/git/trees{/sha}\",\n" + 278 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/currency_converter/statuses/{sha}\",\n" + 279 | " \"languages_url\": \"https://api.github.com/repos/defunkt/currency_converter/languages\",\n" + 280 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/currency_converter/stargazers\",\n" + 281 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/currency_converter/contributors\",\n" + 282 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/currency_converter/subscribers\",\n" + 283 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/currency_converter/subscription\",\n" + 284 | " \"commits_url\": \"https://api.github.com/repos/defunkt/currency_converter/commits{/sha}\",\n" + 285 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/currency_converter/git/commits{/sha}\",\n" + 286 | " \"comments_url\": \"https://api.github.com/repos/defunkt/currency_converter/comments{/number}\",\n" + 287 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/currency_converter/issues/comments{/number}\",\n" + 288 | " \"contents_url\": \"https://api.github.com/repos/defunkt/currency_converter/contents/{+path}\",\n" + 289 | " \"compare_url\": \"https://api.github.com/repos/defunkt/currency_converter/compare/{base}...{head}\",\n" + 290 | " \"merges_url\": \"https://api.github.com/repos/defunkt/currency_converter/merges\",\n" + 291 | " \"archive_url\": \"https://api.github.com/repos/defunkt/currency_converter/{archive_format}{/ref}\",\n" + 292 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/currency_converter/downloads\",\n" + 293 | " \"issues_url\": \"https://api.github.com/repos/defunkt/currency_converter/issues{/number}\",\n" + 294 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/currency_converter/pulls{/number}\",\n" + 295 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/currency_converter/milestones{/number}\",\n" + 296 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/currency_converter/notifications{?since,all,participating}\",\n" + 297 | " \"labels_url\": \"https://api.github.com/repos/defunkt/currency_converter/labels{/name}\",\n" + 298 | " \"releases_url\": \"https://api.github.com/repos/defunkt/currency_converter/releases{/id}\",\n" + 299 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/currency_converter/deployments\",\n" + 300 | " \"created_at\": \"2008-04-24T09:34:31Z\",\n" + 301 | " \"updated_at\": \"2016-08-05T16:22:04Z\",\n" + 302 | " \"pushed_at\": \"2008-04-24T09:36:14Z\",\n" + 303 | " \"git_url\": \"git://github.com/defunkt/currency_converter.git\",\n" + 304 | " \"ssh_url\": \"git@github.com:defunkt/currency_converter.git\",\n" + 305 | " \"clone_url\": \"https://github.com/defunkt/currency_converter.git\",\n" + 306 | " \"svn_url\": \"https://github.com/defunkt/currency_converter\",\n" + 307 | " \"homepage\": \"\",\n" + 308 | " \"size\": 374,\n" + 309 | " \"stargazers_count\": 8,\n" + 310 | " \"watchers_count\": 8,\n" + 311 | " \"language\": \"Objective-C\",\n" + 312 | " \"has_issues\": true,\n" + 313 | " \"has_projects\": true,\n" + 314 | " \"has_downloads\": true,\n" + 315 | " \"has_wiki\": true,\n" + 316 | " \"has_pages\": false,\n" + 317 | " \"forks_count\": 3,\n" + 318 | " \"mirror_url\": null,\n" + 319 | " \"open_issues_count\": 0,\n" + 320 | " \"forks\": 3,\n" + 321 | " \"open_issues\": 0,\n" + 322 | " \"watchers\": 8,\n" + 323 | " \"default_branch\": \"master\"\n" + 324 | " },\n" + 325 | " {\n" + 326 | " \"id\": 18570642,\n" + 327 | " \"name\": \"d3\",\n" + 328 | " \"full_name\": \"defunkt/d3\",\n" + 329 | " \"owner\": {\n" + 330 | " \"login\": \"defunkt\",\n" + 331 | " \"id\": 2,\n" + 332 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 333 | " \"gravatar_id\": \"\",\n" + 334 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 335 | " \"html_url\": \"https://github.com/defunkt\",\n" + 336 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 337 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 338 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 339 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 340 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 341 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 342 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 343 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 344 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 345 | " \"type\": \"User\",\n" + 346 | " \"site_admin\": true\n" + 347 | " },\n" + 348 | " \"private\": false,\n" + 349 | " \"html_url\": \"https://github.com/defunkt/d3\",\n" + 350 | " \"description\": \"A JavaScript visualization library for HTML and SVG.\",\n" + 351 | " \"fork\": true,\n" + 352 | " \"url\": \"https://api.github.com/repos/defunkt/d3\",\n" + 353 | " \"forks_url\": \"https://api.github.com/repos/defunkt/d3/forks\",\n" + 354 | " \"keys_url\": \"https://api.github.com/repos/defunkt/d3/keys{/key_id}\",\n" + 355 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/d3/collaborators{/collaborator}\",\n" + 356 | " \"teams_url\": \"https://api.github.com/repos/defunkt/d3/teams\",\n" + 357 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/d3/hooks\",\n" + 358 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/d3/issues/events{/number}\",\n" + 359 | " \"events_url\": \"https://api.github.com/repos/defunkt/d3/events\",\n" + 360 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/d3/assignees{/user}\",\n" + 361 | " \"branches_url\": \"https://api.github.com/repos/defunkt/d3/branches{/branch}\",\n" + 362 | " \"tags_url\": \"https://api.github.com/repos/defunkt/d3/tags\",\n" + 363 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/d3/git/blobs{/sha}\",\n" + 364 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/d3/git/tags{/sha}\",\n" + 365 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/d3/git/refs{/sha}\",\n" + 366 | " \"trees_url\": \"https://api.github.com/repos/defunkt/d3/git/trees{/sha}\",\n" + 367 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/d3/statuses/{sha}\",\n" + 368 | " \"languages_url\": \"https://api.github.com/repos/defunkt/d3/languages\",\n" + 369 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/d3/stargazers\",\n" + 370 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/d3/contributors\",\n" + 371 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/d3/subscribers\",\n" + 372 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/d3/subscription\",\n" + 373 | " \"commits_url\": \"https://api.github.com/repos/defunkt/d3/commits{/sha}\",\n" + 374 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/d3/git/commits{/sha}\",\n" + 375 | " \"comments_url\": \"https://api.github.com/repos/defunkt/d3/comments{/number}\",\n" + 376 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/d3/issues/comments{/number}\",\n" + 377 | " \"contents_url\": \"https://api.github.com/repos/defunkt/d3/contents/{+path}\",\n" + 378 | " \"compare_url\": \"https://api.github.com/repos/defunkt/d3/compare/{base}...{head}\",\n" + 379 | " \"merges_url\": \"https://api.github.com/repos/defunkt/d3/merges\",\n" + 380 | " \"archive_url\": \"https://api.github.com/repos/defunkt/d3/{archive_format}{/ref}\",\n" + 381 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/d3/downloads\",\n" + 382 | " \"issues_url\": \"https://api.github.com/repos/defunkt/d3/issues{/number}\",\n" + 383 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/d3/pulls{/number}\",\n" + 384 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/d3/milestones{/number}\",\n" + 385 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/d3/notifications{?since,all,participating}\",\n" + 386 | " \"labels_url\": \"https://api.github.com/repos/defunkt/d3/labels{/name}\",\n" + 387 | " \"releases_url\": \"https://api.github.com/repos/defunkt/d3/releases{/id}\",\n" + 388 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/d3/deployments\",\n" + 389 | " \"created_at\": \"2014-04-08T18:45:26Z\",\n" + 390 | " \"updated_at\": \"2016-05-13T16:00:52Z\",\n" + 391 | " \"pushed_at\": \"2014-04-08T18:46:26Z\",\n" + 392 | " \"git_url\": \"git://github.com/defunkt/d3.git\",\n" + 393 | " \"ssh_url\": \"git@github.com:defunkt/d3.git\",\n" + 394 | " \"clone_url\": \"https://github.com/defunkt/d3.git\",\n" + 395 | " \"svn_url\": \"https://github.com/defunkt/d3\",\n" + 396 | " \"homepage\": \"http://d3js.org\",\n" + 397 | " \"size\": 34521,\n" + 398 | " \"stargazers_count\": 2,\n" + 399 | " \"watchers_count\": 2,\n" + 400 | " \"language\": \"JavaScript\",\n" + 401 | " \"has_issues\": false,\n" + 402 | " \"has_projects\": true,\n" + 403 | " \"has_downloads\": true,\n" + 404 | " \"has_wiki\": true,\n" + 405 | " \"has_pages\": false,\n" + 406 | " \"forks_count\": 1,\n" + 407 | " \"mirror_url\": null,\n" + 408 | " \"open_issues_count\": 0,\n" + 409 | " \"forks\": 1,\n" + 410 | " \"open_issues\": 0,\n" + 411 | " \"watchers\": 2,\n" + 412 | " \"default_branch\": \"master\"\n" + 413 | " },\n" + 414 | " {\n" + 415 | " \"id\": 91988,\n" + 416 | " \"name\": \"defunkt.github.com\",\n" + 417 | " \"full_name\": \"defunkt/defunkt.github.com\",\n" + 418 | " \"owner\": {\n" + 419 | " \"login\": \"defunkt\",\n" + 420 | " \"id\": 2,\n" + 421 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 422 | " \"gravatar_id\": \"\",\n" + 423 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 424 | " \"html_url\": \"https://github.com/defunkt\",\n" + 425 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 426 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 427 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 428 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 429 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 430 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 431 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 432 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 433 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 434 | " \"type\": \"User\",\n" + 435 | " \"site_admin\": true\n" + 436 | " },\n" + 437 | " \"private\": false,\n" + 438 | " \"html_url\": \"https://github.com/defunkt/defunkt.github.com\",\n" + 439 | " \"description\": \"My GitHub Page\",\n" + 440 | " \"fork\": false,\n" + 441 | " \"url\": \"https://api.github.com/repos/defunkt/defunkt.github.com\",\n" + 442 | " \"forks_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/forks\",\n" + 443 | " \"keys_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/keys{/key_id}\",\n" + 444 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/collaborators{/collaborator}\",\n" + 445 | " \"teams_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/teams\",\n" + 446 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/hooks\",\n" + 447 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/issues/events{/number}\",\n" + 448 | " \"events_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/events\",\n" + 449 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/assignees{/user}\",\n" + 450 | " \"branches_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/branches{/branch}\",\n" + 451 | " \"tags_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/tags\",\n" + 452 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/git/blobs{/sha}\",\n" + 453 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/git/tags{/sha}\",\n" + 454 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/git/refs{/sha}\",\n" + 455 | " \"trees_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/git/trees{/sha}\",\n" + 456 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/statuses/{sha}\",\n" + 457 | " \"languages_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/languages\",\n" + 458 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/stargazers\",\n" + 459 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/contributors\",\n" + 460 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/subscribers\",\n" + 461 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/subscription\",\n" + 462 | " \"commits_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/commits{/sha}\",\n" + 463 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/git/commits{/sha}\",\n" + 464 | " \"comments_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/comments{/number}\",\n" + 465 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/issues/comments{/number}\",\n" + 466 | " \"contents_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/contents/{+path}\",\n" + 467 | " \"compare_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/compare/{base}...{head}\",\n" + 468 | " \"merges_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/merges\",\n" + 469 | " \"archive_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/{archive_format}{/ref}\",\n" + 470 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/downloads\",\n" + 471 | " \"issues_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/issues{/number}\",\n" + 472 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/pulls{/number}\",\n" + 473 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/milestones{/number}\",\n" + 474 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/notifications{?since,all,participating}\",\n" + 475 | " \"labels_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/labels{/name}\",\n" + 476 | " \"releases_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/releases{/id}\",\n" + 477 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/defunkt.github.com/deployments\",\n" + 478 | " \"created_at\": \"2008-12-17T07:53:14Z\",\n" + 479 | " \"updated_at\": \"2016-12-28T22:00:47Z\",\n" + 480 | " \"pushed_at\": \"2014-08-05T00:38:47Z\",\n" + 481 | " \"git_url\": \"git://github.com/defunkt/defunkt.github.com.git\",\n" + 482 | " \"ssh_url\": \"git@github.com:defunkt/defunkt.github.com.git\",\n" + 483 | " \"clone_url\": \"https://github.com/defunkt/defunkt.github.com.git\",\n" + 484 | " \"svn_url\": \"https://github.com/defunkt/defunkt.github.com\",\n" + 485 | " \"homepage\": \"http://defunkt.io\",\n" + 486 | " \"size\": 3011,\n" + 487 | " \"stargazers_count\": 68,\n" + 488 | " \"watchers_count\": 68,\n" + 489 | " \"language\": null,\n" + 490 | " \"has_issues\": false,\n" + 491 | " \"has_projects\": true,\n" + 492 | " \"has_downloads\": false,\n" + 493 | " \"has_wiki\": false,\n" + 494 | " \"has_pages\": true,\n" + 495 | " \"forks_count\": 51,\n" + 496 | " \"mirror_url\": null,\n" + 497 | " \"open_issues_count\": 3,\n" + 498 | " \"forks\": 51,\n" + 499 | " \"open_issues\": 3,\n" + 500 | " \"watchers\": 68,\n" + 501 | " \"default_branch\": \"master\"\n" + 502 | " },\n" + 503 | " {\n" + 504 | " \"id\": 628288,\n" + 505 | " \"name\": \"djangode\",\n" + 506 | " \"full_name\": \"defunkt/djangode\",\n" + 507 | " \"owner\": {\n" + 508 | " \"login\": \"defunkt\",\n" + 509 | " \"id\": 2,\n" + 510 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 511 | " \"gravatar_id\": \"\",\n" + 512 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 513 | " \"html_url\": \"https://github.com/defunkt\",\n" + 514 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 515 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 516 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 517 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 518 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 519 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 520 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 521 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 522 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 523 | " \"type\": \"User\",\n" + 524 | " \"site_admin\": true\n" + 525 | " },\n" + 526 | " \"private\": false,\n" + 527 | " \"html_url\": \"https://github.com/defunkt/djangode\",\n" + 528 | " \"description\": \"Utilities functions for node.js that borrow some useful concepts from Django\",\n" + 529 | " \"fork\": true,\n" + 530 | " \"url\": \"https://api.github.com/repos/defunkt/djangode\",\n" + 531 | " \"forks_url\": \"https://api.github.com/repos/defunkt/djangode/forks\",\n" + 532 | " \"keys_url\": \"https://api.github.com/repos/defunkt/djangode/keys{/key_id}\",\n" + 533 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/djangode/collaborators{/collaborator}\",\n" + 534 | " \"teams_url\": \"https://api.github.com/repos/defunkt/djangode/teams\",\n" + 535 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/djangode/hooks\",\n" + 536 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/djangode/issues/events{/number}\",\n" + 537 | " \"events_url\": \"https://api.github.com/repos/defunkt/djangode/events\",\n" + 538 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/djangode/assignees{/user}\",\n" + 539 | " \"branches_url\": \"https://api.github.com/repos/defunkt/djangode/branches{/branch}\",\n" + 540 | " \"tags_url\": \"https://api.github.com/repos/defunkt/djangode/tags\",\n" + 541 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/djangode/git/blobs{/sha}\",\n" + 542 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/djangode/git/tags{/sha}\",\n" + 543 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/djangode/git/refs{/sha}\",\n" + 544 | " \"trees_url\": \"https://api.github.com/repos/defunkt/djangode/git/trees{/sha}\",\n" + 545 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/djangode/statuses/{sha}\",\n" + 546 | " \"languages_url\": \"https://api.github.com/repos/defunkt/djangode/languages\",\n" + 547 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/djangode/stargazers\",\n" + 548 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/djangode/contributors\",\n" + 549 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/djangode/subscribers\",\n" + 550 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/djangode/subscription\",\n" + 551 | " \"commits_url\": \"https://api.github.com/repos/defunkt/djangode/commits{/sha}\",\n" + 552 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/djangode/git/commits{/sha}\",\n" + 553 | " \"comments_url\": \"https://api.github.com/repos/defunkt/djangode/comments{/number}\",\n" + 554 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/djangode/issues/comments{/number}\",\n" + 555 | " \"contents_url\": \"https://api.github.com/repos/defunkt/djangode/contents/{+path}\",\n" + 556 | " \"compare_url\": \"https://api.github.com/repos/defunkt/djangode/compare/{base}...{head}\",\n" + 557 | " \"merges_url\": \"https://api.github.com/repos/defunkt/djangode/merges\",\n" + 558 | " \"archive_url\": \"https://api.github.com/repos/defunkt/djangode/{archive_format}{/ref}\",\n" + 559 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/djangode/downloads\",\n" + 560 | " \"issues_url\": \"https://api.github.com/repos/defunkt/djangode/issues{/number}\",\n" + 561 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/djangode/pulls{/number}\",\n" + 562 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/djangode/milestones{/number}\",\n" + 563 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/djangode/notifications{?since,all,participating}\",\n" + 564 | " \"labels_url\": \"https://api.github.com/repos/defunkt/djangode/labels{/name}\",\n" + 565 | " \"releases_url\": \"https://api.github.com/repos/defunkt/djangode/releases{/id}\",\n" + 566 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/djangode/deployments\",\n" + 567 | " \"created_at\": \"2010-04-25T16:41:30Z\",\n" + 568 | " \"updated_at\": \"2016-09-22T18:47:25Z\",\n" + 569 | " \"pushed_at\": \"2010-04-25T16:42:56Z\",\n" + 570 | " \"git_url\": \"git://github.com/defunkt/djangode.git\",\n" + 571 | " \"ssh_url\": \"git@github.com:defunkt/djangode.git\",\n" + 572 | " \"clone_url\": \"https://github.com/defunkt/djangode.git\",\n" + 573 | " \"svn_url\": \"https://github.com/defunkt/djangode\",\n" + 574 | " \"homepage\": \"\",\n" + 575 | " \"size\": 191,\n" + 576 | " \"stargazers_count\": 5,\n" + 577 | " \"watchers_count\": 5,\n" + 578 | " \"language\": \"JavaScript\",\n" + 579 | " \"has_issues\": false,\n" + 580 | " \"has_projects\": true,\n" + 581 | " \"has_downloads\": true,\n" + 582 | " \"has_wiki\": true,\n" + 583 | " \"has_pages\": false,\n" + 584 | " \"forks_count\": 3,\n" + 585 | " \"mirror_url\": null,\n" + 586 | " \"open_issues_count\": 0,\n" + 587 | " \"forks\": 3,\n" + 588 | " \"open_issues\": 0,\n" + 589 | " \"watchers\": 5,\n" + 590 | " \"default_branch\": \"master\"\n" + 591 | " },\n" + 592 | " {\n" + 593 | " \"id\": 2448060,\n" + 594 | " \"name\": \"dodgeball.github.com\",\n" + 595 | " \"full_name\": \"defunkt/dodgeball.github.com\",\n" + 596 | " \"owner\": {\n" + 597 | " \"login\": \"defunkt\",\n" + 598 | " \"id\": 2,\n" + 599 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 600 | " \"gravatar_id\": \"\",\n" + 601 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 602 | " \"html_url\": \"https://github.com/defunkt\",\n" + 603 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 604 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 605 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 606 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 607 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 608 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 609 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 610 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 611 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 612 | " \"type\": \"User\",\n" + 613 | " \"site_admin\": true\n" + 614 | " },\n" + 615 | " \"private\": false,\n" + 616 | " \"html_url\": \"https://github.com/defunkt/dodgeball.github.com\",\n" + 617 | " \"description\": \"yes\",\n" + 618 | " \"fork\": false,\n" + 619 | " \"url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com\",\n" + 620 | " \"forks_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/forks\",\n" + 621 | " \"keys_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/keys{/key_id}\",\n" + 622 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/collaborators{/collaborator}\",\n" + 623 | " \"teams_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/teams\",\n" + 624 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/hooks\",\n" + 625 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/issues/events{/number}\",\n" + 626 | " \"events_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/events\",\n" + 627 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/assignees{/user}\",\n" + 628 | " \"branches_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/branches{/branch}\",\n" + 629 | " \"tags_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/tags\",\n" + 630 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/git/blobs{/sha}\",\n" + 631 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/git/tags{/sha}\",\n" + 632 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/git/refs{/sha}\",\n" + 633 | " \"trees_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/git/trees{/sha}\",\n" + 634 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/statuses/{sha}\",\n" + 635 | " \"languages_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/languages\",\n" + 636 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/stargazers\",\n" + 637 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/contributors\",\n" + 638 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/subscribers\",\n" + 639 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/subscription\",\n" + 640 | " \"commits_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/commits{/sha}\",\n" + 641 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/git/commits{/sha}\",\n" + 642 | " \"comments_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/comments{/number}\",\n" + 643 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/issues/comments{/number}\",\n" + 644 | " \"contents_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/contents/{+path}\",\n" + 645 | " \"compare_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/compare/{base}...{head}\",\n" + 646 | " \"merges_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/merges\",\n" + 647 | " \"archive_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/{archive_format}{/ref}\",\n" + 648 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/downloads\",\n" + 649 | " \"issues_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/issues{/number}\",\n" + 650 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/pulls{/number}\",\n" + 651 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/milestones{/number}\",\n" + 652 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/notifications{?since,all,participating}\",\n" + 653 | " \"labels_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/labels{/name}\",\n" + 654 | " \"releases_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/releases{/id}\",\n" + 655 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/dodgeball.github.com/deployments\",\n" + 656 | " \"created_at\": \"2011-09-24T03:01:09Z\",\n" + 657 | " \"updated_at\": \"2016-09-22T18:48:29Z\",\n" + 658 | " \"pushed_at\": \"2011-09-24T03:01:22Z\",\n" + 659 | " \"git_url\": \"git://github.com/defunkt/dodgeball.github.com.git\",\n" + 660 | " \"ssh_url\": \"git@github.com:defunkt/dodgeball.github.com.git\",\n" + 661 | " \"clone_url\": \"https://github.com/defunkt/dodgeball.github.com.git\",\n" + 662 | " \"svn_url\": \"https://github.com/defunkt/dodgeball.github.com\",\n" + 663 | " \"homepage\": \"\",\n" + 664 | " \"size\": 534,\n" + 665 | " \"stargazers_count\": 6,\n" + 666 | " \"watchers_count\": 6,\n" + 667 | " \"language\": \"Ruby\",\n" + 668 | " \"has_issues\": false,\n" + 669 | " \"has_projects\": true,\n" + 670 | " \"has_downloads\": true,\n" + 671 | " \"has_wiki\": true,\n" + 672 | " \"has_pages\": false,\n" + 673 | " \"forks_count\": 5,\n" + 674 | " \"mirror_url\": null,\n" + 675 | " \"open_issues_count\": 0,\n" + 676 | " \"forks\": 5,\n" + 677 | " \"open_issues\": 0,\n" + 678 | " \"watchers\": 6,\n" + 679 | " \"default_branch\": \"master\"\n" + 680 | " },\n" + 681 | " {\n" + 682 | " \"id\": 5171653,\n" + 683 | " \"name\": \"dotenv\",\n" + 684 | " \"full_name\": \"defunkt/dotenv\",\n" + 685 | " \"owner\": {\n" + 686 | " \"login\": \"defunkt\",\n" + 687 | " \"id\": 2,\n" + 688 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 689 | " \"gravatar_id\": \"\",\n" + 690 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 691 | " \"html_url\": \"https://github.com/defunkt\",\n" + 692 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 693 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 694 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 695 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 696 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 697 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 698 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 699 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 700 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 701 | " \"type\": \"User\",\n" + 702 | " \"site_admin\": true\n" + 703 | " },\n" + 704 | " \"private\": false,\n" + 705 | " \"html_url\": \"https://github.com/defunkt/dotenv\",\n" + 706 | " \"description\": \"Loads environment variables from `.env`. \",\n" + 707 | " \"fork\": true,\n" + 708 | " \"url\": \"https://api.github.com/repos/defunkt/dotenv\",\n" + 709 | " \"forks_url\": \"https://api.github.com/repos/defunkt/dotenv/forks\",\n" + 710 | " \"keys_url\": \"https://api.github.com/repos/defunkt/dotenv/keys{/key_id}\",\n" + 711 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/dotenv/collaborators{/collaborator}\",\n" + 712 | " \"teams_url\": \"https://api.github.com/repos/defunkt/dotenv/teams\",\n" + 713 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/dotenv/hooks\",\n" + 714 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/dotenv/issues/events{/number}\",\n" + 715 | " \"events_url\": \"https://api.github.com/repos/defunkt/dotenv/events\",\n" + 716 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/dotenv/assignees{/user}\",\n" + 717 | " \"branches_url\": \"https://api.github.com/repos/defunkt/dotenv/branches{/branch}\",\n" + 718 | " \"tags_url\": \"https://api.github.com/repos/defunkt/dotenv/tags\",\n" + 719 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/dotenv/git/blobs{/sha}\",\n" + 720 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/dotenv/git/tags{/sha}\",\n" + 721 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/dotenv/git/refs{/sha}\",\n" + 722 | " \"trees_url\": \"https://api.github.com/repos/defunkt/dotenv/git/trees{/sha}\",\n" + 723 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/dotenv/statuses/{sha}\",\n" + 724 | " \"languages_url\": \"https://api.github.com/repos/defunkt/dotenv/languages\",\n" + 725 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/dotenv/stargazers\",\n" + 726 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/dotenv/contributors\",\n" + 727 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/dotenv/subscribers\",\n" + 728 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/dotenv/subscription\",\n" + 729 | " \"commits_url\": \"https://api.github.com/repos/defunkt/dotenv/commits{/sha}\",\n" + 730 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/dotenv/git/commits{/sha}\",\n" + 731 | " \"comments_url\": \"https://api.github.com/repos/defunkt/dotenv/comments{/number}\",\n" + 732 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/dotenv/issues/comments{/number}\",\n" + 733 | " \"contents_url\": \"https://api.github.com/repos/defunkt/dotenv/contents/{+path}\",\n" + 734 | " \"compare_url\": \"https://api.github.com/repos/defunkt/dotenv/compare/{base}...{head}\",\n" + 735 | " \"merges_url\": \"https://api.github.com/repos/defunkt/dotenv/merges\",\n" + 736 | " \"archive_url\": \"https://api.github.com/repos/defunkt/dotenv/{archive_format}{/ref}\",\n" + 737 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/dotenv/downloads\",\n" + 738 | " \"issues_url\": \"https://api.github.com/repos/defunkt/dotenv/issues{/number}\",\n" + 739 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/dotenv/pulls{/number}\",\n" + 740 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/dotenv/milestones{/number}\",\n" + 741 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/dotenv/notifications{?since,all,participating}\",\n" + 742 | " \"labels_url\": \"https://api.github.com/repos/defunkt/dotenv/labels{/name}\",\n" + 743 | " \"releases_url\": \"https://api.github.com/repos/defunkt/dotenv/releases{/id}\",\n" + 744 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/dotenv/deployments\",\n" + 745 | " \"created_at\": \"2012-07-24T21:43:19Z\",\n" + 746 | " \"updated_at\": \"2016-10-23T19:22:03Z\",\n" + 747 | " \"pushed_at\": \"2012-07-24T04:30:34Z\",\n" + 748 | " \"git_url\": \"git://github.com/defunkt/dotenv.git\",\n" + 749 | " \"ssh_url\": \"git@github.com:defunkt/dotenv.git\",\n" + 750 | " \"clone_url\": \"https://github.com/defunkt/dotenv.git\",\n" + 751 | " \"svn_url\": \"https://github.com/defunkt/dotenv\",\n" + 752 | " \"homepage\": null,\n" + 753 | " \"size\": 75,\n" + 754 | " \"stargazers_count\": 4,\n" + 755 | " \"watchers_count\": 4,\n" + 756 | " \"language\": \"Ruby\",\n" + 757 | " \"has_issues\": false,\n" + 758 | " \"has_projects\": true,\n" + 759 | " \"has_downloads\": true,\n" + 760 | " \"has_wiki\": true,\n" + 761 | " \"has_pages\": false,\n" + 762 | " \"forks_count\": 3,\n" + 763 | " \"mirror_url\": null,\n" + 764 | " \"open_issues_count\": 0,\n" + 765 | " \"forks\": 3,\n" + 766 | " \"open_issues\": 0,\n" + 767 | " \"watchers\": 4,\n" + 768 | " \"default_branch\": \"master\"\n" + 769 | " },\n" + 770 | " {\n" + 771 | " \"id\": 1336779,\n" + 772 | " \"name\": \"dotjs\",\n" + 773 | " \"full_name\": \"defunkt/dotjs\",\n" + 774 | " \"owner\": {\n" + 775 | " \"login\": \"defunkt\",\n" + 776 | " \"id\": 2,\n" + 777 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 778 | " \"gravatar_id\": \"\",\n" + 779 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 780 | " \"html_url\": \"https://github.com/defunkt\",\n" + 781 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 782 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 783 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 784 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 785 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 786 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 787 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 788 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 789 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 790 | " \"type\": \"User\",\n" + 791 | " \"site_admin\": true\n" + 792 | " },\n" + 793 | " \"private\": false,\n" + 794 | " \"html_url\": \"https://github.com/defunkt/dotjs\",\n" + 795 | " \"description\": \"~/.js — No longer maintained, sorry.\",\n" + 796 | " \"fork\": false,\n" + 797 | " \"url\": \"https://api.github.com/repos/defunkt/dotjs\",\n" + 798 | " \"forks_url\": \"https://api.github.com/repos/defunkt/dotjs/forks\",\n" + 799 | " \"keys_url\": \"https://api.github.com/repos/defunkt/dotjs/keys{/key_id}\",\n" + 800 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/dotjs/collaborators{/collaborator}\",\n" + 801 | " \"teams_url\": \"https://api.github.com/repos/defunkt/dotjs/teams\",\n" + 802 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/dotjs/hooks\",\n" + 803 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/dotjs/issues/events{/number}\",\n" + 804 | " \"events_url\": \"https://api.github.com/repos/defunkt/dotjs/events\",\n" + 805 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/dotjs/assignees{/user}\",\n" + 806 | " \"branches_url\": \"https://api.github.com/repos/defunkt/dotjs/branches{/branch}\",\n" + 807 | " \"tags_url\": \"https://api.github.com/repos/defunkt/dotjs/tags\",\n" + 808 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/dotjs/git/blobs{/sha}\",\n" + 809 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/dotjs/git/tags{/sha}\",\n" + 810 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/dotjs/git/refs{/sha}\",\n" + 811 | " \"trees_url\": \"https://api.github.com/repos/defunkt/dotjs/git/trees{/sha}\",\n" + 812 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/dotjs/statuses/{sha}\",\n" + 813 | " \"languages_url\": \"https://api.github.com/repos/defunkt/dotjs/languages\",\n" + 814 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/dotjs/stargazers\",\n" + 815 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/dotjs/contributors\",\n" + 816 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/dotjs/subscribers\",\n" + 817 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/dotjs/subscription\",\n" + 818 | " \"commits_url\": \"https://api.github.com/repos/defunkt/dotjs/commits{/sha}\",\n" + 819 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/dotjs/git/commits{/sha}\",\n" + 820 | " \"comments_url\": \"https://api.github.com/repos/defunkt/dotjs/comments{/number}\",\n" + 821 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/dotjs/issues/comments{/number}\",\n" + 822 | " \"contents_url\": \"https://api.github.com/repos/defunkt/dotjs/contents/{+path}\",\n" + 823 | " \"compare_url\": \"https://api.github.com/repos/defunkt/dotjs/compare/{base}...{head}\",\n" + 824 | " \"merges_url\": \"https://api.github.com/repos/defunkt/dotjs/merges\",\n" + 825 | " \"archive_url\": \"https://api.github.com/repos/defunkt/dotjs/{archive_format}{/ref}\",\n" + 826 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/dotjs/downloads\",\n" + 827 | " \"issues_url\": \"https://api.github.com/repos/defunkt/dotjs/issues{/number}\",\n" + 828 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/dotjs/pulls{/number}\",\n" + 829 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/dotjs/milestones{/number}\",\n" + 830 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/dotjs/notifications{?since,all,participating}\",\n" + 831 | " \"labels_url\": \"https://api.github.com/repos/defunkt/dotjs/labels{/name}\",\n" + 832 | " \"releases_url\": \"https://api.github.com/repos/defunkt/dotjs/releases{/id}\",\n" + 833 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/dotjs/deployments\",\n" + 834 | " \"created_at\": \"2011-02-07T07:01:33Z\",\n" + 835 | " \"updated_at\": \"2017-05-07T17:33:12Z\",\n" + 836 | " \"pushed_at\": \"2016-02-20T23:21:29Z\",\n" + 837 | " \"git_url\": \"git://github.com/defunkt/dotjs.git\",\n" + 838 | " \"ssh_url\": \"git@github.com:defunkt/dotjs.git\",\n" + 839 | " \"clone_url\": \"https://github.com/defunkt/dotjs.git\",\n" + 840 | " \"svn_url\": \"https://github.com/defunkt/dotjs\",\n" + 841 | " \"homepage\": \"http://bit.ly/dotjs\",\n" + 842 | " \"size\": 1316,\n" + 843 | " \"stargazers_count\": 3158,\n" + 844 | " \"watchers_count\": 3158,\n" + 845 | " \"language\": \"Ruby\",\n" + 846 | " \"has_issues\": true,\n" + 847 | " \"has_projects\": true,\n" + 848 | " \"has_downloads\": true,\n" + 849 | " \"has_wiki\": false,\n" + 850 | " \"has_pages\": true,\n" + 851 | " \"forks_count\": 370,\n" + 852 | " \"mirror_url\": null,\n" + 853 | " \"open_issues_count\": 21,\n" + 854 | " \"forks\": 370,\n" + 855 | " \"open_issues\": 21,\n" + 856 | " \"watchers\": 3158,\n" + 857 | " \"default_branch\": \"master\"\n" + 858 | " },\n" + 859 | " {\n" + 860 | " \"id\": 69384,\n" + 861 | " \"name\": \"electron-wordwrap\",\n" + 862 | " \"full_name\": \"defunkt/electron-wordwrap\",\n" + 863 | " \"owner\": {\n" + 864 | " \"login\": \"defunkt\",\n" + 865 | " \"id\": 2,\n" + 866 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 867 | " \"gravatar_id\": \"\",\n" + 868 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 869 | " \"html_url\": \"https://github.com/defunkt\",\n" + 870 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 871 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 872 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 873 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 874 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 875 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 876 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 877 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 878 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 879 | " \"type\": \"User\",\n" + 880 | " \"site_admin\": true\n" + 881 | " },\n" + 882 | " \"private\": false,\n" + 883 | " \"html_url\": \"https://github.com/defunkt/electron-wordwrap\",\n" + 884 | " \"description\": null,\n" + 885 | " \"fork\": false,\n" + 886 | " \"url\": \"https://api.github.com/repos/defunkt/electron-wordwrap\",\n" + 887 | " \"forks_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/forks\",\n" + 888 | " \"keys_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/keys{/key_id}\",\n" + 889 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/collaborators{/collaborator}\",\n" + 890 | " \"teams_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/teams\",\n" + 891 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/hooks\",\n" + 892 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/issues/events{/number}\",\n" + 893 | " \"events_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/events\",\n" + 894 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/assignees{/user}\",\n" + 895 | " \"branches_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/branches{/branch}\",\n" + 896 | " \"tags_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/tags\",\n" + 897 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/git/blobs{/sha}\",\n" + 898 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/git/tags{/sha}\",\n" + 899 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/git/refs{/sha}\",\n" + 900 | " \"trees_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/git/trees{/sha}\",\n" + 901 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/statuses/{sha}\",\n" + 902 | " \"languages_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/languages\",\n" + 903 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/stargazers\",\n" + 904 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/contributors\",\n" + 905 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/subscribers\",\n" + 906 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/subscription\",\n" + 907 | " \"commits_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/commits{/sha}\",\n" + 908 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/git/commits{/sha}\",\n" + 909 | " \"comments_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/comments{/number}\",\n" + 910 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/issues/comments{/number}\",\n" + 911 | " \"contents_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/contents/{+path}\",\n" + 912 | " \"compare_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/compare/{base}...{head}\",\n" + 913 | " \"merges_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/merges\",\n" + 914 | " \"archive_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/{archive_format}{/ref}\",\n" + 915 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/downloads\",\n" + 916 | " \"issues_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/issues{/number}\",\n" + 917 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/pulls{/number}\",\n" + 918 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/milestones{/number}\",\n" + 919 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/notifications{?since,all,participating}\",\n" + 920 | " \"labels_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/labels{/name}\",\n" + 921 | " \"releases_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/releases{/id}\",\n" + 922 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/electron-wordwrap/deployments\",\n" + 923 | " \"created_at\": \"2008-10-29T20:03:17Z\",\n" + 924 | " \"updated_at\": \"2016-09-22T18:46:22Z\",\n" + 925 | " \"pushed_at\": \"2008-10-29T20:28:21Z\",\n" + 926 | " \"git_url\": \"git://github.com/defunkt/electron-wordwrap.git\",\n" + 927 | " \"ssh_url\": \"git@github.com:defunkt/electron-wordwrap.git\",\n" + 928 | " \"clone_url\": \"https://github.com/defunkt/electron-wordwrap.git\",\n" + 929 | " \"svn_url\": \"https://github.com/defunkt/electron-wordwrap\",\n" + 930 | " \"homepage\": \"\",\n" + 931 | " \"size\": 76,\n" + 932 | " \"stargazers_count\": 4,\n" + 933 | " \"watchers_count\": 4,\n" + 934 | " \"language\": null,\n" + 935 | " \"has_issues\": true,\n" + 936 | " \"has_projects\": true,\n" + 937 | " \"has_downloads\": true,\n" + 938 | " \"has_wiki\": true,\n" + 939 | " \"has_pages\": false,\n" + 940 | " \"forks_count\": 3,\n" + 941 | " \"mirror_url\": null,\n" + 942 | " \"open_issues_count\": 0,\n" + 943 | " \"forks\": 3,\n" + 944 | " \"open_issues\": 0,\n" + 945 | " \"watchers\": 4,\n" + 946 | " \"default_branch\": \"master\"\n" + 947 | " },\n" + 948 | " {\n" + 949 | " \"id\": 3596,\n" + 950 | " \"name\": \"fixture_scenarios_builder\",\n" + 951 | " \"full_name\": \"defunkt/fixture_scenarios_builder\",\n" + 952 | " \"owner\": {\n" + 953 | " \"login\": \"defunkt\",\n" + 954 | " \"id\": 2,\n" + 955 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 956 | " \"gravatar_id\": \"\",\n" + 957 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 958 | " \"html_url\": \"https://github.com/defunkt\",\n" + 959 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 960 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 961 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 962 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 963 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 964 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 965 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 966 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 967 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 968 | " \"type\": \"User\",\n" + 969 | " \"site_admin\": true\n" + 970 | " },\n" + 971 | " \"private\": false,\n" + 972 | " \"html_url\": \"https://github.com/defunkt/fixture_scenarios_builder\",\n" + 973 | " \"description\": \"Build your fixtures in Ruby.\",\n" + 974 | " \"fork\": false,\n" + 975 | " \"url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder\",\n" + 976 | " \"forks_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/forks\",\n" + 977 | " \"keys_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/keys{/key_id}\",\n" + 978 | " \"collaborators_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/collaborators{/collaborator}\",\n" + 979 | " \"teams_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/teams\",\n" + 980 | " \"hooks_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/hooks\",\n" + 981 | " \"issue_events_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/issues/events{/number}\",\n" + 982 | " \"events_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/events\",\n" + 983 | " \"assignees_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/assignees{/user}\",\n" + 984 | " \"branches_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/branches{/branch}\",\n" + 985 | " \"tags_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/tags\",\n" + 986 | " \"blobs_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/git/blobs{/sha}\",\n" + 987 | " \"git_tags_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/git/tags{/sha}\",\n" + 988 | " \"git_refs_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/git/refs{/sha}\",\n" + 989 | " \"trees_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/git/trees{/sha}\",\n" + 990 | " \"statuses_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/statuses/{sha}\",\n" + 991 | " \"languages_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/languages\",\n" + 992 | " \"stargazers_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/stargazers\",\n" + 993 | " \"contributors_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/contributors\",\n" + 994 | " \"subscribers_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/subscribers\",\n" + 995 | " \"subscription_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/subscription\",\n" + 996 | " \"commits_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/commits{/sha}\",\n" + 997 | " \"git_commits_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/git/commits{/sha}\",\n" + 998 | " \"comments_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/comments{/number}\",\n" + 999 | " \"issue_comment_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/issues/comments{/number}\",\n" + 1000 | " \"contents_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/contents/{+path}\",\n" + 1001 | " \"compare_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/compare/{base}...{head}\",\n" + 1002 | " \"merges_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/merges\",\n" + 1003 | " \"archive_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/{archive_format}{/ref}\",\n" + 1004 | " \"downloads_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/downloads\",\n" + 1005 | " \"issues_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/issues{/number}\",\n" + 1006 | " \"pulls_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/pulls{/number}\",\n" + 1007 | " \"milestones_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/milestones{/number}\",\n" + 1008 | " \"notifications_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/notifications{?since,all,participating}\",\n" + 1009 | " \"labels_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/labels{/name}\",\n" + 1010 | " \"releases_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/releases{/id}\",\n" + 1011 | " \"deployments_url\": \"https://api.github.com/repos/defunkt/fixture_scenarios_builder/deployments\",\n" + 1012 | " \"created_at\": \"2008-03-12T06:24:02Z\",\n" + 1013 | " \"updated_at\": \"2017-04-01T14:45:15Z\",\n" + 1014 | " \"pushed_at\": \"2008-11-12T22:58:39Z\",\n" + 1015 | " \"git_url\": \"git://github.com/defunkt/fixture_scenarios_builder.git\",\n" + 1016 | " \"ssh_url\": \"git@github.com:defunkt/fixture_scenarios_builder.git\",\n" + 1017 | " \"clone_url\": \"https://github.com/defunkt/fixture_scenarios_builder.git\",\n" + 1018 | " \"svn_url\": \"https://github.com/defunkt/fixture_scenarios_builder\",\n" + 1019 | " \"homepage\": \"http://errtheblog.com/posts/61-fixin-fixtures\",\n" + 1020 | " \"size\": 96,\n" + 1021 | " \"stargazers_count\": 12,\n" + 1022 | " \"watchers_count\": 12,\n" + 1023 | " \"language\": \"Ruby\",\n" + 1024 | " \"has_issues\": true,\n" + 1025 | " \"has_projects\": true,\n" + 1026 | " \"has_downloads\": true,\n" + 1027 | " \"has_wiki\": true,\n" + 1028 | " \"has_pages\": false,\n" + 1029 | " \"forks_count\": 5,\n" + 1030 | " \"mirror_url\": null,\n" + 1031 | " \"open_issues_count\": 0,\n" + 1032 | " \"forks\": 5,\n" + 1033 | " \"open_issues\": 0,\n" + 1034 | " \"watchers\": 12,\n" + 1035 | " \"default_branch\": \"master\"\n" + 1036 | " }\n" + 1037 | "]"; 1038 | 1039 | static final String USER_DEFUNKT = "{\n" + 1040 | " \"login\": \"defunkt\",\n" + 1041 | " \"id\": 2,\n" + 1042 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 1043 | " \"gravatar_id\": \"\",\n" + 1044 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 1045 | " \"html_url\": \"https://github.com/defunkt\",\n" + 1046 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 1047 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 1048 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 1049 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 1050 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 1051 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 1052 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 1053 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 1054 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 1055 | " \"type\": \"User\",\n" + 1056 | " \"site_admin\": true,\n" + 1057 | " \"name\": \"Chris Wanstrath\",\n" + 1058 | " \"company\": \"@github \",\n" + 1059 | " \"blog\": \"http://chriswanstrath.com/\",\n" + 1060 | " \"location\": \"San Francisco\",\n" + 1061 | " \"email\": null,\n" + 1062 | " \"hireable\": true,\n" + 1063 | " \"bio\": \"\uD83C\uDF54 \",\n" + 1064 | " \"public_repos\": 107,\n" + 1065 | " \"public_gists\": 273,\n" + 1066 | " \"followers\": 16181,\n" + 1067 | " \"following\": 208,\n" + 1068 | " \"created_at\": \"2007-10-20T05:24:19Z\",\n" + 1069 | " \"updated_at\": \"2017-03-30T21:03:38Z\"\n" + 1070 | "}"; 1071 | 1072 | static final String USER_MOJOMBO = "{\n" + 1073 | " \"login\": \"mojombo\",\n" + 1074 | " \"id\": 1,\n" + 1075 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/1?v=3\",\n" + 1076 | " \"gravatar_id\": \"\",\n" + 1077 | " \"url\": \"https://api.github.com/users/mojombo\",\n" + 1078 | " \"html_url\": \"https://github.com/mojombo\",\n" + 1079 | " \"followers_url\": \"https://api.github.com/users/mojombo/followers\",\n" + 1080 | " \"following_url\": \"https://api.github.com/users/mojombo/following{/other_user}\",\n" + 1081 | " \"gists_url\": \"https://api.github.com/users/mojombo/gists{/gist_id}\",\n" + 1082 | " \"starred_url\": \"https://api.github.com/users/mojombo/starred{/owner}{/repo}\",\n" + 1083 | " \"subscriptions_url\": \"https://api.github.com/users/mojombo/subscriptions\",\n" + 1084 | " \"organizations_url\": \"https://api.github.com/users/mojombo/orgs\",\n" + 1085 | " \"repos_url\": \"https://api.github.com/users/mojombo/repos\",\n" + 1086 | " \"events_url\": \"https://api.github.com/users/mojombo/events{/privacy}\",\n" + 1087 | " \"received_events_url\": \"https://api.github.com/users/mojombo/received_events\",\n" + 1088 | " \"type\": \"User\",\n" + 1089 | " \"site_admin\": false,\n" + 1090 | " \"name\": \"Tom Preston-Werner\",\n" + 1091 | " \"company\": null,\n" + 1092 | " \"blog\": \"http://tom.preston-werner.com\",\n" + 1093 | " \"location\": \"San Francisco\",\n" + 1094 | " \"email\": null,\n" + 1095 | " \"hireable\": null,\n" + 1096 | " \"bio\": null,\n" + 1097 | " \"public_repos\": 61,\n" + 1098 | " \"public_gists\": 62,\n" + 1099 | " \"followers\": 20169,\n" + 1100 | " \"following\": 11,\n" + 1101 | " \"created_at\": \"2007-10-20T05:24:19Z\",\n" + 1102 | " \"updated_at\": \"2017-04-29T10:00:07Z\"\n" + 1103 | "}"; 1104 | 1105 | static final String FIRST_USERS = "[\n" + 1106 | " {\n" + 1107 | " \"login\": \"mojombo\",\n" + 1108 | " \"id\": 1,\n" + 1109 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/1?v=3\",\n" + 1110 | " \"gravatar_id\": \"\",\n" + 1111 | " \"url\": \"https://api.github.com/users/mojombo\",\n" + 1112 | " \"html_url\": \"https://github.com/mojombo\",\n" + 1113 | " \"followers_url\": \"https://api.github.com/users/mojombo/followers\",\n" + 1114 | " \"following_url\": \"https://api.github.com/users/mojombo/following{/other_user}\",\n" + 1115 | " \"gists_url\": \"https://api.github.com/users/mojombo/gists{/gist_id}\",\n" + 1116 | " \"starred_url\": \"https://api.github.com/users/mojombo/starred{/owner}{/repo}\",\n" + 1117 | " \"subscriptions_url\": \"https://api.github.com/users/mojombo/subscriptions\",\n" + 1118 | " \"organizations_url\": \"https://api.github.com/users/mojombo/orgs\",\n" + 1119 | " \"repos_url\": \"https://api.github.com/users/mojombo/repos\",\n" + 1120 | " \"events_url\": \"https://api.github.com/users/mojombo/events{/privacy}\",\n" + 1121 | " \"received_events_url\": \"https://api.github.com/users/mojombo/received_events\",\n" + 1122 | " \"type\": \"User\",\n" + 1123 | " \"site_admin\": false\n" + 1124 | " },\n" + 1125 | " {\n" + 1126 | " \"login\": \"defunkt\",\n" + 1127 | " \"id\": 2,\n" + 1128 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/2?v=3\",\n" + 1129 | " \"gravatar_id\": \"\",\n" + 1130 | " \"url\": \"https://api.github.com/users/defunkt\",\n" + 1131 | " \"html_url\": \"https://github.com/defunkt\",\n" + 1132 | " \"followers_url\": \"https://api.github.com/users/defunkt/followers\",\n" + 1133 | " \"following_url\": \"https://api.github.com/users/defunkt/following{/other_user}\",\n" + 1134 | " \"gists_url\": \"https://api.github.com/users/defunkt/gists{/gist_id}\",\n" + 1135 | " \"starred_url\": \"https://api.github.com/users/defunkt/starred{/owner}{/repo}\",\n" + 1136 | " \"subscriptions_url\": \"https://api.github.com/users/defunkt/subscriptions\",\n" + 1137 | " \"organizations_url\": \"https://api.github.com/users/defunkt/orgs\",\n" + 1138 | " \"repos_url\": \"https://api.github.com/users/defunkt/repos\",\n" + 1139 | " \"events_url\": \"https://api.github.com/users/defunkt/events{/privacy}\",\n" + 1140 | " \"received_events_url\": \"https://api.github.com/users/defunkt/received_events\",\n" + 1141 | " \"type\": \"User\",\n" + 1142 | " \"site_admin\": true\n" + 1143 | " },\n" + 1144 | " {\n" + 1145 | " \"login\": \"pjhyett\",\n" + 1146 | " \"id\": 3,\n" + 1147 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/3?v=3\",\n" + 1148 | " \"gravatar_id\": \"\",\n" + 1149 | " \"url\": \"https://api.github.com/users/pjhyett\",\n" + 1150 | " \"html_url\": \"https://github.com/pjhyett\",\n" + 1151 | " \"followers_url\": \"https://api.github.com/users/pjhyett/followers\",\n" + 1152 | " \"following_url\": \"https://api.github.com/users/pjhyett/following{/other_user}\",\n" + 1153 | " \"gists_url\": \"https://api.github.com/users/pjhyett/gists{/gist_id}\",\n" + 1154 | " \"starred_url\": \"https://api.github.com/users/pjhyett/starred{/owner}{/repo}\",\n" + 1155 | " \"subscriptions_url\": \"https://api.github.com/users/pjhyett/subscriptions\",\n" + 1156 | " \"organizations_url\": \"https://api.github.com/users/pjhyett/orgs\",\n" + 1157 | " \"repos_url\": \"https://api.github.com/users/pjhyett/repos\",\n" + 1158 | " \"events_url\": \"https://api.github.com/users/pjhyett/events{/privacy}\",\n" + 1159 | " \"received_events_url\": \"https://api.github.com/users/pjhyett/received_events\",\n" + 1160 | " \"type\": \"User\",\n" + 1161 | " \"site_admin\": false\n" + 1162 | " },\n" + 1163 | " {\n" + 1164 | " \"login\": \"wycats\",\n" + 1165 | " \"id\": 4,\n" + 1166 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/4?v=3\",\n" + 1167 | " \"gravatar_id\": \"\",\n" + 1168 | " \"url\": \"https://api.github.com/users/wycats\",\n" + 1169 | " \"html_url\": \"https://github.com/wycats\",\n" + 1170 | " \"followers_url\": \"https://api.github.com/users/wycats/followers\",\n" + 1171 | " \"following_url\": \"https://api.github.com/users/wycats/following{/other_user}\",\n" + 1172 | " \"gists_url\": \"https://api.github.com/users/wycats/gists{/gist_id}\",\n" + 1173 | " \"starred_url\": \"https://api.github.com/users/wycats/starred{/owner}{/repo}\",\n" + 1174 | " \"subscriptions_url\": \"https://api.github.com/users/wycats/subscriptions\",\n" + 1175 | " \"organizations_url\": \"https://api.github.com/users/wycats/orgs\",\n" + 1176 | " \"repos_url\": \"https://api.github.com/users/wycats/repos\",\n" + 1177 | " \"events_url\": \"https://api.github.com/users/wycats/events{/privacy}\",\n" + 1178 | " \"received_events_url\": \"https://api.github.com/users/wycats/received_events\",\n" + 1179 | " \"type\": \"User\",\n" + 1180 | " \"site_admin\": false\n" + 1181 | " },\n" + 1182 | " {\n" + 1183 | " \"login\": \"ezmobius\",\n" + 1184 | " \"id\": 5,\n" + 1185 | " \"avatar_url\": \"https://avatars3.githubusercontent.com/u/5?v=3\",\n" + 1186 | " \"gravatar_id\": \"\",\n" + 1187 | " \"url\": \"https://api.github.com/users/ezmobius\",\n" + 1188 | " \"html_url\": \"https://github.com/ezmobius\",\n" + 1189 | " \"followers_url\": \"https://api.github.com/users/ezmobius/followers\",\n" + 1190 | " \"following_url\": \"https://api.github.com/users/ezmobius/following{/other_user}\",\n" + 1191 | " \"gists_url\": \"https://api.github.com/users/ezmobius/gists{/gist_id}\",\n" + 1192 | " \"starred_url\": \"https://api.github.com/users/ezmobius/starred{/owner}{/repo}\",\n" + 1193 | " \"subscriptions_url\": \"https://api.github.com/users/ezmobius/subscriptions\",\n" + 1194 | " \"organizations_url\": \"https://api.github.com/users/ezmobius/orgs\",\n" + 1195 | " \"repos_url\": \"https://api.github.com/users/ezmobius/repos\",\n" + 1196 | " \"events_url\": \"https://api.github.com/users/ezmobius/events{/privacy}\",\n" + 1197 | " \"received_events_url\": \"https://api.github.com/users/ezmobius/received_events\",\n" + 1198 | " \"type\": \"User\",\n" + 1199 | " \"site_admin\": false\n" + 1200 | " },\n" + 1201 | " {\n" + 1202 | " \"login\": \"bmizerany\",\n" + 1203 | " \"id\": 46,\n" + 1204 | " \"avatar_url\": \"https://avatars1.githubusercontent.com/u/46?v=3\",\n" + 1205 | " \"gravatar_id\": \"\",\n" + 1206 | " \"url\": \"https://api.github.com/users/bmizerany\",\n" + 1207 | " \"html_url\": \"https://github.com/bmizerany\",\n" + 1208 | " \"followers_url\": \"https://api.github.com/users/bmizerany/followers\",\n" + 1209 | " \"following_url\": \"https://api.github.com/users/bmizerany/following{/other_user}\",\n" + 1210 | " \"gists_url\": \"https://api.github.com/users/bmizerany/gists{/gist_id}\",\n" + 1211 | " \"starred_url\": \"https://api.github.com/users/bmizerany/starred{/owner}{/repo}\",\n" + 1212 | " \"subscriptions_url\": \"https://api.github.com/users/bmizerany/subscriptions\",\n" + 1213 | " \"organizations_url\": \"https://api.github.com/users/bmizerany/orgs\",\n" + 1214 | " \"repos_url\": \"https://api.github.com/users/bmizerany/repos\",\n" + 1215 | " \"events_url\": \"https://api.github.com/users/bmizerany/events{/privacy}\",\n" + 1216 | " \"received_events_url\": \"https://api.github.com/users/bmizerany/received_events\",\n" + 1217 | " \"type\": \"User\",\n" + 1218 | " \"site_admin\": false\n" + 1219 | " }\n" + 1220 | "]"; 1221 | } 1222 | } 1223 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask10Backpressure.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.server.Log 4 | import com.jraska.rx.codelab.server.RxServer 5 | import com.jraska.rx.codelab.server.RxServerFactory 6 | import io.reactivex.functions.Consumer 7 | import io.reactivex.schedulers.Schedulers 8 | import org.junit.After 9 | import org.junit.Test 10 | 11 | class SolutionTask10Backpressure { 12 | private val rxServer: RxServer = RxServerFactory.create() 13 | 14 | @Test 15 | fun backpressureFail() { 16 | rxServer.allLogsHot() 17 | .observeOn(Schedulers.newThread()) 18 | .subscribe(reallySlowLogConsumer()) 19 | } 20 | 21 | @Test 22 | fun noBackpressure() { 23 | rxServer.allLogsHot() 24 | .toObservable() 25 | .observeOn(Schedulers.newThread()) 26 | .subscribe(reallySlowLogConsumer()) 27 | } 28 | 29 | @Test 30 | fun onBackpressureDrop() { 31 | rxServer.allLogsHot() 32 | .onBackpressureDrop { water -> println("On Drop $water") } 33 | .observeOn(Schedulers.newThread()) 34 | .subscribe(slowLogConsumer()) 35 | } 36 | 37 | @Test 38 | fun buffer_backpressureBatching() { 39 | rxServer.allLogsHot() 40 | .buffer(5) 41 | .observeOn(Schedulers.newThread()) 42 | .subscribe(batchLogsConsumer()) 43 | } 44 | 45 | @Test 46 | fun onBackpressureBuffer() { 47 | rxServer.allLogsHot() 48 | .onBackpressureBuffer(128) 49 | .observeOn(Schedulers.newThread()) 50 | .subscribe(slowLogConsumer()) 51 | } 52 | 53 | private fun slowLogConsumer(): Consumer { 54 | return Consumer { log -> 55 | Thread.sleep(25) 56 | println(log) 57 | } 58 | } 59 | 60 | private fun reallySlowLogConsumer(): Consumer { 61 | return Consumer { log -> 62 | Thread.sleep(100) 63 | println(log) 64 | } 65 | } 66 | 67 | private fun batchLogsConsumer(): Consumer> { 68 | return Consumer { logs -> 69 | Thread.sleep(100) 70 | println(logs) 71 | } 72 | } 73 | 74 | @After 75 | fun after() { 76 | Thread.sleep(3000) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask11OtherLibrariesInteroperability.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | //import hu.akarnokd.rxjava.interop.RxJavaInterop 4 | //import io.reactivex.BackpressureStrategy 5 | //import io.reactivex.Observable 6 | //import io.reactivex.Single 7 | //import io.reactivex.processors.PublishProcessor 8 | //import org.junit.Test 9 | //import reactor.core.publisher.Flux 10 | //import reactor.core.publisher.Mono 11 | 12 | class SolutionTask11OtherLibrariesInteroperability { 13 | // @Test 14 | // fun givenRxJava1and2() { 15 | // val stringObservable = RxJavaInterop.toV1Observable(Observable.just("hi").toFlowable(BackpressureStrategy.DROP)) 16 | // val stringObservable2 = RxJavaInterop.toV2Observable(stringObservable) 17 | // 18 | // val objectSubject = RxJavaInterop.toV1Subject(PublishProcessor.create()) 19 | // val flowableProcessor = RxJavaInterop.toV2Processor(objectSubject) 20 | // } 21 | // 22 | // @Test 23 | // fun reactiveStreams_reactor_rxjava() { 24 | // val hello = Observable.just("Hello") 25 | // val flux = Flux.from(hello.toFlowable(BackpressureStrategy.DROP)) 26 | // flux.subscribe { println(it) } 27 | // 28 | // val single = Single.just("Hello Single") 29 | // val mono = Mono.from(single.toFlowable()) 30 | // mono.subscribe { println(it) } 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask1Basics.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import io.reactivex.Observable 4 | import org.junit.Test 5 | 6 | class SolutionTask1Basics { 7 | @Test 8 | fun dummyObservable() { 9 | val stringObservable = Observable.just("Hello Rx") 10 | 11 | stringObservable.subscribe { println(it) } 12 | } 13 | 14 | @Test 15 | fun methodIntoObservable() { 16 | val currentTimeObservable = Observable.fromCallable { System.currentTimeMillis() } 17 | 18 | currentTimeObservable.subscribe { println(it) } 19 | } 20 | 21 | @Test 22 | fun helloOperator() { 23 | val rangeObservable = Observable.range(1, 10) 24 | 25 | rangeObservable.filter { isOdd(it) } 26 | .subscribe(System.out::println) 27 | } 28 | 29 | @Test 30 | fun receivingError() { 31 | val integerObservable = Observable.error(RuntimeException("I want to crash you")) 32 | 33 | integerObservable.subscribe(::println, System.err::println) 34 | } 35 | 36 | companion object { 37 | fun isOdd(value: Int): Boolean { 38 | return value % 2 == 1 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask2Transformations.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.RxLogging 4 | import com.jraska.rx.codelab.http.GitHubConverter 5 | import com.jraska.rx.codelab.http.HttpModule 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | class SolutionTask2Transformations { 10 | private val gitHubApi = HttpModule.mockedGitHubApi() 11 | 12 | @Before 13 | fun setUp() { 14 | RxLogging.enableObservableSubscribeLogging() 15 | } 16 | 17 | @Test 18 | fun map_convertUserDto() { 19 | gitHubApi.getUser(LOGIN) 20 | .map { GitHubConverter.convert(it) } 21 | .map { it.toString() } 22 | .subscribe { println(it) } 23 | } 24 | 25 | @Test 26 | fun flatMap_getFirstUserDetailAfterGettingList() { 27 | gitHubApi.getFirstUsers() 28 | .flatMap { gitHubUsers -> gitHubApi.getUser(gitHubUsers[0].login) } 29 | .map { GitHubConverter.convert(it) } 30 | .subscribe { println(it) } 31 | } 32 | 33 | @Test 34 | fun replayAutoConnect_oneRequestForTwoSubscriptions() { 35 | val observable = gitHubApi.getUser(LOGIN) 36 | .map { GitHubConverter.convert(it) } 37 | .replay(1) 38 | .autoConnect() 39 | 40 | observable.subscribe { println(it) } 41 | observable.subscribe { println(it) } 42 | } 43 | 44 | companion object { 45 | private const val LOGIN = "defunkt" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask3Combining.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.RxLogging 4 | import com.jraska.rx.codelab.combineLatest 5 | import com.jraska.rx.codelab.http.GitHubConverter 6 | import com.jraska.rx.codelab.http.HttpModule 7 | import com.jraska.rx.codelab.http.UserCache 8 | import com.jraska.rx.codelab.zipWith 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class SolutionTask3Combining { 13 | private val gitHubApi = HttpModule.mockedGitHubApi() 14 | 15 | @Before 16 | fun setUp() { 17 | RxLogging.enableObservableSubscribeLogging() 18 | } 19 | 20 | @Test 21 | fun zipWith_userWithRepos() { 22 | gitHubApi.getUser(LOGIN) 23 | .zipWith(gitHubApi.getRepos(LOGIN), GitHubConverter::convert) 24 | .subscribe { println(it) } 25 | } 26 | 27 | @Test 28 | fun startWith_userInCache() { 29 | gitHubApi.getUser(LOGIN) 30 | .map { GitHubConverter.convert(it) } 31 | .startWith(UserCache.getUser(LOGIN)) 32 | .subscribe { println(it) } 33 | } 34 | 35 | @Test 36 | fun merge_userInCache() { 37 | UserCache.getUser(LOGIN) 38 | .mergeWith(gitHubApi.getUser(LOGIN) 39 | .map { GitHubConverter.convert(it) }) 40 | .subscribe { println(it) } 41 | } 42 | 43 | @Test 44 | fun combineLatest_cachedUserWithRepos() { 45 | val userObservable = gitHubApi.getUser(LOGIN) 46 | .map { GitHubConverter.convert(it) } 47 | .startWith(UserCache.getUser(LOGIN)) 48 | 49 | val reposObservable = gitHubApi.getRepos(LOGIN) 50 | .map { GitHubConverter.convert(it) } 51 | 52 | 53 | reposObservable.combineLatest(userObservable, GitHubConverter::convert) 54 | .subscribe { println(it) } 55 | } 56 | 57 | companion object { 58 | private const val LOGIN = "defunkt" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask4ErrorHandling.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.RxLogging 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import io.reactivex.functions.Consumer 6 | import io.reactivex.internal.functions.Functions 7 | import okhttp3.MediaType 8 | import okhttp3.ResponseBody 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class SolutionTask4ErrorHandling { 13 | private val httpBinApi = HttpModule.httpBinApi() 14 | 15 | @Before 16 | fun before() { 17 | RxLogging.enableObservableSubscribeLogging() 18 | } 19 | 20 | @Test 21 | fun printErrorMessage() { 22 | httpBinApi.failingGet() 23 | .subscribe(System.out::println, System.err::println) 24 | } 25 | 26 | @Test 27 | fun onErrorReturnItem_emitCustomItemOnError() { 28 | httpBinApi.failingGet() 29 | .onErrorReturnItem(syntheticBody()) 30 | .subscribe { println(it) } 31 | } 32 | 33 | @Test 34 | fun onErrorResumeNext_subscribeToExtraObservableOnError() { 35 | httpBinApi.failingGet() 36 | .onErrorResumeNext(httpBinApi.backupGet()) 37 | .subscribe(Consumer { println(it) }, Functions.emptyConsumer()) 38 | } 39 | 40 | @Test 41 | fun retry_retryOnError() { 42 | httpBinApi.flakyGet() 43 | .retry() 44 | .subscribe { println(it) } 45 | } 46 | 47 | companion object { 48 | fun syntheticBody(): ResponseBody { 49 | return ResponseBody.create(MediaType.get("application/json"), "{}") 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask5Threading.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.http.GitHubConverter 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import com.jraska.rx.codelab.zipWith 6 | import io.reactivex.schedulers.Schedulers 7 | import org.junit.After 8 | import org.junit.Test 9 | 10 | class SolutionTask5Threading { 11 | 12 | private val gitHubApi = HttpModule.mockedGitHubApi() 13 | 14 | @After 15 | fun after() { 16 | HttpModule.awaitNetworkRequests() 17 | } 18 | 19 | @Test 20 | fun zip_subscribeOn_getUserAndHisReposInParallel() { 21 | gitHubApi.getUser(LOGIN) 22 | .subscribeOn(Schedulers.io()) 23 | .zipWith(gitHubApi.getRepos(LOGIN), GitHubConverter::convert) 24 | .subscribe { println(it) } 25 | } 26 | 27 | @Test 28 | fun zip_subscribeOn_twoUserAndReposInSerialExplicitly() { 29 | gitHubApi.getUser(LOGIN) 30 | .subscribeOn(Schedulers.single()) 31 | .zipWith(gitHubApi.getRepos(LOGIN).subscribeOn(Schedulers.single()), GitHubConverter::convert) 32 | .subscribeOn(Schedulers.io()) 33 | .subscribe { println(it) } 34 | } 35 | 36 | @Test 37 | fun observeOn_receivingResultsOnDifferentThreads() { 38 | val userObservable = gitHubApi.getUser(LOGIN).map(GitHubConverter::convert) 39 | printWithThreadId("Test thread") 40 | 41 | userObservable.doOnNext { this.printWithThreadId(it) } 42 | .observeOn(Schedulers.single()) 43 | .doOnNext { this.printWithThreadId(it) } 44 | .observeOn(Schedulers.computation()) 45 | .subscribe { this.printWithThreadId(it) } 46 | } 47 | 48 | private fun printWithThreadId(`object`: Any) { 49 | println("Thread id: " + Thread.currentThread().id + ", " + `object`) 50 | } 51 | 52 | companion object { 53 | private const val LOGIN = "defunkt" // One of GitHub founders. <3 GitHub <3 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask6SingleCompletableMaybe.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Single 5 | import io.reactivex.functions.Consumer 6 | import org.junit.After 7 | import org.junit.Test 8 | import java.util.concurrent.TimeUnit 9 | 10 | class SolutionTask6SingleCompletableMaybe { 11 | 12 | @Test 13 | fun helloSingle() { 14 | val single = Single.just("Hello RxJava again") 15 | 16 | single.subscribe(Consumer { println(it) }) 17 | val completable = single.ignoreElement() 18 | 19 | completable.subscribe { println("Completed") } 20 | } 21 | 22 | @Test 23 | fun maybe() { 24 | val maybe = Single.just("Hello RxJava again").toMaybe() 25 | 26 | maybe.subscribe(System.out::println, System.err::println) 27 | } 28 | 29 | @Test 30 | fun transformObservableToCompletable() { 31 | val range = Observable.range(0, 10) 32 | 33 | val completable = range.ignoreElements() 34 | 35 | completable.subscribe { println("Completed") } 36 | } 37 | 38 | @Test 39 | fun intervalRange_firstOrError_observableToSingle() { 40 | val range = Observable.intervalRange(0, 5, 0, 10, TimeUnit.MILLISECONDS) 41 | 42 | range.skip(4).firstOrError().subscribe(Consumer { println(it) }) 43 | } 44 | 45 | @After 46 | fun after() { 47 | // to see easily time dependent operations, because we are in unit tests 48 | Thread.sleep(100) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask7HotObservables.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.RxLogging 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import com.jraska.rx.codelab.http.RequestInfo 6 | import com.jraska.rx.codelab.server.RxServerFactory 7 | import io.reactivex.processors.PublishProcessor 8 | import io.reactivex.schedulers.Schedulers 9 | import org.junit.After 10 | import org.junit.Before 11 | import org.junit.Test 12 | import java.util.concurrent.TimeUnit 13 | 14 | class SolutionTask7HotObservables { 15 | 16 | private val rxServer = RxServerFactory.create() 17 | private val httpBinApi = HttpModule.httpBinApi() 18 | 19 | @Before 20 | fun before() { 21 | RxLogging.enableObservableSubscribeLogging() 22 | } 23 | 24 | @Test 25 | fun coldObservable() { 26 | val getRequest = httpBinApi.getRequest() 27 | .subscribeOn(Schedulers.io()) 28 | .share() 29 | 30 | getRequest.subscribe { println(it) } 31 | getRequest.subscribe { println(it) } 32 | } 33 | 34 | @Test 35 | fun hotObservable() { 36 | val logs = rxServer.debugLogsHot() 37 | 38 | logs.delaySubscription(250, TimeUnit.MILLISECONDS).subscribe { println(it) } 39 | logs.subscribe { println(it) } 40 | } 41 | 42 | @Test 43 | fun createHotObservableThroughSubject() { 44 | val publishProcessor = PublishProcessor.create() 45 | 46 | publishProcessor.subscribe { println(it) } 47 | publishProcessor.subscribe { println(it) } 48 | 49 | val request = httpBinApi.getRequest().subscribeOn(Schedulers.io()) 50 | 51 | request.subscribe(publishProcessor::onNext, publishProcessor::onError) 52 | } 53 | 54 | @After 55 | fun after() { 56 | Thread.sleep(500) 57 | HttpModule.awaitNetworkRequests() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask8Testing.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.SchedulerProvider 4 | import com.jraska.rx.codelab.http.HttpModule 5 | import com.jraska.rx.codelab.http.IpViewModel 6 | import com.jraska.rx.codelab.server.RxServerFactory 7 | import io.reactivex.schedulers.TestScheduler 8 | import io.reactivex.subjects.PublishSubject 9 | import org.junit.Test 10 | import java.util.concurrent.TimeUnit 11 | 12 | class SolutionTask8Testing { 13 | private val rxServer = RxServerFactory.create() 14 | private val httpBinApi = HttpModule.httpBinApi() 15 | 16 | @Test 17 | fun testObserver_onColdObservable() { 18 | val request = httpBinApi.getRequest() 19 | request.test() 20 | .assertSubscribed() 21 | .assertValueCount(1) 22 | .assertValue { requestInfo -> requestInfo.url.contains("show_env") } 23 | .assertComplete() 24 | } 25 | 26 | @Test 27 | fun testSubscriber_onHotFlowable() { 28 | val logObservable = rxServer.debugLogsHot() 29 | 30 | logObservable.test() 31 | .awaitCount(5) 32 | .assertNotComplete() 33 | .assertNotTerminated() 34 | .assertNoErrors() 35 | } 36 | 37 | @Test 38 | fun testScheduler_advancingTime() { 39 | val testScheduler = TestScheduler() 40 | 41 | val subject = PublishSubject.create() 42 | val bufferedObservable = subject.buffer(100, TimeUnit.MILLISECONDS, testScheduler) 43 | bufferedObservable.subscribe { println(it) } 44 | 45 | subject.onNext("First") 46 | subject.onNext("Batch") 47 | 48 | testScheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS) 49 | 50 | subject.onNext("Second") 51 | subject.onNext("Longer") 52 | subject.onNext("Batch") 53 | 54 | testScheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS) 55 | } 56 | 57 | @Test 58 | fun schedulerProvider_runSynchronouslyInTest() { 59 | val viewModel = IpViewModel(httpBinApi, SchedulerProvider.testSchedulers()) 60 | 61 | viewModel.ip().subscribe({ println(it) }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rx-codelab/src/test/java/com/jraska/rx/codelab/solution/SolutionTask9WhereWeCanFindRxJavaHandy.kt: -------------------------------------------------------------------------------- 1 | package com.jraska.rx.codelab.solution 2 | 3 | import com.jraska.rx.codelab.http.HttpModule 4 | import com.jraska.rx.codelab.http.RequestInfoCache 5 | import io.reactivex.schedulers.Schedulers 6 | import io.reactivex.subjects.PublishSubject 7 | import org.junit.After 8 | import org.junit.Test 9 | import java.util.concurrent.TimeUnit 10 | 11 | class SolutionTask9WhereWeCanFindRxJavaHandy { 12 | private val httpBinApi = HttpModule.httpBinApi() 13 | 14 | @Test 15 | fun repeatWhen_refreshFunctionality() { 16 | val refreshSignal = PublishSubject.create() 17 | 18 | val request = httpBinApi.getRequest() 19 | .subscribeOn(Schedulers.io()) 20 | .share() 21 | .repeatWhen { refreshSignal } 22 | .cache() 23 | 24 | request.subscribe() 25 | request.subscribe() 26 | 27 | HttpModule.awaitNetworkRequests() 28 | 29 | request.subscribe() 30 | 31 | refreshSignal.onNext(Any()) 32 | } 33 | 34 | @Test 35 | fun repeat_pollingNetwork() { 36 | val request = httpBinApi.getRequest() 37 | val endOfPolling = System.currentTimeMillis() + 1000 38 | 39 | request.repeatWhen { observable -> observable.delay(100, TimeUnit.MILLISECONDS) } 40 | .takeUntil { System.currentTimeMillis() > endOfPolling } 41 | .subscribe() 42 | } 43 | 44 | @Test 45 | fun ambWith_effectiveCache() { 46 | val request = httpBinApi.getRequest() 47 | .subscribeOn(Schedulers.io()) 48 | .share() 49 | 50 | val requestWithCache = RequestInfoCache.requestInfo.mergeWith(request) 51 | 52 | val observableWithCache = request.ambWith(requestWithCache) 53 | observableWithCache.subscribe { println(it) } 54 | } 55 | 56 | @After 57 | fun after() { 58 | Thread.sleep(1000) 59 | HttpModule.awaitNetworkRequests() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':rx-codelab' 2 | --------------------------------------------------------------------------------