Skip to content

Commit 7cec3b4

Browse files
Add better, more flexible APIs for SDK initialization.
This PR hopes to unify some of these initialization settings int o a single 'configuration' class that can be easily used to intialize parse in a much cleaner way, especially as we add even more initialization options.
1 parent c686f25 commit 7cec3b4

File tree

2 files changed

+255
-36
lines changed

2 files changed

+255
-36
lines changed

Parse/src/main/java/com/parse/Parse.java

+195-36
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.io.RandomAccessFile;
2424
import java.io.UnsupportedEncodingException;
2525
import java.util.ArrayList;
26+
import java.util.Collection;
27+
import java.util.Collections;
2628
import java.util.HashSet;
2729
import java.util.List;
2830
import java.util.Set;
@@ -35,6 +37,160 @@
3537
* library.
3638
*/
3739
public class Parse {
40+
/**
41+
* Represents an opaque configuration for the {@code Parse} SDK configuration.
42+
*/
43+
public static final class Configuration {
44+
/**
45+
* Allows for simple constructing of a {@code Configuration} object.
46+
*/
47+
public static final class Builder {
48+
private Context context;
49+
private String applicationId;
50+
private String clientKey;
51+
private boolean localDataStoreEnabled;
52+
private List<ParseNetworkInterceptor> interceptors;
53+
54+
/**
55+
* Initialize a bulider with a given context.
56+
*
57+
* This context will then be passed through to the rest of the Parse SDK for use during
58+
* initialization.
59+
*
60+
* <p/>
61+
* You may define {@code com.parse.APPLICATION_ID} and {@code com.parse.CLIENT_KEY}
62+
* {@code meta-data} in your {@code AndroidManifest.xml}:
63+
* <pre>
64+
* &lt;manifest ...&gt;
65+
*
66+
* ...
67+
*
68+
* &lt;application ...&gt;
69+
* &lt;meta-data
70+
* android:name="com.parse.APPLICATION_ID"
71+
* android:value="@string/parse_app_id" /&gt;
72+
* &lt;meta-data
73+
* android:name="com.parse.CLIENT_KEY"
74+
* android:value="@string/parse_client_key" /&gt;
75+
*
76+
* ...
77+
*
78+
* &lt;/application&gt;
79+
* &lt;/manifest&gt;
80+
* </pre>
81+
* <p/>
82+
*
83+
* This will cause the values for {@code applicationId} and {@code clientKey} to be set to
84+
* those defined in your manifest.
85+
*
86+
* @param context The active {@link Context} for your application. Cannot be null.
87+
*/
88+
public Builder(Context context) {
89+
this.context = context;
90+
91+
// Yes, our public API states we cannot be null. But for unit tests, it's easier just to
92+
// support null here.
93+
if (context != null) {
94+
Context applicationContext = context.getApplicationContext();
95+
Bundle metaData = ManifestInfo.getApplicationMetadata(applicationContext);
96+
if (metaData != null) {
97+
applicationId = metaData.getString(PARSE_APPLICATION_ID);
98+
clientKey = metaData.getString(PARSE_CLIENT_KEY);
99+
}
100+
}
101+
}
102+
103+
/**
104+
* Set the application id to be used by Parse.
105+
*
106+
* This method is only required if you intend to use a different {@code applicationId} than
107+
* is defined by {@code com.parse.APPLICATION_ID} in your {@code AndroidManifest.xml}.
108+
*
109+
* @param applicationId The application id to set.
110+
* @return The same builder, for easy chaining.
111+
*/
112+
public Builder applicationId(String applicationId) {
113+
this.applicationId = applicationId;
114+
return this;
115+
}
116+
117+
/**
118+
* Set the client key to be used by Parse.
119+
*
120+
* This method is only required if you intend to use a different {@code clientKey} than
121+
* is defined by {@code com.parse.CLIENT_KEY} in your {@code AndroidManifest.xml}.
122+
*
123+
* @param clientKey The client key to set.
124+
* @return The same builder, for easy chaining.
125+
*/
126+
public Builder clientKey(String clientKey) {
127+
this.clientKey = clientKey;
128+
return this;
129+
}
130+
131+
/**
132+
* Add a {@link ParseNetworkInterceptor}.
133+
*
134+
* @param interceptor The interceptor to add.
135+
* @return The same builder, for easy chaining.
136+
*/
137+
public Builder addNetworkInterceptor(ParseNetworkInterceptor interceptor) {
138+
if (interceptors == null) {
139+
interceptors = new ArrayList<>();
140+
}
141+
interceptors.add(interceptor);
142+
return this;
143+
}
144+
145+
/**
146+
* Enable pinning in your application. This must be called before your application can use
147+
* pinning.
148+
* @return The same builder, for easy chaining.
149+
*/
150+
public Builder enableLocalDataStore() {
151+
localDataStoreEnabled = true;
152+
return this;
153+
}
154+
155+
private Builder setNetworkInterceptors(Collection<ParseNetworkInterceptor> interceptors) {
156+
if (interceptors != null) {
157+
this.interceptors.clear();
158+
this.interceptors.addAll(interceptors);
159+
}
160+
return this;
161+
}
162+
163+
private Builder setLocalDatastoreEnabled(boolean enabled) {
164+
localDataStoreEnabled = enabled;
165+
return this;
166+
}
167+
168+
/**
169+
* Construct this builder into a concrete {@code Configuration} instance.
170+
* @return A constructed {@code Configuration} object.
171+
*/
172+
public Configuration build() {
173+
return new Configuration(this);
174+
}
175+
}
176+
177+
/* package for tests */ final Context context;
178+
/* package for tests */ final String applicationId;
179+
/* package for tests */ final String clientKey;
180+
/* package for tests */ final boolean localDataStoreEnabled;
181+
/* package for tests */ final List<ParseNetworkInterceptor> interceptors;
182+
183+
private Configuration(Builder builder) {
184+
this.context = builder.context;
185+
this.applicationId = builder.applicationId;
186+
this.clientKey = builder.clientKey;
187+
this.localDataStoreEnabled = builder.localDataStoreEnabled;
188+
this.interceptors = builder.interceptors != null ?
189+
Collections.unmodifiableList(new ArrayList<>(builder.interceptors)) :
190+
null;
191+
}
192+
}
193+
38194
private static final String PARSE_APPLICATION_ID = "com.parse.APPLICATION_ID";
39195
private static final String PARSE_CLIENT_KEY = "com.parse.CLIENT_KEY";
40196

@@ -133,32 +289,24 @@ public static void enableLocalDatastore(Context context) {
133289
* The active {@link Context} for your application.
134290
*/
135291
public static void initialize(Context context) {
136-
Context applicationContext = context.getApplicationContext();
137-
String applicationId;
138-
String clientKey;
139-
Bundle metaData = ManifestInfo.getApplicationMetadata(applicationContext);
140-
if (metaData != null) {
141-
applicationId = metaData.getString(PARSE_APPLICATION_ID);
142-
clientKey = metaData.getString(PARSE_CLIENT_KEY);
143-
144-
if (applicationId == null) {
145-
throw new RuntimeException("ApplicationId not defined. " +
146-
"You must provide ApplicationId in AndroidManifest.xml.\n" +
147-
"<meta-data\n" +
148-
" android:name=\"com.parse.APPLICATION_ID\"\n" +
149-
" android:value=\"<Your Application Id>\" />");
150-
}
151-
if (clientKey == null) {
152-
throw new RuntimeException("ClientKey not defined. " +
153-
"You must provide ClientKey in AndroidManifest.xml.\n" +
154-
"<meta-data\n" +
155-
" android:name=\"com.parse.CLIENT_KEY\"\n" +
156-
" android:value=\"<Your Client Key>\" />");
157-
}
158-
} else {
159-
throw new RuntimeException("Can't get Application Metadata");
292+
Configuration.Builder builder = new Configuration.Builder(context);
293+
if (builder.applicationId == null) {
294+
throw new RuntimeException("ApplicationId not defined. " +
295+
"You must provide ApplicationId in AndroidManifest.xml.\n" +
296+
"<meta-data\n" +
297+
" android:name=\"com.parse.APPLICATION_ID\"\n" +
298+
" android:value=\"<Your Application Id>\" />");
299+
} if (builder.clientKey == null) {
300+
throw new RuntimeException("ClientKey not defined. " +
301+
"You must provide ClientKey in AndroidManifest.xml.\n" +
302+
"<meta-data\n" +
303+
" android:name=\"com.parse.CLIENT_KEY\"\n" +
304+
" android:value=\"<Your Client Key>\" />");
160305
}
161-
initialize(context, applicationId, clientKey);
306+
initialize(builder.setNetworkInterceptors(interceptors)
307+
.setLocalDatastoreEnabled(isLocalDatastoreEnabled)
308+
.build()
309+
);
162310
}
163311

164312
/**
@@ -188,22 +336,36 @@ public static void initialize(Context context) {
188336
* The client key provided in the Parse dashboard.
189337
*/
190338
public static void initialize(Context context, String applicationId, String clientKey) {
191-
ParsePlugins.Android.initialize(context, applicationId, clientKey);
192-
Context applicationContext = context.getApplicationContext();
339+
initialize(new Configuration.Builder(context)
340+
.applicationId(applicationId)
341+
.clientKey(clientKey)
342+
.setNetworkInterceptors(interceptors)
343+
.setLocalDatastoreEnabled(isLocalDatastoreEnabled)
344+
.build()
345+
);
346+
}
347+
348+
public static void initialize(Configuration configuration) {
349+
// NOTE (richardross): We will need this here, as ParsePlugins uses the return value of
350+
// isLocalDataStoreEnabled() to perform additional behavior.
351+
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;
352+
353+
ParsePlugins.Android.initialize(configuration.context, configuration.applicationId, configuration.clientKey);
354+
Context applicationContext = configuration.context.getApplicationContext();
193355

194356
ParseHttpClient.setKeepAlive(true);
195357
ParseHttpClient.setMaxConnections(20);
196358
// If we have interceptors in list, we have to initialize all http clients and add interceptors
197-
if (interceptors != null) {
198-
initializeParseHttpClientsWithParseNetworkInterceptors();
359+
if (configuration.interceptors != null) {
360+
initializeParseHttpClientsWithParseNetworkInterceptors(configuration.interceptors);
199361
}
200362

201363
ParseObject.registerParseSubclasses();
202364

203-
if (isLocalDatastoreEnabled()) {
204-
offlineStore = new OfflineStore(context);
365+
if (configuration.localDataStoreEnabled) {
366+
offlineStore = new OfflineStore(configuration.context);
205367
} else {
206-
ParseKeyValueCache.initialize(context);
368+
ParseKeyValueCache.initialize(configuration.context);
207369
}
208370

209371
// Make sure the data on disk for Parse is for the current
@@ -583,7 +745,7 @@ private Parse() {
583745
private static List<ParseNetworkInterceptor> interceptors;
584746

585747
// Initialize all necessary http clients and add interceptors to these http clients
586-
private static void initializeParseHttpClientsWithParseNetworkInterceptors() {
748+
private static void initializeParseHttpClientsWithParseNetworkInterceptors(List<ParseNetworkInterceptor> interceptors) {
587749
// This means developers have not called addInterceptor method so we should do nothing.
588750
if (interceptors == null) {
589751
return;
@@ -605,9 +767,6 @@ private static void initializeParseHttpClientsWithParseNetworkInterceptors() {
605767
parseHttpClient.addExternalInterceptor(interceptor);
606768
}
607769
}
608-
609-
// Remove interceptors reference since we do not need it anymore
610-
interceptors = null;
611770
}
612771

613772

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
package com.parse;
10+
11+
import com.parse.http.ParseNetworkInterceptor;
12+
13+
import org.junit.Test;
14+
15+
import static org.junit.Assert.assertEquals;
16+
import static org.junit.Assert.assertFalse;
17+
import static org.junit.Assert.assertNull;
18+
import static org.junit.Assert.assertTrue;
19+
import static org.junit.Assert.fail;
20+
import static org.mockito.Mockito.mock;
21+
22+
public class ParseClientConfigurationTest {
23+
24+
@Test
25+
public void testBuilder() {
26+
Parse.Configuration.Builder builder = new Parse.Configuration.Builder(null);
27+
builder.applicationId("foo");
28+
builder.clientKey("bar");
29+
builder.enableLocalDataStore();
30+
Parse.Configuration configuration = builder.build();
31+
32+
assertNull(configuration.context);
33+
assertEquals(configuration.applicationId, "foo");
34+
assertEquals(configuration.clientKey, "bar");
35+
assertEquals(configuration.localDataStoreEnabled, true);
36+
}
37+
38+
@Test
39+
public void testNetworkInterceptors() {
40+
ParseNetworkInterceptor interceptorA = mock(ParseNetworkInterceptor.class);
41+
ParseNetworkInterceptor interceptorB = mock(ParseNetworkInterceptor.class);
42+
43+
Parse.Configuration.Builder builder = new Parse.Configuration.Builder(null);
44+
45+
builder.addNetworkInterceptor(interceptorA);
46+
Parse.Configuration configurationA = builder.build();
47+
builder.addNetworkInterceptor(interceptorB);
48+
Parse.Configuration configurationB = builder.build();
49+
50+
assertFalse(configurationA.interceptors.contains(interceptorB));
51+
assertTrue(configurationB.interceptors.contains(interceptorB));
52+
53+
try {
54+
configurationA.interceptors.add(interceptorB);
55+
fail("Interceptors shouldn't be mutable.");
56+
} catch (UnsupportedOperationException ex) {
57+
// Expected
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)