Skip to content

Commit

Permalink
Version 2.1.0 (#29)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
arun251 authored Oct 14, 2017
1 parent 224e6b2 commit b4ad5fd
Show file tree
Hide file tree
Showing 18 changed files with 619 additions and 161 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@

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

## [2.1.0] - 2017-10-13
### Added
- `LDConfig.Builder#setUseReport` method to allow switching the request verb from `GET` to `REPORT`. Do not use unless advised by LaunchDarkly.

### Changed
- `LDClient.init` validates that its arguments are non-null.

### Fixed
- 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.

## [2.0.5] - 2017-06-18
### Fixed
- Potential `ConcurrentModificationException` with `LDClient#unregisterFeatureFlagListener`
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Much of the behavior we want to assert is around complicated device state change
app backgrounding, loss of internet connection. These are problematic to test in a programmatic way,
so we rely on a combination of automated emulator tests and manual tests.
If, when running tests, the Android Studio build starts throwing countDebugDexMethods and countReleaseDexMethods errors
using the run configuration dropdown, then switch to the command-line and exclude the two DEX methods causing the trouble.
> ./gradlew -x :launchdarkly-android-client:countDebugDexMethods -x :launchdarkly-android-client:countReleaseDexMethods -x :launchdarkly-android-client:signArchives --stacktrace clean build test cAT
About LaunchDarkly
-----------
Expand Down
10 changes: 7 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
buildscript {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.google.com'}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
classpath 'com.android.tools.build:gradle:2.3.3'

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

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand All @@ -19,6 +21,8 @@ buildscript {
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.google.com'}
}
}

Expand All @@ -36,4 +40,4 @@ subprojects {
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
}
}
}
}
12 changes: 7 additions & 5 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
machine:
environment:
QEMU_AUDIO_DRV: none
version: oraclejdk8

dependencies:
pre:
- unset ANDROID_NDK_HOME
# Android SDK Platform 24
- if [ ! -d "/usr/local/android-sdk-linux/platforms/android-24" ]; then echo y | android update sdk --no-ui --all --filter "android-24"; fi
# Android SDK Build-tools
- 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
- 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
# Android SDK Platform 26
- if [ ! -d "/usr/local/android-sdk-linux/platforms/android-26" ]; then echo y | android update sdk --no-ui --all --filter "android-26"; fi
# brings in appcompat
- if [ ! -d "/usr/local/android-sdk-linux/extras/android/m2repository" ]; then echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"; fi
- mkdir -p /usr/local/android-sdk-linux/licenses
- aws s3 cp s3://launchdarkly-pastebin/ci/android/licenses/android-sdk-license /usr/local/android-sdk-linux/licenses/android-sdk-license
- aws s3 cp s3://launchdarkly-pastebin/ci/android/licenses/intel-android-extra-license /usr/local/android-sdk-linux/licenses/intel-android-extra-license
cache_directories:
- /usr/local/android-sdk-linux/platforms/android-24
- /usr/local/android-sdk-linux/build-tools/25.0.3
- /usr/local/android-sdk-linux/platforms/android-26
- /usr/local/android-sdk-linux/build-tools/26.0.1
- /usr/local/android-sdk-linux/extras/android/m2repository

test:
override:
- unset ANDROID_NDK_HOME
Expand Down
15 changes: 11 additions & 4 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ repositories {
}

