├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imgs └── presentation.gif ├── settings.gradle ├── tools ├── close-task.py ├── download.py ├── upload-keys.py └── upload.py ├── websockets-rxjava-example ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ ├── appunite │ │ │ ├── detector │ │ │ │ ├── ChangesDetector.java │ │ │ │ └── SimpleDetector.java │ │ │ ├── socket │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainAdapter.java │ │ │ │ └── MainPresenter.java │ │ │ └── websocket │ │ │ │ └── rx │ │ │ │ └── object │ │ │ │ └── GsonObjectSerializer.java │ │ │ └── example │ │ │ ├── LoggingObservables.java │ │ │ ├── MoreObservables.java │ │ │ ├── OperatorDoOnNext.java │ │ │ ├── Socket.java │ │ │ ├── SocketConnection.java │ │ │ ├── SocketConnectionImpl.java │ │ │ └── model │ │ │ ├── ChatMessage.java │ │ │ ├── DataMessage.java │ │ │ ├── ErrorMessage.java │ │ │ ├── Message.java │ │ │ ├── MessageType.java │ │ │ ├── PingMessage.java │ │ │ ├── PongMessage.java │ │ │ ├── RegisterMessage.java │ │ │ └── RegisteredMessage.java │ └── res │ │ ├── layout │ │ ├── main_activity.xml │ │ └── main_adapter_item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ ├── appunite │ └── detector │ │ └── ChangesDetectorTest.java │ └── example │ ├── RxObjectWebSocketsRealTest.java │ ├── RxWebSocketsRealTest.java │ ├── SocketRealTest.java │ └── SocketTest.java ├── websockets-rxjava ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── appunite │ └── websocket │ └── rx │ ├── RxMoreObservables.java │ ├── RxWebSockets.java │ ├── ServerHttpError.java │ ├── ServerRequestedCloseException.java │ ├── messages │ ├── RxEvent.java │ ├── RxEventBinaryMessage.java │ ├── RxEventBinaryMessageAbs.java │ ├── RxEventConn.java │ ├── RxEventConnected.java │ ├── RxEventDisconnected.java │ ├── RxEventPong.java │ └── RxEventStringMessage.java │ └── object │ ├── ObjectParseException.java │ ├── ObjectSerializer.java │ ├── ObjectWebSocketSender.java │ ├── RxObjectWebSockets.java │ └── messages │ ├── RxObjectEvent.java │ ├── RxObjectEventConn.java │ ├── RxObjectEventConnected.java │ ├── RxObjectEventDisconnected.java │ ├── RxObjectEventMessage.java │ ├── RxObjectEventWrongBinaryMessageFormat.java │ ├── RxObjectEventWrongMessageFormat.java │ └── RxObjectEventWrongStringMessageFormat.java └── websockets-server ├── Dockerfile ├── README.md └── server ├── example ├── __init__.py └── hub │ ├── __init__.py │ ├── __init__.py~ │ ├── __init__.pz~ │ └── websocket.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | #Python 2 | *.pyc 3 | websockets-server/server/build/** 4 | websockets-server/venv/** 5 | websockets-server/server/websockets_server.egg-info/** 6 | websockets-server/server/dist/** 7 | 8 | #Android generated 9 | bin 10 | gen 11 | lint.xml 12 | 13 | #Eclipse 14 | .project 15 | .classpath 16 | .settings 17 | .checkstyle 18 | 19 | #IntelliJ IDEA 20 | .idea 21 | *.iml 22 | *.ipr 23 | *.iws 24 | classes 25 | gen-external-apklibs 26 | 27 | #Maven 28 | target 29 | release.properties 30 | pom.xml.* 31 | 32 | #Ant 33 | build.xml 34 | ant.properties 35 | local.properties 36 | proguard.cfg 37 | proguard-project.txt 38 | 39 | #Gradle 40 | .gradle 41 | build 42 | gradle.properties 43 | 44 | #Secrets 45 | credentials.properties 46 | 47 | #Other 48 | .DS_Store 49 | tmp 50 | *.swp 51 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - echo "org.gradle.jvmargs=-XX:MaxPermSize=2g" > gradle.properties 3 | 4 | build-app: 5 | image: jacekmarchwicki/android 6 | script: 7 | - python tools/download.py --token N0EhBUUHLRIRcC9ZuVjxRLZU --key-version agxzfmF1dG8tY2xvc2VyGAsSC1Byb2plY3RLZXlzGICAgICG7IcJDA 8 | - ./gradlew --parallel --stacktrace build 9 | - python tools/upload.py --token N0EhBUUHLRIRcC9ZuVjxRLZU --build-name "${CI_BUILD_REF_NAME}-${CI_BUILD_REF}" websockets-rxjava-example/build/outputs/ websockets-rxjava/build/docs/javadoc/ websockets-rxjava/build/libs/ 10 | tags: 11 | - docker 12 | except: 13 | - tags 14 | 15 | build-server: 16 | image: python:2.7.10 17 | script: 18 | - pip install virtualenv==13.1.2 19 | - cd websockets-server 20 | - virtualenv --no-site-packages venv 21 | - source venv/bin/activate 22 | - cd server 23 | - python setup.py install 24 | - websockets-server --host localhost --port 8080 --stdio & 25 | - SERVER_PID=$! 26 | - sleep 10 27 | - kill -0 $SERVER_PID 28 | tags: 29 | - docker 30 | except: 31 | - tags 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | jdk: 4 | - oraclejdk8 5 | 6 | android: 7 | components: 8 | - tools 9 | - platform-tools 10 | - build-tools-25.0.2 11 | - android-25 12 | - extra-google-m2repository 13 | - extra-android-m2repository 14 | 15 | script: ./gradlew build 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaWebsocketClient also for Android 2 | JavaWebsocketClient is library is simple library for Websocket connection in rx for java and Android. 3 | It is designed to be fast and fault tolerant. 4 | 5 | Currently we use okhttp3 for websocket connection because okhttp3 is simple and well tested solution. 6 | 7 | [![Build Status](https://travis-ci.org/jacek-marchwicki/JavaWebsocketClient.svg?branch=master)](https://travis-ci.org/jacek-marchwicki/JavaWebsocketClient) 8 | 9 | [![Release](https://jitpack.io/v/jacek-marchwicki/JavaWebsocketClient.svg)](https://jitpack.io/#jacek-marchwicki/JavaWebsocketClient) 10 | 11 | ![Presentation of example](imgs/presentation.gif) 12 | 13 | ## Content of the package 14 | 15 | * Example websockets server [python twisted server](websockets-server/README.md) 16 | * Rx-java websocket client library `websockets-rxjava/` 17 | * Rx-java websocket android example `websockets-rxjava-example/` 18 | 19 | ## Reactive example 20 | 21 | How to connect to server: 22 | 23 | ```java 24 | final Request request = new Request.Builder() 25 | .get() 26 | .url("ws://10.10.0.2:8080/ws") 27 | .build(); 28 | final Subscription subscribe = new RxWebSockets(new OkHttpClient(), request) 29 | .webSocketObservable() 30 | .subscribe(new Action1() { 31 | @Override 32 | public void call(RxEvent rxEvent) { 33 | System.out.println("Event: " + rxEvent.toString()); 34 | } 35 | }); 36 | Thread.sleep(10000); 37 | subscribe.unsubscribe(); 38 | ``` 39 | 40 | Send message on connected: 41 | 42 | ```java 43 | final Subscription subscription = new RxWebSockets(newWebSocket, request) 44 | .webSocketObservable() 45 | .subscribe(new Action1() { 46 | @Override 47 | public void call(RxEvent rxEvent) { 48 | if (rxEvent instanceof RxEventConnected) { 49 | Observable.just("response") 50 | .compose(RxMoreObservables.sendMessage((RxEventConnected) rxEvent)) 51 | .subscribe(); 52 | } 53 | } 54 | }); 55 | Thread.sleep(1000); 56 | subscription.unsubscribe(); 57 | ``` 58 | 59 | For examples look: 60 | * Android example: [Activity](websockets-rxjava-example/src/main/java/com/appunite/socket/MainActivity.java) [Presenter](websockets-rxjava-example/src/main/java/com/appunite/socket/MainPresenter.java) 61 | * Example Real tests: [RxJsonWebSocketsRealTest](websockets-rxjava-example/src/test/java/com/example/RxObjectWebSocketsRealTest.java), [RxWebSocketsRealTest](websockets-rxjava-example/src/test/java/com/example/RxWebSocketsRealTest.java), [SocketRealTest](websockets-rxjava-example/src/test/java/com/example/SocketRealTest.java) 62 | * [Unit test](websockets-rxjava-example/src/test/java/com/example/SocketTest.java) 63 | 64 | ## Rx-java with json parser 65 | 66 | ```java 67 | class YourMessage { 68 | public String response; 69 | public String error; 70 | } 71 | 72 | final Request request = new Request.Builder() 73 | .get() 74 | .url("ws://10.10.0.2:8080/ws") 75 | .build(); 76 | final RxWebSockets rxWebSockets = new RxWebSockets(new OkHttpClient(), request) 77 | final ObjectSerializer serializer = new GsonObjectSerializer(new Gson(), Message.class) 78 | final RxObjectWebSockets webSockets = new RxObjectWebSockets(rxWebSockets), serializer); 79 | final Subscription subscription = webSockets.webSocketObservable() 80 | .compose(MoreObservables.filterAndMap(RxObjectEventMessage.class)) 81 | .compose(RxObjectEventMessage.filterAndMap(YourMessage.class)) 82 | .subscribe(new Action1() { 83 | @Override 84 | public void call(YourMessage yourMessage) { 85 | System.out.println("your message: " + yourMessage.response); 86 | } 87 | }); 88 | Thread.sleep(1000); 89 | subscription.unsubscribe(); 90 | ``` 91 | 92 | ## Run example from gradle 93 | 94 | To run example first run [websocket server](websockets-server/README.md), than update url to your host in: 95 | * [Rx-java Activity](websockets-rxjava-example/src/main/java/com/appunite/socket/MainActivity.java) 96 | 97 | ```bash 98 | ./gradlew :websockets-rxjava-example:installDebug 99 | ``` 100 | 101 | ## How to add to your project 102 | 103 | to your gradle file: 104 | 105 | ```groovy 106 | repositories { 107 | maven { url "https://jitpack.io" } 108 | } 109 | 110 | dependencies { 111 | 112 | // snapshot version 113 | compile 'com.github.jacek-marchwicki:JavaWebsocketClient:master-SNAPSHOT' 114 | 115 | // or use specific version 116 | compile 'com.github.jacek-marchwicki:JavaWebsocketClient:' 117 | } 118 | ``` 119 | 120 | ## License 121 | 122 | Copyright [2015] [Jacek Marchwicki ] 123 | 124 | Licensed under the Apache License, Version 2.0 (the "License"); 125 | you may not use this file except in compliance with the License. 126 | You may obtain a copy of the License at 127 | 128 | http://www.apache.org/licenses/LICENSE-2.0 129 | 130 | 131 | Unless required by applicable law or agreed to in writing, software 132 | distributed under the License is distributed on an "AS IS" BASIS, 133 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134 | See the License for the specific language governing permissions and 135 | limitations under the License. 136 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | task wrapper(type: Wrapper) { 2 | gradleVersion = '2.1' 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacek-marchwicki/JavaWebsocketClient/a6beec67ce611caa234eef3ed1dc341126ffffb7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 22 17:26:20 CET 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-3.3-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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /imgs/presentation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacek-marchwicki/JavaWebsocketClient/a6beec67ce611caa234eef3ed1dc341126ffffb7/imgs/presentation.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ":websockets-rxjava" 2 | include ":websockets-rxjava-example" 3 | -------------------------------------------------------------------------------- /tools/close-task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Jacek Marchwicki 4 | 5 | __author__ = 'Jacek Marchwicki ' 6 | 7 | 8 | import subprocess 9 | import re 10 | import unittest 11 | import argparse 12 | import sys 13 | 14 | import urllib2 15 | import json 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser(description='Manage task.') 20 | parser.add_argument('--token', dest='token', required=True, nargs='?', 21 | type=str, help='token') 22 | parser.add_argument('--merge', dest='merge', action='store_const', 23 | const=True, default=False, help='task is merged') 24 | parser.add_argument('--base-url', dest='base_url', nargs="?", 25 | type=str, help='base url', default="https://auto-close.appspot.com/") 26 | parser.add_argument('message', metavar='MESSAGE', type=str, nargs=1, 27 | help='an message added to task') 28 | args = parser.parse_args() 29 | 30 | message = args.message[0] 31 | commit_message = get_commit_message_lines() 32 | closed_urls = get_urls(commit_message, lambda line: get_url_by_keyword("Closes", line)) 33 | referred_urls = get_urls(commit_message, lambda line: get_url_by_keyword("Refers", line)) 34 | 35 | if "[wip]" in commit_message[0].lower(): 36 | write_message(args.base_url, referred_urls, args.token, "Referred in [WIP] commit: " + message) 37 | write_message(args.base_url, closed_urls, args.token, "[WIP] commit pushed to review: " + message) 38 | elif args.merge: 39 | write_message(args.base_url, referred_urls, args.token, "Referred commit merged: " + message) 40 | close_urls(args.base_url, closed_urls, args.token, "Commit merged in review: " + message) 41 | else: 42 | write_message(args.base_url, referred_urls, args.token, "Referred in commit: " + message) 43 | review_urls(args.base_url, closed_urls, args.token, "Commit pushed to review: " + message) 44 | 45 | 46 | handler = urllib2.HTTPSHandler(debuglevel=0) 47 | opener = urllib2.build_opener(handler) 48 | 49 | 50 | def write_message(base_url, urls, token, message): 51 | for url in urls: 52 | data = { 53 | "message": message, 54 | "token": token, 55 | "url": url 56 | } 57 | execute(base_url, data) 58 | 59 | 60 | def close_urls(base_url, urls, token, message): 61 | for url in urls: 62 | data = { 63 | "message": message, 64 | "close": "true", 65 | "token": token, 66 | "url": url 67 | } 68 | execute(base_url, data) 69 | 70 | 71 | def review_urls(base_url, urls, token, message): 72 | for url in urls: 73 | data = { 74 | "message": message, 75 | "review": "true", 76 | "token": token, 77 | "url": url 78 | } 79 | execute(base_url, data) 80 | 81 | 82 | def execute(base_url, data): 83 | headers = { 84 | "Content-Type": "application/json" 85 | } 86 | request = urllib2.Request("%sclose" % base_url, json.dumps(data), headers) 87 | try: 88 | response = opener.open(request) 89 | return json.loads(response.read()) 90 | except urllib2.HTTPError as e: 91 | print >> sys.stderr, "Response from server: %s" % e.read() 92 | raise e 93 | 94 | 95 | def get_commit_message_lines(): 96 | commit_message = subprocess.check_output(["git", "log", "-1", "--pretty=%B"]).splitlines() 97 | commit_message = [i.strip() for i in commit_message] 98 | return commit_message 99 | 100 | 101 | def get_urls(commit_message, f): 102 | urls = [] 103 | for commit_line in commit_message: 104 | url = f(commit_line) 105 | if url: 106 | urls.append(url) 107 | return urls 108 | 109 | 110 | def do_work(): 111 | commit_message = get_commit_message_lines() 112 | closed_urls = get_urls(commit_message, lambda line: get_url_by_keyword("Closes", line)) 113 | refered_urls = get_urls(commit_message, lambda line: get_url_by_keyword("Refer", line)) 114 | print closed_urls 115 | print refered_urls 116 | 117 | 118 | def get_url_by_keyword(keyword, line): 119 | match = re.match("^%s:? ([\w:\/\.-]+)$" % keyword, line) 120 | if not match: 121 | return None 122 | return match.group(1) 123 | 124 | 125 | class TestGetLinkMethod(unittest.TestCase): 126 | """ 127 | Run unit tests: python -m unittest close-task 128 | """ 129 | 130 | def test_valid_url(self): 131 | self.assertEqual( 132 | get_url_by_keyword("Closes", "Closes https://appunite.dobambam.com/project/100125/tasks/t2038"), 133 | 'https://appunite.dobambam.com/project/100125/tasks/t2038') 134 | 135 | def test_refer_url(self): 136 | self.assertEqual(get_url_by_keyword("Refer", "Refer https://appunite.dobambam.com/project/100125/tasks/t2038"), 137 | 'https://appunite.dobambam.com/project/100125/tasks/t2038') 138 | 139 | def test_valid_close_with_semicolon(self): 140 | self.assertEqual( 141 | get_url_by_keyword("Closes", "Closes: https://appunite.dobambam.com/project/100125/tasks/t2038"), 142 | 'https://appunite.dobambam.com/project/100125/tasks/t2038') 143 | 144 | def test_invalid_close_after_some_char(self): 145 | self.assertIsNone( 146 | get_url_by_keyword("Closes", "xCloses: https://appunite.dobambam.com/project/100125/tasks/t2038")) 147 | 148 | def test_invalid_url(self): 149 | self.assertIsNone( 150 | get_url_by_keyword("Closes", "Closes: https://appunite.dobambam.com/project/100125/tasks/t2038[]")) 151 | 152 | 153 | if __name__ == '__main__': 154 | main() 155 | -------------------------------------------------------------------------------- /tools/download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Jacek Marchwicki 4 | 5 | __author__ = 'Jacek Marchwicki ' 6 | 7 | 8 | import argparse 9 | import subprocess 10 | import urlparse 11 | 12 | 13 | def main(): 14 | parser = argparse.ArgumentParser(description='Manage task.') 15 | parser.add_argument('--token', dest='token', required=True, nargs="?", 16 | type=str, help='token') 17 | parser.add_argument('--key-version', dest='key_version', required=False, nargs="?", 18 | type=str, help='key_version') 19 | parser.add_argument('--base-url', dest='base_url', nargs="?", 20 | type=str, help='base url', default="https://auto-close.appspot.com/") 21 | args = parser.parse_args() 22 | 23 | if args.key_version: 24 | request = "build/keys?token=%s&version=%s" % (args.token, args.key_version) 25 | else: 26 | request = "build/keys?token=%s" % (args.token, ) 27 | 28 | path = urlparse.urljoin(args.base_url, request) 29 | command = "curl --location --silent \"%s\" | tar -jxvf -" % (path) 30 | subprocess.check_call(command, shell=True) 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /tools/upload-keys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Jacek Marchwicki 4 | 5 | __author__ = 'Jacek Marchwicki ' 6 | 7 | 8 | import argparse 9 | import sys 10 | import urlparse 11 | import urllib2 12 | import json 13 | import subprocess 14 | 15 | 16 | def main(): 17 | parser = argparse.ArgumentParser(description='Manage task.') 18 | parser.add_argument('--token', dest='token', required=True, nargs="?", 19 | type=str, help='token') 20 | parser.add_argument('--base-url', dest='base_url', nargs="?", 21 | type=str, help='base url', default="https://auto-close.appspot.com/") 22 | parser.add_argument('files', metavar='FILES', type=str, nargs="+", 23 | help='files to upload') 24 | args = parser.parse_args() 25 | 26 | response = execute(urlparse.urljoin(args.base_url, "build/keys"), { 27 | "token": args.token, 28 | }) 29 | 30 | url = response["upload"]["url"] 31 | version = response["version"] 32 | 33 | command = "tar jcvf - %s | curl --request PUT --upload-file - \"%s\"" % (" ".join(args.files), url) 34 | subprocess.check_call(command, shell=True) 35 | 36 | print "Version of keys: %s" % version 37 | 38 | handler = urllib2.HTTPSHandler(debuglevel=0) 39 | opener = urllib2.build_opener(handler) 40 | 41 | 42 | def execute(base_url, data): 43 | headers = { 44 | "Content-Type": "application/json" 45 | } 46 | request = urllib2.Request(base_url, json.dumps(data), headers) 47 | try: 48 | response = opener.open(request) 49 | return json.loads(response.read()) 50 | except urllib2.HTTPError as e: 51 | print >> sys.stderr, "Response from server: %s" % e.read() 52 | raise e 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /tools/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Jacek Marchwicki 4 | 5 | __author__ = 'Jacek Marchwicki ' 6 | 7 | 8 | import argparse 9 | import sys 10 | import urlparse 11 | import urllib2 12 | import json 13 | import subprocess 14 | 15 | 16 | def main(): 17 | parser = argparse.ArgumentParser(description='Manage task.') 18 | parser.add_argument('--token', dest='token', required=True, nargs="?", 19 | type=str, help='token') 20 | parser.add_argument('--build-name', dest='build_name', required=True, nargs="?", 21 | type=str, help='build name') 22 | parser.add_argument('--base-url', dest='base_url', nargs="?", 23 | type=str, help='base url', default="https://auto-close.appspot.com/") 24 | parser.add_argument('files', metavar='FILES', type=str, nargs="+", 25 | help='files to upload') 26 | args = parser.parse_args() 27 | 28 | response = execute(urlparse.urljoin(args.base_url, "build"), { 29 | "token": args.token, 30 | "build_name": args.build_name 31 | }) 32 | 33 | url = response["upload"]["url"] 34 | download_url = response["download"] 35 | 36 | command = "tar jcvf - %s | curl --request PUT --upload-file - \"%s\"" % (" ".join(args.files), url) 37 | subprocess.check_call(command, shell=True) 38 | 39 | print "Serving artifacts at: %s" % download_url 40 | 41 | handler = urllib2.HTTPSHandler(debuglevel=0) 42 | opener = urllib2.build_opener(handler) 43 | 44 | 45 | def execute(base_url, data): 46 | headers = { 47 | "Content-Type": "application/json" 48 | } 49 | request = urllib2.Request(base_url, json.dumps(data), headers) 50 | try: 51 | response = opener.open(request) 52 | return json.loads(response.read()) 53 | except urllib2.HTTPError as e: 54 | print >> sys.stderr, "Response from server: %s" % e.read() 55 | raise e 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /websockets-rxjava-example/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.3.0' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.application' 12 | 13 | repositories { 14 | mavenCentral() 15 | jcenter() 16 | maven { url 'https://commondatastorage.googleapis.com/maven-repository/' } 17 | } 18 | 19 | dependencies { 20 | compile project(":websockets-rxjava") 21 | compile 'io.reactivex:rxandroid:0.24.0' 22 | compile 'com.android.support:recyclerview-v7:25.3.0' 23 | compile 'com.android.support:support-v4:25.3.0' 24 | compile 'com.google.guava:guava:20.0' 25 | compile "com.google.code.gson:gson:2.7" 26 | //compile "com.appunite:websockets:1.0" 27 | 28 | testCompile "org.hamcrest:hamcrest-all:1.3" 29 | testCompile 'junit:junit:4.11' 30 | testCompile 'org.mockito:mockito-all:1.9.5' 31 | testCompile 'com.google.truth:truth:0.25' 32 | testCompile 'com.google.guava:guava:20.0' 33 | } 34 | 35 | android { 36 | compileSdkVersion 25 37 | buildToolsVersion '25.0.2' 38 | 39 | defaultConfig { 40 | minSdkVersion 9 41 | targetSdkVersion 25 42 | } 43 | buildTypes { 44 | release { 45 | proguardFiles = [getDefaultProguardFile('proguard-android.txt'), 'src/main/proguard-project.txt'] 46 | } 47 | } 48 | lintOptions { 49 | disable 'InvalidPackage' 50 | textOutput "stdout" 51 | textReport true 52 | } 53 | 54 | compileOptions { 55 | sourceCompatibility JavaVersion.VERSION_1_7 56 | targetCompatibility JavaVersion.VERSION_1_7 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacek-marchwicki/JavaWebsocketClient/a6beec67ce611caa234eef3ed1dc341126ffffb7/websockets-rxjava-example/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/detector/ChangesDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.appunite.detector; 18 | 19 | import com.google.common.base.Function; 20 | import com.google.common.collect.FluentIterable; 21 | import com.google.common.collect.ImmutableList; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import javax.annotation.Nonnull; 27 | 28 | import static com.google.common.base.Preconditions.checkNotNull; 29 | 30 | public class ChangesDetector { 31 | 32 | public static interface ChangesAdapter { 33 | 34 | void notifyItemRangeInserted(int start, int count); 35 | 36 | void notifyItemRangeChanged(int start, int count); 37 | 38 | void notifyItemRangeRemoved(int start, int count); 39 | } 40 | 41 | @Nonnull 42 | public List mItems = new ArrayList<>(); 43 | @Nonnull 44 | public final Detector mDetector; 45 | 46 | public ChangesDetector(@Nonnull Detector detector) { 47 | mDetector = checkNotNull(detector); 48 | } 49 | 50 | public static interface Detector extends Function { 51 | 52 | @SuppressWarnings("NullableProblems") 53 | @Nonnull 54 | H apply(@Nonnull T item); 55 | 56 | boolean matches(@Nonnull H item, @Nonnull H newOne); 57 | 58 | boolean same(@Nonnull H item, @Nonnull H newOne); 59 | } 60 | 61 | private int indexOf(@Nonnull List list, 62 | int start, 63 | @Nonnull H item) { 64 | for (int i = start, listSize = list.size(); i < listSize; i++) { 65 | H t = list.get(i); 66 | if (mDetector.matches(t, item)) { 67 | return i; 68 | } 69 | } 70 | return -1; 71 | } 72 | 73 | public void newData(@Nonnull ChangesAdapter adapter, 74 | @Nonnull List values, 75 | boolean force) { 76 | checkNotNull(adapter); 77 | checkNotNull(values); 78 | 79 | final ImmutableList list = FluentIterable.from(values) 80 | .transform(mDetector) 81 | .toList(); 82 | 83 | int firstListPosition = 0; 84 | int secondListPosition = 0; 85 | 86 | int counter = 0; 87 | int toRemove = 0; 88 | 89 | for (;firstListPosition < mItems.size(); ++firstListPosition) { 90 | final H first = mItems.get(firstListPosition); 91 | final int indexOf = indexOf(list, secondListPosition, first); 92 | if (indexOf >= 0) { 93 | int itemsInserted = indexOf - secondListPosition; 94 | counter = notify(adapter, counter, toRemove, itemsInserted); 95 | toRemove = 0; 96 | secondListPosition = indexOf + 1; 97 | 98 | final H second = list.get(indexOf); 99 | if (force || !mDetector.same(first, second)) { 100 | adapter.notifyItemRangeChanged(counter, 1); 101 | } 102 | counter += 1; 103 | } else { 104 | toRemove += 1; 105 | } 106 | } 107 | 108 | int itemsInserted = values.size() - secondListPosition; 109 | notify(adapter, counter, toRemove, itemsInserted); 110 | mItems = list; 111 | } 112 | 113 | private int notify(@Nonnull ChangesAdapter adapter, 114 | int counter, 115 | int toRemove, 116 | int itemsInserted) { 117 | final int itemsChanged = Math.min(itemsInserted, toRemove); 118 | toRemove -= itemsChanged; 119 | itemsInserted -= itemsChanged; 120 | if (itemsChanged > 0) { 121 | adapter.notifyItemRangeChanged(counter, itemsChanged); 122 | counter += itemsChanged; 123 | } 124 | if (toRemove > 0) { 125 | adapter.notifyItemRangeRemoved(counter, toRemove); 126 | } 127 | if (itemsInserted > 0) { 128 | adapter.notifyItemRangeInserted(counter, itemsInserted); 129 | counter += itemsInserted; 130 | } 131 | return counter; 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/detector/SimpleDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.appunite.detector; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class SimpleDetector> implements ChangesDetector.Detector { 22 | 23 | public static interface Detectable { 24 | public boolean matches(@Nonnull T item); 25 | public boolean same(@Nonnull T item); 26 | } 27 | 28 | @Nonnull 29 | @Override 30 | public T apply(@Nonnull T item) { 31 | return item; 32 | } 33 | 34 | @Override 35 | public boolean matches(@Nonnull T item, @Nonnull T newOne) { 36 | return item.matches(newOne); 37 | } 38 | 39 | @Override 40 | public boolean same(@Nonnull T item, @Nonnull T newOne) { 41 | return item.same(newOne); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/socket/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.appunite.socket; 18 | 19 | import android.os.Bundle; 20 | import android.support.v4.app.Fragment; 21 | import android.support.v4.app.FragmentActivity; 22 | import android.support.v7.widget.DefaultItemAnimator; 23 | import android.support.v7.widget.LinearLayoutManager; 24 | import android.support.v7.widget.RecyclerView; 25 | 26 | import com.appunite.websocket.rx.RxWebSockets; 27 | import com.appunite.websocket.rx.object.GsonObjectSerializer; 28 | import com.appunite.websocket.rx.object.RxObjectWebSockets; 29 | import com.example.Socket; 30 | import com.example.SocketConnection; 31 | import com.example.SocketConnectionImpl; 32 | import com.example.model.Message; 33 | import com.example.model.MessageType; 34 | import com.google.gson.Gson; 35 | import com.google.gson.GsonBuilder; 36 | import okhttp3.OkHttpClient; 37 | import okhttp3.Request; 38 | 39 | import rx.android.schedulers.AndroidSchedulers; 40 | import rx.android.view.ViewActions; 41 | import rx.android.view.ViewObservable; 42 | import rx.functions.Action1; 43 | import rx.schedulers.Schedulers; 44 | import rx.subjects.BehaviorSubject; 45 | import rx.subscriptions.CompositeSubscription; 46 | 47 | public class MainActivity extends FragmentActivity { 48 | 49 | private static final String RETENTION_FRAGMENT_TAG = "retention_fragment_tag"; 50 | 51 | private CompositeSubscription subs; 52 | 53 | @Override 54 | public void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | final MainPresenter presenter = getRetentionFragment(savedInstanceState).presenter(); 57 | 58 | setContentView(R.layout.main_activity); 59 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_activity_recycler_view); 60 | final LinearLayoutManager layout = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 61 | recyclerView.setLayoutManager(layout); 62 | final MainAdapter adapter = new MainAdapter(); 63 | recyclerView.setAdapter(adapter); 64 | recyclerView.setItemAnimator(new DefaultItemAnimator()); 65 | 66 | final BehaviorSubject isLastSubject = BehaviorSubject.create(true); 67 | recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { 68 | boolean manualScrolling = false; 69 | 70 | @Override 71 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 72 | if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { 73 | manualScrolling = true; 74 | } 75 | if (manualScrolling && newState == RecyclerView.SCROLL_STATE_IDLE) { 76 | manualScrolling = false; 77 | 78 | final int lastVisibleItemPosition = layout.findLastVisibleItemPosition(); 79 | final int previousItemsCount = adapter.getItemCount(); 80 | final boolean isLast = previousItemsCount - 1 == lastVisibleItemPosition; 81 | isLastSubject.onNext(isLast); 82 | } 83 | } 84 | }); 85 | 86 | 87 | subs = new CompositeSubscription( 88 | isLastSubject.subscribe(presenter.lastItemInViewObserver()), 89 | presenter.itemsWithScrollObservable() 90 | .subscribe(new Action1() { 91 | @Override 92 | public void call(final MainPresenter.ItemsWithScroll itemsWithScroll) { 93 | adapter.call(itemsWithScroll.items()); 94 | if (itemsWithScroll.shouldScroll()) { 95 | recyclerView.post(new Runnable() { 96 | @Override 97 | public void run() { 98 | recyclerView.smoothScrollToPosition(itemsWithScroll.scrollToPosition()); 99 | } 100 | }); 101 | } 102 | } 103 | }), 104 | presenter.connectButtonEnabledObservable() 105 | .subscribe(ViewActions.setEnabled(findViewById(R.id.main_activity_connect_button))), 106 | presenter.disconnectButtonEnabledObservable() 107 | .subscribe(ViewActions.setEnabled(findViewById(R.id.macin_activity_disconnect_button))), 108 | presenter.sendButtonEnabledObservable() 109 | .subscribe(ViewActions.setEnabled(findViewById(R.id.main_activity_send_button))), 110 | ViewObservable.clicks(findViewById(R.id.main_activity_connect_button)) 111 | .subscribe(presenter.connectClickObserver()), 112 | ViewObservable.clicks(findViewById(R.id.macin_activity_disconnect_button)) 113 | .subscribe(presenter.disconnectClickObserver()), 114 | ViewObservable.clicks(findViewById(R.id.main_activity_send_button)) 115 | .subscribe(presenter.sendClickObserver())); 116 | } 117 | 118 | private RetentionFragment getRetentionFragment(Bundle savedInstanceState) { 119 | if (savedInstanceState == null) { 120 | final RetentionFragment retentionFragment = new RetentionFragment(); 121 | getSupportFragmentManager() 122 | .beginTransaction() 123 | .add(retentionFragment, RETENTION_FRAGMENT_TAG) 124 | .commit(); 125 | return retentionFragment; 126 | } else { 127 | return (RetentionFragment) getSupportFragmentManager() 128 | .findFragmentByTag(RETENTION_FRAGMENT_TAG); 129 | } 130 | } 131 | 132 | @Override 133 | protected void onDestroy() { 134 | super.onDestroy(); 135 | subs.unsubscribe(); 136 | } 137 | 138 | public static class RetentionFragment extends Fragment { 139 | 140 | private final MainPresenter presenter; 141 | 142 | public RetentionFragment() { 143 | final Gson gson = new GsonBuilder() 144 | .registerTypeAdapter(Message.class, new Message.Deserializer()) 145 | .registerTypeAdapter(MessageType.class, new MessageType.SerializerDeserializer()) 146 | .create(); 147 | 148 | final OkHttpClient okHttpClient = new OkHttpClient(); 149 | final RxWebSockets webSockets = new RxWebSockets(okHttpClient, new Request.Builder() 150 | .get() 151 | .url("ws://coreos2.appunite.net:8080/ws") 152 | .addHeader("Sec-WebSocket-Protocol", "chat") 153 | .build()); 154 | final GsonObjectSerializer serializer = new GsonObjectSerializer(gson, Message.class); 155 | final RxObjectWebSockets jsonWebSockets = new RxObjectWebSockets(webSockets, serializer); 156 | final SocketConnection socketConnection = new SocketConnectionImpl(jsonWebSockets, Schedulers.io()); 157 | presenter = new MainPresenter(new Socket(socketConnection, Schedulers.io()), Schedulers.io(), AndroidSchedulers.mainThread()); 158 | } 159 | 160 | @Override 161 | public void onCreate(Bundle savedInstanceState) { 162 | super.onCreate(savedInstanceState); 163 | setRetainInstance(true); 164 | } 165 | 166 | public MainPresenter presenter() { 167 | return presenter; 168 | } 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/socket/MainAdapter.java: -------------------------------------------------------------------------------- 1 | package com.appunite.socket; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import com.appunite.detector.ChangesDetector; 10 | import com.appunite.detector.SimpleDetector; 11 | import com.google.common.collect.ImmutableList; 12 | 13 | import java.text.DateFormat; 14 | import java.util.Date; 15 | 16 | import javax.annotation.Nonnull; 17 | 18 | import rx.android.view.ViewObservable; 19 | import rx.functions.Action1; 20 | import rx.subscriptions.CompositeSubscription; 21 | 22 | import static com.google.common.base.Preconditions.checkNotNull; 23 | 24 | abstract class BaseViewHolder extends RecyclerView.ViewHolder { 25 | 26 | public BaseViewHolder(View itemView) { 27 | super(itemView); 28 | } 29 | 30 | public abstract void bind(@Nonnull MainPresenter.AdapterItem item); 31 | 32 | public abstract void recycle(); 33 | } 34 | 35 | public class MainAdapter extends RecyclerView.Adapter implements 36 | Action1>, ChangesDetector.ChangesAdapter { 37 | 38 | 39 | private DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.MEDIUM); 40 | 41 | @Nonnull 42 | private final ChangesDetector changesDetector; 43 | @Nonnull 44 | private ImmutableList items = ImmutableList.of(); 45 | 46 | public MainAdapter() { 47 | this.changesDetector = new ChangesDetector<>(new SimpleDetector()); 48 | } 49 | 50 | @Override 51 | public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | final View view = LayoutInflater.from(parent.getContext()) 53 | .inflate(R.layout.main_adapter_item, parent, false); 54 | return new MainViewHolder(view); 55 | } 56 | 57 | @Override 58 | public void onBindViewHolder(BaseViewHolder holder, int position) { 59 | holder.bind(items.get(position)); 60 | } 61 | 62 | @Override 63 | public void onViewRecycled(BaseViewHolder holder) { 64 | super.onViewRecycled(holder); 65 | holder.recycle(); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return items.size(); 71 | } 72 | 73 | @Override 74 | public void call(@Nonnull ImmutableList items) { 75 | this.items = items; 76 | changesDetector.newData(this, items, false); 77 | } 78 | 79 | private class MainViewHolder extends BaseViewHolder { 80 | 81 | @Nonnull 82 | private final TextView text; 83 | @Nonnull 84 | private final TextView date; 85 | @Nonnull 86 | private final TextView details; 87 | private CompositeSubscription subscription; 88 | 89 | public MainViewHolder(@Nonnull View itemView) { 90 | super(itemView); 91 | text = checkNotNull((TextView) itemView.findViewById(R.id.main_adapter_item_text)); 92 | date = checkNotNull((TextView) itemView.findViewById(R.id.main_adapter_item_date)); 93 | details = checkNotNull((TextView) itemView.findViewById(R.id.main_adapter_item_details)); 94 | } 95 | 96 | @Override 97 | public void bind(@Nonnull MainPresenter.AdapterItem item) { 98 | text.setText(item.text()); 99 | date.setText(timeInstance.format(new Date(item.publishTime()))); 100 | details.setText(item.details()); 101 | details.setVisibility(item.details() == null ? View.GONE : View.VISIBLE); 102 | if (subscription != null) { 103 | subscription.unsubscribe(); 104 | } 105 | subscription = new CompositeSubscription( 106 | ViewObservable.clicks(text).subscribe(item.clickObserver()) 107 | ); 108 | } 109 | 110 | @Override 111 | public void recycle() { 112 | subscription.unsubscribe(); 113 | } 114 | 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/socket/MainPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.appunite.socket; 18 | 19 | import android.util.Pair; 20 | 21 | import com.appunite.detector.SimpleDetector; 22 | import com.appunite.websocket.rx.object.messages.RxObjectEvent; 23 | import com.appunite.websocket.rx.object.messages.RxObjectEventConn; 24 | import com.appunite.websocket.rx.object.messages.RxObjectEventDisconnected; 25 | import com.appunite.websocket.rx.object.messages.RxObjectEventMessage; 26 | import com.appunite.websocket.rx.object.messages.RxObjectEventWrongMessageFormat; 27 | import com.appunite.websocket.rx.object.messages.RxObjectEventWrongStringMessageFormat; 28 | import com.example.Socket; 29 | import com.example.model.DataMessage; 30 | import com.google.common.collect.ImmutableList; 31 | 32 | import javax.annotation.Nonnull; 33 | import javax.annotation.Nullable; 34 | 35 | import rx.Observable; 36 | import rx.Observer; 37 | import rx.Scheduler; 38 | import rx.Subscriber; 39 | import rx.Subscription; 40 | import rx.functions.Action1; 41 | import rx.functions.Func1; 42 | import rx.functions.Func2; 43 | import rx.observers.Observers; 44 | import rx.subjects.BehaviorSubject; 45 | import rx.subjects.PublishSubject; 46 | 47 | public class MainPresenter { 48 | 49 | private final BehaviorSubject> items; 50 | private final Observable connected; 51 | private final BehaviorSubject requestConnection = BehaviorSubject.create(); 52 | private final PublishSubject connectClick = PublishSubject.create(); 53 | private final PublishSubject disconnectClick = PublishSubject.create(); 54 | private final PublishSubject sendClick = PublishSubject.create(); 55 | private final BehaviorSubject lastItemInView = BehaviorSubject.create(); 56 | private final PublishSubject addItem = PublishSubject.create(); 57 | 58 | public MainPresenter(@Nonnull final Socket socket, 59 | @Nonnull final Scheduler networkScheduler, 60 | @Nonnull final Scheduler uiScheduler) { 61 | items = BehaviorSubject.create(); 62 | 63 | Observable.merge(connectClick.map(funcTrue()), disconnectClick.map(funcFalse())) 64 | .startWith(false) 65 | .subscribe(requestConnection); 66 | 67 | sendClick 68 | .flatMap(flatMapClicksToSendMessageAndResult(socket)) 69 | .map(mapDataMessageOrErrorToPair()) 70 | .map(mapPairToNewAdapterItem()) 71 | .subscribeOn(networkScheduler) 72 | .observeOn(uiScheduler) 73 | .subscribe(addItem); 74 | 75 | requestConnection 76 | .subscribe(new Action1() { 77 | 78 | private Subscription subscribe; 79 | 80 | @Override 81 | public void call(Boolean requestConnection) { 82 | if (requestConnection) { 83 | if (subscribe == null) { 84 | subscribe = socket 85 | .connection() 86 | .subscribeOn(networkScheduler) 87 | .observeOn(uiScheduler) 88 | .subscribe(); 89 | } 90 | } else { 91 | if (subscribe != null) { 92 | subscribe.unsubscribe(); 93 | subscribe = null; 94 | } 95 | } 96 | } 97 | }); 98 | 99 | requestConnection 100 | .map(mapConnectingStatusToString()) 101 | .map(mapStringToNewAdapterItem()) 102 | .subscribe(addItem); 103 | 104 | addItem 105 | .scan(ImmutableList.of(), new Func2, AdapterItem, ImmutableList>() { 106 | @Override 107 | public ImmutableList call(ImmutableList adapterItems, AdapterItem adapterItem) { 108 | return ImmutableList.builder().addAll(adapterItems).add(adapterItem).build(); 109 | } 110 | }) 111 | .subscribe(items); 112 | 113 | socket.events() 114 | .subscribeOn(networkScheduler) 115 | .observeOn(uiScheduler) 116 | .lift(liftRxJsonEventToPairMessage()) 117 | .map(mapPairToNewAdapterItem()) 118 | .subscribe(addItem); 119 | 120 | connected = socket.connectedAndRegistered() 121 | .map(new Func1() { 122 | @Override 123 | public Boolean call(RxObjectEventConn rxJsonEventConn) { 124 | return rxJsonEventConn != null; 125 | } 126 | }) 127 | .distinctUntilChanged() 128 | .subscribeOn(networkScheduler) 129 | .observeOn(uiScheduler); 130 | 131 | connected 132 | .map(mapConnectedStatusToString()) 133 | .map(mapStringToNewAdapterItem()) 134 | .subscribe(addItem); 135 | } 136 | 137 | @Nonnull 138 | public Observable itemsWithScrollObservable() { 139 | return Observable.combineLatest(items, lastItemInView, new Func2, Boolean, ItemsWithScroll>() { 140 | @Override 141 | public ItemsWithScroll call(ImmutableList adapterItems, Boolean isLastItemInList) { 142 | final int lastItemPosition = adapterItems.size() - 1; 143 | final boolean shouldScroll = isLastItemInList && lastItemPosition >= 0; 144 | return new ItemsWithScroll(adapterItems, shouldScroll, lastItemPosition); 145 | } 146 | }); 147 | } 148 | 149 | public Observer lastItemInViewObserver() { 150 | return lastItemInView; 151 | } 152 | 153 | private Func1 mapConnectedStatusToString() { 154 | return new Func1() { 155 | @Override 156 | public String call(Boolean connected) { 157 | return connected ? "connected" : "disconnected"; 158 | } 159 | }; 160 | } 161 | 162 | private Observable.Operator, RxObjectEvent> liftRxJsonEventToPairMessage() { 163 | return new Observable.Operator, RxObjectEvent>() { 164 | @Override 165 | public Subscriber call(final Subscriber> subscriber) { 166 | return new Subscriber(subscriber) { 167 | @Override 168 | public void onCompleted() { 169 | subscriber.onCompleted(); 170 | } 171 | 172 | @Override 173 | public void onError(Throwable e) { 174 | subscriber.onError(e); 175 | } 176 | 177 | @Override 178 | public void onNext(RxObjectEvent rxObjectEvent) { 179 | if (rxObjectEvent instanceof RxObjectEventMessage) { 180 | subscriber.onNext(new Pair<>("message", ((RxObjectEventMessage) rxObjectEvent).message().toString())); 181 | } else if (rxObjectEvent instanceof RxObjectEventWrongMessageFormat) { 182 | final RxObjectEventWrongStringMessageFormat wrongMessageFormat = (RxObjectEventWrongStringMessageFormat) rxObjectEvent; 183 | //noinspection ThrowableResultOfMethodCallIgnored 184 | subscriber.onNext(new Pair<>("could not parse message", wrongMessageFormat.message() 185 | + ", " + wrongMessageFormat.exception().toString())); 186 | } else if (rxObjectEvent instanceof RxObjectEventDisconnected) { 187 | //noinspection ThrowableResultOfMethodCallIgnored 188 | final Throwable exception = ((RxObjectEventDisconnected) rxObjectEvent).exception(); 189 | if (!(exception instanceof InterruptedException)) { 190 | subscriber.onNext(new Pair<>("error", exception.toString())); 191 | } 192 | } 193 | } 194 | }; 195 | } 196 | }; 197 | } 198 | 199 | private Func1 mapConnectingStatusToString() { 200 | return new Func1() { 201 | @Override 202 | public String call(Boolean aBoolean) { 203 | return aBoolean ? "connecting" : "disconnecting"; 204 | } 205 | }; 206 | } 207 | 208 | private Func1> flatMapClicksToSendMessageAndResult(@Nonnull final Socket socket) { 209 | return new Func1>() { 210 | @Override 211 | public Observable call(Object o) { 212 | addItem.onNext(newItem("sending...", null)); 213 | return socket 214 | .sendMessageOnceWhenConnected(new Func1>() { 215 | @Override 216 | public Observable call(String id) { 217 | return Observable.just(new DataMessage(id, "krowa")); 218 | } 219 | }) 220 | .map(new Func1() { 221 | @Override 222 | public DataMessageOrError call(DataMessage dataMessage) { 223 | return new DataMessageOrError(dataMessage, null); 224 | } 225 | }) 226 | .onErrorResumeNext(new Func1>() { 227 | @Override 228 | public Observable call(Throwable throwable) { 229 | return Observable.just(new DataMessageOrError(null, throwable)); 230 | } 231 | }); 232 | } 233 | }; 234 | } 235 | 236 | private Func1> mapDataMessageOrErrorToPair() { 237 | return new Func1>() { 238 | @Override 239 | public Pair call(DataMessageOrError dataMessageOrError) { 240 | if (dataMessageOrError.error != null) { 241 | return new Pair<>("sending error", dataMessageOrError.error.toString()); 242 | } else { 243 | return new Pair<>("sending response", dataMessageOrError.message.toString()); 244 | } 245 | } 246 | }; 247 | } 248 | 249 | 250 | @Nonnull 251 | private Func1 funcTrue() { 252 | return new Func1() { 253 | @Override 254 | public Boolean call(Object o) { 255 | return true; 256 | } 257 | }; 258 | } 259 | 260 | @Nonnull 261 | private Func1 funcFalse() { 262 | return new Func1() { 263 | @Override 264 | public Boolean call(Object o) { 265 | return false; 266 | } 267 | }; 268 | } 269 | 270 | @Nonnull 271 | private Func1 mapStringToNewAdapterItem() { 272 | return new Func1() { 273 | @Override 274 | public AdapterItem call(String s) { 275 | return newItem(s, null); 276 | } 277 | }; 278 | } 279 | 280 | @Nonnull 281 | private Func1, AdapterItem> mapPairToNewAdapterItem() { 282 | return new Func1, AdapterItem>() { 283 | @Override 284 | public AdapterItem call(Pair s) { 285 | return newItem(s.first, s.second); 286 | } 287 | }; 288 | } 289 | 290 | private final Object idLock = new Object(); 291 | private long id = 0; 292 | 293 | @Nonnull 294 | private String newId() { 295 | synchronized (idLock) { 296 | final long id = this.id; 297 | this.id += 1; 298 | return String.valueOf(id); 299 | } 300 | } 301 | 302 | @Nonnull 303 | private AdapterItem newItem(@Nonnull String message, @Nullable String details) { 304 | 305 | return new AdapterItem(newId(), System.currentTimeMillis(), message, details); 306 | } 307 | 308 | @Nonnull 309 | public Observer connectClickObserver() { 310 | return connectClick; 311 | } 312 | 313 | @Nonnull 314 | public Observer disconnectClickObserver() { 315 | return disconnectClick; 316 | } 317 | 318 | @Nonnull 319 | public Observer sendClickObserver() { 320 | return sendClick; 321 | } 322 | 323 | @Nonnull 324 | public Observable connectButtonEnabledObservable() { 325 | return requestConnection.map(not()); 326 | } 327 | 328 | @Nonnull 329 | public Observable disconnectButtonEnabledObservable() { 330 | return requestConnection; 331 | } 332 | 333 | @Nonnull 334 | public Observable sendButtonEnabledObservable() { 335 | return connected; 336 | } 337 | 338 | @Nonnull 339 | private Func1 not() { 340 | return new Func1() { 341 | @Override 342 | public Boolean call(Boolean aBoolean) { 343 | return !aBoolean; 344 | } 345 | }; 346 | } 347 | 348 | public class AdapterItem implements SimpleDetector.Detectable { 349 | 350 | @Nonnull 351 | private final String id; 352 | private final long publishTime; 353 | @Nullable 354 | private final String text; 355 | @Nullable 356 | private final String details; 357 | 358 | public AdapterItem(@Nonnull String id, 359 | long publishTime, 360 | @Nullable String text, 361 | @Nullable String details) { 362 | this.id = id; 363 | this.publishTime = publishTime; 364 | this.text = text; 365 | this.details = details; 366 | } 367 | 368 | @Nonnull 369 | public String id() { 370 | return id; 371 | } 372 | 373 | @Nullable 374 | public String text() { 375 | return text; 376 | } 377 | 378 | @Nullable 379 | public String details() { 380 | return details; 381 | } 382 | 383 | public long publishTime() { 384 | return publishTime; 385 | } 386 | 387 | @Override 388 | public boolean equals(Object o) { 389 | if (this == o) return true; 390 | if (!(o instanceof AdapterItem)) return false; 391 | 392 | final AdapterItem that = (AdapterItem) o; 393 | 394 | return id.equals(that.id) 395 | && !(text != null ? !text.equals(that.text) : that.text != null) 396 | && publishTime == that.publishTime 397 | && !(details != null ? !details.equals(that.details) : that.details != null); 398 | } 399 | 400 | @Override 401 | public int hashCode() { 402 | int result = id.hashCode(); 403 | result = 31 * result + (text != null ? text.hashCode() : 0); 404 | result = 31 * result + (details != null ? details.hashCode() : 0); 405 | result = 31 * result + (int)publishTime; 406 | return result; 407 | } 408 | 409 | @Override 410 | public boolean matches(@Nonnull AdapterItem item) { 411 | return id.equals(item.id); 412 | } 413 | 414 | @Override 415 | public boolean same(@Nonnull AdapterItem item) { 416 | return equals(item); 417 | } 418 | 419 | @Nonnull 420 | public Observer clickObserver() { 421 | return Observers.create(new Action1() { 422 | @Override 423 | public void call(Object o) { 424 | } 425 | }); 426 | } 427 | } 428 | 429 | public static class ItemsWithScroll { 430 | private final ImmutableList items; 431 | private final boolean shouldScroll; 432 | private final int scrollToPosition; 433 | 434 | public ItemsWithScroll(ImmutableList items, boolean shouldScroll, int scrollToPosition) { 435 | this.items = items; 436 | this.shouldScroll = shouldScroll; 437 | this.scrollToPosition = scrollToPosition; 438 | } 439 | 440 | public ImmutableList items() { 441 | return items; 442 | } 443 | 444 | public boolean shouldScroll() { 445 | return shouldScroll; 446 | } 447 | 448 | public int scrollToPosition() { 449 | return scrollToPosition; 450 | } 451 | } 452 | 453 | static class DataMessageOrError { 454 | private final DataMessage message; 455 | private final Throwable error; 456 | 457 | public DataMessageOrError(DataMessage message, Throwable error) { 458 | this.message = message; 459 | this.error = error; 460 | } 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/appunite/websocket/rx/object/GsonObjectSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.appunite.websocket.rx.object; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.JsonParseException; 21 | 22 | import java.lang.reflect.Type; 23 | 24 | import javax.annotation.Nonnull; 25 | 26 | public class GsonObjectSerializer implements ObjectSerializer { 27 | 28 | @Nonnull 29 | private final Gson gson; 30 | @Nonnull 31 | private final Type typeOfT; 32 | 33 | public GsonObjectSerializer(@Nonnull Gson gson, @Nonnull Type typeOfT) { 34 | this.gson = gson; 35 | this.typeOfT = typeOfT; 36 | } 37 | 38 | @Nonnull 39 | @Override 40 | public Object serialize(@Nonnull String message) throws ObjectParseException { 41 | try { 42 | return gson.fromJson(message, typeOfT); 43 | } catch (JsonParseException e) { 44 | throw new ObjectParseException("Could not parse", e); 45 | } 46 | } 47 | 48 | @Nonnull 49 | @Override 50 | public Object serialize(@Nonnull byte[] message) throws ObjectParseException { 51 | throw new ObjectParseException("Could not parse binary messages"); 52 | } 53 | 54 | @Nonnull 55 | @Override 56 | public byte[] deserializeBinary(@Nonnull Object message) throws ObjectParseException { 57 | throw new IllegalStateException("Only serialization to string is available"); 58 | } 59 | 60 | @Nonnull 61 | @Override 62 | public String deserializeString(@Nonnull Object message) throws ObjectParseException { 63 | try { 64 | return gson.toJson(message); 65 | } catch (JsonParseException e) { 66 | throw new ObjectParseException("Could not parse", e); 67 | } 68 | } 69 | 70 | @Override 71 | public boolean isBinary(@Nonnull Object message) { 72 | return false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/LoggingObservables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | 22 | import javax.annotation.Nonnull; 23 | 24 | import rx.Observable; 25 | import rx.Observer; 26 | import rx.internal.operators.OperatorDoOnEach; 27 | 28 | public class LoggingObservables { 29 | 30 | @Nonnull 31 | public static Observer logging(@Nonnull final Logger logger, @Nonnull final String tag) { 32 | return new Observer() { 33 | @Override 34 | public void onCompleted() { 35 | logger.log(Level.INFO, tag + " - onCompleted"); 36 | } 37 | 38 | @Override 39 | public void onError(Throwable e) { 40 | logger.log(Level.SEVERE, tag + " - onError", e); 41 | } 42 | 43 | @Override 44 | public void onNext(Object o) { 45 | logger.log(Level.INFO, tag + " - onNext: {0}", o == null ? "null" : o.toString()); 46 | } 47 | }; 48 | } 49 | 50 | @Nonnull 51 | public static Observer loggingOnlyError(@Nonnull final Logger logger, @Nonnull final String tag) { 52 | return new Observer() { 53 | @Override 54 | public void onCompleted() { 55 | } 56 | 57 | @Override 58 | public void onError(Throwable e) { 59 | logger.log(Level.SEVERE, tag + " - onError", e); 60 | } 61 | 62 | @Override 63 | public void onNext(Object o) { 64 | } 65 | }; 66 | } 67 | 68 | @Nonnull 69 | public static Observable.Operator loggingLift(@Nonnull Logger logger, @Nonnull String tag) { 70 | return new OperatorDoOnEach<>(logging(logger, tag)); 71 | } 72 | 73 | @Nonnull 74 | public static Observable.Operator loggingOnlyErrorLift(@Nonnull Logger logger, @Nonnull String tag) { 75 | return new OperatorDoOnEach<>(loggingOnlyError(logger, tag)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/MoreObservables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import com.appunite.websocket.rx.object.messages.RxObjectEvent; 20 | 21 | import javax.annotation.Nonnull; 22 | 23 | import rx.Observable; 24 | import rx.Subscriber; 25 | import rx.functions.Func0; 26 | import rx.functions.Func1; 27 | import rx.internal.operators.OperatorMulticast; 28 | import rx.subjects.BehaviorSubject; 29 | import rx.subjects.Subject; 30 | 31 | public class MoreObservables { 32 | 33 | @Nonnull 34 | public static Observable.Transformer behaviorRefCount() { 35 | return new Observable.Transformer() { 36 | @Override 37 | public Observable call(Observable tObservable) { 38 | return new OperatorMulticast<>(tObservable, new Func0>() { 39 | 40 | @Override 41 | public Subject call() { 42 | return BehaviorSubject.create(); 43 | } 44 | 45 | }).refCount(); 46 | } 47 | }; 48 | } 49 | 50 | @Nonnull 51 | public static Observable.Transformer filterAndMap(@Nonnull final Class clazz) { 52 | return new Observable.Transformer() { 53 | @Override 54 | public Observable call(Observable observable) { 55 | return observable 56 | .filter(new Func1() { 57 | @Override 58 | public Boolean call(Object o) { 59 | return o != null && clazz.isInstance(o); 60 | } 61 | }) 62 | .map(new Func1() { 63 | @Override 64 | public T call(Object o) { 65 | //noinspection unchecked 66 | return (T) o; 67 | } 68 | }); 69 | } 70 | }; 71 | } 72 | 73 | @Nonnull 74 | public static Func1 throwableToIgnoreError() { 75 | return new Func1() { 76 | @Override 77 | public Object call(Throwable throwable) { 78 | return new Object(); 79 | } 80 | }; 81 | } 82 | 83 | public static Observable.Operator ignoreNext() { 84 | return new Observable.Operator() { 85 | @Override 86 | public Subscriber call(final Subscriber subscriber) { 87 | return new Subscriber(subscriber) { 88 | @Override 89 | public void onCompleted() { 90 | subscriber.onCompleted(); 91 | } 92 | 93 | @Override 94 | public void onError(Throwable e) { 95 | subscriber.onError(e); 96 | } 97 | 98 | @Override 99 | public void onNext(RxObjectEvent rxObjectEvent) {} 100 | }; 101 | } 102 | }; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/OperatorDoOnNext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import rx.Observable.Operator; 20 | import rx.Observer; 21 | import rx.Subscriber; 22 | import rx.exceptions.Exceptions; 23 | import rx.exceptions.OnErrorThrowable; 24 | 25 | public class OperatorDoOnNext implements Operator { 26 | private final Observer doOnNextObserver; 27 | 28 | public OperatorDoOnNext(Observer doOnNextObserver) { 29 | this.doOnNextObserver = doOnNextObserver; 30 | } 31 | 32 | @Override 33 | public Subscriber call(final Subscriber observer) { 34 | return new Subscriber(observer) { 35 | 36 | @Override 37 | public void onCompleted() { 38 | } 39 | 40 | @Override 41 | public void onError(Throwable e) { 42 | } 43 | 44 | @Override 45 | public void onNext(T value) { 46 | doOnNextObserver.onNext(value); 47 | } 48 | }; 49 | } 50 | } -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/Socket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import com.appunite.websocket.rx.*; 20 | import com.appunite.websocket.rx.object.messages.RxObjectEvent; 21 | import com.appunite.websocket.rx.object.messages.RxObjectEventConn; 22 | import com.appunite.websocket.rx.object.messages.RxObjectEventConnected; 23 | import com.appunite.websocket.rx.object.messages.RxObjectEventDisconnected; 24 | import com.appunite.websocket.rx.object.messages.RxObjectEventMessage; 25 | import com.example.model.DataMessage; 26 | import com.example.model.PingMessage; 27 | import com.example.model.RegisterMessage; 28 | import com.example.model.RegisteredMessage; 29 | 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.logging.Level; 32 | import java.util.logging.Logger; 33 | 34 | import javax.annotation.Nonnull; 35 | 36 | import rx.Observable; 37 | import rx.Scheduler; 38 | import rx.Subscriber; 39 | import rx.functions.Func1; 40 | import rx.functions.Func2; 41 | import rx.subjects.BehaviorSubject; 42 | import rx.subjects.PublishSubject; 43 | 44 | public class Socket { 45 | public static final Logger LOGGER = Logger.getLogger("Rx"); 46 | 47 | private final Observable events; 48 | private final Observable connection; 49 | private final BehaviorSubject connectedAndRegistered; 50 | @Nonnull 51 | private final Scheduler scheduler; 52 | 53 | public Socket(@Nonnull SocketConnection socketConnection, @Nonnull Scheduler scheduler) { 54 | this.scheduler = scheduler; 55 | final PublishSubjectevents = PublishSubject.create(); 56 | connection = socketConnection.connection() 57 | .lift(new OperatorDoOnNext<>(events)) 58 | .lift(MoreObservables.ignoreNext()) 59 | .compose(MoreObservables.behaviorRefCount()); 60 | this.events = events; 61 | 62 | 63 | final Observable registeredMessage = events 64 | .compose(com.example.MoreObservables.filterAndMap(RxObjectEventMessage.class)) 65 | .filter(new FilterRegisterMessage()); 66 | 67 | final Observable disconnectedMessage = events 68 | .compose(com.example.MoreObservables.filterAndMap(RxObjectEventDisconnected.class)); 69 | 70 | connectedAndRegistered = BehaviorSubject.create((RxObjectEventConn) null); 71 | disconnectedMessage 72 | .map(new Func1() { 73 | @Override 74 | public RxObjectEventConn call(RxObjectEventDisconnected rxEventDisconnected) { 75 | return null; 76 | } 77 | }) 78 | .mergeWith(registeredMessage) 79 | .subscribe(connectedAndRegistered); 80 | 81 | // Register on connected 82 | final Observable connectedMessage = events 83 | .compose(com.example.MoreObservables.filterAndMap(RxObjectEventConnected.class)) 84 | .lift(LoggingObservables.loggingLift(LOGGER, "ConnectedEvent")); 85 | 86 | connectedMessage 87 | .flatMap(new Func1>() { 88 | @Override 89 | public Observable call(RxObjectEventConnected rxEventConn) { 90 | return RxMoreObservables.sendObjectMessage(rxEventConn.sender(), new RegisterMessage("asdf")) 91 | .toObservable(); 92 | } 93 | }) 94 | .lift(LoggingObservables.loggingOnlyErrorLift(LOGGER, "SendRegisterEvent")) 95 | .onErrorReturn(com.example.MoreObservables.throwableToIgnoreError()) 96 | .subscribe(); 97 | 98 | // Log events 99 | LOGGER.setLevel(Level.ALL); 100 | RxMoreObservables.logger.setLevel(Level.ALL); 101 | events 102 | .subscribe(LoggingObservables.logging(LOGGER, "Events")); 103 | connectedAndRegistered 104 | .subscribe(LoggingObservables.logging(LOGGER, "ConnectedAndRegistered")); 105 | } 106 | public Observable events() { 107 | return events; 108 | } 109 | 110 | public Observable connectedAndRegistered() { 111 | return connectedAndRegistered; 112 | } 113 | 114 | public Observable connection() { 115 | return connection; 116 | } 117 | 118 | public void sendPingWhenConnected() { 119 | Observable.combineLatest( 120 | Observable.interval(5, TimeUnit.SECONDS, scheduler), 121 | connectedAndRegistered, 122 | new Func2() { 123 | @Override 124 | public RxObjectEventConn call(Long aLong, RxObjectEventConn rxEventConn) { 125 | return rxEventConn; 126 | } 127 | }) 128 | .compose(isConnected()) 129 | .flatMap(new Func1>() { 130 | @Override 131 | public Observable call(RxObjectEventConn rxEventConn) { 132 | return RxMoreObservables.sendObjectMessage(rxEventConn.sender(), new PingMessage("send_only_when_connected")) 133 | .toObservable(); 134 | } 135 | }) 136 | .subscribe(); 137 | } 138 | 139 | public void sendPingEvery5seconds() { 140 | Observable.interval(5, TimeUnit.SECONDS, scheduler) 141 | .flatMap(new Func1>() { 142 | @Override 143 | public Observable call(Long aLong) { 144 | return connectedAndRegistered 145 | .compose(isConnected()) 146 | .first() 147 | .flatMap(new Func1>() { 148 | @Override 149 | public Observable call(RxObjectEventConn rxEventConn) { 150 | return RxMoreObservables.sendObjectMessage(rxEventConn.sender(), new PingMessage("be_sure_to_send")) 151 | .toObservable(); 152 | } 153 | }); 154 | } 155 | }) 156 | .subscribe(); 157 | } 158 | 159 | private final Object lock = new Object(); 160 | private int counter = 0; 161 | @Nonnull 162 | public Observable nextId() { 163 | return Observable.create(new Observable.OnSubscribe() { 164 | @Override 165 | public void call(Subscriber subscriber) { 166 | final int current; 167 | synchronized (lock) { 168 | current = counter; 169 | counter += 1; 170 | } 171 | subscriber.onNext(String.valueOf(current)); 172 | subscriber.onCompleted(); 173 | } 174 | }); 175 | } 176 | 177 | @Nonnull 178 | public Observable sendMessageOnceWhenConnected(final Func1> createMessage) { 179 | return connectedAndRegistered 180 | .compose(isConnected()) 181 | .first() 182 | .flatMap(new Func1>() { 183 | @Override 184 | public Observable call(final RxObjectEventConn rxEventConn) { 185 | return requestData(rxEventConn, createMessage); 186 | } 187 | }); 188 | } 189 | 190 | @Nonnull 191 | private Observable requestData(final RxObjectEventConn rxEventConn, 192 | final Func1> createMessage) { 193 | return nextId() 194 | .flatMap(new Func1>() { 195 | @Override 196 | public Observable call(final String messageId) { 197 | 198 | final Observable sendMessageObservable = createMessage.call(messageId) 199 | .flatMap(new Func1>() { 200 | @Override 201 | public Observable call(Object s) { 202 | return RxMoreObservables.sendObjectMessage(rxEventConn.sender(), s) 203 | .toObservable(); 204 | } 205 | }); 206 | 207 | final Observable waitForResponseObservable = events 208 | .compose(com.example.MoreObservables.filterAndMap(RxObjectEventMessage.class)) 209 | .compose(RxObjectEventMessage.filterAndMap(DataMessage.class)) 210 | .filter(new Func1() { 211 | @Override 212 | public Boolean call(DataMessage dataMessage) { 213 | return dataMessage.id().equals(messageId); 214 | } 215 | }) 216 | .first() 217 | .timeout(5, TimeUnit.SECONDS, scheduler); 218 | return Observable.combineLatest(waitForResponseObservable, sendMessageObservable, 219 | new Func2() { 220 | @Override 221 | public DataMessage call(DataMessage dataResponse, Object o) { 222 | return dataResponse; 223 | } 224 | }); 225 | } 226 | }); 227 | } 228 | 229 | @Nonnull 230 | private static Observable.Transformer isConnected() { 231 | return new Observable.Transformer() { 232 | @Override 233 | public Observable call(Observable rxEventConnObservable) { 234 | return rxEventConnObservable.filter(new Func1() { 235 | @Override 236 | public Boolean call(RxObjectEventConn rxEventConn) { 237 | return rxEventConn != null; 238 | } 239 | }); 240 | } 241 | }; 242 | } 243 | 244 | 245 | private static class FilterRegisterMessage implements Func1 { 246 | @Override 247 | public Boolean call(RxObjectEventMessage rxEvent) { 248 | return rxEvent.message() instanceof RegisteredMessage; 249 | } 250 | } 251 | 252 | 253 | } 254 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/SocketConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import com.appunite.websocket.rx.object.messages.RxObjectEvent; 20 | 21 | import javax.annotation.Nonnull; 22 | 23 | import rx.Observable; 24 | 25 | public interface SocketConnection { 26 | @Nonnull 27 | Observable connection(); 28 | } 29 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/SocketConnectionImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example; 18 | 19 | import com.appunite.websocket.rx.object.RxObjectWebSockets; 20 | import com.appunite.websocket.rx.object.messages.RxObjectEvent; 21 | 22 | import java.util.concurrent.TimeUnit; 23 | 24 | import javax.annotation.Nonnull; 25 | 26 | import rx.Observable; 27 | import rx.Scheduler; 28 | import rx.functions.Func1; 29 | 30 | public class SocketConnectionImpl implements SocketConnection { 31 | 32 | @Nonnull 33 | private final RxObjectWebSockets sockets; 34 | @Nonnull 35 | private final Scheduler scheduler; 36 | 37 | public SocketConnectionImpl(@Nonnull RxObjectWebSockets sockets, @Nonnull Scheduler scheduler) { 38 | this.sockets = sockets; 39 | this.scheduler = scheduler; 40 | } 41 | 42 | @Nonnull 43 | @Override 44 | public Observable connection() { 45 | return sockets.webSocketObservable() 46 | .retryWhen(repeatDuration(1, TimeUnit.SECONDS)); 47 | } 48 | 49 | @Nonnull 50 | private Func1, Observable> repeatDuration(final long delay, 51 | @Nonnull final TimeUnit timeUnit) { 52 | return new Func1, Observable>() { 53 | @Override 54 | public Observable call(Observable attemps) { 55 | return attemps 56 | .flatMap(new Func1>() { 57 | @Override 58 | public Observable call(Throwable aLong) { 59 | return Observable.timer(delay, timeUnit, scheduler); 60 | } 61 | }); 62 | } 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/ChatMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class ChatMessage extends Message { 22 | @Nonnull 23 | public final String message; 24 | @Nonnull 25 | public final String from; 26 | 27 | public ChatMessage(@Nonnull String message, @Nonnull String from) { 28 | super(MessageType.CHAT); 29 | this.message = message; 30 | this.from = from; 31 | } 32 | 33 | @Nonnull 34 | public String message() { 35 | return message; 36 | } 37 | 38 | @Nonnull 39 | public String from() { 40 | return from; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (!(o instanceof ChatMessage)) return false; 47 | 48 | ChatMessage that = (ChatMessage) o; 49 | 50 | return message.equals(that.message) && from.equals(that.from); 51 | 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | int result = super.hashCode(); 57 | result = 31 * result + message.hashCode(); 58 | result = 31 * result + from.hashCode(); 59 | return result; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "RegisterRequest{" + 65 | "message='" + message + '\'' + 66 | "from='" + from + '\'' + 67 | "} " + super.toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/DataMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class DataMessage extends Message { 22 | @Nonnull 23 | private final String id; 24 | @Nonnull 25 | private final String message; 26 | 27 | public DataMessage(@Nonnull String id, @Nonnull String message) { 28 | super(MessageType.DATA); 29 | this.id = id; 30 | this.message = message; 31 | } 32 | 33 | @Nonnull 34 | public String message() { 35 | return message; 36 | } 37 | 38 | @Nonnull 39 | public String id() { 40 | return id; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (!(o instanceof DataMessage)) return false; 47 | if (!super.equals(o)) return false; 48 | 49 | DataMessage that = (DataMessage) o; 50 | 51 | if (!id.equals(that.id)) return false; 52 | return message.equals(that.message); 53 | 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | int result = super.hashCode(); 59 | result = 31 * result + id.hashCode(); 60 | result = 31 * result + message.hashCode(); 61 | return result; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "DataResponse{" + 67 | "id='" + id + '\'' + 68 | ", message='" + message + '\'' + 69 | "} " + super.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class ErrorMessage extends Message { 22 | @Nonnull 23 | private final String response; 24 | 25 | public ErrorMessage(@Nonnull String response) { 26 | super(MessageType.ERROR); 27 | this.response = response; 28 | } 29 | 30 | @Nonnull 31 | public String response() { 32 | return response; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (!(o instanceof ErrorMessage)) return false; 39 | if (!super.equals(o)) return false; 40 | 41 | ErrorMessage that = (ErrorMessage) o; 42 | 43 | return response.equals(that.response); 44 | 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | int result = super.hashCode(); 50 | result = 31 * result + response.hashCode(); 51 | return result; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "ErrorResponse{" + 57 | "message='" + response + '\'' + 58 | "} " + super.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/Message.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import com.google.gson.JsonDeserializationContext; 20 | import com.google.gson.JsonDeserializer; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonObject; 23 | import com.google.gson.JsonParseException; 24 | 25 | import java.lang.reflect.Type; 26 | 27 | import javax.annotation.Nonnull; 28 | 29 | public abstract class Message { 30 | @Nonnull 31 | public final MessageType type; 32 | 33 | public Message(@Nonnull MessageType type) { 34 | this.type = type; 35 | } 36 | 37 | @Nonnull 38 | public MessageType type() { 39 | return type; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (!(o instanceof Message)) return false; 46 | 47 | Message message = (Message) o; 48 | 49 | return type == message.type; 50 | 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return type.hashCode(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "Response{" + 61 | "type=" + type + 62 | '}'; 63 | } 64 | 65 | public static class Deserializer implements JsonDeserializer { 66 | 67 | @Override 68 | public Message deserialize(JsonElement jsonElement, 69 | Type type, 70 | JsonDeserializationContext jsonDeserializationContext) 71 | throws JsonParseException { 72 | final JsonObject jsonObject = jsonElement.getAsJsonObject(); 73 | final JsonElement typeElement = jsonObject.get("type"); 74 | if (typeElement == null) { 75 | throw new JsonParseException("No \"type\" field in reference"); 76 | } 77 | final MessageType messageType = jsonDeserializationContext.deserialize(typeElement, MessageType.class); 78 | 79 | if (MessageType.PONG.equals(messageType)) { 80 | return jsonDeserializationContext.deserialize(jsonObject, PongMessage.class); 81 | } else if (MessageType.ERROR.equals(messageType)) { 82 | return jsonDeserializationContext.deserialize(jsonObject, ErrorMessage.class); 83 | } else if (MessageType.REGISTERED.equals(messageType)) { 84 | return jsonDeserializationContext.deserialize(jsonObject, RegisteredMessage.class); 85 | } else if (MessageType.DATA.equals(messageType)) { 86 | return jsonDeserializationContext.deserialize(jsonObject, DataMessage.class); 87 | } else if (MessageType.CHAT.equals(messageType)) { 88 | return jsonDeserializationContext.deserialize(jsonObject, ChatMessage.class); 89 | } else { 90 | throw new JsonParseException("Unknown type " + messageType); 91 | } 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/MessageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import com.google.gson.JsonDeserializationContext; 20 | import com.google.gson.JsonDeserializer; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonParseException; 23 | import com.google.gson.JsonPrimitive; 24 | import com.google.gson.JsonSerializationContext; 25 | import com.google.gson.JsonSerializer; 26 | 27 | import java.lang.reflect.Type; 28 | import java.util.Locale; 29 | 30 | public enum MessageType { 31 | REGISTER, REGISTERED, PING, DATA, PONG, ERROR, CHAT; 32 | 33 | public static class SerializerDeserializer implements JsonDeserializer, JsonSerializer { 34 | 35 | 36 | @Override 37 | public MessageType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 38 | 39 | if (json.isJsonNull()) { 40 | return null; 41 | } 42 | final JsonPrimitive primitive = json.getAsJsonPrimitive(); 43 | if (!primitive.isString()) { 44 | throw new JsonParseException("Non string type of type"); 45 | } 46 | 47 | final String asString = json.getAsString(); 48 | try { 49 | return MessageType.valueOf(asString.toUpperCase(Locale.US)); 50 | } catch (IllegalArgumentException e) { 51 | throw new JsonParseException("Unknown request type: " + asString); 52 | } 53 | } 54 | 55 | 56 | @Override 57 | public JsonElement serialize(MessageType messageType, Type typeOfSrc, JsonSerializationContext context) { 58 | if (messageType == null) { 59 | return null; 60 | } 61 | return new JsonPrimitive(messageType.toString().toLowerCase(Locale.US)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/PingMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class PingMessage extends Message { 22 | @Nonnull 23 | private String message; 24 | 25 | public PingMessage(@Nonnull String message) { 26 | super(MessageType.PING); 27 | this.message = message; 28 | } 29 | 30 | @Nonnull 31 | public String message() { 32 | return message; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (!(o instanceof PingMessage)) return false; 39 | if (!super.equals(o)) return false; 40 | 41 | PingMessage that = (PingMessage) o; 42 | 43 | return message.equals(that.message); 44 | 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | int result = super.hashCode(); 50 | result = 31 * result + message.hashCode(); 51 | return result; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "PingMessage{" + 57 | "message='" + message + '\'' + 58 | "} " + super.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/PongMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import javax.annotation.Nonnull; 20 | 21 | public class PongMessage extends Message { 22 | @Nonnull 23 | private final String response; 24 | 25 | public PongMessage(@Nonnull String response) { 26 | super(MessageType.PONG); 27 | this.response = response; 28 | } 29 | 30 | @Nonnull 31 | public String response() { 32 | return response; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (!(o instanceof PongMessage)) return false; 39 | if (!super.equals(o)) return false; 40 | 41 | PongMessage that = (PongMessage) o; 42 | 43 | return response.equals(that.response); 44 | 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | int result = super.hashCode(); 50 | result = 31 * result + response.hashCode(); 51 | return result; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "PongResponse{" + 57 | "message='" + response + '\'' + 58 | "} " + super.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/RegisterMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | import com.google.gson.annotations.SerializedName; 20 | 21 | import javax.annotation.Nonnull; 22 | 23 | public class RegisterMessage extends Message { 24 | @SerializedName("auth_token") 25 | @Nonnull 26 | public final String authToken; 27 | 28 | public RegisterMessage(@Nonnull String authToken) { 29 | super(MessageType.REGISTER); 30 | this.authToken = authToken; 31 | } 32 | 33 | @Nonnull 34 | public String authToken() { 35 | return authToken; 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (!(o instanceof RegisterMessage)) return false; 42 | 43 | RegisterMessage that = (RegisterMessage) o; 44 | 45 | return authToken.equals(that.authToken); 46 | 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return authToken.hashCode(); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "RegisterRequest{" + 57 | "message='" + authToken + '\'' + 58 | "} " + super.toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/java/com/example/model/RegisteredMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Jacek Marchwicki 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | package com.example.model; 18 | 19 | public class RegisteredMessage extends Message { 20 | 21 | public RegisteredMessage() { 22 | super(MessageType.REGISTERED); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "RegisteredResponse{}"; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | return o instanceof RegisteredMessage; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return 1; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /websockets-rxjava-example/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 |