<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikhil Nigam</title>
    <description>The latest articles on DEV Community by Nikhil Nigam (@nikhilnigamnik).</description>
    <link>https://dev.to/nikhilnigamnik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3996543%2F21796e3b-03c5-4f17-b0b7-87a5bb9ed07b.png</url>
      <title>DEV Community: Nikhil Nigam</title>
      <link>https://dev.to/nikhilnigamnik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikhilnigamnik"/>
    <language>en</language>
    <item>
      <title>How I Used Apple's File Provider API to Bring Android Files Into Finder</title>
      <dc:creator>Nikhil Nigam</dc:creator>
      <pubDate>Mon, 22 Jun 2026 09:32:58 +0000</pubDate>
      <link>https://dev.to/nikhilnigamnik/how-i-used-apples-file-provider-api-to-bring-android-files-into-finder-1o5f</link>
      <guid>https://dev.to/nikhilnigamnik/how-i-used-apples-file-provider-api-to-bring-android-files-into-finder-1o5f</guid>
      <description>&lt;h2&gt;
  
  
  5 Things Nobody Tells You About macOS File Provider Extensions
&lt;/h2&gt;

&lt;p&gt;I spent the last few months building a File Provider extension that mounts an Android phone as a native Finder location — files-on-demand, no FUSE, no drivers. The framework is genuinely great. The docs are genuinely sparse. Here are the five things I wish someone had told me on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. It's not a filesystem — it's a Q&amp;amp;A session with the OS
&lt;/h2&gt;

&lt;p&gt;I came in thinking "File Provider = mount a drive." Wrong mental model. You never implement a filesystem. The OS asks you questions and you answer them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"What's in this folder?"&lt;/em&gt; → enumeration&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Tell me about this item"&lt;/em&gt; → &lt;code&gt;item(for:)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Give me the bytes"&lt;/em&gt; → &lt;code&gt;fetchContents(...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Here's a change, save it"&lt;/em&gt; → writeback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Once it clicked that I'm just a &lt;strong&gt;question-answering service&lt;/strong&gt; and the framework owns all the syncing, caching, and Finder integration, everything got simpler. The corollary: the system decides &lt;em&gt;when&lt;/em&gt; to ask. You don't push; you respond.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Folder listings must be free; bytes must be lazy
&lt;/h2&gt;

&lt;p&gt;The single most important performance rule: &lt;strong&gt;enumeration moves zero file data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My device has years of photos. If listing a folder eagerly fetched anything, browsing would be unusable. So enumeration returns lightweight metadata only — name, size, identifier, dates — and the actual transfer is deferred to &lt;code&gt;fetchContents&lt;/code&gt;, which fires &lt;em&gt;only when the user opens a file&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Get this backwards and a folder with 5,000 items tries to download 60GB on open. Get it right and the same folder appears instantly and transfers nothing until you double-click something. The laziness isn't an optimization; it's the entire reason files-on-demand works.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A blocking extension takes Finder down with it
&lt;/h2&gt;

&lt;p&gt;This is the scary one. Your extension is a process Finder leans on hard. If a call hangs — a dead network socket, an unplugged cable, a sleeping device — &lt;strong&gt;you can beachball Finder itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix isn't "be fast," because you can't be; you're talking to a phone over a cable. The fix is to treat every operation as cancellable and time-bounded, and to translate failures into proper &lt;code&gt;NSFileProviderError&lt;/code&gt; cases (&lt;code&gt;.serverUnreachable&lt;/code&gt;, &lt;code&gt;.noSuchItem&lt;/code&gt;, &lt;code&gt;.notAuthenticated&lt;/code&gt;) instead of letting them stall. Finder knows how to render those gracefully. It does not know how to recover from your deadlock.&lt;/p&gt;

&lt;p&gt;Budget most of your time here. The happy path is a weekend; "fails politely under every kind of disconnect" is the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Identifiers are a contract, not a convenience
&lt;/h2&gt;

&lt;p&gt;Every item has an &lt;code&gt;NSFileProviderItemIdentifier&lt;/code&gt;, and the system uses it to reconcile what changed between enumerations. The rule that bit me: &lt;strong&gt;the same file must always produce the same identifier.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you derive identifiers from something unstable — array index, a hash that includes mtime, a path that shifts when a vendor exposes storage differently — the system thinks files are being deleted and recreated constantly. You get phantom churn, broken "open in place," and weird duplicate states. Pick a stable, deterministic mapping early (I map identifiers to canonical device paths) and never let it drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. You can register a domain backed by literally anything
&lt;/h2&gt;

&lt;p&gt;The realization that made the whole project possible: &lt;strong&gt;File Provider does not care where your data lives.&lt;/strong&gt; iCloud, Dropbox, an S3 bucket, a Postgres row, or — in my case — an Android phone on the other end of &lt;code&gt;adb&lt;/code&gt;. You register a domain, and it shows up in the Finder sidebar. The OS asks its questions; your answers happen to come from &lt;code&gt;adb shell ls&lt;/code&gt; and &lt;code&gt;adb pull&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSFileProviderDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"device-&amp;lt;serial&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Pixel 8"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;NSFileProviderManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;- the phone "mounts" here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever wanted to surface a remote API, a database, or a weird device as a first-class, on-demand Finder location, this is the door. It's far more approachable than the FUSE-shaped solution you were dreading.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You're a Q&amp;amp;A service, not a filesystem.&lt;/li&gt;
&lt;li&gt;Enumeration = metadata only; bytes load on open.&lt;/li&gt;
&lt;li&gt;Never block — translate failures into &lt;code&gt;NSFileProviderError&lt;/code&gt; or you'll beachball Finder.&lt;/li&gt;
&lt;li&gt;Stable identifiers or bust.&lt;/li&gt;
&lt;li&gt;The backing store can be anything — that's the superpower.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This is the framework behind MacPortal, which mounts your Android phone as a native Finder location over USB or Wi-Fi (macOS 13+). &lt;a href="https://macportal.app" rel="noopener noreferrer"&gt;macportal.app&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>filetransfer</category>
      <category>android</category>
    </item>
  </channel>
</rss>
