Code Monkey home page Code Monkey logo

android-client-sdk's Introduction

LaunchDarkly SDK for Android

Actions Status

LaunchDarkly overview

LaunchDarkly is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. Get started using LaunchDarkly today!

Twitter Follow

Supported Android versions

This version of the LaunchDarkly SDK has been tested with Android SDK versions 21 and up (5.0 Lollipop).

Getting started

Refer to the SDK documentation for instructions on getting started with using the SDK.

Learn more

Read our documentation for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the complete reference guide for this SDK or our Javadocs.

Testing

We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly.

Contributing

We encourage pull requests and other contributions from the community. Check out our contributing guidelines for instructions on how to contribute to this SDK.

About LaunchDarkly

  • LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
    • Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
    • Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
    • Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
    • Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
  • LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read our documentation for a complete list.
  • Explore LaunchDarkly

android-client-sdk's People

Contributors

aengelberg avatar apache-hb avatar arun251 avatar audkar avatar bwoskow-ld avatar cwaldren-ld avatar drichelson avatar eli-darkly avatar ember-stevens avatar exaper avatar farhanjkhan avatar gavwhela avatar github-actions[bot] avatar gwhelanld avatar jamesthacker avatar jkodumal avatar jonathanmgrimm avatar keelerm84 avatar kparkinson-ld avatar launchdarklyreleasebot avatar ld-repository-standards[bot] avatar lguipeng avatar louis-launchdarkly avatar mattyway avatar nejifresh avatar res0nance avatar robertjneal avatar tanderson-ld avatar torchhound avatar yvgentroshchiy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

android-client-sdk's Issues

Multiple instances of LDClient, a.k.a. using LD in a library?

Is your feature request related to a problem? Please describe.
I'm building an app, and the core of the app is also shipped as a library to 3rd parties. If I use LD in the core - will it interfere with 3rd party apps integrating my library? The documentation (https://docs.launchdarkly.com/docs/android-sdk-reference#section-multiple-environments) claims that "LaunchDarkly's Android SDK supports having multiple LDClient instances tied to separate mobile keys" but then it also says "All LDClient instances will evaluate against the same LDUser." So, which is it?

Describe the solution you'd like
I would like to create an instance of LDClient for use within my library, that wouldn't interfere with the rest of the app, which could be also using LD.

Describe alternatives you've considered
I am considering not using LD for feature flag, if it doesn't work in libraries.

Additional context
Documentation is lacking.

Remove logging in release builds or provide facility to do so

Upon integrating this client SDK I was immediately presented with a gradle error due to mismatch of slf4j versions between app and test apks.
When further inspecting the source I found the majority of the sdk is implemented depending directly on android.util.Log excluding the LDUser class which uses org.slf4j.Logger.
This is troubling at least for me for two reasons, first being that the inclusion of com.noveogroup.android:android-logger as a dependency seems unnecessary given the tiny usage profile.
Second reason being that the logging via android.util.Log is being left in place for all use cases except where removed via a consuming builds proguard config.

It would be preferable to use a smaller footprint android first logging library such as Timber or remove all logging when releasing artifacts by simply wrap all calls to android.util.Log with an if (BuildConfig.LOGGING)and using something like the following in your build.gradle file:

android {
    buildTypes {
        release {
            buildConfigField "boolean", "LOGGING", "false"
        }
        debug {
            buildConfigField "boolean", "LOGGING", "true"
        }
    }
}

This will result in a compile time constant allowing the if block and logging method call to be completely removed by the compiler.
Which may be preferable when compared to BuildConfig.DEBUG which is implemented as a runtime parsed string thus disallowing automatic compile time removal.

Support for projects targeting JDK 1.7

Transitive dependency com.google.guava:guava:XY.Z on 'com.launchdarkly:launchdarkly-android-client:X.Y.Z` is causing compilation errors on project with compileOptions targeting javac 1.7.

Options:

Workaround:

compile ('com.launchdarkly:launchdarkly-android-client:2.1.0') {
        exclude group: 'com.google.guava', module: 'guava'
}
// swap LD guava dependency with an Android compatible one
compile 'com.google.guava:guava:22.0-android'

Crash with launchDarkly 2.7.0

Describe the bug
App is crashing on launch

To reproduce
This happens in production only for now

Expected behavior
Not crash ...

Logs

Caused by java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.String
       at com.launchdarkly.android.Migration.migrate_2_7_fresh + 78(Migration.java:78)
       at com.launchdarkly.android.Migration.migrateWhenNeeded + 32(Migration.java:32)
       at com.launchdarkly.android.LDClient.init + 129(LDClient.java:129)

SDK version
2.5.4 to 2.7.0

Language version, developer tools
Java/kotlin

OS/platform
Android 9

CONNECTIVITY_CHANGE broadcast not received in apps targeting SDK 24+

While testing changes to my app from updating the targetSdkVersion to 24 and above I noticed that the ConnectivityReceiver no longer receives CONNECTIVITY_CHANGE broadcasts. From https://developer.android.com/about/versions/nougat/android-7.0-changes#bg-opt:

Apps targeting Android 7.0 (API level 24) and higher do not receive CONNECTIVITY_ACTION broadcasts if they declare their broadcast receiver in the manifest. Apps will still receive CONNECTIVITY_ACTION broadcasts if they register their BroadcastReceiver with Context.registerReceiver() and that context is still valid.

The same issue affects the example app included in the repo as well.

We can get around it by registering the receiver manually within the app:

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(new ConnectivityReceiver(), intentFilter);

but wanted to know if there was a better, prefered way to do this.

ANR on Android 9/10 devices

Describe the bug
Google play console shows several ANRs (private package name hidden):

Broadcast of Intent { flg=0x14 cmp=my.package.name/com.launchdarkly.android.PollingUpdater (has extras) }

com.launchdarkly.android.PollingUpdater

Expected behavior
No ANR in console.

Logs

"Thread-7" tid=25 Waiting
"Thread-7" prio=5 tid=25 Waiting
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x13340ce0 self=0x7977182800
  | sysTid=16374 nice=10 cgrp=bg_cached sched=0/0 handle=0x790be2cd50
  | state=S schedstat=( 601500 348923 2 ) utm=0 stm=0 core=1 HZ=100
  | stack=0x790bd2a000-0x790bd2c000 stackSize=1039KB
  | held mutexes=
  at sun.misc.Unsafe.park (Native method)
