Skip to content

Commit b4ad5fd

Browse files
authored
Version 2.1.0 (#29)
* LDConfig.Builder#setUseReport method to allow switching the request verb from GET to REPORT. * LDClient.init validates that its arguments are non-null. * Stream connections are closed completely when the app enters background mode. * Fewer HTTP requests are made to the LaunchDarkly service when feature flags are updated frequently. * Potential NullPointerException in the variation methods. * Removed spurious error when LDClient is initialized while the device is offline.
1 parent 224e6b2 commit b4ad5fd

File tree

18 files changed

+619
-161
lines changed

18 files changed

+619
-161
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33

44
All notable changes to the LaunchDarkly Android SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
55

6+
## [2.1.0] - 2017-10-13
7+
### Added
8+
- `LDConfig.Builder#setUseReport` method to allow switching the request verb from `GET` to `REPORT`. Do not use unless advised by LaunchDarkly.
9+
10+
### Changed
11+
- `LDClient.init` validates that its arguments are non-null.
12+
13+
### Fixed
14+
- Stream connections are closed completely when the app enters background mode.
15+
- Fewer HTTP requests are made to the LaunchDarkly service when feature flags are updated frequently.
16+
- Potential `NullPointerException` in the `variation` methods.
17+
- Removed spurious error when `LDClient` is initialized while the device is offline.
18+
619
## [2.0.5] - 2017-06-18
720
### Fixed
821
- Potential `ConcurrentModificationException` with `LDClient#unregisterFeatureFlagListener`

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ Much of the behavior we want to assert is around complicated device state change
101101
app backgrounding, loss of internet connection. These are problematic to test in a programmatic way,
102102
so we rely on a combination of automated emulator tests and manual tests.
103103
104+
If, when running tests, the Android Studio build starts throwing countDebugDexMethods and countReleaseDexMethods errors
105+
using the run configuration dropdown, then switch to the command-line and exclude the two DEX methods causing the trouble.
106+
107+
> ./gradlew -x :launchdarkly-android-client:countDebugDexMethods -x :launchdarkly-android-client:countReleaseDexMethods -x :launchdarkly-android-client:signArchives --stacktrace clean build test cAT
108+
104109
About LaunchDarkly
105110
-----------
106111

build.gradle

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
buildscript {
44
repositories {
55
jcenter()
6+
mavenCentral()
7+
maven { url 'https://maven.google.com'}
68
}
79
dependencies {
8-
classpath 'com.android.tools.build:gradle:2.3.2'
10+
classpath 'com.android.tools.build:gradle:2.3.3'
911

1012
// For displaying method/field counts when building with Gradle:
1113
// https://github.com/KeepSafe/dexcount-gradle-plugin
12-
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3'
14+
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.3'
1315

1416
// NOTE: Do not place your application dependencies here; they belong
1517
// in the individual module build.gradle files
@@ -19,6 +21,8 @@ buildscript {
1921
allprojects {
2022
repositories {
2123
jcenter()
24+
mavenCentral()
25+
maven { url 'https://maven.google.com'}
2226
}
2327
}
2428

@@ -36,4 +40,4 @@ subprojects {
3640
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
3741
}
3842
}
39-
}
43+
}

circle.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22
machine:
33
environment:
44
QEMU_AUDIO_DRV: none
5+
version: oraclejdk8
56

67
dependencies:
78
pre:
89
- unset ANDROID_NDK_HOME
9-
# Android SDK Platform 24
10-
- if [ ! -d "/usr/local/android-sdk-linux/platforms/android-24" ]; then echo y | android update sdk --no-ui --all --filter "android-24"; fi
1110
# Android SDK Build-tools
12-
- if [ ! -d "/usr/local/android-sdk-linux/build-tools/25.0.2" ]; then echo y | android update sdk --no-ui --all --filter "build-tools-25.0.3"; fi
11+
- if [ ! -d "/usr/local/android-sdk-linux/build-tools/26.0.1" ]; then echo y | android update sdk --no-ui --all --filter "build-tools-26.0.1"; fi
12+
# Android SDK Platform 26
13+
- if [ ! -d "/usr/local/android-sdk-linux/platforms/android-26" ]; then echo y | android update sdk --no-ui --all --filter "android-26"; fi
1314
# brings in appcompat
1415
- if [ ! -d "/usr/local/android-sdk-linux/extras/android/m2repository" ]; then echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"; fi
1516
- mkdir -p /usr/local/android-sdk-linux/licenses
1617
- aws s3 cp s3://launchdarkly-pastebin/ci/android/licenses/android-sdk-license /usr/local/android-sdk-linux/licenses/android-sdk-license
1718
- aws s3 cp s3://launchdarkly-pastebin/ci/android/licenses/intel-android-extra-license /usr/local/android-sdk-linux/licenses/intel-android-extra-license
1819
cache_directories:
19-
- /usr/local/android-sdk-linux/platforms/android-24
20-
- /usr/local/android-sdk-linux/build-tools/25.0.3
20+
- /usr/local/android-sdk-linux/platforms/android-26
21+
- /usr/local/android-sdk-linux/build-tools/26.0.1
2122
- /usr/local/android-sdk-linux/extras/android/m2repository
23+
2224
test:
2325
override:
2426
- unset ANDROID_NDK_HOME

example/build.gradle

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ repositories {
1010
}
1111

1212
android {
13-
compileSdkVersion 24
14-
buildToolsVersion '25.0.3'
13+
compileSdkVersion 26
14+
buildToolsVersion '26.0.1'
1515
defaultConfig {
1616
applicationId "com.launchdarkly.example"
1717
minSdkVersion 15
18-
targetSdkVersion 24
18+
targetSdkVersion 26
1919
versionCode 1
2020
versionName "1.0"
21+
jackOptions {
22+
enabled = true
23+
}
2124
}
2225
buildTypes {
2326
release {
@@ -28,12 +31,16 @@ android {
2831
lintOptions {
2932
abortOnError false
3033
}
34+
compileOptions {
35+
targetCompatibility '1.8'
36+
sourceCompatibility '1.8'
37+
}
3138
}
3239

3340
dependencies {
3441
compile fileTree(include: ['*.jar'], dir: 'libs')
3542
testCompile 'junit:junit:4.12'
36-
compile 'com.android.support:appcompat-v7:24.1.1'
43+
compile 'com.android.support:appcompat-v7:26.0.1'
3744
compile project(path: ':launchdarkly-android-client')
3845
// Comment the previous line and uncomment this one to depend on the published artifact:
3946
// compile 'com.launchdarkly:launchdarkly-android-client:1.0.1'

example/src/main/java/com/launchdarkly/example/MainActivity.java

Lines changed: 97 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
package com.launchdarkly.example;
22

33
import android.os.Bundle;
4+
import android.support.annotation.Nullable;
45
import android.support.v7.app.AppCompatActivity;
56
import android.util.Log;
67
import android.view.View;
78
import android.widget.ArrayAdapter;
89
import android.widget.Button;
9-
import android.widget.CompoundButton;
1010
import android.widget.EditText;
1111
import android.widget.Spinner;
1212
import android.widget.Switch;
1313
import android.widget.TextView;
1414

1515
import com.google.gson.JsonNull;
16-
import com.launchdarkly.android.FeatureFlagChangeListener;
1716
import com.launchdarkly.android.LDClient;
1817
import com.launchdarkly.android.LDConfig;
1918
import com.launchdarkly.android.LDUser;
2019

20+
import java.io.IOException;
2121
import java.util.concurrent.ExecutionException;
2222
import java.util.concurrent.Future;
2323
import java.util.concurrent.TimeUnit;
@@ -41,6 +41,7 @@ protected void onCreate(Bundle savedInstanceState) {
4141

4242
LDConfig ldConfig = new LDConfig.Builder()
4343
.setMobileKey("MOBILE_KEY")
44+
.setUseReport(false) // change to `true` if the request is to be REPORT'ed instead of GET'ed
4445
.build();
4546

4647
LDUser user = new LDUser.Builder("user key")
@@ -51,116 +52,127 @@ protected void onCreate(Bundle savedInstanceState) {
5152
try {
5253
ldClient = initFuture.get(10, TimeUnit.SECONDS);
5354
} catch (InterruptedException | ExecutionException | TimeoutException e) {
54-
Log.e(TAG, "Exception when awaiting LaunchDarkly Client initialization", e);
55+
Log.e(TAG, "Exception when awaiting LaunchDarkly Client initialization", e);
5556
}
5657
}
5758

58-
private void setupFlushButton() {
59-
Button flushButton = (Button) findViewById(R.id.flush_button);
60-
flushButton.setOnClickListener(new View.OnClickListener() {
59+
@Override
60+
public void onStop() {
61+
super.onStop();
62+
try {
63+
ldClient.close();
64+
} catch (IOException e) {
65+
Log.e(TAG, "Exception when closing LaunchDarkly Client", e);
66+
}
67+
}
6168

62-
@Override
63-
public void onClick(View v) {
64-
Log.i(TAG, "flush onClick");
65-
ldClient.flush();
66-
}
69+
private void setupFlushButton() {
70+
Button flushButton = findViewById(R.id.flush_button);
71+
flushButton.setOnClickListener((View v) -> {
72+
Log.i(TAG, "flush onClick");
73+
doSafeClientAction(() -> ldClient.flush());
6774
});
6875
}
6976

70-
private void setupTrackButton() {
71-
Button trackButton = (Button) findViewById(R.id.track_button);
72-
trackButton.setOnClickListener(new View.OnClickListener() {
77+
private interface LDClientFunction {
78+
void call();
79+
}
7380

74-
@Override
75-
public void onClick(View v) {
76-
Log.i(TAG, "track onClick");
77-
ldClient.track("Android event name");
78-
}
79-
});
81+
private interface LDClientGetFunction<V> {
82+
V get();
8083
}
8184

82-
private void setupIdentifyButton() {
83-
Button identify = (Button) findViewById(R.id.identify_button);
84-
identify.setOnClickListener(new View.OnClickListener() {
85+
private void doSafeClientAction(LDClientFunction function) {
86+
if (ldClient != null) {
87+
function.call();
88+
}
89+
}
8590

86-
@Override
87-
public void onClick(View v) {
88-
Log.i(TAG, "identify onClick");
89-
String userKey = ((EditText) findViewById(R.id.userKey_editText)).getText().toString();
91+
@Nullable
92+
private <V> V doSafeClientGet(LDClientGetFunction<V> function) {
93+
if (ldClient != null) {
94+
return function.get();
95+
}
96+
return null;
97+
}
9098

91-
LDUser updatedUser = new LDUser.Builder(userKey)
92-
.build();
99+
private void setupTrackButton() {
100+
Button trackButton = findViewById(R.id.track_button);
101+
trackButton.setOnClickListener(v -> {
102+
Log.i(TAG, "track onClick");
103+
doSafeClientAction(() -> ldClient.track("Android event name"));
104+
});
105+
}
93106

94-
ldClient.identify(updatedUser);
95-
}
107+
private void setupIdentifyButton() {
108+
Button identify = findViewById(R.id.identify_button);
109+
identify.setOnClickListener(v -> {
110+
Log.i(TAG, "identify onClick");
111+
String userKey = ((EditText) findViewById(R.id.userKey_editText)).getText().toString();
112+
LDUser updatedUser = new LDUser.Builder(userKey).build();
113+
doSafeClientAction(() -> ldClient.identify(updatedUser));
96114
});
97115
}
98116

99117
private void setupOfflineSwitch() {
100-
Switch offlineSwitch = (Switch) findViewById(R.id.offlineSwitch);
101-
offlineSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
102-
@Override
103-
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
104-
if (isChecked) {
105-
ldClient.setOffline();
106-
} else {
107-
ldClient.setOnline();
108-
}
118+
Switch offlineSwitch = findViewById(R.id.offlineSwitch);
119+
offlineSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> {
120+
if (isChecked) {
121+
doSafeClientAction(() -> ldClient.setOffline());
122+
} else {
123+
doSafeClientAction(() -> ldClient.setOnline());
109124
}
110125
});
111126
}
112127

113128
private void setupEval() {
114-
final Spinner spinner = (Spinner) findViewById(R.id.type_spinner);
129+
final Spinner spinner = findViewById(R.id.type_spinner);
115130
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
116131
R.array.types_array, android.R.layout.simple_spinner_item);
117132
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
118133
spinner.setAdapter(adapter);
119134

120-
Button evalButton = (Button) findViewById(R.id.eval_button);
121-
evalButton.setOnClickListener(new View.OnClickListener() {
122-
123-
@Override
124-
public void onClick(View v) {
125-
Log.i(TAG, "eval onClick");
126-
final String flagKey = ((EditText) findViewById(R.id.feature_flag_key)).getText().toString();
127-
128-
String type = spinner.getSelectedItem().toString();
129-
final String result;
130-
switch (type) {
131-
case "String":
132-
result = ldClient.stringVariation(flagKey, "default");
133-
Log.i(TAG, result);
134-
((TextView) findViewById(R.id.result_textView)).setText(result);
135-
ldClient.registerFeatureFlagListener(flagKey, new FeatureFlagChangeListener() {
136-
@Override
137-
public void onFeatureFlagChange(String flagKey) {
138-
((TextView) findViewById(R.id.result_textView))
139-
.setText(ldClient.stringVariation(flagKey, "default"));
140-
}
141-
});
142-
break;
143-
case "Boolean":
144-
result = ldClient.boolVariation(flagKey, false).toString();
145-
Log.i(TAG, result);
146-
((TextView) findViewById(R.id.result_textView)).setText(result);
147-
break;
148-
case "Integer":
149-
result = ldClient.intVariation(flagKey, 0).toString();
150-
Log.i(TAG, result);
151-
((TextView) findViewById(R.id.result_textView)).setText(result);
152-
break;
153-
case "Float":
154-
result = ldClient.floatVariation(flagKey, 0F).toString();
155-
Log.i(TAG, result);
156-
((TextView) findViewById(R.id.result_textView)).setText(result);
157-
break;
158-
case "Json":
159-
result = ldClient.jsonVariation(flagKey, JsonNull.INSTANCE).toString();
160-
Log.i(TAG, result);
161-
((TextView) findViewById(R.id.result_textView)).setText(result);
162-
break;
163-
}
135+
Button evalButton = findViewById(R.id.eval_button);
136+
evalButton.setOnClickListener((View v) -> {
137+
Log.i(TAG, "eval onClick");
138+
final String flagKey = ((EditText) findViewById(R.id.feature_flag_key)).getText().toString();
139+
140+
String type = spinner.getSelectedItem().toString();
141+
final String result;
142+
String logResult = "no result";
143+
switch (type) {
144+
case "String":
145+
result = doSafeClientGet(() -> ldClient.stringVariation(flagKey, "default"));
146+
logResult = result == null ? "no result" : result;
147+
Log.i(TAG, logResult);
148+
((TextView) findViewById(R.id.result_textView)).setText(result);
149+
doSafeClientAction(() -> ldClient.registerFeatureFlagListener(flagKey, flagKey1 -> ((TextView) findViewById(R.id.result_textView))
150+
.setText(ldClient.stringVariation(flagKey1, "default"))));
151+
break;
152+
case "Boolean":
153+
result = doSafeClientGet(() -> ldClient.boolVariation(flagKey, false).toString());
154+
logResult = result == null ? "no result" : result;
155+
Log.i(TAG, logResult);
156+
((TextView) findViewById(R.id.result_textView)).setText(result);
157+
break;
158+
case "Integer":
159+
result = doSafeClientGet(() -> ldClient.intVariation(flagKey, 0).toString());
160+
logResult = result == null ? "no result" : result;
161+
Log.i(TAG, logResult);
162+
((TextView) findViewById(R.id.result_textView)).setText(result);
163+
break;
164+
case "Float":
165+
result = doSafeClientGet(() -> ldClient.floatVariation(flagKey, 0F).toString());
166+
logResult = result == null ? "no result" : result;
167+
Log.i(TAG, logResult);
168+
((TextView) findViewById(R.id.result_textView)).setText(result);
169+
break;
170+
case "Json":
171+
result = doSafeClientGet(() -> ldClient.jsonVariation(flagKey, JsonNull.INSTANCE).toString());
172+
logResult = result == null ? "no result" : result;
173+
Log.i(TAG, logResult);
174+
((TextView) findViewById(R.id.result_textView)).setText(result);
175+
break;
164176
}
165177
});
166178
}

0 commit comments

Comments
 (0)