A lightweight library for handling navigation results in Jetpack Compose and AndroidX Fragment applications.
Boomerang provides a clean and efficient way to pass data between screens in Jetpack Compose and AndroidX Fragment navigation without tight coupling between components. It solves the common problem of returning results from one screen to another, similar to the old startActivityForResult
pattern but designed specifically for modern navigation patterns.
The library consists of three main modules:
- Core: Contains the fundamental concepts and interfaces
- Compose: Provides Jetpack Compose integration
- Fragment: Provides AndroidX Fragment integration
- π Pass data between screens without tight coupling
- πΎ Preserve navigation results across configuration changes and process death
- 𧩠Modular design with separate core, compose, and fragment modules
- π Easy integration with any Jetpack Compose or AndroidX Fragment navigation library
- π Support for mixed projects using both Compose and Fragments
- π§ͺ Lightweight with minimal dependencies
Add the following dependencies to your app's build.gradle.kts
file:
// For core functionality only
implementation("io.github.buszi.boomerang:core:1.0.0-alpha01")
// For Jetpack Compose integration
implementation("io.github.buszi.boomerang:compose:1.0.0-alpha01")
// For AndroidX Fragment integration
implementation("io.github.buszi.boomerang:fragment:1.0.0-alpha01")
Choose the modules that fit your project's needs. For example, if you're only using Fragments, you only need the core and fragment modules.
- Wrap your app's content in a
CompositionHostedDefaultBoomerangStoreScope
:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionHostedDefaultBoomerangStoreScope {
// Your app content here
AppNavigation()
}
}
}
}
- Make your Activity implement
BoomerangStoreHost
and initialize the store:
class MainActivity : AppCompatActivity(), BoomerangStoreHost {
override var boomerangStore: BoomerangStore? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createOrRestoreDefaultBoomerangStore(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
saveDefaultBoomerangStoreState(outState)
}
}
When you want to store a result to be consumed by another screen:
@Composable
fun DetailScreen(navController: NavController) {
val store = LocalBoomerangStore.current
Button(onClick = {
// Create a bundle with your result data
val resultBundle = bundleOf("selectedItem" to "Item 1")
// Store the result with a key
store.storeValue("home_screen_result", resultBundle)
// Navigate back
navController.popBackStack()
}) {
Text("Select and Return")
}
}
To catch and process a result when a Compose screen becomes visible:
@Composable
fun HomeScreen() {
var selectedItem by remember { mutableStateOf<String?>(null) }
// Set up a catcher that runs when the screen starts
CatchBoomerangLifecycleEffect("home_screen_result") { bundle: Bundle ->
// Extract data from the bundle
selectedItem = bundle.getString("selectedItem")
true // Return true to indicate the result was successfully processed
}
// Display the result
Text("Selected item: $selectedItem")
}
To catch and process a result when a Fragment becomes visible:
class HomeFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set up a catcher that runs when the fragment starts
catchBoomerangWithLifecycleEvent("home_screen_result") { bundle: Bundle ->
// Extract data from the bundle and process it
val selectedItem = bundle.getString("selectedItem")
// Do something with the result
true // Return true to indicate the result was successfully processed
}
}
}
You can directly access the store to perform operations in Compose:
// Inside a @Composable function
val store = LocalBoomerangStore.current
// Check if a result exists
val hasResult = store.getValue("some_key") != null
// Manually drop a value
store.dropValue("some_key")
You can directly access the store to perform operations in Fragments:
// Inside a Fragment
val store = findBoomerangStore()
// Check if a result exists
val hasResult = store.getValue("some_key") != null
// Manually drop a value
store.dropValue("some_key")
// Store a value
val bundle = Bundle()
bundle.putString("result", "Success")
store.storeValue("some_key", bundle)
Boomerang uses a simple but effective pattern:
- Store: A central repository that holds navigation results as key-value pairs
- Catcher: A functional interface that processes results when they become available
- Lifecycle Integration: Results are caught when a screen becomes visible
The library decouples the component that produces a result from the component that consumes it, allowing for a more flexible and maintainable navigation flow.
- BoomerangStore: Interface for storing and retrieving navigation results
- BoomerangCatcher: Functional interface for processing navigation results
- DefaultBoomerangStore: Default implementation of BoomerangStore using a MutableMap
- BoomerangStoreHost: Interface for components that host a BoomerangStore (only for Fragment and mixed setup)
- LocalBoomerangStore: CompositionLocal for accessing the BoomerangStore
- CompositionHostedDefaultBoomerangStoreScope: Composable function that provides a default BoomerangStore
- CatchBoomerangLifecycleEffect: Composable function that catches results at specific lifecycle events
- catchBoomerangWithLifecycleEvent: Extension function for Fragment to catch results at specific lifecycle events
- findBoomerangStore: Extension function for Fragment to find the BoomerangStore from the hosting Activity
- createOrRestoreDefaultBoomerangStore: Extension function for BoomerangStoreHost to create or restore a DefaultBoomerangStore
- saveDefaultBoomerangStoreState: Extension function for BoomerangStoreHost to save the state of a DefaultBoomerangStore
- ActivityHostedBoomerangStoreScope: Composable function that provides BoomerangStore hosted by activity with BoomerangStoreHost
- Android API level 21+
- Kotlin 1.5.0+
- For Compose module: Jetpack Compose 1.0.0+
- For Fragment module: AndroidX Fragment 1.3.0+
The repository includes a sample app that demonstrates how to use Boomerang in a real-world scenario. The app includes examples of:
- Pure Compose navigation with Boomerang
- Pure Fragment navigation with Boomerang
- Mixed solutions where both Compose and Fragments are used
Check the app
module for complete examples of all these scenarios.
Copyright 2025 Buszi
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.