5 min read

Building a Compose Multiplatform Lightsaber

Building a Compose Multiplatform Lightsaber

My two oldest kids love Star Wars. Recently, their grandparents showed them a Lightsaber app where you can choose a saber and wave the phone around to simulate a battle, complete with all the beloved sound effects. Inspired by the kid's enjoyment, I decided to take the opportunity to recreate the app for them and learn more about the state management capabilities of Compose. I have read about Molecule and the more recent Circuit but have yet to encounter an opportunity to test these promising new app-building paradigms.

Getting Started

I did not set out to build a multiplatform app or, for that matter, use Compose for state management. I am currently enjoying paternity leave, so the initial goal was to recreate a simple Lightsaber experience where the kids could tap the handle, and the blade emerges with the activate and deactivate sound effects. In addition, I wanted the app to play the idling hum after activating and random battle sounds when they swing the phone. In short, I needed to build a Compose UI with shapes and animations to show and hide the saber blade, use the SoundPool API to play the lightsaber sound effects, and the accelerometer to detect the swings.

The Lightsaber does require business logic and state management, and being a staunch Square loyalist 😜, I initially used Workflow to drive the Compose UI. The workflow tree looked something like the following:

A diagram of the initial Lightsaber Workflow Tree

Within roughly a week, I proved I can still do my job! 🙌🏼

A few things to note about this implementation. Everything is a workflow. While not strictly true—there are some small utility classes for using the SoundPool API and the accelerometer—this is how I visualized the architecture because it is easier to show the app as composable state machines. Another point to note is the dedicated rendering workflow required for LightsaberBattleWorkflow. This may not have been required, but I did not bother trying to collapse down into a single workflow.

0:00
/
A demo of the workflow-based implementation

My son approved of the experience and even helped me find the correct rum and hum sounds the Lightsaber needed to emit when swung. However, my daughter also loves Star Wars and I only have one extra Android phone. As a result, I decided to dust off an old iPhone and explore Kotlin Multiplatform (KMP) programming for the first time in a few years. More specifically, I set out to build an identical Lightsaber experience for Android and iOS powered by the most recently announced Compose Multiplatform (Alpha as of this writing) support for iOS.

Transitioning to Compose Multiplatform

I initially attempted to port my existing workflows to KMP and then reuse the Composables, but workflow KMP support seems to be on hold. I should probably have known this and honestly, I was happy to research Compose-based state management options. Workflow provides excellent and scalable app-building paradigms, but being able to drive business logic state machines and UIs from Composables significantly reduces the cognitive load for designing app experiences. Ultimately, I chose Circuit because it supports multiplatform and I wanted to explore navigation.

Wiring a Multiplatform Circuit

I will leave out the foundational details of how to use Circuit, and instead, focus on describing how I set up the Lightsaber wires (the puns are fun here). Circuit is designed for developers to build with two well-known mobile concepts: Presenters and UIs. The Presenters host the business logic, react to events, and emit a state, and UIs render this state and forward events, such as clicks, to the presenter. The diagram from the Circuit docs below visualizes the relationship.

Diagram from the Circuit docs

In the previously shown Workflow implementation, LightsaberBattleWorkflow contains the business logic and LightsaberBattleRenderingWorkflow renders the lightsaber UI. Translating the workflow implementation into Circuit components produces the following diagram.

A diagram of the Circuit-based Composable relationships

Ironically, this implementation does resemble a circuit diagram but landing on this architecture results in the following key points.

  • Everything is a Composable — This is similar to the Workflow implementation except that both the UI and the state machine are implemented with Compose
  • Everything is written in Kotlin — This may appear obvious to some readers, but I certainly did not expect to be able to basically implement the entire app using Kotlin. Thanks to Kotlin Native interop with the iOS platform libraries, I was able to implement IosSoundPlayer and IosSwingDetector entirely in Kotlin and call APIs such as AVAudioEngine
  • The Lightsaber Circuit knows nothing about platform-specific APIs — By using the KMP expect and actual semantics and a simple API definition, the presenter and UI know nothing about Android or iOS implementation details.
  • The Composable used to show the Workflow implementation UI worked as-is with Compose Multiplatform — The UI was pretty simple, but still a powerful testament to the feasibility of sharing composables between iOS and Android with Compose Multiplatform.
0:00
/
A demo of the final multiplatform app

And voila! Both my little Star Wars fans can pretend to be Jedis to their heart's delight on any platform they would like.

Closing Thoughts

I glossed over quite a few details and interesting learnings, but I am back from parental leave and need to close the loop on this fun side project. In no particular order, here are some interesting notes.

  • I used kotlin-inject for dependency injection and found it very easy to use and similar enough to Dagger without too much boilerplate
  • I used Maestro to write some simple UI tests. I was disappointed that I could not run the same tests on both iOS and Android, but once Compose Multiplatform adds semantics support to iOS then the tests I did write should "just work" for both platforms.
  • Circuit navigation is elegantly simple and worked great navigating between the lightsaber and settings screen.
  • I set up a CI pipeline to get a feel for building a KMP app for two platforms. No thoughts here other than CI is always trickier than it seems.
A Github Actions Build Pipeline

I published the project to Github and may tinker with it occasionally, but no promises. A big thank you to all the folks publishing amazing content about Compose, Compose UI, and mobile development best practices.

May The Force be with you.