While building a cross-platform file transfer tool, I ran into challenges around automatic device discovery, hotspot network adaptation, and large-file memory management.
This post documents the problems I encountered, my thought process, and the solutions — hoping it helps developers working on similar features.
Cross-device file sharing still has too much friction in practice:
I wanted a single tool that works across Android, iOS, Windows, and macOS — no accounts, no internet, no cables, no vendor lock-in. This post documents the technical challenges I hit while building it and how I solved them.
I needed to implement pure LAN-based file transfer with no cloud dependency, supporting Android, iOS, Windows, and macOS.
Two implementation approaches were considered:
| Approach | Pros | Cons |
|---|---|---|
| Native code per platform | Best performance | Four separate codebases to maintain |
| Flutter + Platform Channels | Single UI and business logic layer | Requires bridging native network APIs per platform |
I chose the second approach. Dart handles unified transfer scheduling and UI rendering, while Platform Channels invoke each platform's socket and network interface capabilities.
┌─────────────────────────────────────────────────────────────┐
│ Flutter / Dart Layer │
│ UI Widgets │ State Mgmt │ Transfer Scheduler │
├─────────────────────────────────────────────────────────────┤
│ Platform Channel Bridge │
├───────────┬───────────┬───────────┬─────────────────────────┤
│ Android │ iOS │ macOS │ Windows │
│ NSD/mDNS │ Bonjour │ Bonjour │ mDNS/DNS-SD │
│ TCP/UDP │ TCP/UDP │ TCP/UDP │ TCP/UDP │
│ File I/O │ File I/O │ File I/O │ File I/O │
│ Hotspot │ ap1/brg │ bridge0 │ Hotspot Detection │
└───────────┴───────────┴───────────┴─────────────────────────┘
Core principle: All platform differences are encapsulated in the native layer. The Dart layer only faces a unified abstract interface.
Two mainstream approaches exist for LAN device discovery:
NSD (Network Service Discovery), Windows 10+ via built-in mDNS/DNS-SD, Apple platforms via BonjourRelying solely on either approach carries compatibility risks. The actual implementation is configurable — you pick one strategy based on your scenario:
Many implementations (including mine) expose this as a toggle, switching based on real-world effectiveness in the target environment.
This is where most tutorials get it wrong.
Many examples broadcast to 255.255.255.255:
// ❌ Limited broadcast address — blocked primarily on iPhone (iOS 15+ Local Network privacy restricts 255.255.255.255, especially in Personal Hotspot mode)
socket.broadcastTo('255.255.255.255', port);
Here's why this fails: when a phone enables its mobile hotspot, it acts as a DHCP server and assigns IP addresses from vendor-specific subnets (could be 192.168.43.x or 192.168.121.x). In these scenarios, the limited broadcast address gets silently dropped by some phones' firewalls.
The correct approach is to compute the subnet-directed broadcast address:
// ✅ Computed from local IP + subnet mask
// Example: local IP = 192.168.121.5, mask = /24
// Broadcast address = 192.168.121.255
InternetAddress subnetBroadcast = InternetAddress('192.168.121.255');
socket.broadcastTo(subnetBroadcast, port);
Neither iOS nor macOS exposes a public API to directly query "is personal hotspot active?" However, you can infer it by checking for the presence of specific virtual network interfaces:
| Platform | Hotspot Interface Name |
|---|---|
| iOS | ap1 or bridge100 |
| macOS | bridge0 or bridge100 |
Each platform's native code checks for these interfaces and returns a boolean to the Dart layer:
final isHotspot = await platform.invokeMethod('isHotspotActive');
if (isHotspot) {
// Use subnet-directed broadcast on hotspot subnet for discovery
}
Transferring a single 2GB video file means you can't load the entire file into memory before sending — mobile devices will OOM immediately.
Sender Receiver
┌──────────────┐ ┌──────────────┐
│ Open file │ │ Create dest │
│ Read chunk │─TCP Stream─→│ Append write │
│ Send data │ │ Verify MD5 │
│ Progress │ │ Progress │
└──────────────┘ └──────────────┘
Key parameter choices:
| Parameter | Value | Rationale |
|---|---|---|
| Chunk size | 64 KB (example) | Balances memory footprint vs syscall overhead — actual value depends on platform and file type |
| TCP Buffer | 256 KB (example) | Tuned for gigabit LAN — adjust based on measured throughput and device memory |
| Concurrent connections | 1–3 | More connections increase context-switching overhead |
| Approach | Peak Memory (2GB file) |
|---|---|
| Load entire file into memory | > 2 GB (OOM) |
| Chunked Streaming | < 80 MB |
Same Wi-Fi 6 router environment:
| Scenario | Average Speed |
|---|---|
| Android → Windows | > 80 MB/s |
| iPhone → Mac | > 80 MB/s |
| Android → Android (hotspot direct) | ~35 MB/s |
When browsing photos on your phone from a desktop browser, requesting full-resolution images (12MP ≈ 8MB each) means a 50-photo list costs ~400MB of traffic. Terrible experience.
<!-- Server generates 200px thumbnail, embeds as Data URL directly -->
<img src="data:image/png;base64,iVBORw0KGgo..." style="width:100px;">
The browser needs only one HTML request to display all thumbnails — no N additional image requests. Total list page traffic drops from 400MB to ~2MB.
Users often want to clip a segment from a video without re-encoding quality loss.
MP4/MOV containers store video streams as sequences of keyframes (I-frames) and dependent frames (P/B-frames):
Original: [I1] [P2] [P3] [I4] [P5] [P6] [I7]
Cut point lands on I4:
Output: [I4] [P5] [P6] [I7] ← lossless
Cut point lands on P5:
Option A: Search backward to nearest keyframe I4
Option B: Locally re-encode only the I4~P5 segment
As long as the cut start point coincides with a keyframe, you can split the container directly without touching any frame data — achieving true zero-quality-loss trimming.
Beyond file transfer, ShareEasy also supports two media-centric features:
Instead of waiting for a large video to finish transferring before watching, you can stream it directly from another device on the same LAN:
Range header support.ShareEasy can cast video content to DLNA-compatible smart TVs on the same network:
These features are currently available on Android. We follow a phased rollout strategy — new capabilities launch on Android first, then expand to other platforms based on user feedback and demand.
| Problem | Solution |
|---|---|
| Device discovery | Configurable: mDNS (power-efficient) or UDP broadcast (multi-subnet penetration) |
| Broadcast address | Subnet-directed broadcast, not 255.255.255.255 |
| Hotspot detection | Check platform-specific interface names |
| Large file transfer | Chunked streaming (configurable chunk size) |
| Thumbnail loading | Server-generated Base64 embedded in HTML |
| Video trimming | Keyframe-aligned container splitting |
| Video stream playback | HTTP Range request + progressive streaming |
| TV casting | DLNA / SSDP discovery + UPnP AVTransport control |
I've validated all of these approaches in ShareEasy, available on Google Play if you'd like to try it out.
If you're building something similar and run into issues, feel free to discuss in the comments.
Comments