From 0813597a5360ca323991d1cc785434644abc6ecd Mon Sep 17 00:00:00 2001 From: istalri Date: Sat, 25 Jul 2026 19:04:56 +0200 Subject: [PATCH] Small refactor of emoji controller and widget Isolated deserialization of the large emoji json to not keep main thread busy to long Refactored textController logic in EmojiPickerButton to make popping mid buid impossible. The bug from the comment should be impossible now (The bug could be reproduce with future.delayed and microtask but) --- lib/controllers/emoji.dart | 8 +++- lib/widgets/emoji_picker_button.dart | 60 +++++++++++++++++----------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/lib/controllers/emoji.dart b/lib/controllers/emoji.dart index caea3de..0c89896 100644 --- a/lib/controllers/emoji.dart +++ b/lib/controllers/emoji.dart @@ -1,6 +1,7 @@ import "dart:convert"; import "package:emoji_text_field/models/emoji_category.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter/foundation.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:http/http.dart"; @@ -19,9 +20,12 @@ class EmojiController extends AsyncNotifier { throw Exception("Failed to load emoji data"); } - final data = json.decode(response.body); + return compute(_parseEmojiJson, response.body); + } - final entries = (data as List) + static EmojiTuple _parseEmojiJson(String body) { + final data = json.decode(body) as List; + final entries = data .cast>() .map(Emoji.fromJson) .toIList(); diff --git a/lib/widgets/emoji_picker_button.dart b/lib/widgets/emoji_picker_button.dart index 2ac906a..3516590 100644 --- a/lib/widgets/emoji_picker_button.dart +++ b/lib/widgets/emoji_picker_button.dart @@ -20,32 +20,46 @@ class EmojiPickerButton extends HookConsumerWidget { Widget build(_, WidgetRef ref) => IconButton( onPressed: () async { onPressed?.call(); - final controller = this.controller ?? .new(); + + final tempController = controller ?? TextEditingController(); + final shouldDispose = controller == null; + var hasPopped = false; + + void handler() { + if (tempController.text.isEmpty || hasPopped) return; + hasPopped = true; + + onSelection?.call(tempController.text); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) Navigator.of(context).maybePop(); + }); + } final emojis = await ref.watch(EmojiController.provider.future); - if (context.mounted) { - showModalBottomSheet( - context: context, - builder: (context) => EmojiKeyboardView( - config: .new( - showRecentTab: false, - customCategories: emojis.$1.unlock, - customKeywords: emojis.$2.unlock, - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - height: 600, - ), - textController: controller - ..addListener(() { - // Without this, there will sometimes be a debugLocked is not true error sometimes - // It might be preferable to use a microtask instead of a `Future.delayed`. - Future.delayed(.zero, () { - if (context.mounted) Navigator.of(context).pop(); - }); - onSelection?.call(controller.text); - }), - ), - ); + if (!context.mounted) { + if (shouldDispose) tempController.dispose(); + return; } + + await showModalBottomSheet( + context: context, + builder: (sheetContext) => EmojiKeyboardView( + config: .new( + showRecentTab: false, + customCategories: emojis.$1.unlock, + customKeywords: emojis.$2.unlock, + backgroundColor: Theme.of( + sheetContext, + ).colorScheme.surfaceContainer, + height: 600, + ), + textController: tempController..addListener(handler), + ), + ); + + tempController.removeListener(handler); + if (shouldDispose) tempController.dispose(); }, icon: Icon(Icons.emoji_emotions), );