- waiting on an unknown object
  at java.util.concurrent.locks.LockSupport.park (LockSupport.java:190)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await (AbstractQueuedSynchronizer.java:2067)
  at java.util.concurrent.LinkedBlockingQueue.take (LinkedBlockingQueue.java:442)
  at java.util.concurrent.ThreadPoolExecutor.getTask (ThreadPoolExecutor.java:1092)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1152)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at com.launchdarkly.android.BackgroundThreadExecutor$PriorityThreadFactory$1.run (BackgroundThreadExecutor.java:48)
  at java.lang.Thread.run (Thread.java:919)

SDK version
2.10.0

Language version, developer tools
Android with kotlin source code (v1.3.61), error appears on Google Play Console.

OS/platform
Android, only occurs on Android 9/10 devices and (if not just occasionally) only Samsung devices.

Thanks for your help!

Errors are logged when app starts without network connections

When my app starts without network connection (e.g. WiFi is off) - the LD library logs the following errors:

2020-09-23 22:49:28.514 11484-11560/app E/DiagnosticEventProcessor: Unhandled exception in LaunchDarkly client attempting to connect to URI: https://mobile.launchdarkly.com/mobile/events/diagnostic
    java.net.UnknownHostException: Unable to resolve host "mobile.launchdarkly.com": No address associated with hostname
        at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
        at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
        at java.net.InetAddress.getAllByName(InetAddress.java:1152)
        at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49)
        at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:164)
        at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:129)
        at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:71)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:205)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at com.launchdarkly.android.SSLHandshakeInterceptor.intercept(TLSUtils.java:72)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
        at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
        at com.google.firebase.perf.network.FirebasePerfOkHttpClient.execute(com.google.firebase:firebase-perf@@19.0.8:5)
        at com.launchdarkly.android.DiagnosticEventProcessor.sendDiagnosticEventSync(DiagnosticEventProcessor.java:114)
        at com.launchdarkly.android.DiagnosticEventProcessor.access$300(DiagnosticEventProcessor.java:21)
        at com.launchdarkly.android.DiagnosticEventProcessor$4.run(DiagnosticEventProcessor.java:99)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
        at libcore.io.Linux.android_getaddrinfo(Native Method)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:73)
        at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:202)
        at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:73)
        at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
        at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103) 
        at java.net.InetAddress.getAllByName(InetAddress.java:1152) 
        at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49) 
        at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:164) 
        at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:129) 
        at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:71) 
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:205) 
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106) 
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74) 
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255) 
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32) 
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95) 
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83) 
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76) 
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
        at com.launchdarkly.android.SSLHandshakeInterceptor.intercept(TLSUtils.java:72) 
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
        at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201) 
        at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154) 
        at com.google.firebase.perf.network.FirebasePerfOkHttpClient.execute(com.google.firebase:firebase-perf@@19.0.8:5) 
        at com.launchdarkly.android.DiagnosticEventProcessor.sendDiagnosticEventSync(DiagnosticEventProcessor.java:114) 
        at com.launchdarkly.android.DiagnosticEventProcessor.access$300(DiagnosticEventProcessor.java:21) 
        at com.launchdarkly.android.DiagnosticEventProcessor$4.run(DiagnosticEventProcessor.java:99) 
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) 
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:923) 
2020-09-23 22:49:28.744 11484-11560/app E/DiagnosticEventProcessor: Unhandled exception in LaunchDarkly client attempting to connect to URI: https://mobile.launchdarkly.com/mobile/events/diagnostic
    java.net.UnknownHostException: Unable to resolve host "mobile.launchdarkly.com": No address associated with hostname
        at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:124)
        at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
        at java.net.InetAddress.getAllByName(InetAddress.java:1152)
        at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49)
        at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:164)
        at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:129)
        at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:71)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:205)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:106)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:74)
        at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:255)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at com.launchdarkly.android.SSLHandshakeInterceptor.intercept(TLSUtils.java:72)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
        at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
        at com.google.firebase.perf.network.FirebasePerfOkHttpClient.execute(com.google.firebase:firebase-perf@@19.0.8:5)
        at com.launchdarkly.android.DiagnosticEventProcessor.sendDiagnosticEventSync(DiagnosticEventProcessor.java:114)
        at com.launchdarkly.android.DiagnosticEventProcessor.access$300(DiagnosticEventProcessor.java:21)
        at com.launchdarkly.android.DiagnosticEventProcessor$4.run(DiagnosticEventProcessor.java:99)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

It's understandable that the library would try to connect to the mothership and that would fail - but treating these failures as ERRORS is not right. This spams the logs for no good reason.

Please, maybe consider, logging these as Warnings? Ideally, you wouldn't even try to connect if there is no internet connectivity. Thanks.

Attempted to get non-existent flag for key

Describe the bug
We started seeing tons of logs complaining about Attempted to get non-existent flag for key which originates from this line.
It is unclear to me why this is happening. The flags exist on the environment and when I target the device in test, it correctly gets set to true despite the library saying that the flag doesn't exist upon startup.
To make matters stranger, this seems to only happen on some environments which, other than by name, are identical in setting.

To reproduce
Start up the app and check logcat. We have websockets enabled on our project.

Expected behavior
Launch Darkly doesn't complain about Attempted to get non-existent flag for key

SDK version
2.10.0

OS/platform
Tested on API 23 and 29

Crash on Android 10 when proxy is setup on VPN connection.

Is this a support request? -> NO
This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going here and clicking "submit a request", or by emailing [email protected].

Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above.

Describe the bug
Our app is crashing with IllegalArgumentException in the okhttp call with message port out of range -1. This is blocking our release for the supporting proxy on Android 10 devices.

Ours is an enterprise VPN app (Citrix SSO). We are using LD for some time now. When setting up VPN connection, we add proxy (via a PAC file URL) to the VPN interface on device. After that as soon as our app is put in background, above mentioned exception happens and app crashes. It happens almost consistently (8 out of 10 times). By the way our app starts a foreground service for the VPN session, so app has foreground priority at the time of crash.

Any help in addressing the crash is appreciated.

To reproduce
Steps to reproduce the behavior.
This happens almost every time when VPN gets activated on Android 10 which has PROXY setup with PAC file URL. Ours is an enterprise VPN (Citrix SSO) app. We use LD for controlling some features. I am not sure if our scenario can be directly reproduced on your end. In any case, here are the steps to reproduce it reliably.

  • Connect to a VPN server which has Proxy configured with PAC file URL on Android 10 device
  • Put the app (Citrix SSO) using LD in background by tapping recent apps soft button
  • Crash is observed immediately

Expected behavior
App should not crash when LD is running in the background.

