Sandwich: the multiplatform Gradle snippet is missing a closing brace
Project brief: Sandwich is an adaptable and lightweight sealed API library designed for handling API responses and exceptions in Kotlin for Retrofit, Ktor, and Kotlin Multiplatform.
At a glance
- What is it?
- A Kotlin library that replaces Resource and Result wrappers with one sealed response type, published as nine modules behind a bill of materials. Its own install snippet does not compile, and two of the nine modules never appear in it.
- Who is it for?
- Sandwich is a reasonable pick for an Android or multiplatform team that is tired of maintaining its own Resource wrapper, because one sealed type with a data field, a payload field and a throwable field does collapse a lot of boilerplate, and the Retrofit and Ktor modules are split so you only take what you use. Two practical notes.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- skydoves/sandwich1,779 ★ · +13
| Repository | 2026-08-04 | 2026-11-14 |
|---|---|---|
| skydoves/sandwich | 1.8k | 1.8k |
13 Stars in the last 102 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The multiplatform build file is missing its last brace
Two install paths are given. The Android one is a normal Gradle block in a module build file, and it is correct.
dependencies {
implementation(platform("com.github.skydoves:sandwich-bom:2.4.0"))
implementation("com.github.skydoves:sandwich")
implementation("com.github.skydoves:sandwich-retrofit") // For Retrofit (Android)
testImplementation("com.github.skydoves:sandwich-test") // For Testing
}The multiplatform one, for a build file with the Kotlin extension, opens a source sets block, a common main set, a dependencies block, closes them, then opens a common test set and a dependencies block, and closes those. The source sets block is never closed before the fence. The two paths also use different idioms for the same thing: the Android block calls the platform function directly with a hardcoded version, while the multiplatform block reaches through the project dependencies object and passes an undefined version variable. Both details have to be repaired by hand before the snippet will build.
Nine published modules, and the install block names four
The repository root is where the real shape of the library shows, and it does not match the install section. There are nine module directories: the core library, a bill of materials, three Ktor side modules covering the client itself, serialization and the Ktorfit compatibility layer, three Retrofit side modules covering the client, a datasource helper and a serialization helper, and a test module. Neither install snippet mentions two of those, the Retrofit datasource and the Retrofit serialization module, and the Retrofit serialization counterpart is easy to assume exists because its Ktor equivalent is listed. The version number lives in exactly one place, the bill of materials, which is why the Android snippet pins 2.4.0 there and lets the three other coordinates stay versionless. Nothing on the page explains what the datasource module does or when you would want it.
The custom error example ends mid-expression
The response type is a sealed hierarchy, and the last code block on the page shows how to extend it with your own cases. Two error objects are declared as data objects extending the error type, each with a string payload, and a third is declared extending the exception type. That third declaration is where the page stops: the throwable argument is assigned a bare RuntimeException class reference, and then there is no closing parenthesis, no closing brace and no fence content after it. So the one example that shows the exception case, which is the case most people need when they want a typed network error, is the one you cannot copy. The earlier blocks are intact and short, covering how to construct a success with data, how to attach a tag, and how to read a throwable and a message off an exception.
Each of the three cases carries a different field
The design goal is stated plainly: to remove the need for wrapper classes of your own, the Resource and Result pattern, and put business logic back in your code. What replaces them is a sealed type with three cases, and the interesting part is that each case carries a different secondary field. A success holds the payload and an optional tag for recording where the data came from or for post-processing. An error holds a payload, which is where a server's error body goes. An exception holds a throwable and a message, for the client-side failures such as a connection timeout. That asymmetry is the cost of the sealed approach: code that handles all three cases has to know that a message exists only on the exception branch and a payload only on the error branch, so a shared logger across cases has to branch before it can log.
The minimum Android API level is stated only inside a badge
One of the badges near the top of the page is a link to an API level query with the value 21 in its parameters. That is the only place the minimum supported Android level appears; no sentence in the readme states it, and the Gradle snippet does not show a minSdk value either. A reader who wants to know whether the library can be used on an older device has to decode a badge URL. Shrinking is handled better, at least. The page states that the ProGuard and R8 rules are already bundled into the jar through a consumer rules file in the core module and are read by R8 automatically, so there is nothing to copy into an app's shrinker configuration. That convenience is Android specific, and the multiplatform and Ktor consumers get no equivalent guarantee from a line on the page.
Two sections say the same sentence and point at the same page
The readme has a documentation section and a usage section, and both contain the identical sentence asking readers to consult the complete documentation, with the same link, followed in one case by four links to specific integration guides for Retrofit, Ktor, Ktorfit and testing. So a third of the page is a duplicate of another third. The real documentation lives on GitHub Pages with a documentation generator configured in the repository, and the badge row links to a different location entirely, an API reference hosted under a libraries path. Two hosts, two path shapes, and the readme points at one of them twice while the badge points at the other. Anyone writing a link to this library from their own project has three candidate URLs to choose from and no guidance on which one is canonical.
Self-reported numbers and a phrase that appears twice
Two things in the readme are worth reading with care rather than quoting. The download section states that the library has been downloaded more than 1.2 million times across Android and backend projects, with no date, no source and no method, and a self-reported download total is not evidence of anything beyond reach. Next to it is a phrase that appears twice in the file, once in the argument for the library and once in the description of the response type, and in both places the list of things the library handles ends with the words and whatever. It reads as a joke about how varied clients are, and it is the only informal line in an otherwise plain page. The claim that it supports Kotlin Multiplatform is the one that matters for adoption, and that is backed by the module layout and the multiplatform build snippet rather than by a sentence.
Spotless, Renovate, a baseline profile and a JavaScript store
The root entries describe the build and release machinery in more detail than the readme does. There is a wrapper for both platforms, a buildSrc directory, a settings file, a properties file, a Renovate configuration for automated dependency updates, a Spotless directory and a separate license header template file for it, a documentation folder with its generator configuration, a scripts directory, an app directory holding the sample application, and a baseline profile directory for Android. There is also a Kotlin JavaScript dependency store at the root, which is a Yarn lock file store and implies the build touches a JavaScript target, although no install snippet mentions one. The release trail is short and regular: 2.2.2 in April 2026, 2.3.0 at the end of June, and 2.4.0 at the end of July, and the last push to the main branch was on 2026-09-25.
Editorial conclusion
Sandwich is a reasonable pick for an Android or multiplatform team that is tired of maintaining its own Resource wrapper, because one sealed type with a data field, a payload field and a throwable field does collapse a lot of boilerplate, and the Retrofit and Ktor modules are split so you only take what you use. Two practical notes. The multiplatform install snippet on the page is missing a closing brace and will not compile as pasted, so fix it before you copy it. And the page never shows how to depend on the two serialization or datasource modules, so read the module list in the repository rather than the install section when you need those.
Frequently asked questions
What is skydoves/sandwich used for?
It is a sealed API library for Kotlin that models responses from network, database and other I/O calls in one type, so you no longer maintain wrapper classes of your own like Resource or Result. It supports Retrofit, Ktor and Kotlin Multiplatform.
What are the three cases of the Sandwich ApiResponse type?
A success carrying the data and an optional tag, a failure error carrying a payload such as a server error body, and a failure exception carrying a throwable and a message for client side problems like a connection timeout.
How do I add Sandwich to an Android Gradle project?
In your module build file, add the bill of materials as a platform with version 2.4.0, then the sandwich and sandwich-retrofit modules, plus sandwich-test as a test dependency. The readme also states that R8 and ProGuard rules are already bundled in the jar.
Which Sandwich modules exist for Kotlin Multiplatform?
The install snippet lists sandwich, sandwich-ktor, sandwich-ktor-serialization and sandwich-ktorfit, with sandwich-test for the common test set. The repository root also contains a Retrofit datasource module and a Retrofit serialization module that no snippet mentions.
How many downloads does the Sandwich page report?
The page states the library has been downloaded more than 1.2 million times across Android and backend projects, without giving a date, a source or a counting method for that figure.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/skydoves-sandwich)Star history chart for your README
[](https://hysenlabs.com/projects/skydoves-sandwich)Was this useful?