π· Implementing Clipboard Image Pasting in Flutter: A Cross-Platform Demo with PasteSnap
FlutterPulseThis article was translated specially for the channel FlutterPulseYou'll find lots of interesting things related to Flutter on this channel. Don't hesitate to subscribe!π

Struggling with image pasting in Flutter? See how I built a cross-platform demo using MethodChannel and BLoC for clipboard image pasting π
Introduction
Flutter is great for cross-platform apps, but its Clipboard class only supports text, not images. For a project, I needed users to paste images into a chat interface β a common feature in messaging apps. Since Flutter lacks native support, I used MethodChannel to integrate platform-specific code for iOS and Android. To showcase this, I built PasteSnap, a demo app focused on clipboard image pasting in a chat-like interface.
The Problem: Flutter's Text-Only Clipboard
Flutter's Clipboard class (from package:flutter/services) handles text well but doesn't support images. I needed to enable image pasting in a chat interface, like in WhatsApp, which Flutter can't do natively. I solved this by using platform-specific APIs on iOS and Android, connected to Flutter via MethodChannel. To demonstrate this without exposing my main project, I created PasteSnap β a minimal app focused on clipboard image pasting.
The Solution: Native Integration with MethodChannel
The approach involves three key components:
- iOS: Retrieve clipboard images using
UIPasteboardin AppDelegate.swift. - Android: Access clipboard images using
ClipboardManagerin MainActivity.kt. - Flutter: Use
MethodChannelto communicate between the native layer and the Dart layer, integrating the feature into a ChatBloc and ChatScreen.
This cross-platform solution ensures the feature works seamlessly on both iOS and Android, as demonstrated in PasteSnap.
Step-by-Step Implementation
Setting Up the Flutter Project
I set up a Flutter project with the following dependencies in pubspec.yaml. These packages provide the tools needed for state management and file handling.
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
equatable: ^2.0.0
path_provider: ^2.1.5
- flutter_bloc and equatable to enable the BLoC pattern for state management.
- path_provider assists with file system access (though not directly used in the snippets here, it's part of the full project).
Step 1: Define the MethodChannel in Flutter
In your Flutter project, define a MethodChannel in the ChatBloc class. This channel will be used to communicate with the native code to retrieve images from the clipboard.
import 'dart:typed_data';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/services.dart';
part 'chat_bloc_event.dart';
part 'chat_bloc_state.dart';
class ChatBloc extends Bloc<ChatEvent, ChatState> {
static const _imageChannel = MethodChannel('clipboard/image');
ChatBloc() : super(const ChatState(text: '')) {
on<PasteImageEvent>(_onPasteImage);
on<UpdateTextEvent>(_onUpdateText);
on<SendMessageEvent>(_onSendMessage);
}
Future<void> _onPasteImage(PasteImageEvent event, Emitter<ChatState> emit) async {
try {
final imageData = await _imageChannel.invokeMethod('getClipboardImage');
if (imageData != null && imageData is Uint8List) {
emit(ChatState(
text: state.text,
previewImage: imageData,
messages: state.messages,
));
} else {
print('No image data found in clipboard');
}
} catch (e) {
print('Failed to get clipboard image: $e');
}
}
}
Explanation:
- The
_imageChannelis defined as aMethodChannelto communicate with the native layer. - In
_onPasteImage, it invokesgetClipboardImage, expecting a Uint8List (image data). If successful, it updates the previewImage in the state.
Step 2: iOS Implementation (AppDelegate.swift)
In AppDelegate.swift, set up a method call handler to listen for method calls from Flutter. When the getClipboardImage method is called, retrieving the image from the clipboard and returning it as a byte array.
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let imageChannel = FlutterMethodChannel(name: "clipboard/image",
binaryMessenger: controller.binaryMessenger)
imageChannel.setMethodCallHandler { [weak self] (call, result) in
if call.method == "getClipboardImage" {
self?.getClipboardImage(result: result)
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func getClipboardImage(result: FlutterResult) {
if let image = UIPasteboard.general.image,
let data = image.jpegData(compressionQuality: 0.9) {
result(data)
} else {
result(nil)
}
}
}
Explanation:
- UIPasteboard.general.image checks if the clipboard contains an image.
- If an image is found, it's converted to JPEG data with a compression quality of 0.9 to reduce size.
- The byte array (Data) is returned to Flutter via the result. If no image is present, nil is returned.
Step 3: Android Implementation (MainActivity.kt)
In MainActivity.kt, set up a method call handler to listen for method calls from Flutter. When the getClipboardImage method is called, retrieving the image from the clipboard and returning it as a byte array.
package com.example.paste_snap_demo
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayOutputStream
class MainActivity : FlutterActivity() {
private val CHANNEL = "clipboard/image"
override fun configureFlutterEngine(flutterEngine: io.flutter.embedding.engine.FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getClipboardImage") {
val imageBytes = getClipboardImage()
if (imageBytes != null) {
result.success(imageBytes)
} else {
result.success(null)
}
} else {
result.notImplemented()
}
}
}
private fun getClipboardImage(): ByteArray? {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
if (!clipboard.hasPrimaryClip()) {
return null
}
val clipData = clipboard.primaryClip
if (clipData != null && clipData.itemCount > 0) {
val item = clipData.getItemAt(0)
val uri = item.uri
if (uri != null) {
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream)
inputStream?.close()
if (bitmap != null) {
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
val byteArray = stream.toByteArray()
bitmap.recycle()
stream.close()
return byteArray
}
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
}
return null
}
}
Explanation:
- ClipboardManager checks if the clipboard has a primary clip.
- If a clip with a URI is found, it's converted to a Bitmap using the contentResolver.
- The bitmap is compressed to JPEG with 90% quality and returned as a byte array. If no image is present, null is returned.
Step 4: Update the Flutter UI
In chat_screen.dart, update the UI to handle pasting images through a custom context menu in the TextField, and the _handlePaste method that triggers the image paste action. Here's the relevant UI part:
// Method to handle the pasting mechanism
void _handlePaste() {
context.read<ChatBloc>().add(PasteImageEvent());
}
// TextField with custom context menu for pasting
TextField(
controller: _messageController,
decoration: const InputDecoration(
hintText: 'Type a message...',
border: InputBorder.none,
hintStyle: TextStyle(
color: PasteSnapColors.textSecondary,
fontSize: 15,
),
contentPadding: EdgeInsets.symmetric(vertical: 10),
),
maxLines: 5,
minLines: 1,
style: const TextStyle(
color: PasteSnapColors.textPrimary,
fontSize: 15,
),
textCapitalization: TextCapitalization.sentences,
contextMenuBuilder: (context, editableTextState) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: [
ContextMenuButtonItem(
label: 'Paste',
onPressed: () {
ContextMenuController.removeAny();
_handlePaste();
},
),
],
);
},
),
Explanation:
- _handlePaste Method: Dispatches a
PasteImageEventto the ChatBloc, which then handles the clipboard image retrieval via theMethodChannel. - TextField Context Menu: The contextMenuBuilder customizes TextField's context menu to include a
Pasteoption, calling_handlePaste()when tapped. - Note: The
hintTextdynamically changes based on whether a preview image exists (not shown here for brevity but available on GitHub).
Visualizing the Result
Check out PasteSnap in action:

Conclusion: We Did It! π Here We Go! π
Implementing clipboard image pasting in Flutter using MethodChannel and BLoC was a fun challenge. PasteSnap shows how this feature can enhance apps with image-sharing needs, all while keeping the architecture clean. Check out the full code on GitHub repository , try it out, and let me know your thoughts in the comments β I can't wait to hear from you! π¬
Code Repository
Explore the complete PasteSnap codebase on GitHub: PasteSnap GitHub