Logs
If applicable, add any log output related to your problem.
--------- beginning of crash 2020-06-01 13:21:05.732 26334-26520/com.citrix.CitrixVPN E/AndroidRuntime: FATAL EXCEPTION: okhttp-eventsource-stream-[]-0 Process: com.citrix.CitrixVPN, PID: 26334 java.lang.IllegalArgumentException: port out of range:-1 at java.net.InetSocketAddress.checkPort(InetSocketAddress.java:144) at java.net.InetSocketAddress.createUnresolved(InetSocketAddress.java:269) at sun.net.spi.DefaultProxySelector$1.run(DefaultProxySelector.java:315) at sun.net.spi.DefaultProxySelector$1.run(DefaultProxySelector.java:219) at java.security.AccessController.doPrivileged(AccessController.java:43) at sun.net.spi.DefaultProxySelector.select(DefaultProxySelector.java:218) at okhttp3.internal.connection.RouteSelector.resetNextProxy(RouteSelector.java:129) at okhttp3.internal.connection.RouteSelector.<init>(RouteSelector.java:63) at okhttp3.internal.connection.StreamAllocation.<init>(StreamAllocation.java:101) at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:112) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:250) at okhttp3.RealCall.execute(RealCall.java:93) at com.launchdarkly.eventsource.EventSource.connect(EventSource.java:189) at com.launchdarkly.eventsource.EventSource.access$1100(EventSource.java:43) at com.launchdarkly.eventsource.EventSource$2.run(EventSource.java:114) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)

SDK version
Android LD SDK version 2.12.0 (earlier we were using 2.9.0)

Language version, developer tools
Java, Android Studio 3.6.3

OS/platform
Citrix SSO running on Android Emulator with Android 10.

Additional context
Add any other context about the problem here.

Invalid JSON Object from LDClient

This issue started happening with version 2.7.0 (version 2.6.0 works). It is still present in the latest 2.8.3.

We are getting java.lang.IllegalStateException: Not a JSON Object. Here is the snippet with version 2.6.0 that works:

2019-06-11_17-09-14

And here is the snippet with 2.7.0+:

2019-06-11_17-01-38

Note how the jsonElement is wrapped in additional "".

The exception happens when we try to extract jsonObject from jsonElement:

            if (jsonElement != null) {
                JsonObject jsonObject = jsonElement.getAsJsonObject();
...
...

The actual exception is:

    java.lang.IllegalStateException: Not a JSON Object: "{ \"tipsForTransactionType\": [ \"SalesReceipt\" ] }"
        at com.google.gson.JsonElement.getAsJsonObject(JsonElement.java:91)

FeatureFlagChange listener is not trigger for Custom Groups

Hi,
I have a requirement to set the Custom groups for the user. Groups name will be like Free, Enterprise, Standart. Now, based on the Groups name user flags get changed. Now, Everything is configured properly. I have also set the FeatureFlagChangeListener for 1 key to tracking its value. Now, everything looks very state forward but when the FeatureFlagValue get changed on a console then it is not getting reflected to Mobile even the listener is already configured.

Please check below code for I am setting the Custom Groups for the User and also how I set the listener for a particular flag.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	setupEval();
	setupFlushButton();
	setupTrackButton();
	setupIdentifyButton();

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

	// Based on groups, user's flag will get updated.
	LDUser user = new LDUser.Builder("[email protected]")
			.custom("groups", "Free") // e.g. starter, standard, professional, enterprise
			.build();

	Future<LDClient> initFuture = LDClient.init(this.getApplication(), ldConfig, user);
	try {
		ldClient = initFuture.get(5, TimeUnit.SECONDS);
		((TextView) MainActivity.this.findViewById(R.id.result_textView))
				.setText(ldClient.boolVariation(flagKey1, false).toString());

		// Here I have set the listener, so whenever the flag get changed (EVEN THE GROUP IS NOT GET CHANGED IN MOBILE), it will return 					 
        // the flag value as per the group
		MainActivity.this.doSafeClientAction(new LDClientFunction() {
			@Override
			public void call() {
				ldClient.registerFeatureFlagListener(flagKey1, new FeatureFlagChangeListener() {
					@Override
					public void onFeatureFlagChange(String flagKey1) {
						((TextView) MainActivity.this.findViewById(R.id.result_textView))
								.setText(ldClient.boolVariation(flagKey1, false).toString());
					}
				});
			}
		});
	} catch (InterruptedException | ExecutionException | TimeoutException e) {
		Log.e(TAG, "Exception when awaiting LaunchDarkly Client initialization", e);
	}
} 

Please let me know what's wrong is here? Why the listener is not getting a trigger when the value gets changed.
Thanks.

LDClient::close() does not clean up the internal `instances` map.

This sequence:

  • init(...)
  • close()
  • init(...)

does not work. The second init() call will succeed, but the LD library will be non-operational.

This is happening because, LDClient has a static instances map, which isn't cleared by close():

    private static Map<String, LDClient> instances = null;

    ...

    public void close() throws IOException {
        LDClient.closeInstances();
    }

    ...

    private static void closeInstances() {
        for (LDClient client : instances.values()) {
            client.closeInternal();
        }
    }

So, the second init() will find instances and will bail out early:

        if (instances != null) {
            Timber.w("LDClient.init() was called more than once! returning primary instance.");
            return new LDSuccessFuture<>(instances.get(LDConfig.primaryEnvironmentName));
        }

Please do instances = null at the end of closeInstances() so that library can be re-initialized. Thanks.

Fatal Exception: java.lang.SecurityException on Samsung devices

Describe the bug
Crash only impacting Samsung devices, probably related to the way the PendingIntent is retrieved in the PollingUpdater. See https://github.com/launchdarkly/android-client-sdk/blob/master/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/android/PollingUpdater.java#L56

Logs

Fatal Exception: java.lang.SecurityException: !@Too many alarms (500) registered from uid 10085
       at android.os.Parcel.createException + 1966(Parcel.java:1966)
       at android.os.Parcel.readException + 1934(Parcel.java:1934)
       at android.os.Parcel.readException + 1884(Parcel.java:1884)
       at android.app.IAlarmManager$Stub$Proxy.set + 240(IAlarmManager.java:240)
       at android.app.AlarmManager.setImpl + 722(AlarmManager.java:722)
       at android.app.AlarmManager.setInexactRepeating + 823(AlarmManager.java:823)
       at com.launchdarkly.android.PollingUpdater.startPolling + 36(PollingUpdater.java:36)
       at com.launchdarkly.android.PollingUpdater.startBackgroundPolling + 27(PollingUpdater.java:27)
       at com.launchdarkly.android.ConnectivityManager.startBackgroundPolling + 182(ConnectivityManager.java:182)
       at com.launchdarkly.android.ConnectivityManager.attemptTransition + 258(ConnectivityManager.java:258)
       at com.launchdarkly.android.ConnectivityManager.access$300 + 18(ConnectivityManager.java:18)
       at com.launchdarkly.android.ConnectivityManager$1.run + 64(ConnectivityManager.java:64)
       at com.launchdarkly.android.Throttler$1.run + 49(Throttler.java:49)
       at android.os.Handler.handleCallback + 873(Handler.java:873)
       at android.os.Handler.dispatchMessage + 99(Handler.java:99)
       at android.os.Looper.loop + 214(Looper.java:214)
       at android.os.HandlerThread.run + 65(HandlerThread.java:65)

