<?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: Umesh Raj</title>
    <description>The latest articles on DEV Community by Umesh Raj (@umesh_raj).</description>
    <link>https://dev.to/umesh_raj</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%2F4222898%2F5cb7541a-3778-4c27-84c5-bb544a93ec2a.png</url>
      <title>DEV Community: Umesh Raj</title>
      <link>https://dev.to/umesh_raj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/umesh_raj"/>
    <language>en</language>
    <item>
      <title>Flutter iOS &amp; Android permissions: what each plugin needs in Info.plist and AndroidManifest</title>
      <dc:creator>Umesh Raj</dc:creator>
      <pubDate>Mon, 02 Nov 2026 18:33:22 +0000</pubDate>
      <link>https://dev.to/umesh_raj/flutter-ios-android-permissions-what-each-plugin-needs-in-infoplist-and-androidmanifest-256c</link>
      <guid>https://dev.to/umesh_raj/flutter-ios-android-permissions-what-each-plugin-needs-in-infoplist-and-androidmanifest-256c</guid>
      <description>&lt;p&gt;You add &lt;code&gt;camera&lt;/code&gt; to &lt;code&gt;pubspec.yaml&lt;/code&gt;, run &lt;code&gt;flutter pub get&lt;/code&gt;, and the Dart side is done. The&lt;br&gt;
native side isn't. Many Flutter plugins also need a line in &lt;code&gt;ios/Runner/Info.plist&lt;/code&gt;, an&lt;br&gt;
entitlement, a manifest entry or a different &lt;code&gt;MainActivity&lt;/code&gt; base class, and the only place&lt;br&gt;
that's written down is somewhere in each plugin's README.&lt;/p&gt;

&lt;p&gt;This guide collects that setup for 10 popular plugins in one table, answers the "do I really&lt;br&gt;
need this key?" questions, and shows how to check a whole app in one command. It's based on&lt;br&gt;
the docs of each plugin and on a study where I checked 25 popular open-source Flutter apps by&lt;br&gt;
hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every iOS permission your app actually asks for needs a &lt;code&gt;*UsageDescription&lt;/code&gt; in &lt;code&gt;Info.plist&lt;/code&gt;,
with a real sentence, not &lt;code&gt;TODO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Some keys a README lists are only needed for one feature (the camera's microphone key, image_picker's camera key). Others are easy to forget because a plugin arrives indirectly or a feature gets added later (Face ID, the push background mode).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dart pub global activate plumb &amp;amp;&amp;amp; plumb doctor&lt;/code&gt; checks all of this for you, read-only.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why does a Flutter plugin need changes in Info.plist or AndroidManifest?
&lt;/h2&gt;

