Coder Social home page Coder Social logo

splitio / android-client Goto Github PK

View Code? Open in Web Editor NEW
16.0 20.0 6.0 18.11 MB

Android SDK client for Split Software

Home Page: https://split.io

License: Other

Java 97.14% Shell 0.01% Kotlin 2.86%
android java feature-toggles feature-flags experimentation ab-testing

android-client's People

Contributors

cameronbarclift avatar emilianosanchez avatar gchiacchio avatar github-actions[bot] avatar goldensun8891 avatar gthea avatar javrudsky avatar lucianocaravajal avatar mmelograno avatar mredolatti avatar nicozelaya avatar sanzmauro avatar sarrubia avatar senhorcastor avatar

Stargazers

 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

android-client's Issues

Any issue not calling `client.destroy() ` before app shut down?

Hi Split,
I see Android sdk guide recommend us to call client.destroy() before shut down app, is there a specific reason we need to do this? Is there any potential issue if we don't call it?

There are actually some cases we are having user issues if we call this client.destroy() because it also clears cache.
Like for a logged in user, if we call this and clears cache, the next time user launches the app, and split for some reason failed, we don't have any cached config for this user, then we need to fall back to a very old state for this user, but not the latest cached state.

If we do need to call this client.destroy(), would it make sense to break it into two APIs?
Something like client.close() and client.clearCache()? so we could decide if we want to keep the cache or not?

Looking forward to hear back from you,

Thanks & Regards,
Jiusong

Network timeouts are not working

The SDK in its current form doesn't appear to be handling network connectivity issues properly. The issue we're facing is the following:

  1. App starts with bad connectivity (Split backend being slow or general network issues)
  2. Split SDK loads instantly from cache the old treatments and holds them in memory, but also starts the HTTP calls to fetch the treatments from Split backend
  3. App requests a feature flag
  4. Since Split SDK is still waiting for the network, it doesn’t consider itself as being SDK_READY, and returns Control flag, even though it actually has data loaded from cache
  5. Split SDK finishes fetching the data, sets SDK_READY flag
  6. The next time app requests a feature flag, it gets the fresh treatments from Split SDK

We expect that after a period of time (timeout), Split SDK would return the treatments from cache instead of still waiting for the network calls to finish.

As a possible fix, we tried to set connection timeouts for Split:

.connectionTimeout(2000)
.readTimeout(2000)

But this doesn't really have any impact, as they're not even used by Split SDK internally...

Also, when setting a timeout for the ready event:
.ready(2000)

Then the SDK_READY_TIMED_OUT is triggered after the timeout period, but the HTTP calls that Split SDK makes are not cancelled and are still pending and after they finish, SDK_READY still gets triggered.

So we don't really have any control over Split SDK's networking and it's a real blocker in our current implementation... We would require that connectionTimeout and readTimeout actually work internally inside Split SDK, so that Split would return the cached treatments after those specific timeouts.

Seeing a lot of errors from split sdk on Android in my logcat

Hey,

Today I realized that I'm seeing a lot of errors related to the latest split sdk (2.6.0) on Android that I can barely use my logcat for anything else.

 E/SplitSDK: Invalide SSE event received: STREAMING_CONNECTED
 E/SplitSDK: An error has ocurred while parsing stream from https://split-realtime.ably.io/sse : Socket closed
 I/SplitSDK: Periodic fetcher tasks scheduled
 I/SplitSDK: Polling enabled.
 I/SplitSDK: Streaming connection opened
 I/SplitSDK: Sending polling disabled message through event broadcaster.
 I/SplitSDK: Stoping periodic fetching tasks 01d327b2-9c99-4fec-8c74-2b9406689649, 4bee70a9-5afc-4fae-87be-5c4438e20b2c
 I/SplitSDK: Polling disabled.
 I/SplitSDK: Disabling polling
 E/SplitSDK: Invalide SSE event received: STREAMING_CONNECTED

Any reason why they are showing up and how can we turn them off?

Fatal Exception: java.lang.NullPointerException SqLitePersistentMySegmentsStorage

We've been seeing intermittent null pointer exception reports (via Crashlytics) originating from SqLitePersistentMySegmentsStorage from some of our users. This seems to have started ever since we upgraded the Split SDK dependency version to 2.6.6. Is this a known issue?

Fatal Exception: java.lang.NullPointerException
       at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:890)
       at io.split.android.client.storage.mysegments.SqLitePersistentMySegmentsStorage.<init>(SqLitePersistentMySegmentsStorage.java:36)
       at io.split.android.client.storage.db.StorageFactory.getMySegmentsStorage(StorageFactory.java:31)
       at io.split.android.client.SplitFactoryHelper.buildStorageContainer(SplitFactoryHelper.java:68)
       at io.split.android.client.SplitFactoryImpl.<init>(SplitFactoryImpl.java:129)
       at io.split.android.client.SplitFactoryImpl.<init>(SplitFactoryImpl.java:80)
       at com.envoy.app.utils.SplitProvider.init(SplitProvider.java:67)
       at io.split.android.client.SplitFactoryBuilder.build(SplitFactoryBuilder.java:67)
       at com.envoy.app.utils.SplitProvider.initScope(SplitProvider.java:77)
       at com.envoy.app.utils.SplitProvider.onClientUpdate(SplitProvider.java:95)
       at com.envoy.app.ui.screens.office.OfficeViewModel.loadOfficeActions(OfficeViewModel.java:210)
       at com.envoy.app.ui.screens.office.OfficeViewModel$1.invoke(OfficeViewModel.java:155)
       at com.envoy.app.ui.screens.office.OfficeViewModel$1.invoke(OfficeViewModel.java:83)
       at com.envoy.app.account.UserManager$observeUserUpdates$1.invokeSuspend(UserManager.java:204)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
       at kotlinx.coroutines.DispatchedContinuation.resumeUndispatchedWith(DispatchedContinuation.java:226)
       at kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuationKt.java:333)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(CancellableKt.java:26)
       at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.java:109)
       at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.java:158)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(BuildersKt__Builders_commonKt.java:56)
       at kotlinx.coroutines.BuildersKt.launch(BuildersKt.java:1)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(BuildersKt__Builders_commonKt.java:49)
       at kotlinx.coroutines.BuildersKt.launch$default(BuildersKt.java:1)
       at com.envoy.app.account.UserManager.observeUserUpdates(UserManager.java:203)
       at com.envoy.app.ui.screens.office.OfficeViewModel.<init>(OfficeViewModel.java:154)
       at java.lang.reflect.Constructor.newInstance0(Constructor.java)
       at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
       at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:267)
       at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
       at com.android.tools.r8.GeneratedOutlineSupport.outline7(GeneratedOutlineSupport.java:8)
       at com.envoy.app.di.modules.ViewModelModule.providesOfficeViewModel(ViewModelModule.java:7)
       at com.envoy.app.di.modules.ViewModelModule_ProvidesOfficeViewModelFactory.providesOfficeViewModel(ViewModelModule_ProvidesOfficeViewModelFactory.java:37)
       at com.envoy.app.di.components.DaggerActivityComponent.getOfficeViewModel(DaggerActivityComponent.java:221)
       at com.envoy.app.di.components.DaggerActivityComponent.injectOfficeActivity(DaggerActivityComponent.java:502)
       at com.envoy.app.di.components.DaggerActivityComponent.inject(DaggerActivityComponent.java:358)
       at com.envoy.app.ui.screens.office.OfficeActivity.onCreate(OfficeActivity.java:98)
       at android.app.Activity.performCreate(Activity.java:8000)
       at android.app.Activity.performCreate(Activity.java:7984)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:223)
       at android.app.ActivityThread.main(ActivityThread.java:7660)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

