Skip to content

Instantly share code, notes, and snippets.

@AndrewDongminYoo
Created May 31, 2025 08:54
Show Gist options
  • Save AndrewDongminYoo/9102d0725064c63c24a68dc04efc2356 to your computer and use it in GitHub Desktop.
Save AndrewDongminYoo/9102d0725064c63c24a68dc04efc2356 to your computer and use it in GitHub Desktop.
A subscriber class for managing keyboard visibility events.
// 🎯 Dart imports:
import 'dart:async';
// 🐦 Flutter imports:
import 'package:flutter/services.dart';
/// A subscriber class for managing keyboard visibility events.
///
/// This class allows registering callbacks for keyboard visibility changes,
/// including when the keyboard is shown, hidden, or its visibility state changes.
///
/// Provides methods to track and respond to keyboard visibility in a Flutter application.
class KeyboardVisibilitySubscriber {
KeyboardVisibilitySubscriber({
required this.onChange,
this.onShow,
this.onHide,
});
/// A callback function that is triggered when the keyboard visibility changes.
///
/// The [onChange] callback receives a boolean value indicating whether the keyboard is currently visible.
final ValueChanged<bool> onChange;
/// A callback function that is triggered when the keyboard is shown.
///
/// This callback is invoked when the keyboard becomes visible on the screen.
final VoidCallback? onShow;
/// A callback function that is triggered when the keyboard is hidden.
///
/// This callback is invoked when the keyboard becomes invisible on the screen.
final VoidCallback? onHide;
}
/// Manages keyboard visibility events and provides a mechanism for subscribing to keyboard visibility changes.
///
/// This class allows registering and tracking keyboard visibility listeners,
/// handling events when the keyboard is shown, hidden, or its state changes.
/// It provides a centralized way to monitor keyboard visibility across the application.
class KeyboardVisibilityNotification {
KeyboardVisibilityNotification() {
_keyboardVisibilitySubscription ??=
(_keyboardVisibilityStream.receiveBroadcastStream() as Stream<int>).listen(onKeyboardEvent);
}
/// A static [EventChannel] for receiving keyboard visibility events from the platform.
///
/// This channel listens for keyboard visibility changes across the application,
/// allowing tracking of when the keyboard is shown or hidden.
static const _keyboardVisibilityStream = EventChannel('flutter_keyboard_visibility');
/// A static map to store keyboard visibility subscribers, keyed by their unique identifier.
///
/// This map allows tracking and managing multiple keyboard visibility listeners
/// within the [KeyboardVisibilityNotification] class, enabling efficient event
/// distribution and listener management.
static final Map<int, KeyboardVisibilitySubscriber> _list = <int, KeyboardVisibilitySubscriber>{};
/// A static [StreamSubscription] for managing the keyboard visibility event stream.
///
/// This subscription allows listening to keyboard visibility changes and
/// can be used to cancel the stream when no longer needed.
static StreamSubscription<int>? _keyboardVisibilitySubscription;
/// A static index used to generate unique identifiers for keyboard visibility subscribers.
///
/// This variable ensures each subscriber receives a unique ID when registering
/// with the [KeyboardVisibilityNotification] class, allowing for precise
/// listener management and removal.
static int _currentIndex = 0;
/// Indicates whether the keyboard is currently visible on the screen.
///
/// This boolean flag tracks the current visibility state of the keyboard,
/// allowing components to respond to keyboard show/hide events.
bool isKeyboardVisible = false;
/// Handles keyboard visibility events by updating the keyboard visibility state
/// and notifying all registered subscribers about the change.
///
/// This method is called when a keyboard event is received from the platform.
/// It updates the [isKeyboardVisible] flag and triggers corresponding callbacks
/// for each registered subscriber, including the onChange, onShow, and onHide
/// callbacks based on the current keyboard visibility state.
///
/// @param arg An integer representing the keyboard visibility (1 for visible, 0 for hidden)
void onKeyboardEvent(int arg) {
isKeyboardVisible = arg == 1;
_list.forEach((int index, KeyboardVisibilitySubscriber sub) {
try {
sub.onChange(isKeyboardVisible);
if (isKeyboardVisible) {
sub.onShow?.call();
}
if (!isKeyboardVisible) {
sub.onHide?.call();
}
} catch (_) {}
});
}
/// Adds a new keyboard visibility listener with optional show and hide callbacks.
///
/// Registers a new subscriber to track keyboard visibility changes. The method
/// creates a [KeyboardVisibilitySubscriber] with the provided callbacks and
/// assigns it a unique identifier.
///
/// @param onChange A callback that is triggered with the current keyboard visibility state.
/// @param onShow An optional callback triggered when the keyboard becomes visible.
/// @param onHide An optional callback triggered when the keyboard becomes hidden.
///
/// @return An integer representing the unique identifier for the registered listener.
int addNewListener({
required ValueChanged<bool> onChange,
VoidCallback? onShow,
VoidCallback? onHide,
}) {
_list[_currentIndex] = KeyboardVisibilitySubscriber(onChange: onChange, onShow: onShow, onHide: onHide);
return _currentIndex++;
}
/// Adds a new keyboard visibility subscriber to the list of listeners.
///
/// Registers a [KeyboardVisibilitySubscriber] with a unique identifier and
/// stores it in the internal listener collection.
///
/// @param subscriber The keyboard visibility subscriber to be added.
/// @return An integer representing the unique identifier for the registered subscriber.
int addNewSubscriber(KeyboardVisibilitySubscriber subscriber) {
_list[_currentIndex] = subscriber;
return _currentIndex++;
}
/// Removes a keyboard visibility listener from the internal list of subscribers.
///
/// Unregisters a previously added listener using its unique identifier.
///
/// @param subscribingId The unique identifier of the listener to be removed.
void removeListener(int subscribingId) {
_list.remove(subscribingId);
}
/// Disposes of the keyboard visibility subscription if no listeners are active.
///
/// Cancels the underlying keyboard visibility subscription and sets it to null
/// when there are no more registered listeners. This helps clean up resources
/// and prevent memory leaks when the keyboard visibility tracker is no longer needed.
void dispose() {
if (_list.isEmpty) {
try {
_keyboardVisibilitySubscription?.cancel().ignore();
} finally {
_keyboardVisibilitySubscription = null;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment