Skip to content

Redesign data-load state model and normalize the Apollo cache#1

Open
solcott wants to merge 3 commits into
mainfrom
feature/updates
Open

Redesign data-load state model and normalize the Apollo cache#1
solcott wants to merge 3 commits into
mainfrom
feature/updates

Conversation

@solcott

@solcott solcott commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

Reworks how the app models the result of a data load and configures Apollo's
normalized cache so domain entities are shared across queries. Also bumps AGP.

The state redesign makes Data/Error total and non-nullable, replaces a
boolean-soup view state that allowed illegal combinations with a single generic
holder, and introduces a transport-agnostic error vocabulary so the model isn't
coupled to Apollo. The cache change is the plumbing that will let a future write
to one entity propagate to every screen that displays it.

Changes

model — transport-agnostic result type

  • Replace Response<T> (Loading/Data/Error) with Outcome<T>: a
    Data(data, origin) / Error(cause, origin) result. Loading is no longer a
    result case — it's an in-flight concern the consumer tracks.
  • Add DataError, a client-neutral failure vocabulary (Network, Http,
    Api, Serialization, Unknown) so any source (Apollo, Ktor, Store) maps its
    own errors into a shared domain type.
  • Add Origin (Cache/Network), recorded on every Outcome.

repository — Apollo → Outcome adapter

  • mapToOutcome tags each emission's Origin from isFromCache, categorizes
    ApolloExceptions into DataError, and drops cache-miss emissions instead of
    surfacing them as errors.
  • Depend on apollo-normalized-cache to read per-response cache metadata.
  • Remove the dead ContinentRepositoryImpl.kt.

presenter — one generic view-state

  • Replace LoadState/CountriesState/ContinentsState with a single generic
    ContentState<T> plus a LoadStatus (Idle/Loading/Failed) sum
    type, eliminating illegal states.
  • Derive Loading from the flow lifecycle (onEach/onCompletion) with no
    fetch-policy assumption
    ; re-assert Loading on refilter via collectLatest
    while keeping the current list visible (stale-while-revalidate).
  • Move CountryDetailScreen.State onto ContentState<CountryDetail?> with a
    derived isNotFound.

ui

  • Resolve DataError to localized strings; show a refresh indicator while
    reloading over already-visible data.

network — normalized cache configuration

  • Add @typePolicy(keyFields: "code") for Country, Continent, and Language
    so every query shares one record per entity instead of embedded copies.
  • Select continent { code } in the Countries/CountryDetail queries, which
    normalization by key requires.

Build

  • Update Android Gradle Plugin to 9.3.1.

Notes

  • Generated Apollo code is build output and is not committed.
  • The cache normalization has no visible effect yet — the flows still use
    one-shot .toFlow(), not .watch(). This is the groundwork for cache-driven
    reactivity (a follow-up would add .watch() + local cache writes).

Testing

  • ./gradlew assembleDebug test ktfmtCheck — all green.

solcott added 3 commits July 23, 2026 23:08
Rework the data-load state model to be more robust and reusable across
data sources, and to carry richer error and origin information.

model:
- Replace Response<T> (Loading/Data/Error<String>) with Outcome<T>, a
  Data/Error result that drops Loading — an in-flight concern the
  consumer tracks, not a settled result.
- Add DataError, a transport-neutral failure vocabulary (Network, Http,
  Api, Serialization, Unknown) so any client (Apollo, Ktor, Store) maps
  its own errors into a shared domain type.
- Add Origin (Cache/Network), recorded on every Outcome.

repository:
- Map Apollo responses to Outcome via mapToOutcome, tagging Origin from
  isFromCache, categorizing ApolloExceptions into DataError, and dropping
  cache-miss emissions instead of surfacing them as errors.
- Depend on apollo normalized-cache to read per-response cache metadata.
- Remove the dead ContinentRepositoryImpl.kt.

presenter:
- Replace LoadState/CountriesState/ContinentsState with a single generic
  ContentState<T> plus a LoadStatus (Idle/Loading/Failed) sum type,
  eliminating the boolean-soup that allowed illegal states.
- Derive Loading from the flow lifecycle (onEach/onCompletion), making no
  assumption about fetch policy; re-assert Loading on refilter via
  collectLatest while keeping the current list visible.
- Move CountryDetailScreen.State onto ContentState<CountryDetail?> with a
  derived isNotFound.

ui:
- Resolve DataError to localized strings; show a RefreshingIndicator while
  reloading over already-visible data.
This change introduces client-side cache normalization policies to ensure that entity data is shared and kept consistent across different GraphQL queries.

*   **Cache Configuration**: Added `extra.graphqls` to define `@typePolicy` for `Country`, `Continent`, and `Language` types, using the `code` field as the unique cache key.
*   **GraphQL Queries**: Updated `Countries.graphql` and `CountryDetail.graphql` to explicitly select the `code` field for nested continent objects, satisfying the normalization requirement that key fields must be present in the selection set.
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