Skip to content

precomputed client#236

Draft
leoromanovsky wants to merge 16 commits intomainfrom
feature/precomputed-client
Draft

precomputed client#236
leoromanovsky wants to merge 16 commits intomainfrom
feature/precomputed-client

Conversation

@leoromanovsky
Copy link
Member

@leoromanovsky leoromanovsky commented Jan 21, 2026

Motivation

Customers using the standard client with large flag deployments have reported ANRs on Android. The client-side evaluation during initialization can exceed Android's 5-second ANR threshold, causing the app to appear frozen at startup. The precomputed client solves this by moving all flag evaluation to the server. Instead of downloading the full configuration and evaluating rules locally, the client fetches a pre-evaluated response for the specific subject. This makes the response size fixed regardless of how many targeting rules exist, and flag lookups become instant O(1) map accesses with no evaluation overhead.

Changes

This PR adds EppoPrecomputedClient to the Android SDK, matching the precomputed clients already available for browser and iOS. The implementation includes the client itself with Builder pattern initialization, DTOs for the wire protocol, a configuration store with async disk caching, and obfuscation utilities. The example app has been updated to demonstrate both client types side-by-side, with the precomputed demo showing server vs disk initialization, dynamic subject attributes, and flag type selection.

Decisions

The implementation follows the patterns established by the existing iOS and browser precomputed clients. On the Android side, initialization uses CompletableFuture for async operations, and the client integrates with Android lifecycle through pausePolling()/resumePolling() methods that activities can call from onPause()/onResume().

Add EppoPrecomputedClient for precomputed flag assignments computed
server-side at the edge endpoint. Key features:

- Subject-specific initialization (client bound to user at init time)
- HTTP POST to edge endpoint: https://fs-edge-assignment.eppo.cloud/assignments
- Obfuscation support with MD5 hashing and Base64 decoding
- All assignment methods: string, boolean, integer, numeric, JSON, bandit
- Offline mode with initialConfiguration for sync without network
- Polling support for configuration updates
- Assignment logging with deduplication via IAssignmentCache
- Graceful mode for error handling

New files:
- DTOs: PrecomputedFlag, PrecomputedBandit, PrecomputedConfigurationResponse, BanditResult
- Utilities: ObfuscationUtils (MD5 hashing)
- Storage: PrecomputedCacheFile, PrecomputedConfigurationStore
- Client: EppoPrecomputedClient with Builder pattern
- Exception: MissingSubjectKeyException
- Tests: Unit and instrumented tests
Add a new activity to the example app demonstrating the
EppoPrecomputedClient. The new tab allows users to:

- Initialize the precomputed client with a subject ID
- Test different flag types (string, boolean, integer, numeric)
- View assignment logs in real-time

Files changed:
- PrecomputedActivity.java: New activity for precomputed client demo
- activity_precomputed.xml: Layout for the precomputed activity
- MainActivity.java: Add button to launch precomputed activity
- activity_main.xml: Add precomputed button to main layout
- AndroidManifest.xml: Register PrecomputedActivity
- strings.xml: Add new string resources
Use MD5 hash of subject key instead of safeCacheKey() to ensure
consistent length and avoid StringIndexOutOfBoundsException when
subject key is shorter than 8 characters.
- Add dynamic subject attributes table to PrecomputedActivity
- Users can add/remove key-value pairs for subject attributes
- Numeric values are automatically detected and typed correctly
- Add HTTP request/response logging to EppoPrecomputedClient
- Log request URL, payload, and detailed error messages
Both keys and values in bandit actionNumericAttributes and
actionCategoricalAttributes are Base64 encoded in the server response.
- Update Makefile to fetch precomputed-v1.json test data
- Add tests for all assignment types using sdk-test-data fixtures
- Add bandit action tests
- Tests validate string, boolean, integer, numeric, and JSON flags
Keep sdk-test-data tests for canonical evaluation behavior.
Keep mock data tests only for SDK behavior: cache, lifecycle, validation, logging.
The extraLogging and metaData parameters were swapped, causing
getMetaData() to return the wrong map and failing the test.
- Rename MainActivity to HomeActivity
- Rename SecondActivity to StandardClientActivity
- Add action bar back button to StandardClientActivity and PrecomputedActivity
- Set parent activity for proper up navigation
…ctivity

