Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Show clipboard suggestion where users mostly copy-paste

Some text fields are mostly copy-pasted: a promo code, a referral code, a card number, a tracking number. When such a text field gets focus, read the clipboard and show copied value so one tap fills it in:

Some bank apps show it above the keyboard instead. Also fine, but most Android keyboards already show a paste suggestion there, iOS doesn't. Pick the spot that fits the case.

Put the suggestion widget right under the text field and give both the same FocusNode. That's how the suggestion knows the text field was tapped, only then it reads the clipboard:

TextField(
  controller: controller,
  focusNode: focusNode,
),
  
// Shows the clipboard as a chip under the text field
ClipboardSuggestion(
  focusNode: focusNode,
  onTap: (String value) {
    controller.text = value;

    applyPromoCode(value);
  },
),

The widget, design the chip to your taste:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
  
class ClipboardSuggestion extends StatefulWidget {
  final FocusNode focusNode;
  final ValueChanged<String> onTap;
  
  const ClipboardSuggestion({
    super.key,
    required this.focusNode,
    required this.onTap,
  });
  
  @override
  State<ClipboardSuggestion> createState() => _ClipboardSuggestionState();
}
  
class _ClipboardSuggestionState extends State<ClipboardSuggestion> {
  String? _suggestion;
  
  @override
  void initState() {
    super.initState();
  
    widget.focusNode.addListener(_onFocusChanged);
  }
  
  @override
  void dispose() {
    widget.focusNode.removeListener(_onFocusChanged);
  
    super.dispose();
  }
  
  Future<void> _onFocusChanged() async {
    if (!widget.focusNode.hasFocus) {
      _set(null);
      return;
    }
  
    final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
    final String value = data?.text?.trim() ?? '';
  
    // Only short, single-word text, so a copied sentence doesn't show up.
    final bool isCodeLike =
        value.isNotEmpty &&
        value.length <= 32 &&
        !value.contains(RegExp(r'\s'));
  
    _set(isCodeLike ? value : null);
  }

  void _set(String? value) {
    if (!mounted || _suggestion == value) return;
  
    setState(() => _suggestion = value);
  }
  
  @override
  Widget build(BuildContext context) {
    final String? suggestion = _suggestion;
  
    if (suggestion == null) return const SizedBox.shrink();
  
    return GestureDetector(
      onTap: () {
        widget.onTap(suggestion);
  
        _set(null);
      },
      behavior: .opaque,
      child: Chip(
        avatar: const Icon(
          Icons.content_paste_rounded, 
          size: 14,
        ),
        label: Text(
          suggestion,
        ),
      ),
    );
  }
}
Kamran Bekirov
Kamran Bekirov

Want this level of care in your Flutter apps?

Work With Me