IllegalArgumentException on SDK 31+ devices emulators

When the latest version of the Android split.io SDK is used (2.10.2) and the app is started, the app crashes with the following exception:
java.lang.IllegalArgumentException: myapp: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles. at android.app.PendingIntent.checkFlags(PendingIntent.java:375) at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:645) at android.app.PendingIntent.getBroadcast(PendingIntent.java:632) at androidx.work.impl.utils.ForceStopRunnable.getPendingIntent(ForceStopRunnable.java:285) at androidx.work.impl.utils.ForceStopRunnable.isForceStopped(ForceStopRunnable.java:158) at androidx.work.impl.utils.ForceStopRunnable.forceStopRunnable(ForceStopRunnable.java:185) at androidx.work.impl.utils.ForceStopRunnable.run(ForceStopRunnable.java:103) at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91) 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:920)

The reason is that split.io is using androidx.work:work-runtime-ktx:2.6.0 which doesn't use the currect flags that are required on SDK level 31 and laters.

As a workaround, androidx.work:work-runtime-ktx:2.7.0 or later can be added as a dependency in the app that uses the split.io SDK. However, the dependency work-runtime-ktx should be updated to 2.7.0 or later in the split.io SDK for a proper fix.

Creating a Split client on app targeting Android S (API 31) causes crash on Android S devices

Stack trace:

Fatal Exception: java.lang.IllegalArgumentException: uk.co.tickr.android: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:645)
at android.app.PendingIntent.getBroadcast(PendingIntent.java:632)
at androidx.work.impl.utils.ForceStopRunnable.getPendingIntent(ForceStopRunnable.java:196)
at androidx.work.impl.utils.ForceStopRunnable.isForceStopped(ForceStopRunnable.java:128)
at androidx.work.impl.utils.ForceStopRunnable.run(ForceStopRunnable.java:93)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
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:920)

We call SplitFactory.client() on app startup, and our app crashes with the above stack trace on Android S devices.

Looks like the WorkManager dependency in Split is outdated.

A listener was added for SDK_READY on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.

Hi,
I am getting an issue similar to #397 with a listener. It only happens the first time, when the app is installed on the device. I am using the 2.11.0 version, but the problem is still there. I enabled debug 👇

