Balloon 2.0.0 is a full rewrite on Compose Multiplatform. One artifact now runs on Android, iOS, Desktop and Web, and the API is composables and state instead of Context, View and XML.
If you need the View implementation, it stays available at 1.7.6 and is documented separately. Nothing about 1.x is going away.
Supported platforms
| Target | Artifact |
|---|---|
| Android | balloon-android |
| Desktop (JVM) | balloon-desktop |
| iOS (arm64) | balloon-iosarm64 |
| iOS (simulator, arm64) | balloon-iossimulatorarm64 |
| iOS (x64) | balloon-iosx64 |
| Web (Wasm) | balloon-wasm-js |
Gradle picks the right one. Depend on com.github.skydoves:balloon and nothing else.
Installation
Compose Multiplatform:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.github.skydoves:balloon:2.0.0")
}
}
}Android only:
dependencies {
implementation("com.github.skydoves:balloon:2.0.0")
}balloon-compose is gone. It is folded into balloon.
Quick start
val style = rememberBalloonBuilder {
setArrowSize(10.dp)
setPadding(12.dp)
setCornerRadius(8.dp)
setBackgroundColor(Color(0xFF785EF0))
setBalloonAnimation(BalloonAnimation.ELASTIC)
}
val balloonState = rememberBalloonState(style)
Balloon(
state = balloonState,
balloonContent = { Text(text = "Now you can edit your profile!", color = Color.White) },
) {
Button(onClick = { balloonState.showAlignTop() }) {
Text(text = "Edit profile")
}
}Wrap your screen in BalloonHost { ... } once, at the root, and every balloon inside it works.
What changed from 1.7.6
| 1.7.6 | 2.0.0 | |
|---|---|---|
| Artifacts | balloon, balloon-compose |
balloon |
| Platforms | Android | Android, iOS, Desktop (JVM), Web (Wasm) |
| Rendering | PopupWindow plus Android views |
Compose Popup and a Compose Shape |
| Package | com.skydoves.balloon, com.skydoves.balloon.compose |
com.skydoves.balloon |
| Content | setText, TextForm, IconForm, setLayout |
a @Composable slot |
| Anchoring | you pass a View to every show call |
BalloonState knows its own anchor |
| Lifecycle | setLifecycleOwner, manual disposal |
composition disposal |
There is no Context, no View, no Drawable, no Typeface and no XML anywhere in the API.
What is new
- Two ways to attach a balloon. Wrap an anchor with
Balloon(...), or decorate one in place withModifier.balloon(...). - A composable body. The balloon content is a slot, so you build it like any other UI. Interactive children work: a tap a child consumes never reaches the balloon's own handler.
- Coroutine friendly. Every
showhas asuspendtwin.awaitAlignBottom()returns when the balloon closes, so a sequence of tips reads as straight line code. - Restyle in place.
rememberBalloonStatere-applies the style on every recomposition, so an animated style updates a balloon that is already showing without hiding it. derive.base.derive { setBackgroundColor(Color.Red) }makes a variant of an existing style using the same builder block.- Placement that flips. When the requested side has no room and the opposite side has some, the balloon moves and the arrow follows it.
- A baseline profile ships in the Android artifact.
Every 1.x builder setter that has a multiplatform meaning is present, with the same name and the same defaults, so most builder blocks port across unchanged.
Deliberate differences
These behave differently from 1.7.6 on purpose. Each was found by rendering both implementations and diffing the result pixel by pixel.
- A hidden arrow takes no space.
setIsVisibleArrow(false)puts the body flush against the anchor. 1.x left anarrowHeight - 1pxgap. - The arrow points at the anchor by default. In 1.x
arrowOrientationdefaulted toBOTTOM, soshowAlignStart()on a default builder left the arrow pointing down. - Balloons flip instead of clamping. 1.x only flipped vertically, and horizontally it slid the balloon along the window edge until it overlapped its own anchor.
setBalloonStrokedraws the thickness you asked for, around the arrow too.- No drop shadow.
elevationreserves its space and drives the width math, but the shadow is not drawn: Compose can only cast a shadow from a convex outline, and a balloon with an arrow notch is not convex. UseModifier.shadow(...)inside the slot if you need one. BalloonOverlayShape.CircleandRoundRecttakeDp, where 1.x took raw pixels. Convert deliberately.- The arrow never enters a rounded corner. Its base is clamped
cornerRadius + arrowWidth / 2in from each end. setAutoDismissDuration(0L)means "never". 1.x used-1Las the disabled sentinel and treated0Las "dismiss immediately".BalloonHighlightAnimation.ROTATEanimates out of the box. In 1.x it did nothing without an explicitsetBalloonRotationAnimation.
Everything else matches to the pixel.
Not carried over
Mostly Android only surface with no multiplatform equivalent:
setText,TextForm,IconForm,setLayout, and everything taking a resource id. Use the composable slot.setOnBalloonTouchListenerandsetOnBalloonOutsideTouchListener.MotionEventis Android only. UseModifier.pointerInputinside the slot.setPreferenceName,setShowCounts,runIfReachedShowCounts. No multiplatform key value store is assumed. Gateshow()with your own storage.setLifecycleOwner,setDismissWhenLifecycleOnPause,setLifecycleObserver. Composition disposal dismisses the balloon.setIsStatusBarVisible,setIsAttachedInDecor,setIsClippingEnabled,setRtlSupports.PopupWindowspecific, or handled byLocalLayoutDirection.
Listeners moved from the builder onto the state: balloonState.onBalloonClick, onDismiss and onOverlayClick.
Migrating
The migration guide maps every 1.x setter to its 2.0.0 counterpart, including the ones that were dropped and what to use instead.
Documentation
- Overview and quick start
- Getting started
- Showing a balloon
- Arrow, Size and spacing, Customization
- Overlay, Animation, Listeners
- Balloon 1.x (View) for the 1.7.6 API
Staying on 1.7.6
The View implementation is unchanged and still published. Keep using it with:
implementation("com.github.skydoves:balloon:1.7.6")
implementation("com.github.skydoves:balloon-compose:1.7.6")2.0.0 does not replace it in place. It is a different API under the same coordinates, so pin the version you want.