android {
compileSdkVersion 24
buildToolsVersion '25.0.3'
compileSdkVersion 26
buildToolsVersion '26.0.1'
defaultConfig {
applicationId "com.launchdarkly.example"
minSdkVersion 15
targetSdkVersion 24
targetSdkVersion 26
versionCode 1
versionName "1.0"
jackOptions {
enabled = true
}
}
buildTypes {
release {
Expand All @@ -28,12 +31,16 @@ android {
lintOptions {
abortOnError false
}
compileOptions {
targetCompatibility '1.8'
sourceCompatibility '1.8'
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.android.support:appcompat-v7:26.0.1'
compile project(path: ':launchdarkly-android-client')
// Comment the previous line and uncomment this one to depend on the published artifact:
// compile 'com.launchdarkly:launchdarkly-android-client:1.0.1'
Expand Down
182 changes: 97 additions & 85 deletions example/src/main/java/com/launchdarkly/example/MainActivity.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package com.launchdarkly.example;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;

import com.google.gson.JsonNull;
import com.launchdarkly.android.FeatureFlagChangeListener;
import com.launchdarkly.android.LDClient;
import com.launchdarkly.android.LDConfig;
import com.launchdarkly.android.LDUser;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
Expand All @@ -41,6 +41,7 @@ protected void onCreate(Bundle savedInstanceState) {

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

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

private void setupFlushButton() {
Button flushButton = (Button) findViewById(R.id.flush_button);
flushButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onStop() {
super.onStop();
try {
ldClient.close();
} catch (IOException e) {
Log.e(TAG, "Exception when closing LaunchDarkly Client", e);
}
}

@Override
public void onClick(View v) {
Log.i(TAG, "flush onClick");
ldClient.flush();
}
private void setupFlushButton() {
Button flushButton = findViewById(R.id.flush_button);
flushButton.setOnClickListener((View v) -> {
Log.i(TAG, "flush onClick");
doSafeClientAction(() -> ldClient.flush());
});
}

private void setupTrackButton() {
Button trackButton = (Button) findViewById(R.id.track_button);
trackButton.setOnClickListener(new View.OnClickListener() {
private interface LDClientFunction {
void call();
}

@Override
public void onClick(View v) {
Log.i(TAG, "track onClick");
ldClient.track("Android event name");
}
});
private interface LDClientGetFunction<V> {
V get();
}

private void setupIdentifyButton() {
Button identify = (Button) findViewById(R.id.identify_button);
identify.setOnClickListener(new View.OnClickListener() {
private void doSafeClientAction(LDClientFunction function) {
if (ldClient != null) {
function.call();
}
}

@Override
public void onClick(View v) {
Log.i(TAG, "identify onClick");
String userKey = ((EditText) findViewById(R.id.userKey_editText)).getText().toString();
@Nullable
private <V> V doSafeClientGet(LDClientGetFunction<V> function) {
if (ldClient != null) {
return function.get();
}
return null;
}

LDUser updatedUser = new LDUser.Builder(userKey)
.build();
private void setupTrackButton() {
Button trackButton = findViewById(R.id.track_button);
trackButton.setOnClickListener(v -> {
Log.i(TAG, "track onClick");
doSafeClientAction(() -> ldClient.track("Android event name"));
});
}

ldClient.identify(updatedUser);
}
private void setupIdentifyButton() {
Button identify = findViewById(R.id.identify_button);
identify.setOnClickListener(v -> {
Log.i(TAG, "identify onClick");
String userKey = ((EditText) findViewById(R.id.userKey_editText)).getText().toString();
LDUser updatedUser = new LDUser.Builder(userKey).build();
doSafeClientAction(() -> ldClient.identify(updatedUser));
});
}

private void setupOfflineSwitch() {
Switch offlineSwitch = (Switch) findViewById(R.id.offlineSwitch);
offlineSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (isChecked) {
ldClient.setOffline();
} else {
ldClient.setOnline();
}
Switch offlineSwitch = findViewById(R.id.offlineSwitch);
offlineSwitch.setOnCheckedChangeListener((compoundButton, isChecked) -> {
if (isChecked) {
doSafeClientAction(() -> ldClient.setOffline());
} else {
doSafeClientAction(() -> ldClient.setOnline());
}
});
}

private void setupEval() {
final Spinner spinner = (Spinner) findViewById(R.id.type_spinner);
final Spinner spinner = findViewById(R.id.type_spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.types_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);

Button evalButton = (Button) findViewById(R.id.eval_button);
evalButton.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Log.i(TAG, "eval onClick");
final String flagKey = ((EditText) findViewById(R.id.feature_flag_key)).getText().toString();

String type = spinner.getSelectedItem().toString();
final String result;
switch (type) {
case "String":
result = ldClient.stringVariation(flagKey, "default");
Log.i(TAG, result);
((TextView) findViewById(R.id.result_textView)).setText(result);
ldClient.registerFeatureFlagListener(flagKey, new FeatureFlagChangeListener() {
@Override
public void onFeatureFlagChange(String flagKey) {
((TextView) findViewById(R.id.result_textView))
.setText(ldClient.stringVariation(flagKey, "default"));
}
});
break;
case "Boolean":
result = ldClient.boolVariation(flagKey, false).toString();
Log.i(TAG, result);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Integer":
result = ldClient.intVariation(flagKey, 0).toString();
Log.i(TAG, result);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Float":
result = ldClient.floatVariation(flagKey, 0F).toString();
Log.i(TAG, result);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Json":
result = ldClient.jsonVariation(flagKey, JsonNull.INSTANCE).toString();
Log.i(TAG, result);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
}
Button evalButton = findViewById(R.id.eval_button);
evalButton.setOnClickListener((View v) -> {
Log.i(TAG, "eval onClick");
final String flagKey = ((EditText) findViewById(R.id.feature_flag_key)).getText().toString();

String type = spinner.getSelectedItem().toString();
final String result;
String logResult = "no result";
switch (type) {
case "String":
result = doSafeClientGet(() -> ldClient.stringVariation(flagKey, "default"));
logResult = result == null ? "no result" : result;
Log.i(TAG, logResult);
((TextView) findViewById(R.id.result_textView)).setText(result);
doSafeClientAction(() -> ldClient.registerFeatureFlagListener(flagKey, flagKey1 -> ((TextView) findViewById(R.id.result_textView))
.setText(ldClient.stringVariation(flagKey1, "default"))));
break;
case "Boolean":
result = doSafeClientGet(() -> ldClient.boolVariation(flagKey, false).toString());
logResult = result == null ? "no result" : result;
Log.i(TAG, logResult);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Integer":
result = doSafeClientGet(() -> ldClient.intVariation(flagKey, 0).toString());
logResult = result == null ? "no result" : result;
Log.i(TAG, logResult);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Float":
result = doSafeClientGet(() -> ldClient.floatVariation(flagKey, 0F).toString());
logResult = result == null ? "no result" : result;
Log.i(TAG, logResult);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
case "Json":
result = doSafeClientGet(() -> ldClient.jsonVariation(flagKey, JsonNull.INSTANCE).toString());
logResult = result == null ? "no result" : result;
Log.i(TAG, logResult);
((TextView) findViewById(R.id.result_textView)).setText(result);
break;
}
});
}
Expand Down
Loading

0 comments on commit b4ad5fd

Please sign in to comment.