Periodic recording tasks scheduled
2022-07-07 19:39:33.760 22547-22666/org.dev I/SplitSDK: Android SDK initialized!
2022-07-07 19:39:33.963 22547-22735/org.dev D/SplitSDK: Received from: https://sdk.split.io/api/splitChanges?since=-1 -> {"splits":[{"trafficTypeName":"user","name":"aa_ios_test","trafficAllocation":100,"trafficAllocationSeed":-908316409,"seed":899280478,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1657036006539,"algo":2,"configurations":{},"conditions":[{"conditionType":"WHITELIST","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":null,"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["56d7bba0-9614-408f-aeed-2ae589515505","802b8154-6a27-4edb-84c3-1bfa4da5df23"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"variant_1","size":100}],"label":"whitelisted"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"platform"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["iOS","ios","IOS"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":34},{"treatment":"variant_1","size":33},{"treatment":"variant_2","size":33},{"treatment":"excluded","size":0}],"label":"platform in list [iOS, ios, ...]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":0},{"treatment":"variant_1","size":0},{"treatment":"variant_2","size":0},{"treatment":"excluded","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"aa_android_test","trafficAllocation":100,"trafficAllocationSeed":384098175,"seed":-779992597,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1657035101661,"algo":2,"configurations":{},"conditions":[{"conditionType":"WHITELIST","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":null,"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["50016b7e-24b7-4983-bdbd-98724a3d5f1c","6c02333f-ac6e-444f-a1da-9014eb4c98ca"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"variant_1","size":100}],"label":"whitelisted"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"platform"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["Android","android"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":34},{"treatment":"variant_1","size":33},{"treatment":"variant_2","size":33},{"treatment":"excluded","size":0}],"label":"platform in list [Android, android]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":0},{"treatment":"variant_1","size":0},{"treatment":"variant_2","size":0},{"treatment":"excluded","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"aa_android_test_v3","trafficAllocation":100,"trafficAllocationSeed":567840604
2022-07-07 19:39:33.963 22547-22735/org.dev D/SplitSDK: ,"seed":-1889463240,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1657020092957,"algo":2,"configurations":{},"conditions":[{"conditionType":"WHITELIST","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":null,"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["6c02333f-ac6e-444f-a1da-9014eb4c98ca"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"variant_2","size":100}],"label":"whitelisted"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"platform"},"matcherType":"WHITELIST","negate":true,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["blblab"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":34},{"treatment":"variant_1","size":33},{"treatment":"variant_2","size":33},{"treatment":"excluded","size":0}],"label":"platform not in list [blblab]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":0},{"treatment":"variant_1","size":0},{"treatment":"variant_2","size":0},{"treatment":"excluded","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"aa_locale_quick_rampup_v2_test_copy","trafficAllocation":100,"trafficAllocationSeed":1795508390,"seed":-153495606,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1656943759080,"algo":2,"configurations":{},"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"locale"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["en",":en"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":34},{"treatment":"variant1","size":33},{"treatment":"variant2","size":33},{"treatment":"excluded","size":0},{"treatment":"no_english_locale","size":0},{"treatment":"no_en_locale","size":0}],"label":"locale in list [en, :en]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"ctrl","size":0},{"treatment":"variant1","size":0},{"treatment":"variant2","size":0},{"treatment":"excluded","size":0},{"treatment":"no_english_locale","size":0},{"treatment":"no_en_locale","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"test-split","trafficAllocation":100,"trafficAllocationSeed":720217622,"seed":1168524908,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1655888162342,"algo":2,"configurations":{},"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"locale"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["en"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":
  2022-07-07 19:39:33.963 22547-22735/org.dev D/SplitSDK: 100},{"treatment":"off","size":0},{"treatment":"excluded","size":0}],"label":"locale in list [en]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"locale"},"matcherType":"WHITELIST","negate":true,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["en"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":100},{"treatment":"excluded","size":0}],"label":"locale not in list [en]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"excluded","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"aa_country_locale_quick_rampup","trafficAllocation":100,"trafficAllocationSeed":1742300774,"seed":2096363507,"status":"ACTIVE","killed":false,"defaultTreatment":"excluded","changeNumber":1655479780951,"algo":2,"configurations":{},"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":"country"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["US","UK"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null},{"keySelector":{"trafficType":"user","attribute":"locale"},"matcherType":"WHITELIST","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":{"whitelist":["en"]},"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":50},{"treatment":"off","size":50},{"treatment":"excluded","size":0}],"label":"country in list [US, UK] and locale in list [en]"},{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":0},{"treatment":"off","size":0},{"treatment":"excluded","size":100}],"label":"default rule"}]},{"trafficTypeName":"user","name":"show_mentored_course","trafficAllocation":100,"trafficAllocationSeed":437423529,"seed":-1483976337,"status":"ACTIVE","killed":false,"defaultTreatment":"off","changeNumber":1654249554906,"algo":2,"configurations":{},"conditions":[{"conditionType":"ROLLOUT","matcherGroup":{"combiner":"AND","matchers":[{"keySelector":{"trafficType":"user","attribute":null},"matcherType":"ALL_KEYS","negate":false,"userDefinedSegmentMatcherData":null,"whitelistMatcherData":null,"unaryNumericMatcherData":null,"betweenMatcherData":null,"booleanMatcherData":null,"dependencyMatcherData":null,"stringMatcherData":null}]},"partitions":[{"treatment":"on","size":50},{"treatment":"off","size":50}],"label":"default rule"}]}],"since":-1,"till":1657036006539}
2022-07-07 19:39:33.989 22547-22735/org.dev D/SplitSDK: Features have been updated
2022-07-07 19:39:34.027 22547-22717/org.dev D/SplitSDK: Received from: https://sdk.split.io/api/mySegments/64773744-eaff-4542-afe3-d4ce360c9ef1 -> {"mySegments":[]}
2022-07-07 19:39:34.029 22547-22717/org.dev D/SplitSDK: My Segments have been updated
2022-07-07 19:39:34.126 22547-22666/org.dev W/SplitSDK: A listener was added for SDK_READY on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.
2022-07-07 19:39:38.755 22547-22724/org.dev D/SplitSDK: Push notification manager started
2022-07-07 19:39:39.692 22547-22749/org.dev D/SplitSDK: SSE Authentication done, now parsing token
2022-07-07 19:40:40.297 22547-22749/org.dev D/SplitSDK: Streaming connection opened
2022-07-07 19:40:40.299 22547-22749/org.dev D/SplitSDK: Streaming connection success
2022-07-07 19:40:40.300 22547-22749/org.dev D/SplitSDK: Push Subsystem Up event message received.
2022-07-07 19:40:40.376 22547-22725/org.dev D/SplitSDK: Received from: https://sdk.split.io/api/mySegments/64773744-eaff-4542-afe3-d4ce360c9ef1 -> {"mySegments":[]}
2022-07-07 19:40:40.380 22547-22725/org.dev D/SplitSDK: My Segments have been updated
2022-07-07 19:40:40.394 22547-22735/org.dev D/SplitSDK: Received from: https://sdk.split.io/api/splitChanges?since=1657036006539 -> {"splits":[],"since":1657036006539,"till":1657036006539}
2022-07-07 19:40:40.397 22547-22735/org.dev D/SplitSDK: Features have been updated

Thx

Support for Proxy

SplitIO doesn't work when an android phone connected to wifi with NTLM Proxy.
We had the same issue before stated working with SplitIO SDK but we used the retrofit SDK.

OkHTTP client builder:

public OkHttpClient createClient(boolean loggingEnabled, boolean tls12Enforced, int connectTimeout, int readTimeout, int writeTimeout) {

    OkHttpClient.Builder defaultHttpClient = new OkHttpClient.Builder();

    Authenticator proxyAuthenticator = new Authenticator() {

        @Override
        public Request authenticate(Route route, okhttp3.Response response) throws IOException {
            if (response.code() == 407) {

                ProxyCredentials credentials = getProxyCredentials();

                if (credentials == null) {
                    throw new IOException("Failed to authenticate with proxy");
                }

                List<HTTPAuthMethod> httpAuthMethods = HTTPAuthMethod.ReadMethodsFromResponse(response);
                ProxyAuthenticator resolvedAuthenticator = null;
                HTTPAuthMethod resolvedMethod = null;
                for (ProxyAuthenticator authenticator : proxyAuthenticators) {
                    for (HTTPAuthMethod authMethod : httpAuthMethods) {
                        if (authenticator.supportsAuthMethod(authMethod)) {
                            resolvedAuthenticator = authenticator;
                            resolvedMethod = authMethod;

                            break;
                        }
                    }

                    if (resolvedAuthenticator != null) {
                        break;
                    }
                }

                if (resolvedAuthenticator == null) {
                    return null;
                }

                try {
                    return resolvedAuthenticator.authenticate(credentials, resolvedMethod, route, response);
                } catch (Throwable e) {
                    if (e instanceof ProxyErrors.CredentialsAreInvalidException) {
                        updateProxyCredentials(null);
                    }

                    throw new IOException("Failed to authenticate with proxy");
                }
            }

            return null;
        }
    };

    defaultHttpClient = defaultHttpClient
            .proxyAuthenticator(proxyAuthenticator);

    return modifyClient(defaultHttpClient.build(), loggingEnabled, tls12Enforced, connectTimeout, readTimeout, writeTimeout);
}
        @Override
        public void onFailure(Call<T> call, Throwable t) {
            t.printStackTrace();
            if (result == null) {
                return;
            } else if ("Canceled".equalsIgnoreCase(t.getMessage())) {
                return;
            }

            if (t.getMessage() != null && t.getMessage().equals("Failed to authenticate with proxy")) {
                handleProxyError(call, result, context, t);
                return;
            }

            notifyErrorResult(call, result, context, t);
        }
public static <T> void handleProxyError(final Call<T> call, final CallbackResponse<T> result, final Context context, final Throwable error) {
    if (instance.proxyErrorHandler == null) {
        notifyErrorResult(call, result, context, error);
        return;
    }

    ErrorRequestFulfiller fulfiller = new ErrorRequestFulfiller() {
        @Override
        void notifyError() {
            notifyErrorResult(call, result, context, error);
        }

        @Override
        void redo() {
            enqueue(call.clone(), result, context);
        }
    };

    inflightRequests.add(fulfiller);

    if (handlingProxyError) {
        return;
    }

    handlingProxyError = true;
    instance.proxyErrorHandler.handle(new ProxyErrorHandler.Callback() {
        @Override
        public void onResult(ProxyErrorHandler.Result resolveResult) {
            handlingProxyError = false;


            if (resolveResult.getError() != null) {
                for (ErrorRequestFulfiller fulfiller : inflightRequests) {
                    fulfiller.notifyError();
                }
            } else {
                ProxyData proxyData = resolveResult.getProxyData();
                ProxyCredentials credentials = new ProxyCredentials(proxyData.getProxyUser(), proxyData.getProxyPass());
                instance.updateProxyCredentials(credentials);
                for (ErrorRequestFulfiller fulfiller : inflightRequests) {
                    fulfiller.redo();
                }
            }
        }
    });
}

In case of a proxy authentication error, we show a dialog and ask users to enter a login/password to save it for the current and next session.

Environment

Android:
implementation 'io.split.client:android-client:2.4.5'

NTLM setup on a local machine for testing

Requirements:

Windows
I use parallels, install windows there, establish a connection between host and virtual machine (ping to check)

Steps:

Install WinGate
a) https://www.wingate.com/download/wingate/download.php?gclid=EAIaIQobChMI0bbYzoT14wIVlOiaCh1XzQ_EEAAYASABEgIioPD_BwE

