-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add android-sdk-framework module #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
b82fbbf
bbb0003
45934db
882786a
7944817
9aa5bd6
264661f
aa31c74
d4f57b6
2099f50
5bda22e
62fc396
edb6b14
4e501d9
8d16eeb
99dfdcb
6bc6813
639dad6
3ea6600
05d2ff5
09d03e0
bfdc0d9
cbde4f7
c1e373c
7f26560
0bf7884
e151d8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| plugins { | ||
| id 'com.android.library' | ||
| id 'maven-publish' | ||
| id "com.vanniktech.maven.publish" version "0.32.0" | ||
| id 'signing' | ||
| id "com.diffplug.spotless" version "8.0.0" | ||
| } | ||
|
|
||
| group = "cloud.eppo" | ||
| version = "0.1.0" | ||
|
|
||
| android { | ||
| namespace "cloud.eppo.android.framework" | ||
| compileSdk 34 | ||
|
|
||
| buildFeatures.buildConfig true | ||
|
|
||
| defaultConfig { | ||
| minSdk 26 | ||
| targetSdk 34 | ||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
| } | ||
|
|
||
| buildTypes { | ||
| def FRAMEWORK_VERSION = "FRAMEWORK_VERSION" | ||
| // EPPO_VERSION is used as the sdkVersion reported to Eppo. It matches FRAMEWORK_VERSION | ||
| // because the framework and eppo modules are versioned together. | ||
| def EPPO_VERSION = "EPPO_VERSION" | ||
| release { | ||
| minifyEnabled false | ||
| buildConfigField "String", FRAMEWORK_VERSION, "\"${project.version}\"" | ||
| buildConfigField "String", EPPO_VERSION, "\"${project.version}\"" | ||
| } | ||
| debug { | ||
| minifyEnabled false | ||
| buildConfigField "String", FRAMEWORK_VERSION, "\"${project.version}\"" | ||
| buildConfigField "String", EPPO_VERSION, "\"${project.version}\"" | ||
| } | ||
| } | ||
|
|
||
| compileOptions { | ||
| sourceCompatibility JavaVersion.VERSION_1_8 | ||
| targetCompatibility JavaVersion.VERSION_1_8 | ||
| } | ||
|
|
||
| testOptions { | ||
| unitTests.returnDefaultValues = true | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| api 'cloud.eppo:eppo-sdk-framework:0.1.0-SNAPSHOT' | ||
|
|
||
| api 'com.google.code.gson:gson:2.10.1' | ||
| api 'org.slf4j:slf4j-android:1.7.36' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't see this in the code, should this be |
||
| compileOnly 'org.jetbrains:annotations:24.0.0' | ||
|
|
||
| testImplementation 'cloud.eppo:sdk-common-jvm:4.0.0-SNAPSHOT' | ||
|
Comment on lines
+51
to
+58
|
||
| testImplementation 'junit:junit:4.13.2' | ||
| testImplementation 'org.mockito:mockito-core:5.14.2' | ||
| testImplementation 'org.robolectric:robolectric:4.12.1' | ||
|
|
||
| androidTestImplementation 'junit:junit:4.13.2' | ||
| androidTestImplementation 'org.mockito:mockito-android:5.14.2' | ||
| androidTestImplementation 'androidx.test.ext:junit:1.2.1' | ||
| androidTestImplementation 'androidx.test:core:1.6.1' | ||
| androidTestImplementation 'androidx.test:runner:1.6.2' | ||
| androidTestImplementation 'com.fasterxml.jackson.core:jackson-databind:2.19.1' | ||
| } | ||
|
|
||
| spotless { | ||
| format 'misc', { | ||
| target '*.gradle', '.gitattributes', '.gitignore' | ||
|
|
||
| trimTrailingWhitespace() | ||
| leadingTabsToSpaces(2) | ||
| endWithNewline() | ||
| } | ||
| java { | ||
| target '**/*.java' | ||
|
|
||
| googleJavaFormat() | ||
| formatAnnotations() | ||
| } | ||
| } | ||
|
|
||
| signing { | ||
| if (System.getenv("GPG_PRIVATE_KEY") && System.getenv("GPG_PASSPHRASE")) { | ||
| useInMemoryPgpKeys(System.env.GPG_PRIVATE_KEY, System.env.GPG_PASSPHRASE) | ||
| } | ||
|
|
||
| sign publishing.publications | ||
| } | ||
|
|
||
| tasks.withType(Sign) { | ||
| onlyIf { | ||
| (System.getenv("GPG_PRIVATE_KEY") && System.getenv("GPG_PASSPHRASE")) || | ||
| (project.hasProperty('signing.keyId') && | ||
| project.hasProperty('signing.password') && | ||
| project.hasProperty('signing.secretKeyRingFile')) | ||
| } | ||
| } | ||
|
|
||
| mavenPublishing { | ||
| publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL) | ||
| signAllPublications() | ||
| coordinates("cloud.eppo", "android-sdk-framework", project.version) | ||
|
|
||
| pom { | ||
| name = 'Eppo Android SDK Framework' | ||
| description = 'Android SDK Framework for Eppo - Library-independent EppoClient and PrecomputedEppoClient (abstracts JSON, HTTP, storage)' | ||
| url = 'https://github.com/Eppo-exp/android-sdk' | ||
| licenses { | ||
| license { | ||
| name = 'MIT License' | ||
| url = 'http://www.opensource.org/licenses/mit-license.php' | ||
| } | ||
| } | ||
| developers { | ||
| developer { | ||
| name = 'Eppo' | ||
| email = 'https://www.geteppo.com' | ||
| } | ||
| } | ||
| scm { | ||
| connection = 'scm:git:git://github.com/Eppo-exp/android-sdk.git' | ||
| developerConnection = 'scm:git:ssh://github.com/Eppo-exp/android-sdk.git' | ||
| url = 'https://github.com/Eppo-exp/android-sdk/tree/main' | ||
| } | ||
| } | ||
| } | ||
|
|
||
| task checkVersion { | ||
| doLast { | ||
| if (!project.hasProperty('release') && !project.hasProperty('snapshot')) { | ||
| throw new GradleException("You must specify either -Prelease or -Psnapshot") | ||
| } | ||
| if (project.hasProperty('release') && project.version.endsWith('SNAPSHOT')) { | ||
| throw new GradleException("You cannot specify -Prelease with a SNAPSHOT version") | ||
| } | ||
| if (project.hasProperty('snapshot') && !project.version.endsWith('SNAPSHOT')) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 Comment from AI: This is the line that makes the snapshot publish path impossible for the new module. Since |
||
| throw new GradleException("You cannot specify -Psnapshot with a non-SNAPSHOT version") | ||
| } | ||
| project.ext.shouldPublish = true | ||
| } | ||
| } | ||
|
|
||
| tasks.named('publish').configure { | ||
| dependsOn checkVersion | ||
| } | ||
|
|
||
| tasks.withType(PublishToMavenRepository) { | ||
| onlyIf { | ||
| project.ext.has('shouldPublish') && project.ext.shouldPublish | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,217 @@ | ||||||
| package cloud.eppo.android.framework; | ||||||
|
|
||||||
| import static cloud.eppo.android.framework.util.Utils.logTag; | ||||||
| import static org.junit.Assert.assertNotNull; | ||||||
|
|
||||||
| import android.util.Log; | ||||||
| import androidx.test.core.app.ApplicationProvider; | ||||||
| import cloud.eppo.api.Configuration; | ||||||
| import cloud.eppo.http.EppoConfigurationClient; | ||||||
| import cloud.eppo.parser.ConfigurationParser; | ||||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||||
| import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.concurrent.ExecutionException; | ||||||
| import org.junit.Before; | ||||||
| import org.junit.Test; | ||||||
| import org.mockito.Mock; | ||||||
| import org.mockito.MockitoAnnotations; | ||||||
|
|
||||||
| /** | ||||||
| * Tests for EppoClient polling pause/resume functionality. | ||||||
| * | ||||||
| * <p>These tests use offline mode to avoid needing to mock complex configuration loading behavior. | ||||||
| * They focus on verifying that pausePolling() and resumePolling() can be called safely in various | ||||||
| * sequences. | ||||||
| */ | ||||||
| public class EppoClientPollingTest { | ||||||
| private static final String TAG = logTag(EppoClientPollingTest.class); | ||||||
| private static final String DUMMY_API_KEY = "mock-api-key"; | ||||||
|
|
||||||
| @Mock private ConfigurationParser<JsonNode> mockConfigParser; | ||||||
| @Mock private EppoConfigurationClient mockConfigClient; | ||||||
|
|
||||||
| @Before | ||||||
| public void setUp() { | ||||||
| MockitoAnnotations.openMocks(this); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Builds a client in offline mode with polling enabled. | ||||||
| * | ||||||
| * @param pollingIntervalMs Polling interval in milliseconds | ||||||
| * @return Initialized EppoClient | ||||||
| */ | ||||||
| private AndroidBaseClient<JsonNode> buildOfflineClientWithPolling(long pollingIntervalMs) | ||||||
| throws ExecutionException, InterruptedException { | ||||||
| // Use an empty configuration for offline mode | ||||||
| CompletableFuture<Configuration> initialConfig = | ||||||
| CompletableFuture.completedFuture(Configuration.emptyConfig()); | ||||||
|
|
||||||
| return new AndroidBaseClient.Builder<>( | ||||||
| DUMMY_API_KEY, | ||||||
| ApplicationProvider.getApplicationContext(), | ||||||
| mockConfigParser, | ||||||
| mockConfigClient) | ||||||
| .forceReinitialize(true) | ||||||
| .offlineMode(true) | ||||||
| .initialConfiguration(initialConfig) | ||||||
| .pollingEnabled(true) | ||||||
| .pollingIntervalMs(pollingIntervalMs) | ||||||
| .isGracefulMode(true) // Enable graceful mode to handle initialization issues | ||||||
| .buildAndInitAsync() | ||||||
| .get(); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Builds a client in offline mode without polling enabled. | ||||||
| * | ||||||
| * @return Initialized EppoClient | ||||||
| */ | ||||||
| private AndroidBaseClient<JsonNode> buildOfflineClientWithoutPolling() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pretty much same as previous method but with a different boolean passed for |
||||||
| throws ExecutionException, InterruptedException { | ||||||
| CompletableFuture<Configuration> initialConfig = | ||||||
| CompletableFuture.completedFuture(Configuration.emptyConfig()); | ||||||
|
|
||||||
| return new AndroidBaseClient.Builder<>( | ||||||
| DUMMY_API_KEY, | ||||||
| ApplicationProvider.getApplicationContext(), | ||||||
| mockConfigParser, | ||||||
| mockConfigClient) | ||||||
| .forceReinitialize(true) | ||||||
| .offlineMode(true) | ||||||
| .initialConfiguration(initialConfig) | ||||||
| .pollingEnabled(false) | ||||||
| .isGracefulMode(true) // Enable graceful mode to handle initialization issues | ||||||
| .buildAndInitAsync() | ||||||
| .get(); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| public void testPauseAndResumePolling() throws ExecutionException, InterruptedException { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test doesn't seem to be doing much. Consider a shorter polling interval and then pausing longer than the interval to make sure no polls happens and then check polling happened again after unpausing. |
||||||
| AndroidBaseClient<JsonNode> androidBaseClient = buildOfflineClientWithPolling(100); | ||||||
| assertNotNull("Client should be initialized", androidBaseClient); | ||||||
|
|
||||||
| // Test pause | ||||||
| androidBaseClient.pausePolling(); | ||||||
| Log.d(TAG, "Polling paused"); | ||||||
|
|
||||||
| // Wait a bit to ensure no crashes | ||||||
| Thread.sleep(50); | ||||||
|
|
||||||
| // Test resume | ||||||
| androidBaseClient.resumePolling(); | ||||||
| Log.d(TAG, "Polling resumed"); | ||||||
|
|
||||||
| // Wait a bit to ensure no crashes | ||||||
| Thread.sleep(50); | ||||||
|
|
||||||
| // Final pause for cleanup | ||||||
| androidBaseClient.pausePolling(); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| public void testResumePollingWithoutStarting() throws ExecutionException, InterruptedException { | ||||||
| AndroidBaseClient<JsonNode> androidBaseClient = buildOfflineClientWithoutPolling(); | ||||||
| assertNotNull("Client should be initialized", androidBaseClient); | ||||||
|
|
||||||
| // Try to resume polling (should log warning and not crash per EppoClient.java:436-441) | ||||||
|
||||||
| // Try to resume polling (should log warning and not crash per EppoClient.java:436-441) | |
| // Try to resume polling; AndroidBaseClient.resumePolling should log a warning and not crash if polling was never started |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like overkill
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is now a test dependency only (isn't that the whole point of all of this 😅 )