Skip to content

Commit f6e5c4d

Browse files
Joerogerhu
authored andcommitted
Refactor the client's generic type parameters (#27)
* Add tests for hetergeneous subscriptions Ensures that we are able to subscribe to multiple ParseQuery, regardless of class. The current tests work fine, because the client is declared as ParseLiveQueryClient<ParseObject>, so subclasses are accepted. But many method parameters have to be cast to more specific (or in some cases, less specific) class types. The problem with this approach is that you are forced to use the top-level `ParseLiveQueryClient<ParseObject>` type parameter, which means all other type parameters must match `ParseObject`, including `ParseQuery<ParseObject>`, and the callback events all have to take `ParseObject` and then it would have to be cast to the desired heterogeneous types. The goal would be that the type parameter is only enforced during subscription, not at the Client declaration level: ParseLiveQueryClient client = ... handle1 = client.subscribe(ParseQuery.getQuery(Subclass1.class)); handle2 = client.subscribe(ParseQuery.getQuery(Subclass2.class)); In its current state, `handle1` and `handle2` would be forced to use an event callback locked to `<ParseObject>`, and cast the objects to `Subclass1` and `Subclass2`. OR the only other option is to create two separate ParseLiveQueryClient objects (which therefore requires multiple websocket connections). * Refactor the client's generic type parameters The way it was written, a single client could only ever be used for a single `<T extends ParseObject>` type parameter. That means to have multiple LiveQuery objects for multiple ParseObject classes, we're forced to create multiple ParseLiveQueryClient objects, each with its own `<T>` type parameter. Since the client itself does not actually require being locked to a single type, we can move those type parameters to individual methods instead.
1 parent 7c1ead1 commit f6e5c4d

File tree

3 files changed

+74
-21
lines changed

3 files changed

+74
-21
lines changed

ParseLiveQuery/src/main/java/com/parse/ParseLiveQueryClient.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,27 @@
33
import java.net.URI;
44
import java.util.concurrent.Executor;
55

6-
public interface ParseLiveQueryClient<T extends ParseObject> {
6+
public interface ParseLiveQueryClient {
77

8-
SubscriptionHandling<T> subscribe(ParseQuery<T> query);
8+
<T extends ParseObject> SubscriptionHandling<T> subscribe(ParseQuery<T> query);
99

10-
void unsubscribe(final ParseQuery<T> query);
10+
<T extends ParseObject> void unsubscribe(final ParseQuery<T> query);
1111

12-
void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling subscriptionHandling);
12+
<T extends ParseObject> void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling<T> subscriptionHandling);
1313

1414
void reconnect();
1515

1616
void disconnect();
1717