Start Wingate
a) Select the first square button
b) In wizard select win proxy as a provider (should be automatically though)
c) Select + on the same screen where we selected the first button and click on in popup
d). Select the created a button and choose to Use another account
e) Name: Administrator, Pass:

Enable NTLM and Select Port
a) Control panel -> services -> www proxy
b) In General select port (let's say 9020), enable NTLM, disable basic (we want to check NTLM right?)

Force NTLM authentication
a) Right-click, new policy.
b) Source Type: Any HTTPProxy, Event type: Request.
c) The easiest way to configure policy is to drag and drop some policy elements from other policies.
d) To do this Open web authenticate policy in another window (you can see this policy in the list)
e) Drag and drop to a new policy from authenticating policy: "Auth", "Allow", "Is authenticated?" elements.
f) Drag and drop to new policy from a list of elements "WWW Proxy Server: Request" element.
g) Connect "WWW Proxy Server: Request" to "Is authenticated" element
h) Connect the bottom of "Is authenticated" (Yes) to "Allow" element
i) Connect right of "Is authenticated" (No) to "Auth" element
j) Save (the policy will start to work automatically)

Create user.
a) Control Panel -> Users and Groups
b) Right-click -> new Users
c) Fill in name pass etc.

Check (For mac).

Add proxy.
a) Go to settings -> Network -> Choose network used for internet -> Advanced -> Proxy
b) Fill in IP and port of proxy for Web Proxy and Secure Web Proxy (Do not fill in pass etc)
Check proxy is used
c) Go to, say, google.com.
d) Name and Pass requests should be requested.
e) Use ones from 5.iii
Check (For Android Device and Wingate is on a virtual machine).

Add Proxy in android Settings
Add port forwarding to port selected on step 3(b)
Warnings.

Wingate caches auth status. To force re-auth change user password 5(c) and restart WinGate (Kill Wingate Manager from Tasks Manager (Ctrl + Shift + Esc))

Crash when upgrading from version 2.5.0-SNAPSHOT.1 to 2.6.0

Hello

After migrating from version 2.5.0-SNAPSHOT.1 to 2.6.0, a crash is happening for all devices.

Stacktrace:

Fatal Exception: java.lang.IllegalStateException
Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
androidx.room.RoomOpenHelper.checkIdentity (RoomOpenHelper.java:154)
androidx.room.RoomOpenHelper.onOpen (RoomOpenHelper.java:135)
androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen (FrameworkSQLiteOpenHelper.java:195)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked (SQLiteOpenHelper.java:428)
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase (SQLiteOpenHelper.java:317)
androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase (FrameworkSQLiteOpenHelper.java:145)
androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase (FrameworkSQLiteOpenHelper.java:106)
androidx.room.RoomDatabase.inTransaction (RoomDatabase.java:476)
androidx.room.RoomDatabase.assertNotSuspendingTransaction (RoomDatabase.java:281)
io.split.android.client.storage.db.GeneralInfoDao_Impl.getByName (GeneralInfoDao_Impl.java:68)
io.split.android.client.storage.db.migrator.StorageMigrator$MigrationChecker.run (StorageMigrator.java:92)

Android Custom Widgets and Keyboard not getting updated Split Partitions Value when Apps is in Background

Hi Team,

I'm experiencing a problem with Split Configuration when using Split in Widgets and Keyboard, where our Widgets / Keyboard is not getting an updated value (After changes in Dashboard, of course) if i don't open the main app to foreground.

I've been trying to trace the problem until SplitsStorageImpl.java class where after some delay after Dashboard changes, i'm getting an update and mInMemorySplits variable is updated with proper value. But as long as i let the main apps stay in background, whenever i'm getting value from SplitsStorageImpl.java from Widgets it'll still get the old non-updated value.

Currently i'm assuming that our Widgets / Keyboard is not holding the same instances of mInMemorySplits that being updated in update() method, and because split uses local variable instead of database update (i see that database update only called in loadLocal()), our Widgets / Keyboard somehow miss the references. I'll keep trying to trace the problem on my side and get back if i got a new info.

My main question is whether there's any need for specific configuration for these cases? I'm currently toggling on all background-related config i found in https://help.split.io/hc/en-us/articles/360020343291-Android-SDK#configuration although to no result.

Thanks before.

Crashes in SplitUpdatesWorker and MySegmentsUpdateWorker

Hey!

I have seen crashes with some of our customers related to these two files:

MySegmentsUpdateWorker.java line 33
io.split.android.client.service.sseclient.reactor.MySegmentsUpdateWorker.onWaitForNotificationLoop
SplitUpdatesWorker.java line 32
io.split.android.client.service.sseclient.reactor.SplitUpdatesWorker.onWaitForNotificationLoop

Unfortunately no stacktrace was generated from crashlytics, all I got was this:

Fatal Exception: java.lang.OutOfMemoryError
OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available

splitSDKVersion = '2.6.6'
android version = 6.0.1
device = Galaxy S5

Very slow startup, split consuming 100% CPU

Hi guys,

I'm seeing a new issue on startup causing our app to take 140 seconds to start and using 100% cpu during that time.

I've tracked the hot spot down to BCrypt.java:641-644 where it does 1024 rounds initializing the key.

