diff --git a/Example/src/main/AndroidManifest.xml b/Example/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0ce1391 --- /dev/null +++ b/Example/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/src/main/java/com/parse/example/MainActivity.java b/Example/src/main/java/com/parse/example/MainActivity.java new file mode 100644 index 0000000..9e949e4 --- /dev/null +++ b/Example/src/main/java/com/parse/example/MainActivity.java @@ -0,0 +1,84 @@ +package com.parse.example; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.parse.GetCallback; +import com.parse.Parse; +import com.parse.ParseException; +import com.parse.ParseLiveQueryClient; +import com.parse.ParseObject; +import com.parse.ParseQuery; +import com.parse.SubscriptionHandling; +import com.parse.interceptors.ParseLogInterceptor; + +import java.net.URI; +import java.net.URISyntaxException; + +public class MainActivity extends AppCompatActivity { + + String URL = "http://192.168.3.9:1337/parse/"; + String wsURL = "ws://192.168.3.9:1337/parse/"; + String applicationId = "mytest"; + String DEBUG_TAG = "debug"; + + Room mRoom; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + ParseObject.registerSubclass(Room.class); + ParseObject.registerSubclass(Message.class); + + Parse.setLogLevel(Parse.LOG_LEVEL_DEBUG); + Parse.initialize(new Parse.Configuration.Builder(this) + .applicationId(applicationId) // should correspond to APP_ID env variable + .clientKey("clientKey") // set explicitly blank unless clientKey is configured on Parse server + .addNetworkInterceptor(new ParseLogInterceptor()) + .server(URL).build()); + + ParseQuery roomParseQuery = ParseQuery.getQuery(Room.class); + // fixme - why isn't it retrieving + roomParseQuery.whereEqualTo("name", "test"); + roomParseQuery.getFirstInBackground(new GetCallback() { + @Override + public void done(Room room, ParseException e) { + if (e != null) { + Log.d(DEBUG_TAG, "Found exception" + e); + e.printStackTrace(); + } else { + Log.d(DEBUG_TAG, "Found room: " + room); + } + mRoom = room; + + ParseLiveQueryClient parseLiveQueryClient = ParseLiveQueryClient.Factory.getClient(); + + if (parseLiveQueryClient != null) { + // op=subscribe, className=Message, roomName=test, requestId=1 + // op=subscribe, className=Message, roomName=null, requestId=1, order=createdAt + ParseQuery parseQuery = ParseQuery.getQuery(Message.class); + // FIXME + parseQuery.whereEqualTo("roomName", "test"); + + // FIXME (rhu) - parse query hates created at +// parseQuery.orderByAscending("createdAt"); + + SubscriptionHandling subscriptionHandling = parseLiveQueryClient + .subscribe(parseQuery); + subscriptionHandling.handleEvent(SubscriptionHandling.Event.CREATE, + new SubscriptionHandling.HandleEventCallback() { + @Override + public void onEvent(ParseQuery query, Message object) { + Log.d(DEBUG_TAG, "Message" + object); + } + }); + } + } + }); + + + } +} diff --git a/Example/src/main/java/com/parse/example/Message.java b/Example/src/main/java/com/parse/example/Message.java new file mode 100644 index 0000000..43dd9f3 --- /dev/null +++ b/Example/src/main/java/com/parse/example/Message.java @@ -0,0 +1,56 @@ +package com.parse.example; + + +import com.parse.ParseClassName; +import com.parse.ParseObject; +import com.parse.ParseUser; + +@ParseClassName("Message") +public class Message extends ParseObject { + + private final String PARSE_USER_KEY = "parseUser"; + private final String AUTHOR_KEY = "author"; + private final String MESSAGE_KEY = "message"; + private final String ROOM_KEY = "room"; + + public Message() { + + } + + public ParseUser getParseUser() { + return getParseUser(PARSE_USER_KEY); + } + + public void setParseUser(ParseUser parseUser) { + put(PARSE_USER_KEY, parseUser); + } + + public String getAuthorName() { + return getString(AUTHOR_KEY); + } + + public void setAuthorName(String authorName) { + put(AUTHOR_KEY, authorName); + } + + public String getMessage() { + return getString(MESSAGE_KEY); + } + + public void setMessage(String message) { + put(MESSAGE_KEY, message); + } + + public Room getRoom() { + return (Room) get(ROOM_KEY); + } + + public void setRoom(Room room) { + put(ROOM_KEY, room); + } + + @Override + public String toString() { + return String.format("objectId=%s: message=%s room=%s", getObjectId(), getMessage(), getRoom()); + } +} diff --git a/Example/src/main/java/com/parse/example/Room.java b/Example/src/main/java/com/parse/example/Room.java new file mode 100644 index 0000000..dc34918 --- /dev/null +++ b/Example/src/main/java/com/parse/example/Room.java @@ -0,0 +1,29 @@ +package com.parse.example; + +import com.parse.ParseClassName; +import com.parse.ParseObject; + +@ParseClassName("Room") +public class Room extends ParseObject { + + private final String NAME_KEY = "name"; + + public Room() { + + } + + String name; + + public String getName() { + return getString(NAME_KEY); + } + + public void setName(String name) { + put(NAME_KEY, name); + } + + @Override + public String toString() { + return String.format("objectId=%s: name=%s", getObjectId(), getName()); + } +} diff --git a/Example/src/main/res/layout/activity_main.xml b/Example/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4eca4f1 --- /dev/null +++ b/Example/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/Example/src/main/res/mipmap-hdpi/ic_launcher.png b/Example/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/Example/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/Example/src/main/res/mipmap-hdpi/ic_launcher_round.png b/Example/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a078e3 Binary files /dev/null and b/Example/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/Example/src/main/res/mipmap-mdpi/ic_launcher.png b/Example/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/Example/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/Example/src/main/res/mipmap-mdpi/ic_launcher_round.png b/Example/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..efc028a Binary files /dev/null and b/Example/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/Example/src/main/res/mipmap-xhdpi/ic_launcher.png b/Example/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/Example/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/Example/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/Example/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3af2608 Binary files /dev/null and b/Example/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/Example/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Example/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/Example/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/Example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9bec2e6 Binary files /dev/null and b/Example/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/Example/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Example/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/Example/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/Example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..34947cd Binary files /dev/null and b/Example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/Example/src/main/res/values/colors.xml b/Example/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/Example/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/Example/src/main/res/values/strings.xml b/Example/src/main/res/values/strings.xml new file mode 100644 index 0000000..0057fcb --- /dev/null +++ b/Example/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Example + diff --git a/Example/src/main/res/values/styles.xml b/Example/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/Example/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/ParseLiveQuery/src/main/java/com/parse/OkHttp3WebSocketClient.java b/ParseLiveQuery/src/main/java/com/parse/OkHttp3WebSocketClient.java new file mode 100644 index 0000000..f8439ab --- /dev/null +++ b/ParseLiveQuery/src/main/java/com/parse/OkHttp3WebSocketClient.java @@ -0,0 +1,104 @@ +package com.parse; + +import android.util.Log; + +import java.net.URI; +import java.util.Locale; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocketListener; +import okio.ByteString; + +/* package */ class OkHttp3WebSocketClient implements WebSocketClient { + + private static final String LOG_TAG = "OkHttpWebSocketClient"; + + private final WebSocketClientCallback webSocketClientCallback; + private okhttp3.WebSocket webSocket; + private State state = State.NONE; + private OkHttpClient client; + private final String url; + private final int STATUS_CODE = 200; + private final String CLOSING_MSG = "User invoked close"; + + private final WebSocketListener handler = new WebSocketListener() { + @Override + public void onOpen(okhttp3.WebSocket webSocket, Response response) { + setState(State.CONNECTED); + webSocketClientCallback.onOpen(); + } + + @Override + public void onMessage(okhttp3.WebSocket webSocket, String text) { + webSocketClientCallback.onMessage(text); + } + + @Override + public void onMessage(okhttp3.WebSocket webSocket, ByteString bytes) { + Log.w(LOG_TAG, String.format(Locale.US, "Socket got into inconsistent state and received %s instead.", bytes.toString())); + } + + @Override + public void onClosed(okhttp3.WebSocket webSocket, int code, String reason) { + setState(State.DISCONNECTED); + webSocketClientCallback.onClose(); + } + + @Override + public void onFailure(okhttp3.WebSocket webSocket, Throwable t, Response response) { + webSocketClientCallback.onError(t); + } + }; + + private OkHttp3WebSocketClient(WebSocketClientCallback webSocketClientCallback, URI hostUrl) { + this.webSocketClientCallback = webSocketClientCallback; + client = new OkHttpClient(); + url = hostUrl.toString(); + } + + @Override + public synchronized void open() { + if (State.NONE == state) { + // OkHttp3 connects as soon as the socket is created so do it here. + Request request = new Request.Builder() + .url(url) + .build(); + + webSocket = client.newWebSocket(request, handler); + setState(State.CONNECTING); + } + } + + @Override + public synchronized void close() { + setState(State.DISCONNECTING); + webSocket.close(STATUS_CODE, CLOSING_MSG); + } + + @Override + public void send(String message) { + if (state == State.CONNECTED) { + webSocket.send(message); + } + } + + @Override + public State getState() { + return state; + } + + private synchronized void setState(State newState) { + this.state = newState; + this.webSocketClientCallback.stateChanged(); + } + + /* package */ static class OkHttp3SocketClientFactory implements WebSocketClientFactory { + @Override + public WebSocketClient createInstance(WebSocketClientCallback webSocketClientCallback, URI hostUrl) { + return new OkHttp3WebSocketClient(webSocketClientCallback, hostUrl); + } + } + +} diff --git a/ParseLiveQuery/src/main/java/com/parse/SubscribeClientOperation.java b/ParseLiveQuery/src/main/java/com/parse/SubscribeClientOperation.java index e9387d4..8675207 100644 --- a/ParseLiveQuery/src/main/java/com/parse/SubscribeClientOperation.java +++ b/ParseLiveQuery/src/main/java/com/parse/SubscribeClientOperation.java @@ -27,7 +27,7 @@ // TODO: add support for fields // https://github.com/ParsePlatform/parse-server/issues/3671 - + PointerEncoder pointerEncoder = PointerEncoder.get(); queryJsonObject.put("where", pointerEncoder.encode(state.constraints())); diff --git a/README.md b/README.md index 2361cd9..825b450 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Download [the latest JAR][latest] or define in Gradle: ```groovy dependencies { - compile 'com.parse:parse-livequery-android:1.0.0' + compile 'com.parse:parse-livequery-android:1.0.1' } ``` @@ -39,6 +39,22 @@ ParseLiveQueryClient parseLiveQueryClient = ParseLiveQueryClient.Factory.getClie ### Creating Live Queries +If you wish to pass in your own OkHttpClient instance for troubleshooting or custom configs, you can instantiate the client as follows: + +```java +ParseLiveQueryClient parseLiveQueryClient = +ParseLiveQueryClient.Factory.getClient(new OkHttp3SocketClientFactory(new OkHttpClient())); +``` + +The URL is determined by the Parse initialization, but you can override by specifying a `URI` object: + +```java +ParseLiveQueryClient parseLiveQueryClient = +ParseLiveQueryClient.Factory.getClient(new URI("wss://myparseinstance.com")); +``` + +Note: The expected protocol for URI is `ws` instead of `http`, like in this example: `URI("ws://192.168.0.1:1337/1")`. + Live querying depends on creating a subscription to a `ParseQuery`: ```java diff --git a/settings.gradle b/settings.gradle index 467a08b..ece2b2e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':ParseLiveQuery' +include ':ParseLiveQuery', ':Example'