Caused by android.os.RemoteException: Remote stack trace:
	at com.android.server.SamsungAlarmManagerService.checkMaliciousAppLocked(SamsungAlarmManagerService.java:306)
	at com.android.server.AlarmManagerService.setImpl(AlarmManagerService.java:1748)
	at com.android.server.AlarmManagerService$2.set(AlarmManagerService.java:2090)
	at android.app.IAlarmManager$Stub.onTransact(IAlarmManager.java:92)
	at android.os.Binder.execTransact(Binder.java:739)

SDK version
2.8.4

Additional context
https://stackoverflow.com/questions/29344971/java-lang-securityexception-too-many-alarms-500-registered-from-pid-10790-u

Cache toggles between app launches

We expected LaunchDarkly to cache the toggles and their rules across app launches so that way when someone opens the app when they have no internet they get the same toggles that they got previously when they did have a connection.

Remove guava as a dependency

From the whole dependency list most are admissible due to common usage on android or based on minimal size impact (dexCount < 2500)

The primary offender being compile 'com.google.guava:guava:19.0'

Based on the very small portion of guava that is actually being consumed it seems a shame to pull in a 2MB jar to only directly use 8 unique classes. The patterns observed here is frequently the reason guava is advocated against on android.

You can see the dependency report on your library here

Actual immediate dependency on guava is as follows:

from com.launchdarkly.android.EventProcessor:

  • ThreadFactoryBuilder

from com.launchdarkly.android.HttpFeatureFlagFetcher:

  • ListenableFuture
  • SettableFuture

from com.launchdarkly.android.LDClient:

from com.launchdarkly.android.StreamProcessor:

  • ListenableFuture
  • SettableFuture

from com.launchdarkly.android.UserManager:

  • Function
  • ArrayListMultimap
  • Multimap
  • FuturesCallback
  • Futures
  • ListenableFuture

Timber update 4.6.0 --> 4.7.0

Hi,
We don't use Timber in our app but pulling in LaunchDarkly's client pulls in v4.6.0, which breaks our lint task. This is a known issue that was fixed in Timber v4.7.0, see more details here:
JakeWharton/timber#292

The impact to us: Our lint report is incomplete because it fails on IncompatibleClassChangeError early on, hiding any other lint issues.

This is not urgent since we mitigated the issue by explicitly pulling down Timber 4.7.0. However, it'd be nice to remove this explicit dependency eventually.

Cheers,
Brian

ClassCastException when calling LDClient.boolVariation

Hi, we are getting reports of this crash happening to our customers, however we can't reproduce it ourselves. It doesn't seem related to the boolean value, rather the flagVersion is somehow stored as a float when it is expecting a string.

java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.String
        at android.app.SharedPreferencesImpl.getString(SharedPreferencesImpl.java:255)
        at com.launchdarkly.android.response.BaseUserSharedPreferences.getValueAsJsonObject(BaseUserSharedPreferences.java:37)
        at com.launchdarkly.android.response.BaseUserSharedPreferences.extractValueFromPreferences(BaseUserSharedPreferences.java:27)
        at com.launchdarkly.android.response.UserFlagResponseSharedPreferences.getStoredFlagVersion(UserFlagResponseSharedPreferences.java:77)
        at com.launchdarkly.android.response.UserFlagResponseSharedPreferences.getVersionForEvents(UserFlagResponseSharedPreferences.java:153)
        at com.launchdarkly.android.LDClient.boolVariation(LDClient.java:326)

Can't modify custom attribute of existing LDUser

Since release #36 it is forbidden to change custom attirbutes of existing LDUser.
If you create user by regular:

val user = LDUser.Builder("1")
.email("[email protected]")
.custom("k1","v1")
.custom("k2","v2")
.build()

and than you want to modify by using

val builder = LDUser.Builder(user)

you can call builder.email("[email protected]") but you can't use builder.custom("k1", "otherValue") - UnsupportedOperationException is thrown due to Collections.unmodifiableMap.

It is expected behavior?

I wanted to upgrade library from version 2.1.0 to 2.5.0, but I couldn't find any information what caused this change and how should I migrate to new API.

PollingUpdater's onReceive() doing certain operations on main thread causing Application Not Responding error

It appears that PollingUpdate.java onReceive() method is trying to get user from UserManager on main thread which is causing Application Not Responding (ANR) error in our app.

Version: com.launchdarkly:launchdarkly-android-client:2.5.0

Below is the stack trace in Android vitals from Google Play Console.

"main" prio=5 tid=1 TimedWaiting
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73670ca0 self=0x70d32c5a00
  | sysTid=24151 nice=0 cgrp=default sched=0/0 handle=0x70d90009c8
  | state=S schedstat=( 4484132241 6486322535 11275 ) utm=262 stm=186 core=1 HZ=100
  | stack=0x7ff0c29000-0x7ff0c2b000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait (Native method)
- waiting on <0x09d42fb6> (a java.lang.Object)
  at java.lang.Thread.parkFor$ (Thread.java:2135)
- locked <0x09d42fb6> (a java.lang.Object)
  at sun.misc.Unsafe.park (Unsafe.java:358)
  at java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:230)
  at com.google.a.f.a.a.get (AbstractFuture.java:393)
  at com.google.a.f.a.a$h.get (AbstractFuture.java:86)
  at com.launchdarkly.android.PollingUpdater.onReceive (PollingUpdater.java:34)
  at android.app.ActivityThread.handleReceiver (ActivityThread.java:3390)
  at android.app.ActivityThread.-wrap18 (ActivityThread.java)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1780)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6938)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)

Please fix this asap. Thanks!

Usage: LDClient::close() on application termination unclear

Describe the bug
I'm confused on how to close the LD client from the docs:

Lastly, when your application is about to terminate, shut down ldClient. This ensures that the client releases any resources it is using, and that any pending analytics events are delivered to LaunchDarkly. If your application quits without this shutdown step, you may not see your requests and users on the dashboard, because they are derived from analytics events. This is something you only need to do once.
https://docs.launchdarkly.com/sdk/client-side/android#getting-started