crypt_raw:639, BCrypt (io.split.android.client.utils)
hashpw:710, BCrypt (io.split.android.client.utils)
convertApiKeyToFolder:88, Utils (io.split.android.client.utils)
buildDatabaseName:54, SplitFactoryHelper (io.split.android.client)
<init>:135, SplitFactoryImpl (io.split.android.client)
<init>:84, SplitFactoryImpl (io.split.android.client)
build:67, SplitFactoryBuilder (io.split.android.client)
<GoDaddy Code>

As an aside it is a universally bad idea to roll your own cryptography instead of using what is built into the platform, as you take on the responsibility of any security vulnerabilities and updates.

Set user ID without creating a new instance

Hi, I am planning to use a same instance of SplitClient as a singleton instance, is there a way to set the user ID without creating a new instance? I am also having difficulty to set the user ID correctly, as the targeting rule is not working properly.
How I am setting the user ID now:

val config = SplitClientConfig.builder()
            .impressionsRefreshRate(60)
            .connectionTimeout(10000)
            .readTimeout(10000)
            .build()
val userId = "123"
val splitFactory = SplitFactoryBuilder.build(
            configuration.splitKey,
            Key(userId),
            config,
            application)
val instance = splitFactory.client()
instance.on(SplitEvent.SDK_READY, object: SplitEventTask() {
            override fun onPostExecution(client: SplitClient?) {
                Log.d("test", "SDK_READY")
            }
        })        

NPE in SplitFactoryBuilder

I was using a Charles proxy when running the app and it crashed due to a NPE in SplitFactoryBuilder, probably because it couldn't connect to the server / ERR_CONNECTION_REFUSED.
This should be avoidable by returning empty lists instead of nulls.

The call:

// Build SDK configuration by default
val config: SplitClientConfig = SplitClientConfig.builder().build()

// Create a new user key to be evaluated
val key = Key(lastId, null)

// Create factory
val splitFactory: SplitFactory = SplitFactoryBuilder.build(SPLIT_API_KEY, key, config, context)

Stacktrace:

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List io.split.android.client.impressions.StoredImpressions.impressions()' on a null object reference
        at io.split.android.client.impressions.ImpressionsFileStorage.read(ImpressionsFileStorage.java:75)
        at io.split.android.client.impressions.ImpressionsStorageManager.loadEventsFilesByLine(ImpressionsStorageManager.java:187)
        at io.split.android.client.impressions.ImpressionsStorageManager.loadImpressionsFromDisk(ImpressionsStorageManager.java:182)
2019-10-14 11:26:51.938 6534-6640/dk.coop.coopplus.demo E/AndroidRuntime:     at io.split.android.client.impressions.ImpressionsStorageManager.<init>(ImpressionsStorageManager.java:63)
        at io.split.android.client.impressions.ImpressionsStorageManager.<init>(ImpressionsStorageManager.java:55)
        at io.split.android.client.SplitFactoryImpl.<init>(SplitFactoryImpl.java:151)
        at io.split.android.client.SplitFactoryBuilder.build(SplitFactoryBuilder.java:67)

Calling removeObserver from wrong thread

I'm seeing these errors in my logs:

    java.lang.IllegalStateException: Method removeObserver must be called on the main thread
        at androidx.lifecycle.LifecycleRegistry.enforceMainThreadIfNeeded(LifecycleRegistry.java:317)
        at androidx.lifecycle.LifecycleRegistry.removeObserver(LifecycleRegistry.java:219)
        at io.split.android.client.lifecycle.LifecycleManager.destroy(LifecycleManager.java:37)
        at io.split.android.client.SplitFactoryImpl$1.run(SplitFactoryImpl.java:163)
        at java.lang.Thread.run(Thread.java:919)

Looking at the code, you are indeed calling _lifecycleManager.destroy() from a non-Main thread. See

[QUESTION] Best practices for re-initializing Split after shutdown()

Hi team,

We have an issue in our app where splitClient.getTreatment() is returning control after the app is brought back to the foreground after it had been killed in the background.

Our set-up involves initializing the SplitClient in our app's splash screen. We also integrated a custom LifecycleObserver which initializes the SplitClient in Lifecycle.Event.ON_START, to handle cases where user returns to the app.

Our custom LifecycleObserver also calls splitClient.shutdown() in Lifecycle.Event.ON_STOP, which is a slight adjustment to the recommendation provided in the official Split docs.

Executing the client.destroy() method into onStop callback of your MainActivity is a good practice.

class LifecycleListener(
        private val context: Context,
        private val accountRepo: AccountRepo
) : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onMoveToForeground() {
        SplitUtils.initialize(context, uniqueId = accountRepo.getUniqueId()).subscribe { 
            // ignore result
         }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onMoveToBackground() {
        SplitUtils.closeClient() // calls splitClient.destroy()
    }
}

object class SplitUtils {
    fun initialize(context: Context, uniqueId: String): Single<SplitClient> {
        return Single.create { subscriber ->
            val config = SplitClientConfig.builder()
                    .connectionTimeout(TIMEOUT_INITIALIZE) // sdk default is 15000 ms
                    .build()
    
            val key = Key(uniqueId, null)
    
            val splitFactory = SplitFactoryBuilder.build(apiKey, key, config, context)
            splitClient = splitFactory.client()
    
            splitClient.on(SplitEvent.SDK_READY_TIMED_OUT, object : SplitEventTask() {
                override fun onPostExecution(splitClient: SplitClient) {
                    subscriber.onError(Throwable("Timeout"))
                }
            })
            splitClient.on(SplitEvent.SDK_READY, object : SplitEventTask() {
                override fun onPostExecution(client: SplitClient) {
                    subscriber.onSuccess(client)
                }
            })
        }
    }
    
    fun closeClient() {
        splitClient.destroy()
    }

Our Activities call splitClient.getTreatment() typically in onCreate().

My main questions are:

  • What is the Split team's recommended approach for when to call splitClient.destroy()? The docs seem to recommend a slightly misleading approach, implying we should destroy in onStop() for all our activities (hence our adjustment above using LifecycleObserver)
  • After we call splitClient.destroy() in onStop(), what is the Split team's recommended approach for when to re-initialize SplitClient? From our code, it seems we're re-initializing too late, after our Activity's onCreate() is returned (which, after this time, our experiment treatments have been evaluated as control)
  • Is calling splitClient.destroy() necessary? I've experimented with removing this line, and doing so appears to resolve the issue.

To illustrate a hypothetical scenario with our testing strategy used:

  • Assume we have a split_experiment defined with "on" and "off" treatments
  • Enable "Don't keep activities" in developer options
  • Launch app, reach Activity. Activity shows the value of the split_experiment as "on"
  • Put app in background
  • Bring app back to foreground. Activity now shows the value of split_experiment as "control"

Metadata:

