Shuttle

io.github.grarcht由 GitHub 账号 grarcht 发布v4.0.0更新于 Oct 24, 2026

Android framework and solution that prevents TransactionTooLargeException crashes by design.

已验证STDIO仅桌面知识与记忆开发者工具

概览

AI 生成的概览

本地 MCP 服务器,为助手提供 Shuttle Android 框架知识:生成集成样板代码、识别 TransactionTooLargeException 风险并检索权威规范。

可以做什么
根据已发布的 Shuttle 规范与 README 内容,为 Hilt、Koin 或手动装配生成可直接粘贴的 Kotlin 集成样板代码。
适用场景
适用于采用 Shuttle 框架的 Android 项目:希望助手生成集成代码,或检查粘贴的 Kotlin/Java 代码片段是否存在 TransactionTooLargeException 风险,而无需克隆仓库。
运行要求
以本地进程方式通过 stdio 运行 OCI 镜像(ghcr.io/grarcht/shuttle-mcp:4.0.0),需要容器运行时。未声明认证、环境变量或请求头。仅限桌面端。
安装前请注意
清单未列出任何工具,除 README 声明外实际能力未经验证。粘贴的代码片段会发送给助手所用模型,请勿包含密钥或不愿与该提供商共享的专有代码。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Shuttle,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

[Shuttle]

Prevent TransactionTooLargeException crashes. For good.

A modern Android framework for safely transporting large Serializable objects between components, without the crashes.


[License: MIT] [Maven Central] [API Docs] [Min SDK] [Kotlin]