I think its atypical to listen for application termination as it's not something an application can listen to? Looking at an Application's onTerminate() callback the doc states:

    /**
     * This method is for use in emulated process environments.  It will
     * never be called on a production Android device, where processes are
     * removed by simply killing them; no user code (including this callback)
     * is executed when doing so.
     */
    @CallSuper
    public void onTerminate() {
    }

The ProcessLifecycleOwner.java states something similar. Am I missing something? Is there an example I can reference to help me out in this regard? Thank you for your time!

SDK version
2.13.0

NullPointerException in HttpFeatureFlagFetcher

Notes

In production, we are seeing the following crash at the rate of 1 per hour.

Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.launchdarkly.android.LDUser.getAsUrlSafeBase64()' on a null object reference
       at com.launchdarkly.android.HttpFeatureFlagFetcher.fetch(HttpFeatureFlagFetcher.java:69)
       at com.launchdarkly.android.UserManager.updateCurrentUser(UserManager.java:133)
       at com.launchdarkly.android.PollingUpdater.onReceive(PollingUpdater.java:34)
       at android.app.ActivityThread.handleReceiver(ActivityThread.java:2838)
       at android.app.ActivityThread.access$1700(ActivityThread.java:178)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1547)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at android.os.Looper.loop(Looper.java:194)
       at android.app.ActivityThread.main(ActivityThread.java:5637)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)

From the stacktrace, the apparent codepath through LD is:

UserManager.java:133
HttpFeatureFlagFetcher.java:69

Steps to Reproduce

Unknown at this time.

Expected Behavior

0 crashes from LaunchDarkly SDK

Unnecessary string allocations when logging

I noticed that the client does unnecessary string allocations on many places when logging. For example:

Timber.d("jsonVariation: returning variation: " + result + " flagKey: " + flagKey + " user key: " + userManager.getCurrentUser().getKeyAsString());

should be changed into something like this to avoid that:

Timber.d("jsonVariation: returning variation: %s flagKey: %s user key: %s", result, flagKey, userManager.getCurrentUser().getKeyAsString());

Timber has actually lint rules for that:
https://github.com/JakeWharton/timber#lint

Can't init the LDClient that return Future<LDClient> in background thread

Hello,
Thanks for a great library and service.

I notice an issue related to initialization the launch darkly when using the init method that return Future<LDClient>

When I run this init inside background thread, I receive this error :

01-28 11:31:01.939 Can't create handler inside thread that has not called Looper.prepare()
01-28 11:31:01.939 init failed
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.(Handler.java:200)
at android.os.Handler.(Handler.java:114)
at com.launchdarkly.android.Throttler.(Throttler.java:39)
at com.launchdarkly.android.LDClient.(LDClient.java:223)
at com.launchdarkly.android.LDClient.init(LDClient.java:102)

When I run it inside UI thread, it works.
Can you please check why I can't run it inside background thread ?
Thanks.
Ronny.

ClassCastException when calling LDClient.init

Describe the bug
We're getting another class cast exception, this time from the LDClient.init call.

To reproduce
Steps to reproduce the behavior.
Migrate from an older version to a newer version cached flags

Expected behavior
Don't crash

Logs
Caused by: java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.String
at com.launchdarkly.android.Migration.migrate_2_7_fresh(Migration.java:78)
at com.launchdarkly.android.Migration.migrateWhenNeeded(Migration.java:32)
at com.launchdarkly.android.LDClient.init(LDClient.java:129)
at com.launchdarkly.android.LDClient.init(LDClient.java:177)

SDK version
2.7.0

Language version, developer tools
Kotlin

OS/platform
Android, all versions

Additional context

        try {
            LDClient.get().identify(ldUser)
        } catch (e: LaunchDarklyException) {
            LDClient.init(application, ldConfig, ldUser, 0)
        }

It's happening when identify throws an exception that we catch, and then init throws the classcastexception. We've had this code for over a year, but it just now started causing this issue.

Upadate to latest version of OKHttp

I have been struggling to integrate your SDK with our app but finally got the bottom of the problem. It appears you have a dependency on OKhttp 3.4.1, while other components in our application use 3.6.0. Gradle chooses the higher versions in this instance, which led to OKHttp to hang when initializing the SDK asynchronously.

I have managed to patch the issue using the following code

configurations.all { //launch darkly requires this version resolutionStrategy.force 'com.squareup.okhttp3:okhttp:3.4.1' }

However, I am unsure of any knock-on effects of such a hack.

I can see that the latest version of your okhttp-eventsource library (1.1.0) uses the latest okhttp (3.6.0), so would assume that upgrading the android-client version should not be too difficult

OutOfMemory after adding 8k+ user rule

In my currently scenario, I had to handpick some of my app users myself and add them with an API script I've created in order to add them to a specific flag.

Soon after the script finished, I started receiving crash reports for OutOfMemory with launchDarkly artifact id on it.

Fatal Exception: java.lang.OutOfMemoryError: Could not allocate JNI Env
       at java.lang.Thread.nativeCreate(Thread.java)
       at java.lang.Thread.start(Thread.java:1063)
       at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:921)
       at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1339)
       at com.launchdarkly.eventsource.AsyncEventHandler.onMessage(AsyncEventHandler.java:31)
       at com.launchdarkly.eventsource.EventSource.com.launchdarkly.eventsource.EventParser.dispatchEvent(EventSource.java:9080)
       at com.launchdarkly.eventsource.EventSource$1.run(EventSource.java:69)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
       at java.lang.Thread.run(Thread.java:818)

Other crashes are being reported for the same reason in different parts of the code, but I guess the reason is the same.

Why would that happen? It doesn't make any sense to me, but I reckon it's probably not a coincidence.
When launchDarkly fetches the flags, does the response contains all users added in a rule for a specific flag?

`identify()` future may not complete if device is backgrounded during the call

Are you sure it's correctly fixed? I'm facing a similar issue with 2.9.0.

I basically have wrapped all LD interactions in Rx streams. And at some point it involves having identify calls running on a specific (non-main) thread.

But if the app goes into the background when identify() is being called, the call is cancelled, the event, the connection is shut down

And identify().get() will never return, no matter if I retry it after a timeout.

I had to downgrade to 2.7.0 to have it work as expected.

Originally posted by @drommk in #86 (comment)

LaunchDarkly causing GcmBroadcastReceiver issue when using it along OneSignal.

Description:
So, i'm using onesignal for notification and launchdarkly for feature flagging in my app.
And i found that when the app closed (by swiping from recent apps) the device can't get notification. Another scenario like receiving notification when the app is active and receiving notification when the app in the stand by mode (pressing back button) is working fine.

In logcat i see that the notification was delivered to the device but canceled.
2019-05-14 12:02:02.405 21243-21243/? W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10000000 pkg=id.qasir.mitra.staging (has extras) }