  • Split version: 2.6.7
  • Min SDK version: 23
  • Target SDK version: 29

Please advise if more info is needed. Thanks in advance.

name: android.database.sqlite.SQLiteFullException database or disk is full (code 13)

This crash on Android app has seen an increase after 31st December 2020. Full crash stack below.
Attached is the report from Embrace.
Embrace - Crash Details.pdf

at android.database.sqlite.SQLiteConnection.nativeExecute (Native Method)

  | at android.database.sqlite.SQLiteConnection.execute (SQLiteConnection.java:569)
  | at android.database.sqlite.SQLiteSession.endTransactionUnchecked (SQLiteSession.java:439)
  | at android.database.sqlite.SQLiteSession.endTransaction (SQLiteSession.java:403)
  | at android.database.sqlite.SQLiteDatabase.endTransaction (SQLiteDatabase.java:588)
  | at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.endTransaction (FrameworkSQLiteDatabase.java:90)
  | at androidx.room.RoomDatabase.endTransaction (RoomDatabase.java:364)
  | at io.split.android.client.storage.db.ImpressionDao_Impl.insert (ImpressionDao_Impl.java:5)
  | at io.split.android.client.storage.impressions.SqLitePersistentImpressionsStorage.push (SqLitePersistentImpressionsStorage.java:20)
  | at io.split.android.client.storage.impressions.SqLitePersistentImpressionsStorage.push (SqLitePersistentImpressionsStorage.java:20)
  | at io.split.android.client.service.synchronizer.RecorderSyncHelperImpl$1.run (RecorderSyncHelperImpl.java:66)
  | at java.lang.Thread.run (Thread.java:761)

In app profiling I see 4 threads related to split that are eating up CPU cycles

Recently I have been profiling my app and I saw that the top threads that are eating CPU cycles are related to split.

Screen Shot 2020-09-23 at 2 14 35 PM

Is there a reason for that? I already initialized split on app launch and this profile came from going to another activity in my app where I just query split one for a specific feature on activity create and I feel this affects the performance of the app.

Proguard issue

Hi, when I integrate split and enable proguard, i got this warning, i believe it is from internal dependency of the split sdk which is the snackyaml. I try to keep class but it still fails

-keep class java.beans.** { *; }
-keep class org.yaml.snakeyaml.introspector.** { *; }

Warning thrown by proguard:

  Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.MethodProperty: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.IntrospectionException
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.Introspector
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.Introspector
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.BeanInfo
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.BeanInfo
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.FeatureDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.FeatureDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.PropertyDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.IntrospectionException
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.FeatureDescriptor
Warning: org.yaml.snakeyaml.introspector.PropertyUtils: can't find referenced class java.beans.FeatureDescriptor

Crash CursorWindow.java - Could not allocate CursorWindow

This crash happens since we fully migrate our online config to Split.io SDK, is it possible because our apps are having so much config that the database on Split causing this error?

I see on code you have this query which queries all cached config :

@Query("SELECT name, body, updated_at FROM splits") List<SplitEntity> getAll();

is it possible if you add @Transation on that query https://developer.android.com/reference/androidx/room/Transaction?authuser=2 ?

"If the result of the query is fairly big, it is better to run it inside a transaction to receive a consistent result. Otherwise, if the query result does not fit into a single CursorWindow, the query result may be corrupted due to changes in the database in between cursor window swaps."

Stack-trace

Fatal Exception: android.database.CursorWindowAllocationException: Could not allocate CursorWindow '/data/user/0//databases/2a108jbd3qchur8ehofuhfgquOVOoASMity1TtqCXkbQnI7WOrG0YZ1K' of size 2097152 due to error -25.
       at android.database.CursorWindow.nativeCreate(CursorWindow.java)
       at android.database.CursorWindow.<init>(CursorWindow.java:139)
       at android.database.CursorWindow.<init>(CursorWindow.java:120)
       at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:202)
       at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
       at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:140)
       at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:232)
       at android.database.AbstractCursor.moveToNext(AbstractCursor.java:281)
       at androidx.room.InvalidationTracker$1.DoublePoint(SourceFile:417)
       at androidx.room.InvalidationTracker$1.run(SourceFile:388)
       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)
       
       
      .................
      
      Split-EventsManager-0
       at sun.misc.Unsafe.park(Unsafe.java)
       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.ArrayBlockingQueue.take(ArrayBlockingQueue.java:387)
       at io.split.android.client.events.SplitEventsManager.equals(SourceFile:150)
       at io.split.android.client.events.SplitEventsManager.run(SourceFile:144)
       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:919)

Local splits data is not refreshing when using SplitFilter

Hello team, I faced some interesting issue.
When I use SplitFilter splitFilter = SplitFilter.byName(Arrays.asList("SPLIT_NAME_1", "SPLIT_NAME_2")); split treatments data is not updated with time. Split data is loaded once during the first start of the application after install and then never updated. If I don't use filters everything works fine. I tried changing refresh rate using SplitClientConfig, but this didn't help. I am using latest version 2.6.6.

Thanks

IllegalStateException: Problem fetching splitChanges: HttpException: Something happened while retrieving data: Chain validation failed

When setting the device date 2+ days in the future, the Split SDK HTTP calls fail with the below exception:

E/SplitSDK: RefreshableSplitFetcher failed: Problem fetching splitChanges: HttpException: Something happened while retrieving data: Chain validation failed
    java.lang.IllegalStateException: Problem fetching splitChanges: HttpException: Something happened while retrieving data: Chain validation failed
        at io.split.android.client.HttpSplitChangeFetcher.fetch(HttpSplitChangeFetcher.java:86)
        at io.split.android.client.HttpSplitChangeFetcher.fetch(HttpSplitChangeFetcher.java:52)
        at io.split.android.engine.experiments.RefreshableSplitFetcher.runWithoutExceptionHandling(RefreshableSplitFetcher.java:140)
        at io.split.android.engine.experiments.RefreshableSplitFetcher.run(RefreshableSplitFetcher.java:117)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:307)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:302)
        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)
     Caused by: io.split.android.client.network.HttpException: HttpException: Something happened while retrieving data: Chain validation failed
        at io.split.android.client.network.HttpRequestImpl.getRequest(HttpRequestImpl.java:64)
        at io.split.android.client.network.HttpRequestImpl.execute(HttpRequestImpl.java:35)
        at io.split.android.client.HttpSplitChangeFetcher.fetch(HttpSplitChangeFetcher.java:72)
        at io.split.android.client.HttpSplitChangeFetcher.fetch(HttpSplitChangeFetcher.java:52) 
        at io.split.android.engine.experiments.RefreshableSplitFetcher.runWithoutExceptionHandling(RefreshableSplitFetcher.java:140) 
        at io.split.android.engine.experiments.RefreshableSplitFetcher.run(RefreshableSplitFetcher.java:117) 
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) 
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:307) 
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:302) 
        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) 

