Skip to content

Migration from 2.x

petterp edited this page Aug 30, 2026 · 2 revisions

Migrating from 2.x to 3.0

This page is kept in sync with docs/MIGRATION.md in the repository; the two say the same thing.

3.0 is a full rewrite and keeps none of the 2.x API. The old io.github.petterpx:floatingx / io.github.petterpx:floatingx-compose coordinates are no longer published, and the com.petterp.floatingx.* package has been split into five module sub-packages. This page maps every "2.x way → 3.0 way".

0. Dependencies and packages

// 2.x
implementation 'io.github.petterpx:floatingx:2.3.7'
implementation 'io.github.petterpx:floatingx-compose:2.3.7'

// 3.0: take what you need, core comes transitively with the others
implementation "io.github.petterpx:floatingx-core:3.0.0"
implementation "io.github.petterpx:floatingx-app:3.0.0"
implementation "io.github.petterpx:floatingx-system:3.0.0"
implementation "io.github.petterpx:floatingx-scope:3.0.0"
implementation "io.github.petterpx:floatingx-compose:3.0.0"
2.x package 3.0 package
com.petterp.floatingx.FloatingX com.petterp.floatingx.core.FloatingX
com.petterp.floatingx.assist.* com.petterp.floatingx.core.config.* / core.layout.* / core.gesture.*
com.petterp.floatingx.assist.helper.FxAppHelper com.petterp.floatingx.app.AppHost + com.petterp.floatingx.system.SystemHost
com.petterp.floatingx.assist.helper.FxScopeHelper com.petterp.floatingx.scope.ViewGroupHost / FragmentHost
com.petterp.floatingx.listener.control.IFxControl com.petterp.floatingx.core.FxControl

No more AndroidManifest edits. 2.x made you declare SYSTEM_ALERT_WINDOW yourself; in 3.0 floatingx-system already declares the permission and the transparent permission-request Activity in its own manifest, and floatingx-app registers the Activity tracker from a ContentProvider (which is why setContext is gone too).

1. Entry point

// 2.x: one install for everything, scopeType picks the implementation
FloatingX.install {
    setContext(context)
    setLayout(R.layout.item_floating)
    setScopeType(FxScopeType.SYSTEM_AUTO)
}.show()