After tracing the code and removing some dependencies that may contains notification services / broadcast receiver, i found that the problem is from launchdarkly. I'm still learning launchdarkly source code and not yet find the issue.

To make sure that "notification not shown issue" is not from the OS, i'm trying the OneSignal example app and its working fine in same device.

So, my temporary solution right now is by removing launchdarkly from my app and replacing the feature flag with firebase remote config. Both library (Onesignal and LaunchDarkly) are important to me and can't replaced with other libraries.

Any help will be appreciated.

Environment

Android Compile SDK Version: 28
Build Tools Version: 28.0.3
Kotlin Version: 1.3.31
OneSignal Gradle plugin: [0.12.1, 0.99.99]
OneSignal library: 3.10.8
Launchdarkly: 2.8.0

Device testing:

  • Xiaomi Pocophone F1 (OS 9)
  • Samsung SM-P355 (OS 7.1.1)
  • Xiaomi Redmi Note 3 (OS 6.0.1)
  • ASUS Z017DB (OS 9)

Steps to Reproduce Issue:

  1. Implement Onesignal using this step https://documentation.onesignal.com/docs/android-sdk-setup
    2.Implement Launchdarkly using this step https://docs.launchdarkly.com/docs/android-sdk-reference
  2. Then launch the app
  3. Close the app by swiping from recent app
  4. Send notification to device

Get Actual values of Feature flags on first app launch

Is your feature request related to a problem? Please describe.
As per the documentation SDK will return fallback values for feature flags for first time app launch. I heard there was similar behavior on iOS and recently it got fixed by adding new function called "startCompleteWhenFlagsReceived". I was expecting something similar for Android SDK. Is it in pipeline? or is there a way to achieve this?

Describe the solution you'd like
I would like to get actual feature flag values on first app launch since we are tracking some experiments before user signs up or logs in. based on these values we will be showing specific screens and fields to user

Describe alternatives you've considered
N/A

Additional context
Currently I am using SDK version 2.8.5 and planning to upgrade to 2.9.1

Cannot identify user (when blocking main thread?)

Describe the bug
I've migrated my app from 2.7.0 to 2.8.3 and I started observing wrong behaviour when calling LDClientInterface#indentify - the app started hanging because toggle call does not complete anymore.
I have my own RxJava2 wrapper for LDClient library which looks follows:

Completable.create {
                    val future = client.identify(newUser)

                    try {
                        future.get()
                        it.onComplete()
                    } catch (exception: InterruptedException) {
                        if (!it.isDisposed) {
                            it.onError(exception)
                        }
                    }
                    it.setCancellable { future.cancel(true) }
                }.subscribeOn(Schedulers.io())

Starting from LaunchDarkly client version 2.8.0 the future.get() never completes.

To reproduce
As for now I found out that to reproduce wrong behaviour it is enoug to call blockingAwait on returned Completable from above snippet. (I tried to call it directly from applicataion class, but it doesn't work when called from Fragment class too)
I'll try to provide sample project with smallest possible repro, it should be reproducible withouth rxjava wrapper too.

Expected behavior
I'd like the Future returned by indetify to complete at some point.

Logs
I tried to register ConnectionInformation observer but hasn't been ever called when the problem occurs.

SDK version
I've tried 2.7.0 (works), 2.8.0, 2.8.1, 2.8.3 (it doesn't work)

Language version, developer tools
AGP 3.4.1
Kotlin 1.3.31

OS/platform
Tested on Android api 26, 28.

Additional context

  • My main app does not call blockingAwait() or ever blocks the thread. Repro steps I provided I made only in smaller app.
  • Additional interesting thing is that LD version 2.8.X does not work with my main app only when I clear app data completely (android:allowBackup is set to false). Otherwise, it works properly sometimes for few people. So by reporting this bug I'm hoping those are somehow connected because of some race condition.
  • And as a confirmation, my general LD setup is correct I should add that I'm able to evaluate flags before I call the blocky identify call.
  • It may be possible that my main app somehow, somewhere blocks the main thread when waiting for LD call to complete, and that's why I'm able to reproduce the issue by calling blockingAwait() and I'm not by calling subscribe and doing it asynchronously. I'll keep investigating that.

Auth header parsing issue

Hi guys,

While testing something that uses a LaunchDarkly flag, I noticed there is an Auth header parsing error occurring with the Android SDK. The issue doesn't seem to happen with the iOS SDK. The place I work at has 2 different apps that use LD and both have the same parsing issue. One app uses version 2.1.2 and the other uses 2.3.2. I had a look at the "CHANGELOG.md" but couldn't find any fix that related to this issue. I was able to resolve the issue with Charles Proxy by prefixed "api_key" to the auth token/header and it fixed the parsing issue.

FYI, I still get 200 responses even with this parsing issue. I would have thought that all the calls should fail if the auth header/token can't be parsed.

Failed to parse date header

There is a problem at https://github.com/launchdarkly/android-client/blob/master/launchdarkly-android-client/src/main/java/com/launchdarkly/android/EventProcessor.java

This kind of parsing: SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
doesnt work, when user's device runs other than US locale. Im trying it with czech and getting Failed to parse date header error every time.

However, I think it could work this way SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);

Logging Should Be Configurable

Is your feature request related to a problem? Please describe.
Logging to Timber is based solely on the BuildConfig.DEBUG flag. SDK Users should be able to turn logging on and off via a configuration.

Describe the solution you'd like
Expose the ability to enable/disable debug logging explicitly.

Describe alternatives you've considered
Uproot all Timber trees and then add one that filters out logs from the Launch Darkly packages.

ConcurrentModificationException inside ConnectivityReceiver.onReceive

There is a ConcurrentModificationException inside ConnectivityReceiver.onReceive. Below are the logs for this crash.

Also, a similar thing might be happening inside PollingUpdater.onReceive, no crash logs for this, but there is a ANR report on the Play Console that refers to the PollingUpdater which seems to contain similar code to the ConnectivityReceiver.

Fatal Exception: java.lang.RuntimeException: Error receiving broadcast Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x4200010 (has extras) } in com.launchdarkly.android.ConnectivityReceiver@f08c875
       at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1493)
       at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(-.java:2)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:214)
       at android.app.ActivityThread.main(ActivityThread.java:7045)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
Caused by java.util.ConcurrentModificationException
       at java.util.HashMap$HashIterator.nextNode(HashMap.java:1441)
       at java.util.HashMap$KeyIterator.next(HashMap.java:1465)
       at com.launchdarkly.android.ConnectivityReceiver.onReceive(ConnectivityReceiver.java:29)
       at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1483)
       at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(-.java:2)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:214)
       at android.app.ActivityThread.main(ActivityThread.java:7045)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)