Getting Crash from Split IO android client.

After implementing Split into our native android app the following crash has started to occur.

Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'void java.io.FileOutputStream.close()' on a null object reference at io.split.android.client.storage.FileStorage.write + 81(FileStorage.java:81) at io.split.android.client.cache.SplitCache.writeSplitsToDisk + 147(SplitCache.java:147) at java.lang.reflect.Method.invoke(Method.java) at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback + 216(ClassesInfoCache.java:216) at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent + 194(ClassesInfoCache.java:194) at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks + 185(ClassesInfoCache.java:185) at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged + 36(ReflectiveGenericLifecycleObserver.java:36) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent + 361(LifecycleRegistry.java:361) at androidx.lifecycle.LifecycleRegistry.backwardPass + 316(LifecycleRegistry.java:316) at androidx.lifecycle.LifecycleRegistry.sync + 334(LifecycleRegistry.java:334) at androidx.lifecycle.LifecycleRegistry.moveToState + 145(LifecycleRegistry.java:145) at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent + 131(LifecycleRegistry.java:131) at androidx.lifecycle.ProcessLifecycleOwner.dispatchPauseIfNeeded + 140(ProcessLifecycleOwner.java:140) at androidx.lifecycle.ProcessLifecycleOwner$1.run + 67(ProcessLifecycleOwner.java:67) at android.os.Handler.handleCallback + 739(Handler.java:739) at android.os.Handler.dispatchMessage + 95(Handler.java:95) at android.os.Looper.loop + 158(Looper.java:158) at android.app.ActivityThread.main + 7225(ActivityThread.java:7225) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run + 1230(ZygoteInit.java:1230) at com.android.internal.os.ZygoteInit.main + 1120(ZygoteInit.java:1120)

Seems the problem is in this method.

Sometimes, an error occurs during the execution of "new FileOutputStream (file)", which causes the property to remain in the value "fileOutputStream == null". And in the future, we try to execute the code "fileOutputStream.close ();" in finally block, when property equals null. Resulting in a crash with NPE.

(Network) Instrumentation/Telemetry on top of OkHttp

Hi, we want to integrate instrumentation SDK that should be put on top of OkHttp instance.
Any plan to take out the OkHttp "instantiator" into caller code, so we can set/modify the OkHttp instance by ourself? or
Any workaround that we can do for now?

Currently we are planning to look at how Firebase Performance bytecode manipulation to put OkHttp instrumentation.

Thank you

Obfuscation problems

Could you update the proguard rules file?
I'm trying to run Split.io with proguard but it was crashing on startup.
After adding those lines below, the crash stopped, but Split.io doesn't retrieve any Treatment when it is obfuscated.

-keep class io.split.android.client.dtos.* { *; }
-keep class io.split.android.client.storage.db.** { *; }

[v2.13.0] NPE on SplitEventTask.onPostExecution

Hi, we are using android-client v2.13.0 and recently we found crashes in Firebase Crashlytics
(85% happen when app in background)

the stack trace

Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'void io.split.android.client.events.SplitEventTask.onPostExecution(io.split.android.client.SplitClient)' on a null object reference
       at io.split.android.client.events.executors.SplitEventExecutorWithClient$1.doInBackground(SplitEventExecutorWithClient.java:39)
       at io.split.android.client.events.executors.SplitEventExecutorWithClient$1.doInBackground(SplitEventExecutorWithClient.java:28)

We also check on the code

