Skip to content

feat: add pluggable Base64Codec interface for Android compatibility#223

Merged
typotter merged 45 commits intofeature/v4from
typo/base64codec
Feb 23, 2026
Merged

feat: add pluggable Base64Codec interface for Android compatibility#223
typotter merged 45 commits intofeature/v4from
typo/base64codec

Conversation

@typotter
Copy link
Copy Markdown
Collaborator

@typotter typotter commented Feb 19, 2026

Motivation and Context

java.util.Base64 is only available on Android API 26+ (Android 8.0 Oreo). The Android SDK needs to use android.util.Base64 for compatibility with older devices. This change introduces a pluggable Base64 codec interface that allows platform-specific implementations.

Reference: Eppo-exp/android-sdk#188

Description

  • Added Base64Codec public interface in Utils.java with base64Encode(String) and base64Decode(String) methods
  • Added setBase64Codec(Base64Codec) static method to allow consumers to inject custom implementations
  • Created DefaultBase64Codec private inner class that uses java.util.Base64 (existing behavior)
  • Modified existing base64Encode() and base64Decode() static methods to delegate to the codec instance
  • Removed duplicate base64Decode implementation from FlagConfigResponseDeserializer.java
  • Backward compatible - existing code continues to work without changes

How has this been documented?

  • Interface and setter method have Javadoc comments
  • Code is self-documenting with clear naming conventions

How has this been tested?

  • Added testCustomBase64Codec() to verify custom codec injection works correctly
  • Added testBase64EncodeDecodeDefault() to verify default codec behavior including null handling and round-trip encoding
  • Added @AfterEach reset method to ensure test isolation
  • All existing tests continue to pass
  • Ran ./gradlew spotlessApply and ./gradlew check successfully

- EppoConfigurationRequest: @NotNull on getters
- EppoConfigurationRequestFactory: @NotNull on factory method returns
- EppoConfigurationResponse: @Nullable/@NotNull on constructor, factory methods, and getters
- ConfigurationParser: @NotNull on method params and returns
- EppoConfigurationClient: @NotNull on get() method
Add pluggable Base64 codec support to allow platform-specific
implementations (e.g., Android SDK using android.util.Base64).

- Add public Base64Codec interface with encode/decode methods
- Add setBase64Codec() method for custom codec injection
- Create DefaultBase64Codec inner class using java.util.Base64
- Delegate existing base64Encode/base64Decode methods to codec
Remove duplicate base64Decode implementation from
FlagConfigResponseDeserializer and use the centralized version
from Utils via static import.
Add tests to verify Base64 codec pluggability:
- testCustomBase64Codec: verifies custom codec injection works
- testBase64EncodeDecodeDefault: verifies default codec behavior
- @AfterEach reset method ensures test isolation
- Add volatile keyword to base64Codec field for thread safety
- Add null check in setBase64Codec() throwing IllegalArgumentException
- Add resetBase64Codec() method for test cleanup
- Use explicit StandardCharsets.UTF_8 in base64Decode
- Add test for null codec rejection
- Simplify test reset to use resetBase64Codec method
@typotter typotter marked this pull request as ready for review February 19, 2026 06:37
@typotter typotter requested a review from aarsilv February 19, 2026 06:37
@typotter typotter mentioned this pull request Feb 19, 2026
@aarsilv aarsilv requested a review from Copilot February 20, 2026 19:36
Copy link
Copy Markdown

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 pluggable Base64 codec interface to enable Android compatibility. The java.util.Base64 class is only available on Android API 26+, so this change allows platform-specific implementations (e.g., using android.util.Base64 for older Android versions) to be injected at runtime.

Changes:

  • Added Base64Codec public interface with encoding/decoding methods and a setter for custom implementations
  • Refactored existing Base64 logic into a DefaultBase64Codec implementation that maintains backward compatibility
  • Removed duplicate base64Decode implementation from FlagConfigResponseDeserializer to use centralized Utils method
  • Added comprehensive test coverage for custom codec injection, default behavior, and null handling

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/main/java/cloud/eppo/Utils.java Added Base64Codec interface, setBase64Codec() setter, resetBase64Codec() for testing, and DefaultBase64Codec implementation with improved charset handling
eppo-sdk-common/src/main/java/cloud/eppo/ufc/dto/adapters/FlagConfigResponseDeserializer.java Removed duplicate base64Decode implementation in favor of centralized Utils method
src/test/java/cloud/eppo/UtilsTest.java Added comprehensive tests for custom codec injection, default behavior, null handling, and test isolation

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

Copy link
Copy Markdown
Contributor

@aarsilv aarsilv left a comment

Choose a reason for hiding this comment

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

Nice! Approving with minor comments

Comment on lines +152 to +153
"zero byte output from Base64; if not running on Android hardware be sure to use"
+ " RobolectricTestRunner");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if we need something similar for base64Encode

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤔 Looking a little wider at it, we're only actually using the encode method in tests. I'd prefer to keep the Codec here just in case prod code needs to encode in the future we don't have to make a breaking change to add it.

public class UtilsTest {

@AfterEach
void resetCodec() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

any reason these are package level instead of class? (e.g., why no public keyword here?)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

package-private is enough for JUnit to find them (uses reflection/annotation to find the methods)

Comment on lines +182 to +186
assertEquals("encoded:test", Utils.base64Encode("test"));
assertTrue(encodeCalled.get());

assertEquals("decoded:test", Utils.base64Decode("test"));
assertTrue(decodeCalled.get());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nice

@aarsilv aarsilv assigned typotter and unassigned aarsilv Feb 21, 2026
Base automatically changed from typo/cleanup to feature/v4 February 23, 2026 21:07
@typotter typotter merged commit 1dd488f into feature/v4 Feb 23, 2026
4 checks passed
@typotter typotter deleted the typo/base64codec branch February 23, 2026 21:35
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.

3 participants