SDK version
com.launchdarkly:launchdarkly-android-client-sdk:2.8.3

Language version, developer tools
Java 7

OS/platform
Android 8 and 9

Bad slf4j dependency in noveogroup

Hello Arun,

launchdarkly-android-client:2.4.0 depends on noveogroup-android-logger and
noveogroup-android-logger:1.3.5 explicity packages org.slf4j.impl but only classes: {AndroidLoggerAdapter,AndroidLoggerFactory,StaticLoggerBinder}.

However this is an old or modified version of org.slf4j.impl -- it's not possible to tell which because there is no version data.

We currently use org.slf4j-simple, which holds org.slf4j.* classes: {SimpleLogger,SimpleLoggerFactory,StaticLoggerBinder}

The problem is that gradle will pull in two copies of StaticLoggerBinder; one from noveogroup and the other from slf4j-simple.

A better approach is for noveogroup package to NOT include an unversioned and incomplete copy of org.slf4j.impl, but to refer to it as a dependency, which can be managed by gradle, the same as other third-party dependencies. If launch-darkly cannot change the noveogroup package, then an alternative is for launch-darkly to stop using noveogroup and use alternate logging (perhaps Timber?)

screen shot 2018-06-05 at 1 43 41 pm

identify().get() not complete

Describe the bug
Call to identify().get() never return if it's called from another thread than main thread and app is on backgorund at the same time.

It should be possible to identify user without blocking main thread even if app is on background.

To reproduce
Make Android project and put this activity in it. Then create layout with run_button.

class MainActivity : AppCompatActivity(R.layout.activity_main) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val ldConfig = LDConfig.Builder()
            .setMobileKey(API_KEY)
            .build()

        val ldUser = LDUser.Builder("1234").build()
        val ld = LDClient.init(application, ldConfig, ldUser).get()

        run_button.setOnClickListener {
            thread(start = true) {
                Log.d("LD", "delay")
                TimeUnit.SECONDS.sleep(3)

                Log.d("LD", "identify")
                ld.identify(LDUser.Builder("1234").build()).get()

                Log.d("LD", "completed")
            }
        }
    }
}

After cklick on run button you have 3 second to put app to background. Then observe logcat and you will see identify log but not completed log. If you click on button and don't put app to background then you will see both logs.

Expected behavior
Return from identify().get() method even if called from background thread if app is not in foreground.

SDK version
2.8.5

Language version, developer tools
Kotlin 1.3.50

OS/platform
Android

Put your HTTP cache into a subdirectory, Thanks.

Describe the bug
When I look in the /data/data/my.app.id/cache directory - there is a bunch of files along with "journal" -- which refer to launchdarkly services (e.g. https://app.launchdarkly.com/msdk/...). Most applications use this "cache" directory for many things, and an SDK shouldn't crap all over it.

To reproduce
Just run an app with the launchdarkly SDK, then look inside the /data/data/my.app.id/cache directory with Android Studio's Device File Explorer.

Expected behavior
The HTTP cache that launchdarkly creates should go into a subdirectory, named appropriately, to avoid name conflicts.. For example: "com.launchdarkly.http_cache"

SDK version
2.9.1

LaunchDarklyCacheFiles

Failing in processDebugManifest

Getting below error in processDebugManifest gradle task,

Manifest merger failed : Attribute application@name value=(com.replicon.timeintelligence.MainApplication) from AndroidManifest.xml:18:9-40
	is also present at [com.launchdarkly:launchdarkly-android-client:2.3.1] AndroidManifest.xml:20:9-52 value=(com.launchdarkly.android.App).
	Suggestion: add 'tools:replace="android:name"' to <application> element at AndroidManifest.xml:13:5-126:19 to override.

Is there a way to reinitialize the LDClient or change mobile keys?

Hey there,

I am working for a company using LaunchDarkly in some existing products and we are creating an Android app. In debug and "beta" builds (distributed to QA testers internally), we provide users the ability to select a specific server (staging, demo, production, etc). from a drop down on the login screen. Each server has a different LD mobile key associated with it.

I have been trying to figure out how to update the LDClient to react to this since it is possible to log in on one server (and initialize LD with that mobile key), logout, then log in a different server (and hopefully reinit LD with this server's mobile key).

At first, I thought I might be able to just call LDClient.init() again, but it is set up to only be called once as you know. I was hoping there might be some alternative, like de-initing the client so I can re-init or being able to update the config/mobileKey like you can do with the LDUser.

If this is currently not possible, I'm willing to help out with making the change! We would really love to have this capability so we can continue supporting this flexibility for testing our app.

Thanks

NullPointerException when passing null as the fallback value of jsonVariation

Describe the bug
Upgraded LaunchDarkly to 2.10.0 from an older version, once the app is updated from Google Play store, the app will crash 1-2 times due to this line:
LDClient.jsonVariation(FLAG_KEY, null)

Logs
Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Object.equals(java.lang.Object)' on a null object reference at com.launchdarkly.android.UserSummaryEventSharedPreferences.addOrUpdateEvent(UserSummaryEventSharedPreferences.java:49) at com.launchdarkly.android.LDClient.updateSummaryEvents(LDClient.java:630) at com.launchdarkly.android.LDClient.variationDetailInternal(LDClient.java:394) at com.launchdarkly.android.LDClient.jsonVariation(LDClient.java:353)

SDK version
Android sdk 2.10.0

Language version, developer tools
Kotlin, Android Studio

OS/platform
Android

ANR reported

Users are reporting ANR - application not responding to Google Play Console
Here's the stacktrace for the main thread

"main" prio=5 tid=1 TimedWaiting
  | group="main" sCount=1 dsCount=0 obj=0x737c02a0 self=0xb4c76500
  | sysTid=8533 nice=0 cgrp=default sched=0/0 handle=0xb6f19b34
  | state=S schedstat=( 3826523440 8140176062 8894 ) utm=311 stm=71 core=3 HZ=100
  | stack=0xbe7e0000-0xbe7e2000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait! (Native method)
- waiting on <0x000ef70a> (a java.lang.Object)
  at java.lang.Thread.parkFor$ (Thread.java:1220)
- locked <0x000ef70a> (a java.lang.Object)
  at sun.misc.Unsafe.park (Unsafe.java:299)
  at java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:198)
  at com.google.common.util.concurrent.a.get (AbstractFuture.java:369)
  at com.google.common.util.concurrent.a$h.get (AbstractFuture.java:84)
  at com.launchdarkly.android.BackgroundUpdater.onReceive (BackgroundUpdater.java:32)
  at android.app.ActivityThread.handleReceiver (ActivityThread.java:2782)
  at android.app.ActivityThread.access$1800 (ActivityThread.java:154)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1468)
  at android.os.Handler.dispatchMessage (Handler.java:102)
  at android.os.Looper.loop (Looper.java:234)
  at android.app.ActivityThread.main (ActivityThread.java:5526)
  at java.lang.reflect.Method.invoke! (Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:616)

