Skip to main content

Command Palette

Search for a command to run...

I Built a Mac App to Fix Android File Transfer — Here's What I Learned

Updated
5 min read
I Built a Mac App to Fix Android File Transfer — Here's What I Learned
H
Indie developer from Japan. Building macOS tools in Rust. Currently working on Hiyoko MTP — an Android file transfer app for Mac.

Why I Built This Late at night, I plugged my Android phone into my Mac. It wasn't recognized. I swapped the cable. Restarted. Still nothing.

Sound familiar?

Transfer stops halfway — restarting doesn't help Sending a few GBs takes 20–30 minutes, then fails halfway through and you have to start over Used a sync tool, made a mistake, and accidentally deleted an entire important folder That last one was the final straw. I decided to build a simple, reliable tool focused purely on file transfer — and that's how this app was born.

What I Built Hiyoko MTP — an Android file transfer app for Mac.

Tech Stack Tech Role Rust Transfer engine, nusb integration Tauri Desktop app shell React UI nusb Rust-native USB library, direct IOKit calls Tokio Async runtime Why Rust + Tauri? The root cause of instability in existing tools is that they handle the MTP protocol on a single thread. When one file transfer gets stuck, it blocks everything — leading to crashes and freezes.

By using Rust's concurrency model and isolating transfer queues per thread, I achieved a structure where one file failing doesn't block the whole queue. That's the heart of the multi-lane transfer engine.

I chose Tauri over Electron because it's lighter and feels closer to a native Mac app.

Technical Deep-Dive Switching from libmtp to nusb I initially built the whole thing with libmtp. Then I hit a critical problem.

libmtp and libusb were originally designed for Linux, and macOS handles USB devices in a fundamentally different way:

libmtp (designed for Linux) ↓ libusb ↓ macOS IOKit ← conflict happens here On Linux, you can call detach_kernel_driver() to release the kernel driver — but this function doesn't work on macOS. The result: phones wouldn't be recognized at all.

The fix was switching to nusb:

Custom MTP stack (Rust) ↓ nusb (Rust-native, calls IOKit directly) ↓ macOS IOKit ← no conflict! nusb is a Rust-native USB library that calls IOKit directly on macOS. Phone recognition issues? Gone.

Transfer Design Inspired by Video Streaming The design for the multi-lane transfer engine was surprisingly inspired by video streaming.

Streaming services fetch multiple chunks in parallel so that one slow chunk doesn't stall the whole video. I applied the same idea to MTP transfers — by isolating transfer queues per thread, one file failing no longer blocks the rest.

Handling Edge Cases Cable disconnect recovery If you unplug the cable mid-transfer, you can reconnect and hit "Resume" to pick up right where you left off. The transfer queue state is managed independently from the session, so you don't have to start over.

Special character filenames Characters like ?, :, * are fine on Mac but invalid in Android's MTP filesystem. These are automatically replaced with _ during transfer. I actually tested this by force-creating files with special characters in the terminal.

Tuning Turbo Mode Turbo mode was the hardest part to implement. When sending files over MTP, chunk size directly affects speed:

Too small → more overhead → slower Too large → too much memory → freezes I tuned the parameters through a lot of trial and error. The concept of "adaptive bitrate" from video streaming was useful here too.

Lessons from Shipping Shipping an indie app turned out to be harder than building it.

No Apple Developer Signature (Yet) Since I'm not enrolled in the Apple Developer Program (~$99/year), Gatekeeper shows a warning on first launch. The current workaround:

xattr -cr /Applications/Hiyoko\ MTP.app I plan to add proper signing once the app starts generating revenue.

Building a Universal Binary Without Apple Silicon Hardware I'm on an Intel Mac, so I haven't been able to test on Apple Silicon personally. The build is set up as a Universal Binary and early M1/M2 users have reported it working — but I'd love more feedback from Apple Silicon users to confirm stability. If you try it, please let me know how it goes!

Fighting Gumroad's Identity Verification Gumroad requires identity verification through Stripe. My ID kept getting rejected, and at one point I found myself at midnight holding a lamp to get a clearer photo of my ID card.

The cause? I'd registered on Gumroad with my name in Roman characters, but my Japanese ID shows it in kanji. A completely avoidable mistake — discovered at midnight. 😅

Key Features The core is the multi-lane transfer engine. Everything else is "nice to have" extras I packed in.

File Management Multi-lane transfer engine: Rust concurrency dramatically reduces transfer wait times Dual pane: View Mac and Android side by side for intuitive operation Drag & drop: Drop files directly from Finder 4GB+ file support: Stable transfer for 4K video files Conflict resolution dialog: Choose Skip / Overwrite / Keep Both per file Smart Resume: Auto-skips already-transferred files by size comparison Batch rename: Three modes — Replace Text / Add Text / Format Priority Strategy: Small Files first / Large Files first / Sequential Auto-retry: Retry failed tasks individually or all at once Transfer Modes Mode Speed Timestamp Best for Turbo Fastest Updated When you just need speed Balanced Medium Preserved Everyday use Safe (1 Lane) Slower Preserved Important files When in doubt, use Balanced.

Requirements macOS 13 Ventura or later Intel Mac / Apple Silicon (Universal Binary) Android 11–16 I'm just getting started with indie dev, but I hope this reaches someone struggling with the same problem. 🐤

If you have an M1/M2 Mac and want to try it out, feedback is especially welcome — bug reports and thoughts via the English form below.

🐤 Hiyoko MTP

One-time purchase: $20 — https://hiyokoko.gumroad.com/l/bwfmdk 30-day no-questions-asked refund — "device not recognized" or "speed not what I expected" are both totally valid reasons Bug reports & feedback (English): https://forms.gle/wxorzWq1MSt32Ru99

3 views