Skip to content

2.0.0

Latest

Choose a tag to compare

@skydoves skydoves released this 29 Aug 10:38
· 7 commits to main since this release
31c2e0a

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 with Modifier.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 show has a suspend twin. awaitAlignBottom() returns when the balloon closes, so a sequence of tips reads as straight line code.
  • Restyle in place. rememberBalloonState re-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.

  1. A hidden arrow takes no space. setIsVisibleArrow(false) puts the body flush against the anchor. 1.x left an arrowHeight - 1px gap.
  2. The arrow points at the anchor by default. In 1.x arrowOrientation defaulted to BOTTOM, so showAlignStart() on a default builder left the arrow pointing down.
  3. 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.
  4. setBalloonStroke draws the thickness you asked for, around the arrow too.
  5. No drop shadow. elevation reserves 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. Use Modifier.shadow(...) inside the slot if you need one.
  6. BalloonOverlayShape.Circle and RoundRect take Dp, where 1.x took raw pixels. Convert deliberately.
  7. The arrow never enters a rounded corner. Its base is clamped cornerRadius + arrowWidth / 2 in from each end.
  8. setAutoDismissDuration(0L) means "never". 1.x used -1L as the disabled sentinel and treated 0L as "dismiss immediately".
  9. BalloonHighlightAnimation.ROTATE animates out of the box. In 1.x it did nothing without an explicit setBalloonRotationAnimation.

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.
  • setOnBalloonTouchListener and setOnBalloonOutsideTouchListener. MotionEvent is Android only. Use Modifier.pointerInput inside the slot.
  • setPreferenceName, setShowCounts, runIfReachedShowCounts. No multiplatform key value store is assumed. Gate show() with your own storage.
  • setLifecycleOwner, setDismissWhenLifecycleOnPause, setLifecycleObserver. Composition disposal dismisses the balloon.
  • setIsStatusBarVisible, setIsAttachedInDecor, setIsClippingEnabled, setRtlSupports. PopupWindow specific, or handled by LocalLayoutDirection.

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

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.