agra de0a96b557 keyboard(android): pre-R IME tracking + flush dirty marks during persistent callback
KeyboardPlugin.setupInsetsCallback used to early-return on SDK < R, so
the FFI height stayed at 0 on API 29 devices like the Huawei Mate 20 Pro
— the chat composer never tracked the IME. Run the global-layout
listener on all SDKs, and on pre-R also wire setOnApplyWindowInsetsListener
since EMUI 10's IME-hide dispatches new insets without a follow-up layout
pass. Pre-R IME height comes from systemWindowInsetBottom −
stableInsetBottom (stable insets exclude things that animate in/out).

Inside XKeyboard._onFrame, follow notifyListeners with an explicit
scheduleFrame. _onFrame runs as a persistent frame callback after the
build phase has finished, so setState in listeners marks elements dirty
but ensureVisualUpdate is a no-op in this phase — the steady-state pump
masked the issue while the keyboard was open but on the close-edge
(h transitions to 0) the pump stops and the final rebuild was never
scheduled.
2026-05-22 20:55:07 +03:00
2019-08-07 15:37:44 +01:00
2026-04-21 16:54:56 +03:00
2019-10-17 14:34:21 +01:00
zzz
2020-05-04 10:58:19 +01:00

ux

A Flutter toolkit for building fluid, native-feeling UIs.

XKeyboard

Frame-accurate keyboard height tracking for iOS and Android, with interactive dismiss.

Flutter's built-in MediaQuery.viewInsets.bottom lags behind the actual keyboard position and doesn't support interactive dismiss. XKeyboard reads the keyboard height directly from the native layer via FFI — zero channel latency, every frame.

Features

  • Real-time height — reads the keyboard's actual position each frame via FFI (iOS) / JNI (Android)
  • Native animation curves — sampled from CADisplayLink (iOS) and WindowInsetsAnimation (Android), with adaptive learning that refines the curve from observations
  • Interactive dismiss — swipe the keyboard down like iMessage/Telegram, with snap-back or dismiss
  • Scroll freezeisTracking flag lets you freeze scrolling during interactive dismiss

Quick start

final keyboard = XKeyboard.instance;

// Enable swipe-to-dismiss. trackingInset is the height of your input bar.
keyboard.enableInteractiveDismiss(trackingInset: 56);

Use ListenableBuilder to rebuild when the keyboard height changes:

Scaffold(
  resizeToAvoidBottomInset: false, // we handle it ourselves
  body: ListenableBuilder(
    listenable: keyboard,
    builder: (context, _) {
      final keyboardHeight = keyboard.height;
      final safeBottom = MediaQuery.viewPaddingOf(context).bottom;
      final bottom = max(keyboardHeight, safeBottom);

      return Column(
        children: [
          Expanded(
            child: ListView.builder(
              reverse: true,
              // Freeze scrolling during interactive dismiss
              physics: keyboard.isTracking
                  ? NeverScrollableScrollPhysics()
                  : null,
              // ...
            ),
          ),
          Container(
            padding: EdgeInsets.only(bottom: 8 + bottom),
            // your input bar
          ),
        ],
      );
    },
  ),
);

API

Member Description
XKeyboard.instance Singleton instance
.height Current keyboard height in logical pixels
.systemHeight Last system-reported keyboard height
.isOpen Whether the keyboard is visible
.isTracking Whether a dismiss pan gesture is active
.enableInteractiveDismiss({trackingInset}) Enable swipe-to-dismiss
.disableInteractiveDismiss() Disable swipe-to-dismiss
addListener / removeListener Standard ChangeNotifier API

Key points

  • Set resizeToAvoidBottomInset: false on your Scaffold — otherwise Flutter's built-in resize fights with XKeyboard
  • Use MediaQuery.viewPaddingOf(context).bottom for the safe area (not paddingOf, which is consumed by Scaffold)
  • Use max(keyboardHeight, safeBottom) for bottom padding — the keyboard height includes the safe area when open, and safeBottom covers the home indicator when closed

Other utilities

  • BendBox — a flexible layout widget
  • Bezier — bezier curve utilities
Description
No description provided
Readme MIT 1.5 MiB
Languages
Dart 40.2%
Swift 29.1%
Kotlin 18.4%
Java 5.2%
C++ 3.1%
Other 4%