public SplitEventExecutorWithClient(SplitEventTask task, SplitClient client) {

        super(task);
        checkNotNull(task);
        _sclient = checkNotNull(client);
    }

    public void execute(){

        _asyncTansk = new AsyncTask<SplitClient, Void, SplitClient>() {

            @Override
            protected SplitClient doInBackground(SplitClient... splitClients) {

                if (splitClients.length > 0) {

                    SplitClient client = checkNotNull(splitClients[0]);

                    //BACKGROUND POST EXECUTION
                    try {
                        _task.onPostExecution(client);

but couldn't find a possibility of _task field being null

Thank you

App crash on launch after upgrading from 2.7.2 to 2.7.3

Our Android app started crashing on app launch after updating to the latest version of Split. I confirmed that the crash only occurs with the 2.7.3 version of the SDK, and does not with the 2.7.2 version. Based off the stack trace, it appears the issue has to do with a WorkManager related change in the latest version of the SDK.

Stack trace:

2021-10-11 13:36:37.988 E/AndroidRuntime: FATAL EXCEPTION: main Process: com.latch.android.latchapp.dev, PID: 22120 java.lang.RuntimeException: Unable to create application com.latch.android.latchapp.app.LatchApplication: java.lang.IllegalStateException: WorkManager is already initialized. Did you try to initialize it manually without disabling WorkManagerInitializer? See WorkManager#initialize(Context, Configuration) or the class level Javadoc for more information. at android.app.ActivityThread.handleMakeApplication(ActivityThread.java:7512) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7446) at android.app.ActivityThread.access$1500(ActivityThread.java:301) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2148) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:246) at android.app.ActivityThread.main(ActivityThread.java:8512) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130) Caused by: java.lang.IllegalStateException: WorkManager is already initialized. Did you try to initialize it manually without disabling WorkManagerInitializer? See WorkManager#initialize(Context, Configuration) or the class level Javadoc for more information. at androidx.work.impl.WorkManagerImpl.initialize(WorkManagerImpl.java:183) at androidx.work.WorkManager.initialize(WorkManager.java:210) at com.latch.android.latchapp.app.initialization.impl.WorkerFactoryInitTask.init(WorkerFactoryInitTask.kt:18) at com.latch.android.latchapp.app.initialization.AppInitQueue.process(AppInitQueue.kt:72) at com.latch.android.latchapp.app.LatchApplication.initialize(LatchApplication.kt:63) at com.latch.android.latchapp.app.LatchApplication.onCreate(LatchApplication.kt:50) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1192) at android.app.ActivityThread.handleMakeApplication(ActivityThread.java:7507) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7446)  at android.app.ActivityThread.access$1500(ActivityThread.java:301)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2148)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:246)  at android.app.ActivityThread.main(ActivityThread.java:8512)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

I would appreciate if you guys can investigate what could cause this crash to start happening in 2.7.3, as our app will be unable to update until it is resolved.

Thank you!

Callbacks are not executing on client.on(SplitEvent.SDK_READY)

Platform: Android

I have followed the documentation and used everything stated there, created my SDK API Key for client-side inside an Environment as below

Screenshot 2023-10-06 at 11 35 29

I have hidden organization details.

But after adding everything just to test on my MainActivity

        val sdkKey = "my-api-key-here"
        val config: SplitClientConfig = SplitClientConfig.builder().build()
        val matchingKey = "key"
        val key = Key(matchingKey)
        val splitFactory: SplitFactory =
            SplitFactoryBuilder.build(sdkKey, key, config, applicationContext)
        val client: SplitClient = splitFactory.client()
        
        client.on(SplitEvent.SDK_READY, object : SplitEventTask() {
            override fun onPostExecution(client: SplitClient) {
                // Execute background logic here
                when (client.getTreatment("my_feature_flag")) {
                    "on" -> {
                        // insert code here to show on treatment
                        Log.d("SPLIT", "onPostExecution: ON")
                    }
                    "off" -> {
                        // insert code here to show off treatment
                        Log.d("SPLIT", "onPostExecution: OFF")
                    }
                    else -> {
                        // insert your control treatment code here
                        Log.d("SPLIT", "onPostExecution: ELSE")
                    }
                }
            }
            override fun onPostExecutionView(client: SplitClient) {
                // Execute main thread logic here
                when (client.getTreatment("ncuenta_version")) {
                    "on" -> {
                        // insert code here to show on treatment
                        Log.d("SPLIT", "onPostExecutionView: ON")
                    }
                    "off" -> {
                        // insert code here to show off treatment
                        Log.d("SPLIT", "onPostExecutionView: OFF")
                    }
                    else -> {
                        // insert your control treatment code here
                        Log.d("SPLIT", "onPostExecutionView: ELSE")
                    }
                }
            }
        })

Nothing returns in the logs, I have already created the feature flag and everything, granted INTERNET access into the manifest, IDK what is happening, it should work

implementation("io.split.client:android-client:3.3.0")

compileSdk = 34

I'm only receiving

Screenshot 2023-10-06 at 11 38 41

Also added

val config: SplitClientConfig = SplitClientConfig.builder().impressionsMode(ImpressionsMode.DEBUG).build()

but cannot see anything in the console

I have also followed the demo app here

https://github.com/Split-Community/Split-SDKs-Examples/blob/main/android-sdk/UI_App/app/src/main/java/com/example/example_app/ui_app/SplitSDK.java

but no success

SdkTargetPath.mySegments does not URL encode the Key String

I'm seeing a crash happen when trying to create a SplitClient using a matchingKey that contains non-URL encoded characters (to be specific, |).

We're getting this key from our auth provider, Auth0, and the key is in the format auth0|uuid. We'd like to be able to use this string as the matching key, and not need to manually URL-encode that string everywhere else we use it in our systems.

Split SDK version: 2.6.8

Stacktrace:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xx.xx.xxx.android.xxx, PID: 28442
    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.net.URISyntaxException: Illegal character in path at index 41: https://sdk.split.io/api/mySegments/auth0|randomlygeneratedcode1203234
        at java.net.URI$Parser.fail(URI.java:2893)
        at java.net.URI$Parser.checkChars(URI.java:3066)
        at java.net.URI$Parser.parseHierarchical(URI.java:3150)
        at java.net.URI$Parser.parse(URI.java:3098)
        at java.net.URI.<init>(URI.java:584)
        at io.split.android.client.network.SdkTargetPath.buildUrl(SdkTargetPath.java:47)
        at io.split.android.client.network.SdkTargetPath.buildUrl(SdkTargetPath.java:39)
        at io.split.android.client.network.SdkTargetPath.mySegments(SdkTargetPath.java:23)
        at io.split.android.client.service.ServiceFactory.getMySegmentsFetcher(ServiceFactory.java:63)
        at io.split.android.client.SplitFactoryHelper.buildApiFacade(SplitFactoryHelper.java:97)
        at io.split.android.client.SplitFactoryImpl.<init>(SplitFactoryImpl.java:164)
        at io.split.android.client.SplitFactoryImpl.<init>(SplitFactoryImpl.java:84)
        at io.split.android.client.SplitFactoryBuilder.build(SplitFactoryBuilder.java:67)
        at xx.xxx.xxx.service.feature_flag.NativeSplitIoClient.<init>(NativeSplitIoClient.kt:29)

Crash caused by WorkManager

Hi, there is a crash on WorkManager which is caused when calling SplitFactoryBuilder.build. It happens only on certain device, it happened on Samsung S20, Android OS 10. I tested on emulator OS 10 is actually working fine. Is there anything I need to define to avoid this? I am using version 2.6.4
My split client initialization code

private lateinit var instance: SplitClient

fun refreshInstance(userId: String?) {
        if (this::instance.isInitialized) {
            instance.flush()
            instance.destroy()
        }

        val config = SplitClientConfig.builder()
            .build()

        val splitFactory = SplitFactoryBuilder.build(
            configuration.splitKey,
            Key(userId ?: "GUEST"),
            config,
            application.applicationContext
        )
        instance = splitFactory.client()
    }

Location of crash at the Split SDK:

//SplitFactoryHelper line 94
    WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig splitClientConfig,
                                               String apiKey, String key, String databaseName) {
        return new WorkManagerWrapper(
                WorkManager.getInstance(context), splitClientConfig, apiKey, key, databaseName);

    }

[v2.13.1] Splits with custom targeting rules always return `control`

Hi,

We're using the Android Client v2.13.1 in our app and lately all the splits that have custom targeting rules are coming back with a value of control instead of on or off

We've followed the docs here and are using the SplitEvent.SDK_READY to get the treatments but that hasn't helped

Here's the code snippet

override fun retrieveFeatureWithAttributeFromApi(
        feature: SplitFeature,
        attributes: Map<String, String>,
        statusRetrieved: (status: FeatureStatus) -> Unit
    ) {
        if (this::splitClient.isInitialized) {
            splitClient.on(
                SplitEvent.SDK_READY,
                object : SplitEventTask() {
                    override fun onPostExecution(client: SplitClient) {}
                    override fun onPostExecutionView(client: SplitClient) {
                        when (client.getTreatment(feature.label, attributes).uppercase()) {
                            "ON" -> statusRetrieved(FeatureStatus.ON)
                            "OFF" -> statusRetrieved(FeatureStatus.OFF)
                            else -> statusRetrieved(FeatureStatus.UNKNOWN)
                        }
                    }
                }
            )
        } else {
            Log.e(
                TAG_FEATURE_FLIPPER,
                "Cannot retrieve features on uninitialized feature flipper instance"
            )
        }
    }

And here's what we get when we call client.getTreatment(features, attributes)

{
  "android-split-test": "control",
  "cross-platform-upgrades": "control",
  "ideas-v0": "off",
  "android-selected-profile-id-refactor": "control",
  "android-ideas-url-highlighting": "control",
  "android-attributes-test": "off",
  "android-edit-update-using-model": "control",
  "android-app-tour": "off",
  "ideas-android-share-extension": "control"
}

Would help us a lot if we got a pair of eyes on this. Thanks! 😇

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.