[Readme] Is proguard-config section up-to-date?

Describe the bug
I started wondering if current version of Proguard-config is still valid.
Is there really a requirement to add those lines to app's proguard file?

As far as I can see the library publishes proper proguard rules using consumer-proguard mechanism

If adding those lines is still a valid requirement, can I ask to add missing rules to published file to make library integration more straightforward?

Crashes on library 2.12.0 on Android 6

Is this a support request?
This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going here and clicking "submit a request", or by emailing [email protected].

Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above.

Describe the bug
We saw some crashes on Crashlytics (62 crashes affecting 4 users) on SharedPrefsSummaryEventStore.java line 100. It only occurs on Android 6

To reproduce
Unable to, crash occur on end user

Expected behavior
A clear and concise description of what you expected to happen.

Logs
Stack Trace:

Fatal Exception: java.lang.IllegalStateException: Not a JSON Object: null
       at com.google.gson.JsonElement.getAsJsonObject(JsonElement.java:91)
       at com.launchdarkly.android.SharedPrefsSummaryEventStore.getSummaryEventNoSync(SharedPrefsSummaryEventStore.java:100)
       at com.launchdarkly.android.SharedPrefsSummaryEventStore.getSummaryEventAndClear(SharedPrefsSummaryEventStore.java:114)
       at com.launchdarkly.android.DefaultEventProcessor$Consumer.flush(DefaultEventProcessor.java:126)
       at com.launchdarkly.android.DefaultEventProcessor$Consumer.run(DefaultEventProcessor.java:116)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
       at java.lang.Thread.run(Thread.java:818)

SDK version
2.12.0

Language version, developer tools
Kotlin 1.3.72

OS/platform
Android 6

Additional context
Add any other context about the problem here.

NullPointerException in HttpFeatureFlagFetcher

We're sometimes seeing the following NPE:

java.lang.RuntimeException: 
at android.app.ActivityThread.handleBindApplication (ActivityThread.java:6020)
at android.app.ActivityThread.-wrap3 (ActivityThread.java)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1727)
at android.os.Handler.dispatchMessage (Handler.java:102)
at android.os.Looper.loop (Looper.java:154)
at android.app.ActivityThread.main (ActivityThread.java:6823)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:1563)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1451)
Caused by: java.lang.NullPointerException: 
at com.launchdarkly.android.HttpFeatureFlagFetcher.deleteRecursive (HttpFeatureFlagFetcher.java)
at com.launchdarkly.android.HttpFeatureFlagFetcher.<init> (HttpFeatureFlagFetcher.java)
at com.launchdarkly.android.HttpFeatureFlagFetcher.init (HttpFeatureFlagFetcher.java)
at com.launchdarkly.android.LDClient.<init> (LDClient.java)
at com.launchdarkly.android.LDClient.init (LDClient.java)
...

After some investigation, the likely culprit is the call to fileOrDirectory.listFiles() in deleteRecursive():

    private void deleteRecursive(File fileOrDirectory) {
        if (fileOrDirectory.isDirectory()) {
            for (File child : fileOrDirectory.listFiles()) {
                deleteRecursive(child);
            }
        }
        fileOrDirectory.delete();
    }

According to the official documentation, that can return null when there is an I/O error retrieving the file list:

An array of abstract pathnames denoting the files and directories in the directory denoted by this abstract pathname. The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.

Version 2.3.0 still produces logs in production

While there have been some changes recently to use Timber for logging, there are still places that use Log.d, Log.e, etc. for logging and these logs show up in release builds of apps including LD. This is true in particular of the HttpFeatureFlagFetcher class (logged as LDFeatureFlagFetcher). Ideally all remaining logging in the app would be configurable (by, for example, switching to use Timber or Logger).

Trouble with Latest Gradle plugin and LD Fatal Exception: java.lang.AbstractMethodError abstract method "java.util.Collection com.google.common.collect.Multimap.b(java.lang.Object)"

Hello,

We are having trouble with latest launch darkly client and gradle plugin classpath 'com.android.tools.build:gradle:3.3.0-alpha11'

After compiling the app and releasing it to stores, some of our users who are updating the app gets a crash, when we look at the crash reports we get following exception

Fatal Exception: java.lang.AbstractMethodError
abstract method "java.util.Collection com.google.common.collect.Multimap.b(java.lang.Object)"

With following stack trace :

com.launchdarkly.android.UserLocalSharedPreferences.syncCurrentUserToActiveUser (UserLocalSharePreferences.java:26)
--
  | com.launchdarkly.android.UserManager.syncCurrentUserToActiveUserAndLog (UserManager.java:1)
  | com.launchdarkly.android.UserManager.saveFlagSettings (UserManager.java:7)
  | com.launchdarkly.android.UserManager.access$100 (UserManager.java:1)
  | com.launchdarkly.android.UserManager$2.onSuccess (UserManager.java:3)
  | com.launchdarkly.android.UserManager$2.onSuccess (UserManager.java:1)
  | com.google.common.util.concurrent.Futures$CallbackListener.run (Futures.java:2)
  | com.google.common.util.concurrent.SettableFuture.set (SettableFuture.java:1)
  | com.launchdarkly.android.HttpFeatureFlagFetcher$1.onResponse (HttpFeatureFlagFetcher.java:14)
  | okhttp3.RealCall$AsyncCall.execute (RealCall.java:4)
  | okhttp3.internal.NamedRunnable.run (NamedRunnable.java:3)
  | java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  | java.lang.Thread.run (Thread.java:764) 

We believe this is happening due to use of Guava android and some new changes in gradle plugin. This happens only to some users and there is no device or OS pattern. There is 1 common pattern of upgrade, ie when user trying to open and upgraded app compiled with 3.3.0-alpha11.

We were able to fix this issue by downgrading to 3.2.0, but even in that for some users the sync didn't happen properly and they still have a false value for feature flags that they are true for there id.

The above error happens in LDClient.get().identify(ldUser) when we add identity of user to LD.

Upgrade to androidx

Is your feature request related to a problem? Please describe.
Androidx has been the standard for a long time on Android now. We have a large project and LaunchDarkly is one of the last libraries that needs to upgrade before we can fully drop jetifier.

Describe the solution you'd like
Migrate the project to androidx. It will help the build times of all clients.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.