This is a collection of private or subscriber-first articles written by the Dove Letter, skydoves (Jaewoong). These articles can be released somewhere like Medium in the future, but always they will be revealed for Dove Letter members first.
This book is designed for Kotlin developers who want to deep dive into the Kotlin fundamentals, internal mechanisms, and leverage that knowledge in their daily work right away.
Google Maps popularized a bottom sheet pattern that most Android developers recognize immediately: a small panel peeking from the bottom of the screen, expandable to a mid height for quick details, and draggable to full screen for comprehensive information. The user interacts with the map behind the sheet at all times. This pattern looks simple on the surface, but implementing it correctly requires solving several problems: multi state anchoring, non modal interaction, dynamic content adaptation, and nested scroll coordination. Jetpack Compose's standard ModalBottomSheet only supports two states expanded and hidden and blocks background interaction with a scrim, making it unsuitable for this use case.
Jetpack Compose's stability system determines whether a composable function can be skipped during recomposition. When all parameters are stable, Compose can compare them and skip the function entirely if nothing changed.
Friday, February 13, 2026Modern Android applications commonly adopt multi layered architectures such as MVVM or MVI, where data flows through distinct layers: a data source, a repository, and a ViewModel or presentation layer. Each layer has a specific responsibility, and network responses must propagate through all of them before reaching the UI. While this separation produces clean, testable code, it introduces a real challenge: how do you handle API responses, including errors and exceptions, as they cross each layer boundary? Most developers solve this by wrapping API calls in try-catch blocks and returning fallback values. This works for small projects, but as the number of API calls grows, the approach creates ambiguous results, scattered boilerplate, and lost context that downstream layers need. You end up with ViewModels that cannot tell whether an empty list means "no data" or "network failure," repositories that swallow important error details, and data sources that repeat the same error handling pattern dozens of times. In this article, you'll explore the problems that emerge when handling Retrofit API calls across layered architectures, why conventional approaches break down at scale, and how Sandwichhttps://github.com/skydoves/sandwich provides a type safe, composable solution that simplifies response handling from the network layer all the way to the UI. You'll also walk through the full set of Sandwich APIs, from basic response handling to advanced patterns like sequential composition, response merging, global error mapping, and Flow integration, each with real world use cases that show when and why you would reach for them. Retrofit API calls with coroutines Most Android projects use Retrofithttps://github.com/square/retrofit with Kotlin coroutineshttps://github.com/Kotlin/kotlinx.coroutines for network communication. A typical service interface looks like this: kotlin interface PosterService {
Kotlin's internal visibility modifier provides a useful mechanism for hiding implementation details within a module while exposing a clean public API. But as codebases grow and libraries modularize, a tension emerges: the logical boundaries of your API don't always align with the compilation boundaries of your modules. Test modules need access to production internals. Library families like kotlinx.coroutines want to share implementation details across artifacts without exposing them to consumers. The current workaround, "friend modules," is an undocumented compiler feature that lacks language-level design.
Jetpack Compose's Modifier system has been the primary way to apply visual properties to composables. You chain modifiers like background, padding, and border to build up the appearance and behavior of UI elements. While powerful, this approach has limitations when dealing with interactive states. When you want a button to change color when pressed, you need to manually track state, create animated values, and conditionally apply different modifiers. The new experimental Styles APIhttps://android-review.googlesource.com/c/platform/frameworks/support/+/3756487 aims to solve this by providing a declarative way to define state-dependent styling with automatic animations.
Making REST API calls has been a fundamental requirement in Android development, yet the complexity of managing HTTP requests, serialization, error handling, and thread management has long been a persistent challenge. Retrofit emerged as Square's solution to this problem, transforming a verbose, error-prone process into an elegant, annotation-driven API. But the real power of Retrofit isn't just its simplified interface, it's the sophisticated machinery working behind the scenes to turn interface methods into HTTP calls. In this article, you'll dive deep into the internal mechanisms of Retrofit, exploring how Java's dynamic proxies create implementation classes at runtime, how annotations are parsed and cached using sophisticated locking strategies, how the framework transforms method calls into OkHttp requests through a layered architecture, and the subtle optimizations that make it production-ready. This isn't a beginner's guide to using Retrofit, it's a deep dive into how Retrofit actually works under the hood. Understanding the core abstraction: What makes Retrofit special At its heart, Retrofit is a type-safe HTTP client that uses dynamic proxies and annotation processing to convert interface method declarations into HTTP requests. What distinguishes Retrofit from manual HTTP clients is its adherence to two fundamental principles: declarative API definition and pluggable architecture. The declarative API definition means you don't manually construct HTTP requests for every endpoint. Instead, Retrofit provides annotations that describe the request: kotlin interface GitHubApi { @GET"users/{user}/repos" fun listRepos@Path"user" user: String: Call<List<Repo } // Implementation generated automatically: val api = retrofit.create<GitHubApi val call = api.listRepos"octocat" The pluggable architecture means Retrofit separates concerns through factory patterns. Every aspect of request/response handling is customizable: - CallAdapter: Transforms Call<T into other types RxJava Observable, Kotlin suspend fun, Java 8 CompletableFuture - Converter: Serializes/deserializes request/response bodies Gson, Jackson, Moshi, Protobuf - Call.Factory: Creates HTTP calls typically OkHttp, but swappable
Like what you see?
Subscribe to Dove Letter to get weekly insights about Android and Kotlin development, plus access to exclusive content and discussions.