&lt;p&gt;A plugin's Dart code calls into iOS and Android, and both operating systems want some things&lt;br&gt;
declared up front, in files the plugin can't edit for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS &lt;code&gt;Info.plist&lt;/code&gt;:&lt;/strong&gt; a &lt;em&gt;purpose string&lt;/em&gt; for every protected resource (camera, microphone,
photos, location, Face ID…). iOS shows it in the permission prompt. Apple's docs: if it's
missing, "attempts to access the resource fail, and might cause your app to crash"
(&lt;a href="https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS entitlements&lt;/strong&gt; (&lt;code&gt;Runner.entitlements&lt;/code&gt;): capabilities like push notifications
(&lt;code&gt;aps-environment&lt;/code&gt;) or Sign in with Apple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android &lt;code&gt;AndroidManifest.xml&lt;/code&gt;:&lt;/strong&gt; permissions, &lt;code&gt;&amp;lt;queries&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;meta-data&amp;gt;&lt;/code&gt; such as API keys,
and components like receivers. If a plugin's own manifest already declares a permission,
Gradle's manifest merger adds it to your app, so you don't need to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android &lt;code&gt;MainActivity&lt;/code&gt;:&lt;/strong&gt; a few plugins need a specific base class.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The native setup table for 10 popular Flutter plugins
&lt;/h2&gt;

&lt;p&gt;"Must add" is what the plugin's docs require for basic use. "Only if" is needed for a specific&lt;br&gt;
feature. Versions are the ones whose docs I compared.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin (versions)&lt;/th&gt;
&lt;th&gt;iOS: must add&lt;/th&gt;
&lt;th&gt;iOS: only if you use…&lt;/th&gt;
&lt;th&gt;Android&lt;/th&gt;
&lt;th&gt;Docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;camera&lt;/strong&gt; (0.9–0.12)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NSCameraUsageDescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NSMicrophoneUsageDescription&lt;/code&gt;: audio recording (&lt;code&gt;enableAudio&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;nothing in your manifest&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/camera#ios" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;image_picker&lt;/strong&gt; (0.8–1.x)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NSCameraUsageDescription&lt;/code&gt;: &lt;code&gt;ImageSource.camera&lt;/code&gt;; &lt;code&gt;NSMicrophoneUsageDescription&lt;/code&gt;: recording video&lt;/td&gt;
&lt;td&gt;nothing in your manifest&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/image_picker#ios" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;local_auth&lt;/strong&gt; (2.x–3.x)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NSFaceIDUsageDescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MainActivity&lt;/code&gt; must extend &lt;code&gt;FlutterFragmentActivity&lt;/code&gt;. Only if: &lt;code&gt;USE_BIOMETRIC&lt;/code&gt;; an AppCompat launch theme on Android 8 and below&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/local_auth#setup" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;firebase_messaging&lt;/strong&gt; (14–17)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aps-environment&lt;/code&gt; entitlement; &lt;code&gt;UIBackgroundModes&lt;/code&gt; → &lt;code&gt;remote-notification&lt;/code&gt;. Also: upload your APNs key, keep method swizzling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fetch&lt;/code&gt; background mode&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://firebase.google.com/docs/cloud-messaging/flutter/client" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;flutter_local_notifications&lt;/strong&gt; (19–22)&lt;/td&gt;
&lt;td&gt;Set the notification center delegate in &lt;code&gt;AppDelegate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Must: core library desugaring (Gradle). Only if: boot, exact-alarm, full-screen-intent permissions and the plugin's receivers, for scheduled notifications&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/flutter_local_notifications#-android-setup" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;geolocator&lt;/strong&gt; (11–14)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NSLocationWhenInUseUsageDescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Always" and temporary-precision strings; &lt;code&gt;location&lt;/code&gt; background mode&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ACCESS_COARSE_LOCATION&lt;/code&gt; (required from 14.1.1); fine and background location only if you use them&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/geolocator#usage" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;google_maps_flutter&lt;/strong&gt; (2.x)&lt;/td&gt;
&lt;td&gt;Your API key via &lt;code&gt;GMSServices.provideAPIKey&lt;/code&gt; in &lt;code&gt;AppDelegate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;meta-data android:name="com.google.android.geo.API_KEY"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/google_maps_flutter#platform-setup" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;sign_in_with_apple&lt;/strong&gt; (5–8)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;com.apple.developer.applesignin&lt;/code&gt; entitlement&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Only if you use the web flow on Android: the callback activity and its launch mode&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/sign_in_with_apple#ios" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;permission_handler&lt;/strong&gt; (11–13)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;*UsageDescription&lt;/code&gt; for each permission you request; with CocoaPods, the matching &lt;code&gt;PERMISSION_…=1&lt;/code&gt; Podfile macro&lt;/td&gt;
&lt;td&gt;The manifest permissions for what you request&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/permission_handler#setup" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;url_launcher&lt;/strong&gt; (6.x)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LSApplicationQueriesSchemes&lt;/code&gt;: every scheme you pass to &lt;code&gt;canLaunchUrl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;queries&amp;gt;&lt;/code&gt; entries for &lt;code&gt;canLaunchUrl&lt;/code&gt; and in-app browser checks&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/url_launcher#configuration" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row comes from a small YAML recipe in plumb, with a link to the doc it was taken from. If&lt;br&gt;
you spot something out of date, the recipes are&lt;br&gt;
&lt;a href="https://github.com/UmeshRaj7/plumb/tree/main/recipes" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What a purpose string should look like
&lt;/h2&gt;

&lt;p&gt;The value is shown to your user in the permission prompt, so write it for them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSCameraUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Scan a QR code to add a server.&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSFaceIDUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Unlock the app with Face ID.&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;TODO&lt;/code&gt;. Not &lt;code&gt;your usage description here&lt;/code&gt; (copied straight from a README; it happens).&lt;br&gt;
Not an empty &lt;code&gt;&amp;lt;string&amp;gt;&amp;lt;/string&amp;gt;&lt;/code&gt;. All three compile fine, and all three mean the prompt&lt;br&gt;
explains nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  4 native config slips I found in popular open-source Flutter apps
&lt;/h2&gt;

&lt;p&gt;I ran a checker over the 25 most-starred open-source Flutter apps and verified every finding&lt;br&gt;
by hand. 22 of 25 were clean. These are the slips in the other three, described without app&lt;br&gt;
names (the maintainers have been told):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An empty purpose string.&lt;/strong&gt; &lt;code&gt;NSPhotoLibraryAddUsageDescription&lt;/code&gt; existed with an empty value,
in an app that saves images to Photos. Users get a prompt with no reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A missing Face ID string with local_auth.&lt;/strong&gt; local_auth's iOS docs: "To support Face ID on
iOS devices, you must add an &lt;code&gt;NSFaceIDUsageDescription&lt;/code&gt; entry to your &lt;code&gt;Info.plist&lt;/code&gt; file."
The twist: local_auth never appeared in that app's &lt;code&gt;pubspec.yaml&lt;/code&gt;. A shared library pulled
it in, so only the lockfile showed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;remote-notification&lt;/code&gt; background mode with firebase_messaging,&lt;/strong&gt; in an app that
registers &lt;code&gt;FirebaseMessaging.onBackgroundMessage&lt;/code&gt;. Apple: "To receive background
notifications, you must add the remote notifications background mode to your app."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt; with image_picker.&lt;/strong&gt; image_picker's README: "This
permission will not be requested if you always pass &lt;code&gt;false&lt;/code&gt; for &lt;code&gt;requestFullMetadata&lt;/code&gt;, but
App Store policy requires including the plist entry." Low impact for that app (it isn't on
the App Store), but worth adding.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full method, raw output and evidence are public:&lt;br&gt;
&lt;a href="https://github.com/UmeshRaj7/plumb/tree/main/study/2026-11-launch" rel="noopener noreferrer"&gt;the study on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Do I need NSMicrophoneUsageDescription for the camera plugin?
&lt;/h2&gt;

&lt;p&gt;Only if the camera records audio. camera's README lists the key without conditions, but its iOS&lt;br&gt;
implementation (&lt;code&gt;camera_avfoundation&lt;/code&gt;) asks for microphone access only when &lt;code&gt;enableAudio&lt;/code&gt; is&lt;br&gt;
true. The catch: &lt;strong&gt;&lt;code&gt;enableAudio&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;&lt;/strong&gt;. If you create&lt;br&gt;
&lt;code&gt;CameraController(camera, ResolutionPreset.high)&lt;/code&gt; and never record video, add the key anyway, or&lt;br&gt;
pass &lt;code&gt;enableAudio: false&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Do I need NSCameraUsageDescription for image_picker?
&lt;/h2&gt;

&lt;p&gt;Only if you call &lt;code&gt;pickImage(source: ImageSource.camera)&lt;/code&gt; (or the video equivalent). The&lt;br&gt;
image_picker iOS code asks for camera access only for the camera source. A gallery-only app&lt;br&gt;
doesn't need it. You do still need &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt;: the README says App Store&lt;br&gt;
policy requires it even if the permission is never requested.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why does local_auth need FlutterFragmentActivity?
&lt;/h2&gt;

&lt;p&gt;local_auth's Android docs: "&lt;code&gt;local_auth&lt;/code&gt; requires the use of a &lt;code&gt;FragmentActivity&lt;/code&gt; instead of an&lt;br&gt;
&lt;code&gt;Activity&lt;/code&gt;." Change your &lt;code&gt;MainActivity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.embedding.android.FlutterFragmentActivity&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FlutterFragmentActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check your Flutter app's native config in one command
&lt;/h2&gt;

&lt;p&gt;Instead of walking through the table by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub global activate plumb
&lt;span class="nb"&gt;cd &lt;/span&gt;your_flutter_app
plumb doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1knzu42kj7w7emvcobl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1knzu42kj7w7emvcobl.png" alt="plumb doctor output: a placeholder camera purpose string, a missing photo library purpose string and the wrong MainActivity base class, each with a fix and a docs link" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;plumb reads &lt;code&gt;pubspec.lock&lt;/code&gt; (so it sees plugins that arrive through other packages too), picks&lt;br&gt;
the recipe for each plugin version, and checks &lt;code&gt;Info.plist&lt;/code&gt;, entitlements, &lt;code&gt;AndroidManifest.xml&lt;/code&gt;&lt;br&gt;
and your &lt;code&gt;MainActivity&lt;/code&gt;. Any &lt;code&gt;*UsageDescription&lt;/code&gt; that's empty or still placeholder text gets a ⚠,&lt;br&gt;
whether or not plumb has a recipe for the plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxhqx8zhdo7zs06rqo3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxhqx8zhdo7zs06rqo3a.png" alt="How plumb works: pubspec.lock, then recipes, then your native files, then findings. Read-only." width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;✗&lt;/strong&gt; missing, &lt;strong&gt;⚠&lt;/strong&gt; placeholder or wrong type, &lt;strong&gt;✓&lt;/strong&gt; fine, &lt;strong&gt;ℹ&lt;/strong&gt; only needed for a
feature (see them with &lt;code&gt;--verbose&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It's read-only. It never changes a file, and it doesn't need a Mac or Xcode.&lt;/li&gt;
&lt;li&gt;If your shell can't find &lt;code&gt;plumb&lt;/code&gt; (common on Windows), run &lt;code&gt;dart pub global run plumb doctor&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conditional keys show up as ℹ, not as errors, so a gallery-only app doesn't get told off about&lt;br&gt;
the camera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfwqa3g098864hz4hykj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfwqa3g098864hz4hykj.png" alt="The same app checked by plumb 0.0.1, which reported the microphone key as missing, and 0.1.0, which reports it as conditional." width="799" height="434"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  In CI
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart-lang/setup-dart@v1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart pub global activate plumb&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plumb doctor --strict&lt;/span&gt;   &lt;span class="c1"&gt;# fails on anything missing or still a placeholder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  For scripts and AI agents
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;plumb doctor --json&lt;/code&gt; returns every finding with an id, the file, the key, a fix and the doc it&lt;br&gt;
came from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image_picker/ios/info_plist/NSPhotoLibraryUsageDescription"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ios/Runner/Info.plist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NSPhotoLibraryUsageDescription"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add NSPhotoLibraryUsageDescription to Info.plist describing why your app needs the photo library. ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pub.dev/packages/image_picker#ios"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;source&lt;/code&gt; field matters more than it looks. An agent that can cite the doc behind a change&lt;br&gt;
is one you can review in ten seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What plumb doesn't check (yet)
&lt;/h2&gt;

&lt;p&gt;10 plugins so far. Only the main &lt;code&gt;Info.plist&lt;/code&gt; and &lt;code&gt;AndroidManifest.xml&lt;/code&gt;: no flavor-specific&lt;br&gt;
files, Gradle, Podfile or AppDelegate (those show up as manual steps). It reports; it doesn't&lt;br&gt;
fix. That's on purpose: I'd rather you trust what it says before you trust it to touch your&lt;br&gt;
project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which plugin should get a recipe next?&lt;/strong&gt; Comment below or&lt;br&gt;
&lt;a href="https://github.com/UmeshRaj7/plumb/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pub.dev: &lt;a href="https://pub.dev/packages/plumb" rel="noopener noreferrer"&gt;https://pub.dev/packages/plumb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/UmeshRaj7/plumb" rel="noopener noreferrer"&gt;https://github.com/UmeshRaj7/plumb&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Keep your pipes clean.&lt;/em&gt; — Umesh&lt;/p&gt;

&lt;p&gt;I'm Umesh Raj, a tech lead and AI-first engineer. I ship Flutter, native Android and&lt;br&gt;
full-stack Java/Spring Boot, and I'm building plumb in public.&lt;br&gt;
&lt;a href="https://github.com/UmeshRaj7" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/umesh-raj-425b00135/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ios</category>
      <category>android</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
