Shuttle offers a modern, protected option to transfer large serializable objects with intent objects or to save them in bundle objects in order to avoid app crashes.

Organizations often experience the adverse side effects of risks inherent in day-to-day software engineering. These adverse side effects include the time and money spent investigating app crashes, fixing app crashes, testing quality assurance, posting hotfixes, and additional governance through code reviews.

Shuttle reduces the high level of governance required to intercept Transaction Too Large Exception-inducing code by:

  1. Saving the Serializable and transferring an identifier for the Serializable
  2. Use a small bundle for binder transactions
  3. Avoiding app crashes due to excessive transaction exceptions
  4. Activate the retrieval of the saved Serializable at the destination.

Why spend more time and money on governance through code reviews? Why not embrace the problem by offering a solution to it?

When introducing, designing and creating the architecture, quality attributes and best practices were taken into account. These attributes include, but are not limited to, ease of use, readability, discoverability, reusability, and maintainability.


The shuttle framework takes its name from the freight transportation in the freight industry. Moving and storage companies experience scenarios where large moving trucks cannot move cargo all the way to the destination (warehouses, houses, etc.). These scenarios can result from road restrictions, overweight truck due to large cargo, and much more. As a result, companies use small shuttle vans to move smaller groups of cargo on multiple trips and deliver the entire shipment.

After the delivery is complete, the employees remove the remains of the load from the vans and trucks. This cleanup task is one of the final steps for the job.

The shuttle framework has its roots in these scenarios by creating a smaller cargo bundle object that can be used to successfully deliver the data to the destination in order to move the corresponding large cargo to a warehouse and store it for pickup, with the smaller cargo associated with the larger cargo is an identifier that provides a single source of truth (shuttle interface) for the transport of cargo and offers convenience functions for removing cargo (automatically or on request)

Coming soon

More tests.


Read the documentation and the demo app as a starting point. The documentation is located in the “Documentation” directory of each module. Modeling documents for the project are also located in the project’s modeling directory.

To use the Maven dependency artifacts with Gradle, add the following to the appropriate build.gradle files:

 implementation 'com.grarcht.shuttle:framework:1.0.0-alpha01' // Needed implementation 'com.grarcht.shuttle:framework-integrations-extensions-room:1.0.0-alpha01' // Needed implementation 'com.grarcht.shuttle:framework-integrations-persistence:1.0.0-alpha01' // Needed depending on the set up implementation 'com.grarcht.shuttle:framework-addons-navigation-component:1.0.0-alpha01' // Optional for integration with the Navigation Component

Recommended use

For end users who want to include the Shuttle Framework in a project, the best way to start is to use the Shuttle interface with the CargoShuttle object as implementation. This interface offers a single source of truth.

Example of use with intent

To transport data with shuttle and intent objects, you can do the following:

 val cargoId = ImageMessageType.ImageData.value val startClass = val destinationClass = shuttle.intentCargoWith(context, destinationClass) .transport(cargoId, imageModel) .cleanShuttleOnReturnTo(startClass, destinationClass, cargoId) // Important: this ensures cargo is not retained in the Warehouse when not needed .deliver(context)

At the destination you can load the data with Shuttle as follows:

 MainScope().async { getShuttleChannel() .consumeAsFlow() .collect { shuttleResult -> when (shuttleResult) { ShuttlePickupCargoResult.Loading -> { view?.let { initLoadingView(it) } } is ShuttlePickupCargoResult.Success<*> -> { showSuccessView(view, as ImageModel) cancel() } is ShuttlePickupCargoResult.Error<*> -> { showErrorView(view) cancel() } } } }

Example for the use with the add-on and the data binding of the navigation component