// 3.0: install must name a host, and the host is "where it hangs"
FloatingX.install("tag") {
    layout(R.layout.item_floating)
    systemHost(app) { fallback(AppHost.builder(app).build()) }   // equivalent to SYSTEM_AUTO
}.show()
2.x 3.0
FxScopeType.APP appHost(app) { … }
FxScopeType.SYSTEM systemHost(app) { … } (no fallback)
FxScopeType.SYSTEM_AUTO systemHost(app) { fallback(AppHost.builder(app).build()) }
setContext(context) Removed. appHost(app) / systemHost(app) take the Application directly
setTag("x") FloatingX.install("x") { … } (the tag is install's first argument)

2. Content

2.x 3.0
setLayout(R.layout.x) layout(R.layout.x)
setLayoutView(view) view(view), or view { ctx -> … } to build it from a context on the spot
updateView(resource) / updateView(view) / updateView(provider) control.setContent(FxContent.layout(id)) / FxContent.view(v) / FxContent.provider {}
updateViewContent { holder -> … } control.updateContent { holder -> … } (works before show in 3.0)

3. Position

2.x 3.0
setGravity(FxGravity.RIGHT_OR_BOTTOM) anchor(FxGravity.BOTTOM_END)
setX(x) / setY(y) / setXY(x, y) anchor(gravity, dx, dy) (offset from the anchored edge, no longer an absolute coordinate)
setOffsetXY(x, y) anchor(gravity, dx = x, dy = y)
setBorderMargin(t, l, b, r) margin(left, top, right, bottom)
setTopBorderMargin(t) and the other three margin(top = t) (Kotlin named arguments)
setEnableScrollOutsideScreen(true) overflow(top = true, bottom = true, left = true, right = true), per edge
setEnableSafeArea(false) safeArea = false
setEnableEdgeAdsorption(true) adsorb(FxAdsorb.Edges(setOf(FxEdge.START, FxEdge.END)))
setEdgeAdsorbDirection(FxAdsorbDirection.LEFT_OR_RIGHT) adsorb(FxAdsorb.horizontal()); vertical() for top/bottom, all() for all four
setEdgeOffset(edge) margin(...) (the inset after adsorption is just the margin, no separate offset)
setEnableHalfHide(true) + setHalfHidePercent(0.3f) adsorb(FxAdsorb.Edges(edges, halfHide = FxHalfHide(0.3f))), and start/end may differ via FxHalfHide(start, end)
(rebound always on) FxAdsorb.Edges(rebound = true/false), can be turned off
setSaveDirectionImpl(IFxConfigStorage) persist(FxSpStorage(context)), or implement FxStorage yourself
FloatingX.clearConfig() FxStorage.clear(key); the framework builds the key as "$tag:$orientation" (portrait and landscape remembered separately)

The nine FxGravity values

2.x 3.0
FxGravity.DEFAULT / LEFT_OR_TOP FxGravity.TOP_START
FxGravity.TOP_OR_CENTER FxGravity.TOP_CENTER
FxGravity.RIGHT_OR_TOP FxGravity.TOP_END
FxGravity.LEFT_OR_CENTER FxGravity.CENTER_START
FxGravity.CENTER FxGravity.CENTER
FxGravity.RIGHT_OR_CENTER FxGravity.CENTER_END
FxGravity.LEFT_OR_BOTTOM FxGravity.BOTTOM_START
FxGravity.BOTTOM_OR_CENTER FxGravity.BOTTOM_CENTER
FxGravity.RIGHT_OR_BOTTOM FxGravity.BOTTOM_END

START / END are logical directions and flip automatically under RTL (2.x's LEFT/RIGHT were hard-coded physical directions).

4. Gestures

2.x packed "can it be clicked, can it be dragged" into the single FxDisplayMode enum; 3.0 splits that into independent switches.

2.x 3.0
setDisplayMode(FxDisplayMode.Normal) The default, FxGesture.Normal
setDisplayMode(FxDisplayMode.ClickOnly) gesture { drag = FxDrag.DISABLED }, or gesture(FxGesture.ClickOnly)
setDisplayMode(FxDisplayMode.DisplayOnly) gesture(FxGesture.DisplayOnly) (touchable = false, full pass-through)
setEnableTouch(false) gesture { touchable = false }
(not in 2.x) gesture { drag = FxDrag.AFTER_LONG_PRESS } drag only after a long press, or FxGesture.LongPressToDrag
(not in 2.x) gesture { dragRegion = FxRegion.child(R.id.header) } drags start only on the named child view
(2.x left it to IFxTouchListener) gesture { childPriority = FxChildPriority.AUTO / PARENT / CHILD }, the conflict policy with scrollable children
setTouchListener(IFxTouchListener) Drag policy moved into gesture {}; for callbacks only use FxListener.onDragStart/onDrag/onDragEnd
setScrollListener(...) (deprecated) Same as above
setOnClickListener(listener) / setOnLongClickListener(listener) control.addListener(object : FxListener { override fun onClick(control, view) {} })

5. Lifecycle and callbacks

2.x 3.0
setViewLifecycle(IFxViewLifecycle) (deprecated) / addViewLifecycle(...) control.addListener(FxListener): onAttach / onDetach / onShow / onHide / onCancel
(2.x had no position callback) FxListener.onPositionChanged(control, anchor), tells you which edge it adsorbed to
Custom container behaviour meant patching the library addFeature(FxFeature), a container behaviour plugin (Location / Gesture / Animation / ModalScrim built in)

6. Global (app-level) windows

2.x 3.0
addInstallBlackClass(vararg Class) appHost(app) { blacklist(X::class.java) }, matched with isInstance, so subclasses are hit too
addInstallBlackClass(vararg String) appHost(app) { blacklist("com.x.YActivity") } (exact fully-qualified name)
addInstallWhiteClass(...) appHost(app) { whitelist(...) }
setEnableAllInstall(false) + a whitelist Just write whitelist(...): once a whitelist exists, only whitelisted pages show the window
(2.x had no custom rule) appHost(app) { filter { activity -> !activity.isFinishing } }, callable repeatedly; all must pass
(2.x always used DecorView) appHost(app) { attachTo(AppAttachTarget.DECOR / CONTENT) }
(2.x used the Application context, so Material components crashed) appHost(app) { theme(R.style.Theme_App) }

7. System windows

2.x 3.0
setPermissionInterceptor(IFxPermissionInterceptor) systemHost(app) { permission(FxPermissionStrategy.manual { request -> … }) }; request.proceed() / useFallback() / deny()
(2.x had no "skip the check") permission(FxPermissionStrategy.skip())
setManagerParams(FrameLayout.LayoutParams) systemHost(app) { layoutParams { it.type = …; it.flags = … } }, editing WindowManager.LayoutParams directly
setEnableKeyBoardAdapt(true, ...) systemHost(app) { keyboard(R.id.etInput) }, registered by EditText id
setKeyBackListener(IKeyBackListener) systemHost(app) { onBackPressed { true } }
No retry after a permission denial (control.host as? SystemHost)?.retryPermission()

8. Local windows

// 2.x
ScopeHelper.builder {
    setLayout(R.layout.item_floating)
}.toControl(activity)      // or toControl(fragment) / toControl(viewGroup)

private val scopeFx by createFx {
    setLayout(R.layout.item_floating)
    build().toControl(this)
}

// 3.0
val fx = activity.fxScope("tag") { layout(R.layout.item_floating) }   // call after setContentView
val fx = viewGroup.fxScope("tag") { layout(R.layout.item_floating) }
val fx = fragment.fxScope("tag") { layout(R.layout.item_floating) }   // fine inside onCreate
fx.show()
2.x 3.0
ScopeHelper.builder{}.toControl(activity) activity.fxScope {}
ScopeHelper.builder{}.toControl(fragment) fragment.fxScope {}
ScopeHelper.builder{}.toControl(viewGroup) viewGroup.fxScope {}
The createFx { … } delegate Just fxScope {} (it returns FxControl; keep it in a field yourself)
toControl(...) from Java FloatingX.create(config, ViewGroupHost.of(viewGroup), "tag")

Local windows are still not registered: they never appear in FloatingX.controls() and their lifetime belongs to the caller. New automatic cleanup: Activity.fxScope cancels itself when the page is destroyed on API 29+, and Fragment.fxScope cancels itself when the fragment is destroyed.

9. Compose

// 2.x: flip a switch on AppHelper, then hand it a ComposeView yourself
FloatingX.install {
    setContext(context)
    enableComposeSupport()
    setLayoutView(ComposeView(context).apply { setContent { … } })
}

// 3.0: the content is a composable
FloatingX.install("compose") {
    compose { control ->
        val state by control.stateFlow().collectAsState()
        …
    }
    appHost(app)
}.show()
2.x 3.0
enableComposeSupport() + setLayoutView(ComposeView) compose { control -> … }
The owner lived on the host Activity (lost on page change) Every window owns an FxComposeOwner, so viewModel() / rememberSaveable survive page changes
(not in 2.x) control.stateFlow() / control.positionFlow()

10. Control and config updates

2.x 3.0
control.updateConfig { … } (IFxConfigControl) control.update { … } (the same FxConfigScope DSL; anything you don't set keeps its old value)
control.isShow() control.isShowing
control.getX() / getY() control.position (FxPoint, identical semantics for all three hosts: screen coords of the content's top-left)
control.getView() / getViewHolder() control.contentView / control.holder
control.getManagerView() No equivalent: the container belongs to core and is no longer exposed
control.move(x, y) / move(x, y, useAnimation) control.moveTo(x, y) / moveTo(x, y, animate)
control.moveByVector(x, y) control.moveBy(dx, dy) / moveBy(dx, dy, animate)
setEnableAnimation(true) + setAnimationImpl(FxAnimation) animation(FxAnimations.fade()) / animation(FxAnimations.scale()) / animation(your own FxAnimation) (pass null to disable)
setEnableLog(true, "tag") enableLog("tag") (completely silent unless called)

The FloatingX registry: install(tag) / create(tag) / control(tag) / controlOrNull(tag) / controls() / isInstalled(tag) / uninstall(tag) / uninstallAll(). tag defaults to FloatingX.DEFAULT_TAG.

11. Capabilities removed in 3.0 with no equivalent

2.x Why
Position "force fix" / assisted location (enableAssistLocation) 3.0 stores an anchor rather than absolute coordinates and recomputes on size / usable-area changes, so no per-device fix switch is needed
setTagActivityLifecycle(IFxProxyTagActivityLifecycle) The hook proxying the host Activity's lifecycle is gone; to observe attachment use FxListener.onAttach/onDetach and control.attachedActivity
Multi-process Out of scope. The FloatingX registry is per-process, so a child process sees a separate, empty registry (#129)
Container APIs beyond FxViewHolder (getManagerView and friends) The container belongs to core and is no longer public API

12. Migration checklist

  1. Swap the dependency coordinates and imports; delete the permission declaration you added to AndroidManifest for the window.
  2. Add a host inside install {} (appHost / systemHost / viewGroupHost), and delete setContext / setScopeType / setTag.
  3. setGravity + setOffsetXY → a single anchor(gravity, dx, dy); turn setX/setY into offsets from the anchored edge.
  4. setDisplayMode / setEnableTouch / setTouchListenergesture {}.
  5. setOnClickListener / addViewLifecyclecontrol.addListener(FxListener).
  6. ScopeHelper.builder{}.toControl(x)x.fxScope {}.
  7. enableComposeSupport() + ComposeViewcompose {}.
  8. updateConfig {}update {}; updateViewsetContent; updateViewContentupdateContent.

Start at Getting Started, see every config option in Configuration, or go back to Home.

Clone this wiki locally