- Replace single Initialize button with "From Server" and "From Disk" buttons
- From Server: fetches configuration from edge endpoint
- From Disk: uses offline mode with cached configuration only
- Update status messages to indicate initialization source
- Disable Get Assignment button until client is initialized
- Update Clear Cache to also delete precomputed cache files
- Show count of deleted precomputed caches in toast message
@leoromanovsky leoromanovsky requested a review from Copilot January 22, 2026 03:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new EppoPrecomputedClient that solves Android ANR (Application Not Responding) issues during initialization for deployments with many flags and complex targeting rules. Instead of evaluating flags client-side, all assignments are computed server-side and delivered as a batch, providing instant O(1) lookups with zero evaluation time.

Changes:

  • Implemented EppoPrecomputedClient with builder pattern matching existing EppoClient API style, supporting all flag types and bandit actions
  • Added supporting infrastructure including PrecomputedConfigurationStore for in-memory/disk caching, ObfuscationUtils for MD5 hashing, and related DTOs
  • Enhanced example app with new PrecomputedActivity demonstrating the precomputed client functionality

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
example/src/main/res/values/strings.xml Added string resource for launching precomputed client
example/src/main/res/layout/activity_precomputed.xml New layout for precomputed client demo with subject attributes, flag type selection, and assignment logging
example/src/main/res/layout/activity_home.xml Added button to launch precomputed activity
example/src/main/java/cloud/eppo/androidexample/StandardClientActivity.java Renamed from SecondActivity and added back navigation support
example/src/main/java/cloud/eppo/androidexample/PrecomputedActivity.java New activity demonstrating precomputed client with server/disk initialization options
example/src/main/java/cloud/eppo/androidexample/HomeActivity.java Renamed from MainActivity, added precomputed activity launcher and cache clearing for precomputed clients
example/src/main/AndroidManifest.xml Updated activity names and added parent activity declarations for back navigation
eppo/src/test/java/cloud/eppo/android/PrecomputedConfigurationResponseTest.java Unit tests for precomputed configuration response deserialization
eppo/src/test/java/cloud/eppo/android/ObfuscationUtilsTest.java Unit tests for MD5 hashing utility
eppo/src/main/java/cloud/eppo/android/util/ObfuscationUtils.java Utility class for MD5 hashing used in flag key obfuscation
eppo/src/main/java/cloud/eppo/android/exceptions/MissingSubjectKeyException.java New exception for missing subject key validation
eppo/src/main/java/cloud/eppo/android/dto/PrecomputedFlag.java DTO for precomputed flag assignments with Base64-encoded fields
eppo/src/main/java/cloud/eppo/android/dto/PrecomputedConfigurationResponse.java Wire protocol response from precomputed edge endpoint
eppo/src/main/java/cloud/eppo/android/dto/PrecomputedBandit.java DTO for precomputed bandit assignments
eppo/src/main/java/cloud/eppo/android/dto/BanditResult.java Return type for bandit action assignments
eppo/src/main/java/cloud/eppo/android/PrecomputedConfigurationStore.java Configuration storage with disk caching
eppo/src/main/java/cloud/eppo/android/PrecomputedCacheFile.java Disk cache file management for precomputed configuration
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java Main precomputed client implementation with assignment methods, logging, and polling
eppo/src/androidTest/java/cloud/eppo/android/EppoPrecomputedClientTest.java Integration tests for precomputed client using test data
Makefile Updated test data copying to include precomputed configuration files

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import java.util.List;

/**
* Example activity demonstrating the EppoPrecomputedClient. The precomputed client computes all
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'recieve' to 'receive' in class documentation.

Suggested change
* Example activity demonstrating the EppoPrecomputedClient. The precomputed client computes all
* Example activity demonstrating the EppoPrecomputedClient. The precomputed client receives all

Copilot uses AI. Check for mistakes.
- Add synchronized block to loadConfigFromCache to prevent race condition
- Recreate executor in resumePolling if it was previously shut down
- Remove unnecessary @nullable annotation from getPrecomputedAssignment
- Decode both keys and values in extraLogging (was only decoding values)
- Redact API key from debug log output
- Add test for resumePolling after stopPolling
- Add test for non-graceful mode initialization
- Extract common cache file logic into BaseCacheFile base class
- ConfigCacheFile and PrecomputedCacheFile now extend BaseCacheFile
- Add @nullable annotations to PrecomputedBandit attribute maps
- Fix toBytes() to serialize environment as object (matching server format)
- Add null safety checks for bandit field decoding
- Fix polling to start after initial fetch completes
- Update in-memory config even if disk write fails
- Remove double-set of configuration on init
- Extract SUBJECT_KEY_HASH_LENGTH constant
- Add @nonnull annotation to baseUrl builder method
- Add PrecomputedConfigurationStoreTest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant