Building a Kotlin Multiplatform Data Pipeline for Tilt
How a custom data framework replaced complex coroutine plumbing with a unified source-based architecture.
1The Challenge of Shared Logic
Tilt uses a single Kotlin Multiplatform module to manage networking, caching, and business logic for Android and iOS. Previously, this layer relied on hand-rolled coroutine plumbing. As the team grew in late 2025, the complexity of managing Flows and error handling led to inconsistent code patterns and skipped error checks.
2Limitations of Manual Flow Handling
The original codebase suffered from repetitive ladder-like structures using isSuccess and getOrThrow. Developers frequently forgot branches or incorrectly handled exceptions. Additionally, the existing combineTiltResult helper required new overloads for every input type combination, making the system difficult to scale and maintain as the application's data requirements expanded over time.
3Strategic Decision to Build
The team chose to build a custom framework rather than adopting an external SDK. This decision ensured full control over the underlying architecture and allowed for rapid iteration. By keeping the framework small and focused on wrapping inputs, combining them, and standardizing error handling, the team avoided unnecessary learning curves and dependencies.
4The Failed First Attempt
In November 2025, the team introduced a builder DSL to simplify data pipelines. While it looked clean on slides, it proved difficult to use in practice. IDE auto-completion was ineffective, and the runtime checks for source registration were brittle. The split between fetch and observe calls further confused the developers using the tool.
5Everything Is a Source
The January 2026 rewrite introduced the PipelineSource concept, where every data input is treated as a source. Under the hood, this is a Flow of TiltResult, ensuring that failures propagate as values rather than escaping as exceptions. This shift allowed developers to build, connect, and terminate pipelines without touching raw Flows.
6Standardizing Data Operators
The framework provides factory methods for Apollo calls, Flows, or plain values. Operators are named by intent, such as transform or validate, and consistently catch exceptions to turn them into typed failures. This design ensures that a checkNotNull call results in a manageable parsing error rather than a system crash.
7Type-Safe Source Combination
The new system replaced 11 manual overloads with fixed-arity functions like sourceCombine. These functions short-circuit on the first failure, ensuring the lambda only executes when all inputs succeed. For dynamic inputs, the team implemented combineSources, removing the need for manual result wrapping and significantly cleaning up the repository layer code.
8Default Propagation Rules
A core rule of the framework is that failures propagate by default to the consumer. Developers must explicitly decide to degrade functionality using fallback operators. This prevents silent outages where errors are swallowed, ensuring that developers remain aware of data staleness or service failures within the application's complex data streams.
9Explicit Degradation Patterns
In the home feed, the team implemented explicit degradation for non-critical data. By using timeout and withFallbackValue, the settings call can fail without blocking the feed. This approach makes the decision-making process visible in the code, distinguishing between a successful absent value and a genuine failure that requires logging.
10Refining the Cache Strategy
A migration of the live auction stream initially attempted to treat the stream as the cache. However, this caused performance issues with distinctUntilChangedBy. The team reverted to a boring cache that returns sources, which improved logging and error visibility compared to the previous imperative code that silently discarded failed fetches.
11Unexpected AI Agent Benefits
The framework was originally designed as guardrails for human developers unfamiliar with Flow nuances. Surprisingly, it proved equally effective for AI coding agents. Because the framework enforces a strict, readable structure, AI agents are less likely to fall back on unreliable training patterns, resulting in more robust and maintainable generated code.
12Impact of the Architecture
The transition to a source-based pipeline reduced 1,600 lines of complex plumbing into expressive, readable expressions. By standardizing error propagation and removing manual boilerplate, the team created a resilient foundation. This architecture now supports advanced features like OpenTelemetry tracing, proving that simple, consistent abstractions outperform complex, hand-rolled solutions in production environments.
Original article: ProAndroidDev - Medium — Building a Kotlin Multiplatform Data Pipeline for Tilt