The start view:

 val cargoId = ImageMessageType.ImageData.value val startClass = val destinationClass = navController.navigateWithShuttle(shuttle, ?.logTag(LOG_TAG) ?.transport(cargoId, imageModel as Serializable) ?.cleanShuttleOnReturnTo(startClass, destinationClass, cargoId) ?.deliver()

The target view:

 onPropertyChangeCallback = object : OnPropertyChangedCallback() { override fun onPropertyChanged(sender: Observable?, propertyId: Int) { when (propertyId) { BR.shuttlePickupCargoResult -> { when (viewModel.shuttlePickupCargoResult) { ShuttlePickupCargoResult.Loading -> { view?.let { initLoadingView(it) } } is ShuttlePickupCargoResult.Success<*> -> { if (null != view && viewModel.imageModel != null) { showSuccessView(view, viewModel.imageModel as ImageModel) } } is ShuttlePickupCargoResult.Error<*> -> { view?.let { showErrorView(it) } } else -> { // ignore } } } } } }

The target ViewModel:

 viewModelScope.launch { shuttle.pickupCargo<Serializable>(cargoId = cargoId) .consumeAsFlow() .collect { shuttleResult -> shuttlePickupCargoResult = shuttleResult when (shuttleResult) { ShuttlePickupCargoResult.Loading -> { notifyPropertyChanged(BR.shuttlePickupCargoResult) } is ShuttlePickupCargoResult.Success<*> -> { imageModel = as ImageModel notifyPropertyChanged(BR.shuttlePickupCargoResult) cancel() } is ShuttlePickupCargoResult.Error<*> -> { notifyPropertyChanged(BR.shuttlePickupCargoResult) cancel() } else -> { // ignore } } } }

Shuttle Cargo States

When storing the freight transported by shuttle, the returned object is a channel of the ShuttleStoreCargoResult type.

When retrieving the cargo transported by shuttle, the returned object is a channel of the ShuttlePickupCargoResult type.

When the cargo transported by Shuttle is removed, the returned object is a channel of the ShuttleRemoveCargoResult type.

These returned types are multi-state sealed classes. Shuttle uses this type to encourage the use of the Loading-Content-Error (LCE) pattern and similar patterns.

By providing these states, consuming apps can take UI actions, analytics, and other use cases.

Cleaning after use

Convenience functions are available to remove persistent freight data after it is not required.


This project architecture offers both the solution and the SBB framework (Solution Building Block).


The Solution Building Block (SBB) puts together logical groupings in the respective modules. These modules include the following:

  1. Frame: This module contains the most important essential solution components to get you started.
  2. Framework integrations: These modules contain the bridging / interfaces required to integrate various technologies into the framework module.
  3. Framework integration extensions: These modules contain the necessary code to complete solutions with the framework module via framework integration modules.
  4. Framework addons: These modules are useful solutions or Solution Building Blocks (SBBs) based on the Shuttle Framework.

Framework integrations and extensions

The design of the Shuttle Framework allows flexibility by not forcing certain technologies to consume projects. One example of this is the persistence module, which provides the interfaces required for the integration of extensions into the Shuttle Framework. One such extension is the room extension, which allows the space to be used to hold the charge.

Framework addons

The Shuttle Framework is a Solution Building Block (SBB). The add-ons intend to include solutions / SBBs based on the Shuttle Framework. An example of an add-on is the navigation module, which enables the program-controlled use of the shuttle framework with the navigator from the navigation architecture component from Google.

Technologies used by the Shuttle Framework

The design of the Shuttle Framework includes avoiding imposing technologies on consumers and packing large transitive dependencies. Often, despite its size, the framework packaging contains reactive libraries. The Shuttle Framework uses Kotlin Coroutines to provide the asynchronous communication necessary to achieve the goals.

heads up

If other data is present, e.g. B. Package objects contained in intent data can cause app crashes due to exceptions for overly large transactions.

In EA / SA and software engineering, the advantages and disadvantages of topics are often weighed up. In Android, different data types can be transferred with bundle objects. It is considered a best practice to use package objects over serializable objects to take advantage of faster load times. Unfortunately, package objects are heavily optimized for IPC (Inter-Process Communication) and cannot be safely stored on the hard drive. Therefore, Google recommends using standard serialization or some other form of serialization. To ensure proper saving and loading of objects, this project uses standard serializable objects to store data. The disadvantage of this approach is that the loading speeds are slower than package objects. This disadvantage can also bring some risk with it, as implementations have to wait a little longer for objects to be loaded. In order to reduce this risk, the above-mentioned freight conditions have been provided, which enable the consumers to handle the user interface with the desired indication of the loading progress.

The demo apps

The demo apps introduce the problem by showing that one of the most common use cases is the transport of image data in serializable objects. This includes the use of Shuttle with MVVM and MVC architectures.

One should use Glide, Picasso, or some other equivalent library to work with images. In governance, it is determined that image data contributes to the exception “Transaction Too Large”. This use case is why it is used to demonstrate the simplicity and effectiveness of Shuttle.

With MVVM, the activities and fragments are part of the view component. The ViewModel is the connection between the view and the model. It keeps the state of the view. It can take actions from the view events and take actions on the model. It can react to events from the model and edit the view.

In the demo app, the ViewModel component uses the ViewModel architecture component from Google. The asynchronous notification mechanism used in MVVM is provided using Kotlin channels, similar to the observables of the Google Databinding Library. Some demos also include data binding to demonstrate basic integration.

In MVC, the activities and fragments are part of the controller component. The controllers receive inputs and change them for the models or views.


The MIT license

GRARCHT ™ 2021

Any person who receives a copy of this software and the associated documentation files (the “Software”) is hereby granted permission free of charge to use the software without restriction, including, but not limited to, the rights to use, copy, modify, merge , publish, distribute, sublicense and / or sell copies of the software and allow persons to whom the software is made available to do so under the following conditions:

The above copyright notice and this permission notice are included in all copies or substantial portions of the software.



Please enter your comment!
Please enter your name here