feat(implementations): migrate Android reference implementation to contentful.java CDA SDK (NT-3947) - #439
Merged
Felipe Mamud (fmamud) merged 3 commits intoAug 19, 2026
Conversation
Felipe Mamud (fmamud)
force-pushed
the
fix/nt-3947-android-reference-implementation
branch
from
August 19, 2026 10:29
d863c39 to
c59f0ea
Compare
…ntentful.java CDA SDK (NT-3947)
Replace hand-rolled HTTP/JSON Contentful fetching in the Android reference
implementation with Contentful's official com.contentful.java:java-sdk
CDAClient, mirroring the iOS (NT-3946) migration.
- MockContentfulClient builds the shared CDAClient against the mock server
and synthesizes the /locales and /content_types responses CDAClient
requires (mock server only serves /entries), bundling real content-type
definitions so Rich Text field resolution can find them.
- ContentfulFetcher now fetches by-ID entries through CDAClient (single
locale, include=10) and decodes them with the SDK's typed entry APIs
instead of hand-rolled JSON parsing/link resolution.
- MockPreviewContentfulClient wraps the same CDAClient for the preview
panel's audience/experience fetch, fixing two regressions found while
validating against the full Maestro suite:
- Concurrent nt_audience/nt_experience fetches against the same shared
CDAClient could interleave and drop entries (CDAClient mutates shared
instance state while a call is in flight); calls are now serialized
with a Mutex.
- The mock's content-type-filtered entries endpoint never returns an
includes section, so contentful.java silently drops any Link field it
can't resolve rather than keeping it as a stub, breaking the preview
panel's audience/experience grouping; raw link stubs are now restored
from CDAEntry.rawFields() when contentful.java drops them.
Verified with the full Compose and Views Maestro suites (35/35 passing on
both), including all preview-panel-overrides scenarios.
…ference implementation Rework the app-owned entry-rendering components (ContentEntryView, NestedContentEntryView, ContentEntryViewBinder, NestedContentEntryViewBinder, MainScreen, MainActivity) to accept and pass CTEntry instead of raw Map<String, Any>, mirroring the SDK's own typed OptimizedEntry(entry: CDAEntry) entry point. Also drops redundant nullable type arguments on getField calls. Key CTEntry, having no structural equals/hashCode, are compared by reference: remember/LaunchedEffect keys in ContentEntryView and NestedContentEntryView now key on entry.toMap() (structurally comparable) instead of the entry instance or its id, restoring correct re-resolution of merge-tag text after identify() calls.
…roid's in-app synthesis The mock CDA gained a real `/locales` route in #450 for iOS's contentful.swift migration. Android's `LocalesInterceptor` still synthesized both `/locales` and `/content_types` in-app because neither existed on the mock when it was written. `/locales` is now redundant and removed outright: a real request succeeds. `/content_types` is NOT redundant on its own — contentful.java's `ResourceFactory.array()` unconditionally calls `RichTextFactory.resolveRichTextField` on every array response, which unconditionally calls `ResourceUtils.ensureContentType` for every entry, regardless of whether that entry has a Rich Text field, and throws `CDAContentTypeNotFoundException` on a 404. The reference app's fixtures do have Rich Text fields (RichText.kt is exercised by ContentEntryView/NestedContentEntryView), so this path is genuinely load-bearing. Rather than keep the in-app fixture shim, add a `/content_types` route to the mock server that serves `ctfl-space-data.json`'s `contentTypes` array, mirroring the `/locales` route's shape and #450's precedent of fixing the mock rather than shimming around it in a file customers copy from. This lets Android's bundled `content_types.json`/`ContentTypesFixture` be deleted entirely. With both interceptor-synthesized routes gone, MockContentfulClient.kt no longer earns its own file: consolidate the shared CDAClient and the fetch-by-ids loop into ContentfulFetcher.kt, mirroring iOS's single ContentfulClient.swift shape. Validated: `pnpm --filter mocks typecheck`, `pnpm lint`, `pnpm format:check` clean; both APKs assemble; full Maestro suite passes 70/70 flows across Compose and Views.
Felipe Mamud (fmamud)
force-pushed
the
fix/nt-3947-android-reference-implementation
branch
from
August 19, 2026 11:30
c59f0ea to
530e24b
Compare
Felipe Mamud (fmamud)
marked this pull request as ready for review
August 19, 2026 11:44
Charles Hudson (phobetron)
approved these changes
Aug 19, 2026
Felipe Mamud (fmamud)
deleted the
fix/nt-3947-android-reference-implementation
branch
August 19, 2026 12:25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
com.contentful.java:java-sdk), fixing aCDAClientconcurrency hazard (Mutex) and a link-field-dropping bug inMockPreviewContentfulClient.CTEntryover rawMap<String, Any>across the app-owned entry-rendering components (ContentEntryView,NestedContentEntryView,ContentEntryViewBinder,NestedContentEntryViewBinder,MainScreen,MainActivity), mirroring the SDK's own typedOptimizedEntry(entry: CDAEntry)entry point, and drops redundant nullable generic type arguments.remember/LaunchedEffectkeys in the Compose entry views were keyed onentryId(aString), which no longer changed acrossidentify()calls and stopped merge-tag text from re-resolving.CTEntryhas no structuralequals/hashCode, so the keys now useentry.toMap()instead, restoring the previous (Map-based) re-resolution behavior.Test plan
./gradlew :compose:compileDebugKotlin :views:compileDebugKotlin :compose:lintDebug :views:lintDebug— passes🤖 Generated with Claude Code