desktop_drop
Receive files, directories, text, and URLs dragged into Flutter applications.
Each DropTarget decides which content to accept.
Requires Flutter 3.47.7 / Dart 3.13.5 or later.
| Platform | Supported |
|---|---|
| Windows | ✅ |
| Linux | ✅ |
| macOS | ✅ |
| iOS 13+ | ✅ |
| Web | ✅ |
| Android | ✅ |
Usage
import 'package:desktop_drop/desktop_drop.dart';
DropTarget(
acceptedKinds: const {DropItemKind.file},
onDrop: (details) async {
for (final item in details.items.whereType<DropFileItem>()) {
final bytes = await item.file.readAsBytes();
print('${item.name}: ${bytes.length} bytes');
}
},
child: const SizedBox(width: 200, height: 200),
)
By default, targets accept all content kinds using copy. acceptedKinds accepts
any drag containing a matching kind; it does not filter the delivered items.
See the example app for files, directories, text, and nested targets.
Receiving content
| Item | Access |
|---|---|
DropFileItem |
name, mimeType, and file (XFile) for reads |
DropStringItem |
mimeType and value, including text/plain, text/html, and text/uri-list |
DropDirectoryItem |
name and directory.list() for immediate XFile / XDirectory children |
- Await all reads inside
onDrop. Provider access and temporary files are released when the callback completes or throws. Save content there if you need it later. No manual disposal is required. - Files and directories may have no
localPath. Read throughfileordirectoryinstead. Usefile.openRead()to stream large files. - A drag can contain several representations of the same content, such as an image file, HTML, and a URL. Choose which to use. URLs are delivered as strings; the plugin does not download them.
- Android directory reads never open permission UI. If the source does not grant
access to children,
directory.list()throwsPlatformExceptionwith codedirectory_access_required. Explain the request in your UI, then explicitly callDesktopDrop.instance.requestDirectoryAccess(directory: item)after a user action and retry listing. Await the entire flow insideonDrop; access expires when it finishes. Cancelling authorization throwsdirectory_access_denied. No broad storage access is used. - Sandboxed macOS files and directories may supply an
appleBookmark. Keep security-scoped access active while reading, and stop access afterward.
Accepting drops
Use the optional onWillAccept callback for additional conditions:
DropTarget(
acceptedKinds: const {DropItemKind.file},
onWillAccept: (details) => details.items.any(
(item) => item.kind == DropItemKind.file && item.mimeType == 'image/png',
),
onDrop: (details) async {
for (final item in details.items.whereType<DropFileItem>()) {
if (item.mimeType != 'image/png') continue;
final bytes = await item.file.readAsBytes();
print('${item.name}: ${bytes.length} bytes');
}
},
child: const SizedBox(width: 200, height: 200),
)
onWillAccept receives DragDetails with preview items and local/global
positions. It returns bool synchronously. Do not call setState, perform I/O,
or make platform calls here. Preview fields may be absent until drop; a MIME
type does not validate file contents. Directory targets tentatively accept
incomplete file previews until the source reveals whether they are directories.
Set operation to DropOperation.move or link if needed. Unsupported operations
reject the drop. Move can tell the source to remove its data; errors during async
onDrop processing may not undo that operation. DropDetails.operation reports
the accepted operation alongside the received items and positions.
Nested targets follow Flutter's hit-test path. A rejecting child can fall back to
an accepting parent. Use onEnter, onMove, and onLeave to update hover UI.
onLeave takes no arguments and also clears hover on drop, rejection, or disabling.
These callbacks can call setState. Set enabled: false to disable a target.
macOS secondary windows
For desktop_multi_window, register the plugin for each new engine in
MainFlutterWindow.swift, after the main window's generated plugin registration:
import desktop_drop
import desktop_multi_window
FlutterMultiWindowPlugin.setOnWindowCreatedCallback { controller in
DesktopDropPlugin.register(
with: controller.registrar(forPlugin: "DesktopDropPlugin"))
}
Migrating from 0.8.x to 0.9.0
Version 0.9.0 changes the public API and requires Flutter 3.47.7 / Dart 3.13.5 or later. Update existing targets and content handling using the mapping below.
| 0.8.x | 0.9.0 |
|---|---|
onDragDone(DropDoneDetails) |
Required onDrop(DropDetails), which can be async |
details.files |
details.items, containing files, directories, and strings |
onDragEntered / onDragUpdated |
onEnter / onMove, receiving DragDetails previews |
onDragExited(details) |
onLeave(), with no arguments |
enable |
enabled |
DropItemFile |
DropFileItem; read through item.file (XFile) |
DropItemDirectory.children |
DropDirectoryItem.directory.list(), a stream of immediate XFile / XDirectory children |
item.path |
Optional item.localPath; use the file or directory API when null |
item.extraAppleBookmark / item.fromPromise |
item.appleBookmark / item.isPromised |
DropDoneDetails.rawText |
DropStringItem.value, with its mimeType |
For an existing file-only receiver, replace onDragDone with an async onDrop:
DropTarget(
acceptedKinds: const {DropItemKind.file},
onDrop: (details) async {
for (final item in details.items.whereType<DropFileItem>()) {
final bytes = await item.file.readAsBytes();
// Save or process the bytes before this callback returns.
print('${item.name}: ${bytes.length} bytes');
}
},
child: const SizedBox(width: 200, height: 200),
)
DropItem no longer extends XFile. Move file reads such as readAsBytes() and
openRead() to item.file. The old byte-backed drop-item constructors and
forwarded saveTo() method are removed. To retain content, read or copy it to
application-owned storage inside onDrop. Await directory listings and all
provider reads before returning: callback completion releases provider access
and deletes temporary transfer files, including macOS promised files. Original
local files are not deleted.
Targets now accept all content kinds by default. Use acceptedKinds to keep a
file-only receiver, and filter details.items as shown above because accepting
a kind does not remove other representations from the delivered items. Use
optional onWillAccept for additional synchronous conditions. Copy is the
default operation; set operation explicitly to request move or link.
Target selection now follows Flutter hit testing. Overlapping targets no longer
each receive the same drop: the first enabled target that accepts it receives
it, with parent fallback when a child rejects it. onLeave also clears hover
on drop, rejection, or disabling. Use behavior to control hit testing.
Plain text, HTML, and URI lists are delivered as DropStringItem with their MIME
types. URLs are strings and are not downloaded into files. On Linux, portal
transfers are resolved by the plugin; consume the resulting file items instead
of resolving a portal key from rawText.
Raw listener registration and the old broadcast event classes (DropEvent,
DropEnterEvent, DropUpdateEvent, DropExitEvent, and DropDoneEvent) are
removed. Use DropTarget callbacks instead. The macOS security-scoped access
methods on DesktopDrop.instance remain available with the renamed
appleBookmark field.
License
See LICENSE. Vendored Dart API DL sources retain their upstream BSD license.