1818
class Factory {
1919

20-
public static <T extends ParseObject> ParseLiveQueryClient<T> getClient(URI uri) {
21-
return new ParseLiveQueryClientImpl<>(uri);
20+
public static ParseLiveQueryClient getClient(URI uri) {
21+
return new ParseLiveQueryClientImpl(uri);
2222
}
2323

2424
/* package */
25-
static <T extends ParseObject> ParseLiveQueryClient<T> getClient(URI uri, WebSocketClientFactory webSocketClientFactory, Executor taskExecutor) {
26-
return new ParseLiveQueryClientImpl<>(uri, webSocketClientFactory, taskExecutor);
25+
static ParseLiveQueryClient getClient(URI uri, WebSocketClientFactory webSocketClientFactory, Executor taskExecutor) {
26+
return new ParseLiveQueryClientImpl(uri, webSocketClientFactory, taskExecutor);
2727
}
2828

2929
}

ParseLiveQuery/src/main/java/com/parse/ParseLiveQueryClientImpl.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515

1616
import static com.parse.Parse.checkInit;
1717

18-
/* package */ class ParseLiveQueryClientImpl<T extends ParseObject> implements ParseLiveQueryClient<T> {
18+
/* package */ class ParseLiveQueryClientImpl implements ParseLiveQueryClient {
1919

2020
private static final String LOG_TAG = "ParseLiveQueryClient";
2121

2222
private final Executor taskExecutor;
2323
private final String applicationId;
2424
private final String clientKey;
25-
private final SparseArray<Subscription<T>> subscriptions = new SparseArray<>();
25+
private final SparseArray<Subscription<? extends ParseObject>> subscriptions = new SparseArray<>();
2626
private final URI uri;
2727
private final WebSocketClientFactory webSocketClientFactory;
2828
private final WebSocketClient.WebSocketClientCallback webSocketClientCallback;
@@ -46,7 +46,7 @@
4646
}
4747

4848
@Override
49-
public SubscriptionHandling<T> subscribe(ParseQuery<T> query) {
49+
public <T extends ParseObject> SubscriptionHandling<T> subscribe(ParseQuery<T> query) {
5050
int requestId = requestIdGenerator();
5151
Subscription<T> subscription = new Subscription<>(requestId, query);
5252
subscriptions.append(requestId, subscription);
@@ -63,7 +63,7 @@ public SubscriptionHandling<T> subscribe(ParseQuery<T> query) {
6363
}
6464

6565
@Override
66-
public void unsubscribe(final ParseQuery<T> query) {
66+
public <T extends ParseObject> void unsubscribe(final ParseQuery<T> query) {
6767
if (query != null) {
6868
for (int i = 0; i < subscriptions.size(); i++) {
6969
Subscription subscription = subscriptions.valueAt(i);
@@ -75,7 +75,7 @@ public void unsubscribe(final ParseQuery<T> query) {
7575
}
7676

7777
@Override
78-
public void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling subscriptionHandling) {
78+
public <T extends ParseObject> void unsubscribe(final ParseQuery<T> query, final SubscriptionHandling<T> subscriptionHandling) {
7979
if (query != null && subscriptionHandling != null) {
8080
for (int i = 0; i < subscriptions.size(); i++) {
8181
Subscription subscription = subscriptions.valueAt(i);
@@ -199,15 +199,15 @@ private void parseMessage(String message) throws LiveQueryException {
199199
}
200200
}
201201

202-
private void handleSubscribedEvent(JSONObject jsonObject) throws JSONException {
202+
private <T extends ParseObject> void handleSubscribedEvent(JSONObject jsonObject) throws JSONException {
203203
final int requestId = jsonObject.getInt("requestId");
204204
final Subscription<T> subscription = subscriptionForRequestId(requestId);
205205
if (subscription != null) {
206206
subscription.didSubscribe(subscription.getQuery());
207207
}
208208
}
209209

210-
private void handleUnsubscribedEvent(JSONObject jsonObject) throws JSONException {
210+
private <T extends ParseObject> void handleUnsubscribedEvent(JSONObject jsonObject) throws JSONException {
211211
final int requestId = jsonObject.getInt("requestId");
212212
final Subscription<T> subscription = subscriptionForRequestId(requestId);
213213
if (subscription != null) {
@@ -216,7 +216,7 @@ private void handleUnsubscribedEvent(JSONObject jsonObject) throws JSONException
216216
}
217217
}
218218

219-
private void handleObjectEvent(Subscription.Event event, JSONObject jsonObject) throws JSONException {
219+
private <T extends ParseObject> void handleObjectEvent(Subscription.Event event, JSONObject jsonObject) throws JSONException {
220220
final int requestId = jsonObject.getInt("requestId");
221221
final Subscription<T> subscription = subscriptionForRequestId(requestId);
222222
if (subscription != null) {
@@ -225,7 +225,7 @@ private void handleObjectEvent(Subscription.Event event, JSONObject jsonObject)
225225
}
226226
}
227227

228-
private void handleErrorEvent(JSONObject jsonObject) throws JSONException {
228+
private <T extends ParseObject> void handleErrorEvent(JSONObject jsonObject) throws JSONException {
229229
int requestId = jsonObject.getInt("requestId");
230230
int code = jsonObject.getInt("code");
231231
String error = jsonObject.getString("error");
@@ -236,11 +236,12 @@ private void handleErrorEvent(JSONObject jsonObject) throws JSONException {
236236
}
237237
}
238238

239-
private Subscription<T> subscriptionForRequestId(int requestId) {
240-
return subscriptions.get(requestId);
239+
private <T extends ParseObject> Subscription<T> subscriptionForRequestId(int requestId) {
240+
//noinspection unchecked
241+
return (Subscription<T>) subscriptions.get(requestId);
241242
}
242243

243-
private void sendSubscription(final Subscription<T> subscription) {
244+
private <T extends ParseObject> void sendSubscription(final Subscription<T> subscription) {
244245
ParseUser.getCurrentSessionTokenAsync().onSuccess(new Continuation<String, Void>() {
245246
@Override
246247
public Void then(Task<String> task) throws Exception {

ParseLiveQuery/src/test/java/com/parse/TestParseLiveQueryClient.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.junit.Test;
88
import org.junit.runner.RunWith;
99
import org.mockito.ArgumentCaptor;
10+
import org.mockito.Mockito;
1011
import org.mockito.invocation.InvocationOnMock;
1112
import org.mockito.stubbing.Answer;
1213
import org.robolectric.RobolectricGradleTestRunner;
@@ -42,7 +43,7 @@ public class TestParseLiveQueryClient {
4243
private PauseableExecutor executor;
4344
private WebSocketClient webSocketClient;
4445
private WebSocketClient.WebSocketClientCallback webSocketClientCallback;
45-
private ParseLiveQueryClient<ParseObject> parseLiveQueryClient;
46+
private ParseLiveQueryClient parseLiveQueryClient;
4647

4748
private ParseUser mockUser;
4849

@@ -163,6 +164,48 @@ public void testErrorWhenSubscribedToCallback() throws Exception {
163164
assertEquals(serverError.isReconnect(), true);
164165
}
165166

167+
@Test
168+
public void testHeterogeneousSubscriptions() throws Exception {
169+
ParseObject.registerSubclass(MockClassA.class);
170+
ParseObject.registerSubclass(MockClassB.class);
171+
172+
ParseQuery<MockClassA> query1 = ParseQuery.getQuery(MockClassA.class);
173+
ParseQuery<MockClassB> query2 = ParseQuery.getQuery(MockClassB.class);
174+
SubscriptionHandling<MockClassA> handle1 = parseLiveQueryClient.subscribe(query1);
175+
SubscriptionHandling<MockClassB> handle2 = parseLiveQueryClient.subscribe(query2);
176+
177+
handle1.handleError(new SubscriptionHandling.HandleErrorCallback<MockClassA>() {
178+
@Override
179+
public void onError(ParseQuery<MockClassA> query, LiveQueryException exception) {
180+
throw new RuntimeException(exception);
181+
}
182+
});
183+
handle2.handleError(new SubscriptionHandling.HandleErrorCallback<MockClassB>() {
184+
@Override
185+
public void onError(ParseQuery<MockClassB> query, LiveQueryException exception) {
186+
throw new RuntimeException(exception);
187+
}
188+
});
189+
190+
SubscriptionHandling.HandleEventCallback<MockClassA> eventMockCallback1 = mock(SubscriptionHandling.HandleEventCallback.class);
191+
SubscriptionHandling.HandleEventCallback<MockClassB> eventMockCallback2 = mock(SubscriptionHandling.HandleEventCallback.class);
192+
193+
handle1.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback1);
194+
handle2.handleEvent(SubscriptionHandling.Event.CREATE, eventMockCallback2);
195+
196+
ParseObject parseObject1 = new MockClassA();
197+
parseObject1.setObjectId("testId1");
198+
199+
ParseObject parseObject2 = new MockClassB();
200+
parseObject2.setObjectId("testId2");
201+
202+
webSocketClientCallback.onMessage(createObjectCreateMessage(handle1.getRequestId(), parseObject1).toString());
203+
webSocketClientCallback.onMessage(createObjectCreateMessage(handle2.getRequestId(), parseObject2).toString());
204+
205+
validateSameObject((SubscriptionHandling.HandleEventCallback) eventMockCallback1, (ParseQuery) query1, parseObject1);
206+
validateSameObject((SubscriptionHandling.HandleEventCallback) eventMockCallback2, (ParseQuery) query2, parseObject2);
207+
}
208+
166209
@Test
167210
public void testCreateEventWhenSubscribedToCallback() throws Exception {
168211
ParseQuery<ParseObject> parseQuery = new ParseQuery<>("test");
@@ -370,6 +413,7 @@ private void validateSameObject(SubscriptionHandling.HandleEventCallback<ParseOb
370413

371414
ParseObject newParseObject = objectCaptor.getValue();
372415

416+
assertEquals(originalParseObject.getClassName(), newParseObject.getClassName());
373417
assertEquals(originalParseObject.getObjectId(), newParseObject.getObjectId());
374418
}
375419

@@ -488,4 +532,12 @@ public void execute(Runnable runnable) {
488532
}
489533
}
490534
}
535+
536+
@ParseClassName("MockA")
537+
static class MockClassA extends ParseObject {
538+
}
539+
540+
@ParseClassName("MockB")
541+
static class MockClassB extends ParseObject {
542+
}
491543
}

0 commit comments

Comments
 (0)