[Android Weekly #594] [Android Weekly #455]


Get To It Quick


🚨 Why Shuttle?

🗞️ Featured in Android Weekly #594 and Android Weekly #455, validated by the Android community twice.

You've seen this before. Maybe last week:

android.os.TransactionTooLargeException: data parcel size X bytes

It didn't show up in dev. It didn't show up in QA. It showed up at 2am, in production, for real users. Your Play Store rating took the hit before anyone on the team even knew.

So you triaged it. Filed the ticket. Wrote the fix. Reviewed the PR. Ran QA again. Cut the hotfix. And then you added it to the code review checklist, hoping the next engineer would catch it before it happened again.

They won't. Not reliably. You can't review your way out of a structural problem.

Shuttle provides a modern, guarded way to pass large Serializable objects with Intent objects or save them in Bundle objects to avoid app crashes. The crash class is structurally prevented, not governed against.

Why keep spending more time and money on governance through code reviews? Why not embrace the problem by providing a solution for it?

Shuttle reduces the high level of governance needed to catch TransactionTooLargeException inducing code by:

  1. storing the Serializable and passing an identifier for the Serializable
  2. using a small-sized Bundle for binder transactions
  3. avoiding app crashes from TransactionTooLargeExceptions
  4. enabling retrieval of the stored Serializable at the destination

Shuttle also excels by:

  1. providing a solution with maven artifacts
  2. providing Solution Building Blocks (SBBs) for building on
  3. saving time by avoiding DB and table setup, especially when creating many tables for the content of different types of objects

When envisioning, designing, and creating the architecture, quality attributes and best practices were kept in mind. These attributes include usability, readability, recognizability, reusability, maintainability, and more.

Without ShuttleWith Shuttle
Large Serializable passed in Intent/BundleObject stored in a warehouse; only a small identifier is passed
Silent in dev, catastrophic in productionBinder transaction stays within safe size limits, everywhere
Time and money spent on crash investigation, fixes, QA, and hotfixesCrash class is structurally impossible
Requires constant code review governanceShip with confidence
Engineers manually manage object lifecyclesAutomatic or on-demand cargo cleanup built in
[图片][图片]

⚙️ How It Works

[Saving Android Apps From Crashes | The Shuttle Framework and Solution]

The Shuttle framework takes its name from cargo transportation in the freight industry. Moving and storage companies experience scenarios where large moving trucks cannot transport cargo the entire way to the destination (warehouses, houses, et cetera). These scenarios might occur from road restrictions, trucks being overweight from large cargo, and more. As a result, companies use small Shuttle vans to transport smaller cargo groups on multiple trips to deliver the entire shipment.

After the delivery is complete, employees remove the cargo remnants from the shuttle vans and trucks. This clean-up task is one of the last steps for the job.

The Shuttle framework takes its roots in these scenarios:

  • creating a smaller cargo bundle object to use in successfully delivering the data to the destination
  • shuttling the corresponding large cargo to a warehouse and storing it for pickup
  • linking the smaller cargo with the larger cargo by an identifier
  • providing a single source of truth (Shuttle interface) to use for transporting cargo
  • providing convenience functions to remove cargo (automatically or on-demand)

Shuttle applies this same logic to Android's binder transaction limit:

┌─────────────────────────────────────────────────────────────┐│  Source Component                                           ││  1. Large Serializable -> stored in Warehouse (Room/DB)     ││  2. Small cargo ID     -> passed in Intent/Bundle           │└──────────────────────────────┬──────────────────────────────┘                               │ (tiny binder transaction)┌──────────────────────────────▼──────────────────────────────┐│  Destination Component                                      ││  3. Cargo ID received -> retrieved from Warehouse           ││  4. Large Serializable -> delivered via Kotlin Channel      ││  5. Cleanup -> cargo removed from Warehouse automatically   │└─────────────────────────────────────────────────────────────┘

Requirements

RequirementMinimum
Android min SDK26
Kotlin2.2.10
AGP (Android Gradle Plugin)8.0+
KSPRequired only when using @ShuttleCargo
Java21

🚀 Quick Start

1. Add Dependencies

settings.gradle.kts — apply the Shuttle Gradle plugin so the @ShuttleCargo annotation processor is wired into your build:

@ShuttleCargo annotates the cargo class you want to transport between screens. At build time, the annotation processor generates the serialization code Shuttle needs to store and retrieve cargo, without any manual Serializable boilerplate.

kotlin
pluginManagement {    plugins {        id("com.grarcht.shuttle.cargo") version "4.0.0"    }}

The Shuttle Cargo Gradle plugin configures KSP and registers the Shuttle compiler plugin automatically. Without it, @ShuttleCargo-annotated classes will not generate the required serialization glue code.

build.gradle.kts:

kotlin
plugins {    id("com.grarcht.shuttle.cargo")}
dependencies {    implementation(platform("com.grarcht.shuttle:shuttle-bom:4.0.0"))    implementation("com.grarcht.shuttle:framework")    implementation("com.grarcht.shuttle:framework-integrations-persistence")    implementation("com.grarcht.shuttle:framework-integrations-extensions-room")    implementation("com.grarcht.shuttle:framework-addons-navigation-component") // Optional
    // Annotation-based API — use @ShuttleCargo to own the serialization contract    implementation("com.grarcht.shuttle:framework-annotations")    ksp("com.grarcht.shuttle:framework-annotations-processor")}

Version Catalog (libs.versions.toml):

toml
[versions]shuttle = "4.0.0"
[plugins]shuttle-cargo = { id = "com.grarcht.shuttle.cargo", version.ref = "shuttle" }
[libraries]shuttle-bom = { group = "com.grarcht.shuttle", name = "shuttle-bom", version.ref = "shuttle" }shuttle-framework = { group = "com.grarcht.shuttle", name = "framework" }shuttle-persistence = { group = "com.grarcht.shuttle", name = "framework-integrations-persistence" }shuttle-room = { group = "com.grarcht.shuttle", name = "framework-integrations-extensions-room" }shuttle-navigation = { group = "com.grarcht.shuttle", name = "framework-addons-navigation-component" }shuttle-annotations = { group = "com.grarcht.shuttle", name = "framework-annotations" }shuttle-annotations-processor = { group = "com.grarcht.shuttle", name = "framework-annotations-processor" }

2. Initialize Shuttle

Wire CargoShuttle once as a singleton. With Hilt:

kotlin
@Provides @Singletonfun provideShuttle(facade: ShuttleFacade, warehouse: ShuttleWarehouse): Shuttle =    CargoShuttle(facade, warehouse)
@Provides @Singletonfun provideShuttleFacade(    @ApplicationContext context: Context,    warehouse: ShuttleWarehouse): ShuttleFacade = ShuttleCargoFacade(context as Application, warehouse)
@Provides @Singletonfun provideShuttleWarehouse(    dao: ShuttleDataAccessObject,    factory: ShuttleDataModelFactory,    @ApplicationContext context: Context,    gateway: ShuttleFileSystemGateway): ShuttleWarehouse = ShuttleRepository(dao, factory, context.filesDir.absolutePath, gateway)

The ShuttleDataAccessObject comes from ShuttleRoomDataDb.getInstance(ShuttleRoomDbConfig(context)).shuttleDataAccessObject. Inject Shuttle wherever you need to transport data.

3. Ship Your First Cargo

kotlin
// Source: transport a large Serializable via Intentshuttle.intentCargoWith(context, DestinationActivity::class.java)    .transport(cargoId, myLargeSerializable)    .cleanShuttleOnReturnTo(SourceFragment::class.java, DestinationActivity::class.java, cargoId)    .deliver(context)
kotlin
// Destination: pick up the cargolifecycleScope.launch {    getShuttleChannel()        .consumeAsFlow()        .collectLatest { result ->            when (result) {                is ShuttlePickupCargoResult.Success<*> -> render(result.data as MyModel)                is ShuttlePickupCargoResult.Error<*>   -> showError()                ShuttlePickupCargoResult.Loading        -> showLoading()            }        }}

That's it. No custom DB setup. No table management. No crash.


📦 Usage

The recommended entry point is the Shuttle interface with CargoShuttle as the implementation. It's a single source of truth for all cargo transport operations.

Transport with Intents

Source component:

kotlin
val cargoId = ImageMessageType.ImageData.value
shuttle.intentCargoWith(context, MVCSecondControllerActivity::class.java)    .transport(cargoId, imageModel)    .cleanShuttleOnReturnTo(        MVCFirstControllerFragment::class.java,        MVCSecondControllerActivity::class.java,        cargoId    )    .deliver(context)

ℹ️ cleanShuttleOnReturnTo is important. It ensures cargo is purged from the Warehouse when it's no longer needed.

Transport with the Navigation Component

Source fragment:

kotlin
val cargoId = ImageMessageType.ImageData.value
navController.navigateWithShuttle(shuttle, R.id.MVVMNavSecondViewActivity)    ?.logTag(LOG_TAG)    ?.transport(cargoId, imageModel as Serializable)    ?.cleanShuttleOnReturnTo(        MVVMNavFirstViewFragment::class.java,        MVVMNavSecondViewActivity::class.java,        cargoId    )    ?.deliver()

Pick Up Cargo at the Destination

In a Fragment/Activity:

kotlin
lifecycleScope.launch {    getShuttleChannel()        .consumeAsFlow()        .collectLatest { result ->            when (result) {                ShuttlePickupCargoResult.Loading        -> initLoadingView(view)                is ShuttlePickupCargoResult.Success<*>  -> { showSuccessView(view, result.data as ImageModel); cancel() }                is ShuttlePickupCargoResult.Error<*>    -> { showErrorView(view); cancel() }            }        }}

In a ViewModel:

kotlin
viewModelScope.launch {    shuttle.pickupCargo<Serializable>(cargoId = cargoId)        .consumeAsFlow()        .collectLatest { result ->            pickupCargoMutableStateFlow.value = result            when (result) {                is ShuttlePickupCargoResult.Success<*>,                is ShuttlePickupCargoResult.Error<*> -> cancel()                else -> { /* await */ }            }        }}

Cargo States (LCE Pattern)

Shuttle returns sealed class results that promote the Loading-Content-Error (LCE) pattern, giving consumers full control over UI state, analytics, and error handling.

OperationReturn TypeStates
Store cargoChannel<ShuttleStoreCargoResult>Storing, Success, Error
Pick up cargoChannel<ShuttlePickupCargoResult>Loading, Success, Error
Remove cargoChannel<ShuttleRemoveCargoResult>Removing, Success, Error

Using @ShuttleCargo

Annotate any data class you want to transport through Shuttle. The annotation processor generates the serialization code at build time so you never write it by hand:

kotlin
@ShuttleCargodata class ImageModel(    val id: String,    val title: String,    val byteArray: ByteArray)

That's the entire declaration. No Serializable implementation, no custom read/write methods. Then transport it the same way as any other object:

kotlin
shuttle.intentCargoWith(context, DestinationActivity::class.java)    .transport(cargoId, imageModel)    .deliver(context)

Cleaning Up

Cargo is automatically removed when using cleanShuttleOnReturnTo. For manual control:

kotlin
// Remove a specific cargo itemshuttle.removeCargoBy(cargoId)
// Remove all cargoshuttle.removeAllCargo()

🏗️ Architecture

Shuttle is a layered Solution Building Block (SBB) framework. Each layer has a well-defined responsibility and no layer forces technology choices on consumers.

Module Overview

ModuleRoleRequired?
frameworkCore interfaces, transport logic, sealed result types✅ Yes
framework-integrations-persistencePersistence abstraction/interfaces✅ Yes
framework-integrations-extensions-roomRoom implementation of persistence interfaces⚡ Default (swappable)
framework-addons-navigation-componentNavigation Component integration➕ Optional
framework-annotations@ShuttleCargo annotation for marking data classes as transportable➕ Optional
framework-annotations-processorKSP processor that generates serialization code at build time➕ Optional
framework-annotations-gradle-pluginGradle plugin that wires KSP and the compiler plugin automatically➕ Optional

Context Diagram

[Shuttle Context Diagram]

Module Dependency Diagram

mermaid
graph TD    A[Your Application]    A --> B[framework / core]    A --> C[framework-addons-navigation-component]    A --> D[your custom integration]    B --> E[framework-integrations-persistence / abstraction layer]    E --> F[framework-integrations-extensions-room / default implementation]

Why this layering matters: The persistence abstraction means you can swap Room for any other storage implementation without touching the framework or your application code. Bring your own persistence layer by implementing the integration interfaces.

Shuttle avoids bundling large reactive libraries. Asynchronous communication runs on Kotlin Coroutines and Channels only, which keeps the transitive dependency footprint lean.


🎬 Demo Apps

The demo apps show both the crash scenario and the Shuttle solution side-by-side, using image data transport. Image data is one of the most common real-world contributors to TransactionTooLargeException.

Two architecture patterns are covered:

MVVM: Activities/Fragments as View, ViewModel as state owner and liaison, Kotlin Channels for async notification.

Flow 1: Navigate with Shuttle ✅

Tap "Navigate using Shuttle" -> image loads successfully via warehouse pickup.

Main MenuCargo Unloaded
[图片][图片]

Flow 2: Navigate Normally ❌

Tap "Navigate Normally" -> app crashes with TransactionTooLargeException.

Main MenuAfter Crash
[图片][图片]

ℹ️ For image loading in production, use Glide or Coil. The demo uses raw image data intentionally to trigger the crash condition.


⚠️ Heads Up: Know the Tradeoffs

Other Parcelable objects in the same Intent can still crash your app. Shuttle protects the Serializable payload. It doesn't protect unrelated Parcelable data you're also passing.

Serializable is slower than Parcelable. Parcelable is optimized for IPC and faster to load, but it's unsafe for disk storage. Google recommends serialization for persistence, which is why Shuttle uses it. The LCE state pattern (loading state) gives your UI the hook it needs to handle the slightly longer load time gracefully.

These are documented tradeoffs, not bugs. Architecture is always about weighing options. This one is worth it.


🤖 AI

Model Context Protocol (MCP)

The Model Context Protocol is an open standard that lets AI assistants connect to external tools and data sources. Rather than relying solely on training data, an MCP-enabled assistant can call live tools the moment you need them, generating accurate boilerplate, analysing your actual code, and returning up-to-date documentation without ever leaving your editor.

Shuttle publishes an MCP server to the MCP Registry, which means any developer can point their MCP-compatible editor at it and get Shuttle expertise inside their own project, without cloning or opening this repo. Your AI assistant can scaffold a complete Shuttle integration with ready-to-paste Kotlin boilerplate for Hilt, Koin, or manual wiring; detect TransactionTooLargeException risk in any Kotlin or Java snippet you paste; and retrieve the authoritative Shuttle spec on demand, covering core concepts, setup, transport, pickup, cleanup, and annotations.

For teams, this is the governance multiplier that code review alone cannot be. Every developer, regardless of experience level or editor preference, gets the same accurate, up-to-date guidance on integration patterns and risk from day one.

See the MCP README file for the full list of supported editors and the verification checklist.


🤝 Contributing

Pull requests are welcome. Check the Contributing Guide and Code of Conduct before opening one.

  1. Fork the repo
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -m 'Add: my feature'
  4. Push the branch: git push origin feature/my-feature
  5. Open a Pull Request

For bugs and feature requests, open an issue.


📄 License

MIT. See LICENSE.md for full terms.

Copyright © 2023 Craft & Graft LLC · GRARCHT ™ 2021


来源:README.md,提交 218a773

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v4.0.0最新Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分