<?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: Faizan gondal</title>
    <description>The latest articles on DEV Community by Faizan gondal (@fazigondal).</description>
    <link>https://dev.to/fazigondal</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%2F3623057%2Fa19b5e98-5b69-448b-ac23-86375ae917b6.jpeg</url>
      <title>DEV Community: Faizan gondal</title>
      <link>https://dev.to/fazigondal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fazigondal"/>
    <language>en</language>
    <item>
      <title>flet media library</title>
      <dc:creator>Faizan gondal</dc:creator>
      <pubDate>Wed, 23 Sep 2026 06:54:47 +0000</pubDate>
      <link>https://dev.to/fazigondal/flet-media-library-590d</link>
      <guid>https://dev.to/fazigondal/flet-media-library-590d</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5" class="crayons-story__hidden-navigation-link"&gt;flet-media-library — Real Device Media Access for Flet (Android + iOS)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/fazigondal" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3623057%2Fa19b5e98-5b69-448b-ac23-86375ae917b6.jpeg" alt="fazigondal profile" class="crayons-avatar__image" width="337" height="337"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/fazigondal" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Faizan gondal
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Faizan gondal
                
                
              
              &lt;div id="story-author-preview-content-4722042" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/fazigondal" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3623057%2Fa19b5e98-5b69-448b-ac23-86375ae917b6.jpeg" class="crayons-avatar__image" alt="" width="337" height="337"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Faizan gondal&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 23&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5" id="article-link-4722042"&gt;
          flet-media-library — Real Device Media Access for Flet (Android + iOS)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/flet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;flet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/fletmedialibrary"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;fletmedialibrary&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/fletextension"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;fletextension&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/medialibrary"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;medialibrary&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>android</category>
      <category>ios</category>
      <category>mobile</category>
      <category>python</category>
    </item>
    <item>
      <title>flet-media-library — Real Device Media Access for Flet (Android + iOS)</title>
      <dc:creator>Faizan gondal</dc:creator>
      <pubDate>Wed, 23 Sep 2026 06:49:15 +0000</pubDate>
      <link>https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5</link>
      <guid>https://dev.to/fazigondal/flet-media-library-real-device-media-access-for-flet-android-ios-45b5</guid>
      <description>&lt;p&gt;I’ve been building Flet apps for a while now, and one thing that kept frustrating me was media.&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%2Ft4vel4muw5r3qyx0pegi.jpg" 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%2Ft4vel4muw5r3qyx0pegi.jpg" alt=" " width="800" height="337"&gt;&lt;/a&gt;&lt;br&gt;
Want to show the user’s actual photos and videos?&lt;br&gt;&lt;br&gt;
Want to save a recording or a downloaded video so it actually appears in the Gallery / Photos app?&lt;br&gt;&lt;br&gt;
Want to do it without requesting the nuclear &lt;code&gt;MANAGE_EXTERNAL_STORAGE&lt;/code&gt; permission?&lt;/p&gt;

&lt;p&gt;Until recently the answer was basically “good luck”.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;flet-media-library&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s a proper Flet service that gives you full, modern access to the device media library on both Android and iOS — from pure Python.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/fazi-gondal/Flet-media-library" rel="noopener noreferrer"&gt;https://github.com/fazi-gondal/Flet-media-library&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;PyPI&lt;/strong&gt;: &lt;code&gt;pip install flet-media-library&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Current version&lt;/strong&gt;: 1.1.2&lt;/p&gt;

&lt;h3&gt;
  
  
  What it actually does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Query albums and paginate through photos, videos, and audio&lt;/li&gt;
&lt;li&gt;Generate thumbnails (base64 or file path — your choice)&lt;/li&gt;
&lt;li&gt;Save images, videos, and audio directly into the public folders (&lt;code&gt;DCIM/&lt;/code&gt;, &lt;code&gt;Pictures/&lt;/code&gt;, &lt;code&gt;Movies/&lt;/code&gt;, &lt;code&gt;Music/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Rename, move, copy, and delete assets&lt;/li&gt;
&lt;li&gt;Get real-time change notifications when the user adds or deletes media outside your app&lt;/li&gt;
&lt;li&gt;Handles the messy modern permission model (Android 13+ granular permissions + iOS limited access) correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the important part: it never asks for all-files access. It plays nicely with Google Play and the App Store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flet_media_library&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MediaLibrary&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaLibrary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request_permissions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_granted&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any_limited&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&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;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Need media permission&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_assets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GridView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_extent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;thumb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_base64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;thumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;border_radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&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;grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s real gallery access in under 30 lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I built the custom Android bits
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;photo_manager&lt;/code&gt; (the Flutter package under the hood) is excellent, but it doesn’t cover everything I needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saving audio files into the &lt;code&gt;Music/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Renaming assets in-place&lt;/li&gt;
&lt;li&gt;Moving assets between folders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I wrote a small Kotlin fallback for those three operations. Everything else stays in the well-maintained photo_manager path. Clean separation, no unnecessary native code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it without building anything
&lt;/h3&gt;

&lt;p&gt;There’s a pre-built demo APK on the GitHub releases page.&lt;br&gt;&lt;br&gt;
Install it, grant permission, and you can immediately test the gallery, camera capture → save, audio recording → Music folder, rename/move/delete, and the live change events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who this is for
&lt;/h3&gt;

&lt;p&gt;Anyone building Flet apps that deal with media:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gallery / photo picker apps&lt;/li&gt;
&lt;li&gt;Camera or screen-recording apps&lt;/li&gt;
&lt;li&gt;Voice recorders&lt;/li&gt;
&lt;li&gt;Video downloaders&lt;/li&gt;
&lt;li&gt;Social-style apps that need to save content to the device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve been avoiding proper media features in Flet because the platform side felt too painful, this should remove that blocker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/fazi-gondal/Flet-media-library" rel="noopener noreferrer"&gt;https://github.com/fazi-gondal/Flet-media-library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/flet-media-library/" rel="noopener noreferrer"&gt;https://pypi.org/project/flet-media-library/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo APK: check the Releases page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m actively maintaining it. Issues and PRs are welcome.&lt;/p&gt;

&lt;p&gt;If you’re working on something media-related with Flet, give it a spin and let me know how it goes.&lt;/p&gt;

</description>
      <category>flet</category>
      <category>fletmedialibrary</category>
      <category>fletextension</category>
      <category>medialibrary</category>
    </item>
    <item>
      <title>I Built a Media Library for Flet Because Real Mobile Apps Need Native APIs</title>
      <dc:creator>Faizan gondal</dc:creator>
      <pubDate>Wed, 23 Sep 2026 06:13:32 +0000</pubDate>
      <link>https://dev.to/fazigondal/i-built-a-media-library-for-flet-because-real-mobile-apps-need-native-apis-2e2f</link>
      <guid>https://dev.to/fazigondal/i-built-a-media-library-for-flet-because-real-mobile-apps-need-native-apis-2e2f</guid>
      <description>&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/fazi-gondal/Flet-media-library" rel="noopener noreferrer"&gt;https://github.com/fazi-gondal/Flet-media-library&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/flet-media-library/" rel="noopener noreferrer"&gt;https://pypi.org/project/flet-media-library/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been building mobile applications with &lt;strong&gt;Flet&lt;/strong&gt;, and one thing becomes obvious pretty quickly when you move beyond basic apps:&lt;/p&gt;

&lt;p&gt;Python and Flet are great for building the application itself, but eventually you need to talk to the operating system.&lt;/p&gt;

&lt;p&gt;Media handling is a good example.&lt;/p&gt;

&lt;p&gt;As soon as an app needs to browse photos, display videos, save downloads to the gallery, record audio, manage files, or react to changes in the device's media library, you start dealing with platform-specific APIs, permissions, scoped storage, MediaStore, PhotoKit, thumbnails, and a lot of edge cases.&lt;/p&gt;

&lt;p&gt;I didn't want to solve those problems separately in every Flet project.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;flet-media-library&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is an open-source &lt;strong&gt;Flet service extension&lt;/strong&gt; that gives Flet applications a Python API for working with the device media library on &lt;strong&gt;Android and iOS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The current release is &lt;strong&gt;v1.1.2&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem I was trying to solve
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a Flet application that downloads videos.&lt;/p&gt;

&lt;p&gt;The download itself isn't particularly interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL
 ↓
download
 ↓
temporary file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real problem starts afterward.&lt;/p&gt;

&lt;p&gt;Where should that video go?&lt;/p&gt;

&lt;p&gt;Users expect it to appear in something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Movies/MyApp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and show up in the system gallery.&lt;/p&gt;

&lt;p&gt;The same problem appears with other applications.&lt;/p&gt;

&lt;p&gt;A camera app needs to save captured media.&lt;/p&gt;

&lt;p&gt;A voice recorder needs to save recordings to Music.&lt;/p&gt;

&lt;p&gt;A media editor needs to read assets and save the output.&lt;/p&gt;

&lt;p&gt;A gallery app needs to query thousands of files efficiently.&lt;/p&gt;

&lt;p&gt;A file manager may need to rename or move media.&lt;/p&gt;

&lt;p&gt;And all of this has to work within the platform's permission and storage model.&lt;/p&gt;

&lt;p&gt;That's the part I wanted to make reusable.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is flet-media-library?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;flet-media-library&lt;/code&gt; is a Flet &lt;code&gt;Service&lt;/code&gt; that exposes media-library functionality directly to Python.&lt;/p&gt;

&lt;p&gt;A basic setup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flet_media_library&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MediaLibrary&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaLibrary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request_permissions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_granted&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any_limited&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&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;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Media permission was not granted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_assets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sort_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_added&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part for me is that the application code stays Python.&lt;/p&gt;

&lt;p&gt;You don't have to build your application around &lt;code&gt;photo_manager&lt;/code&gt; types or write platform-specific media logic throughout your UI code.&lt;/p&gt;




&lt;h1&gt;
  
  
  What it currently supports
&lt;/h1&gt;

&lt;p&gt;The library covers most of the things you'd expect from a reusable media service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;You can check and request permissions for images, videos, and audio independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_permissions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result contains per-type states such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;granted
limited
denied
denied_forever
restricted
unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matters on newer Android versions because media permissions are no longer just one big switch.&lt;/p&gt;

&lt;p&gt;The project also exposes the limited-access picker and a helper for opening the application settings.&lt;/p&gt;




&lt;h1&gt;
  
  
  Querying albums and assets
&lt;/h1&gt;

&lt;p&gt;You can query device albums:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_albums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asset_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then query assets with pagination and sorting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_assets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sort_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_added&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query API supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image, video, audio, or all media&lt;/li&gt;
&lt;li&gt;album filtering&lt;/li&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;date added&lt;/li&gt;
&lt;li&gt;date modified&lt;/li&gt;
&lt;li&gt;filename&lt;/li&gt;
&lt;li&gt;size&lt;/li&gt;
&lt;li&gt;duration&lt;/li&gt;
&lt;li&gt;MIME filtering where supported&lt;/li&gt;
&lt;li&gt;date-range filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes it practical for a real gallery rather than just a small demo with ten files.&lt;/p&gt;




&lt;h1&gt;
  
  
  Thumbnails became more important than I expected
&lt;/h1&gt;

&lt;p&gt;One of the first things you notice when building a media gallery is that loading original files everywhere is a bad idea.&lt;/p&gt;

&lt;p&gt;The package provides a normal thumbnail API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;thumb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works nicely with Flet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;src_base64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;thumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I also added another API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_thumbnail_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes the thumbnail into a local cache and returns the path.&lt;/p&gt;

&lt;p&gt;You can then use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for having both is pretty simple.&lt;/p&gt;

&lt;p&gt;Base64 is convenient.&lt;/p&gt;

&lt;p&gt;But when you're scrolling through a large gallery, continuously moving image data through the Python/Flutter boundary isn't something I wanted to rely on.&lt;/p&gt;

&lt;p&gt;So the path-based API is there specifically for gallery-scale rendering.&lt;/p&gt;




&lt;h1&gt;
  
  
  Saving media to the system gallery
&lt;/h1&gt;

&lt;p&gt;This is probably the part most Flet media applications will care about.&lt;/p&gt;

&lt;p&gt;For images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pictures/MyApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Movies/MyApp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is that these aren't just application-private files anymore.&lt;/p&gt;

&lt;p&gt;They are inserted into the platform's media library.&lt;/p&gt;

&lt;p&gt;On Android, the public destinations include locations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DCIM/
Pictures/
Movies/
Music/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on the type of media and the API being used.&lt;/p&gt;

&lt;p&gt;This is particularly useful for downloaders, camera apps, recorders, editors, and other applications that create media for the user.&lt;/p&gt;




&lt;h1&gt;
  
  
  Audio exposed an interesting platform gap
&lt;/h1&gt;

&lt;p&gt;While working on this, I ran into one of the reasons native extensions are sometimes unavoidable.&lt;/p&gt;

&lt;p&gt;The upstream Flutter media-library functionality I was using didn't provide everything I needed for Android audio saving.&lt;/p&gt;

&lt;p&gt;So I added a custom Android Kotlin implementation using &lt;code&gt;MediaStore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That gives the Python API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/recording.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_note.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Music/FletMediaLibrary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is currently an &lt;strong&gt;Android-only capability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I prefer exposing that limitation explicitly rather than pretending Android and iOS have exactly the same capabilities.&lt;/p&gt;

&lt;p&gt;That's also why the library has capability discovery.&lt;/p&gt;




&lt;h1&gt;
  
  
  Platform capability discovery
&lt;/h1&gt;

&lt;p&gt;You can ask the library what the current platform supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_capabilities&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response can tell you things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform
supports_audio_save
supports_move
supports_rename
supports_copy
supports_mime_filter
supports_limited_access
supports_thumbnail_path
supports_change_notify
android_sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of scattering platform checks everywhere, an application can do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;supports_rename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# show rename action
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For cross-platform Flet applications, that can make the UI much cleaner.&lt;/p&gt;




&lt;h1&gt;
  
  
  Rename and move are where mobile storage gets interesting
&lt;/h1&gt;

&lt;p&gt;Reading media is one thing.&lt;/p&gt;

&lt;p&gt;Changing it is another.&lt;/p&gt;

&lt;p&gt;The library provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;asset_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_name.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;asset_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Movies/Archive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Android, these operations are implemented through native media APIs and have to respect the rules introduced by scoped storage.&lt;/p&gt;

&lt;p&gt;Depending on the Android version and operation, the system may ask the user to confirm the modification.&lt;/p&gt;

&lt;p&gt;That's expected.&lt;/p&gt;

&lt;p&gt;One thing I learned while building this is that it's better to work with the platform's security model than to try to work around it.&lt;/p&gt;

&lt;p&gt;That's also why the project intentionally avoids requiring broad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MANAGE_EXTERNAL_STORAGE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;access for normal media-library operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Live media changes
&lt;/h1&gt;

&lt;p&gt;Another feature that became useful in the demo is change notifications.&lt;/p&gt;

&lt;p&gt;Suppose your Flet gallery is open and another application creates a photo.&lt;/p&gt;

&lt;p&gt;Your application shouldn't necessarily have to be manually restarted or rebuilt to know something changed.&lt;/p&gt;

&lt;p&gt;You can subscribe to changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asset_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_change&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_change_notify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event can represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;added
modified
removed
other
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;other&lt;/code&gt; case is intentional.&lt;/p&gt;

&lt;p&gt;The underlying platform doesn't always give you perfectly precise change information, so I'd rather expose that honestly than invent precision that isn't really there.&lt;/p&gt;




&lt;h1&gt;
  
  
  The implementation
&lt;/h1&gt;

&lt;p&gt;The project is packaged as an actual Flet extension.&lt;/p&gt;

&lt;p&gt;The Python side exposes the public API.&lt;/p&gt;

&lt;p&gt;The Flutter side implements the service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;photo_manager&lt;/code&gt; is used as the primary media-library backend for common functionality such as querying, thumbnails, saving and deletion.&lt;/p&gt;

&lt;p&gt;Then Android Kotlin fills specific gaps where native handling is needed.&lt;/p&gt;

&lt;p&gt;So the important boundary is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flet / Python application
        ↓
MediaLibrary service
        ↓
Flutter extension
        ↓
photo_manager
        ↓
Android native MediaStore where required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python package owns its own models such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MediaAsset
MediaAlbum
MediaAssetPage
MediaPermissionStatus
MediaChangeEvent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the application isn't tightly coupled to the underlying Flutter package.&lt;/p&gt;

&lt;p&gt;I think that's an important detail when building an extension that other projects may depend on.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why not just use filesystem access?
&lt;/h1&gt;

&lt;p&gt;That was one of the questions I kept coming back to during development.&lt;/p&gt;

&lt;p&gt;A phone isn't just a normal desktop filesystem.&lt;/p&gt;

&lt;p&gt;Modern Android deliberately separates application storage and shared media.&lt;/p&gt;

&lt;p&gt;iOS also has its own photo-library model.&lt;/p&gt;

&lt;p&gt;So a solution based on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't automatically give you correct integration with the user's gallery.&lt;/p&gt;

&lt;p&gt;You also have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;media indexing&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;scoped storage&lt;/li&gt;
&lt;li&gt;limited access&lt;/li&gt;
&lt;li&gt;user confirmation&lt;/li&gt;
&lt;li&gt;public media directories&lt;/li&gt;
&lt;li&gt;platform-specific identifiers&lt;/li&gt;
&lt;li&gt;external media changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why I built this around the &lt;strong&gt;media-library APIs themselves&lt;/strong&gt;, rather than treating the problem as generic filesystem access.&lt;/p&gt;




&lt;h1&gt;
  
  
  The demo application
&lt;/h1&gt;

&lt;p&gt;I also didn't want the repository to contain only a library with a few isolated code snippets.&lt;/p&gt;

&lt;p&gt;There is a full demo application in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;examples/media_library_demo/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It acts as both a showcase and a test harness.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Gallery
&lt;/h3&gt;

&lt;p&gt;Browse photos, videos, and audio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Albums
&lt;/h3&gt;

&lt;p&gt;Load actual device albums.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thumbnails
&lt;/h3&gt;

&lt;p&gt;Render image and video thumbnails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Playback
&lt;/h3&gt;

&lt;p&gt;Play video and audio from inside the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Camera
&lt;/h3&gt;

&lt;p&gt;Capture photos and record videos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microphone
&lt;/h3&gt;

&lt;p&gt;Record audio and save it to Android's Music collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  File management
&lt;/h3&gt;

&lt;p&gt;Move, rename, copy, and delete media.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live updates
&lt;/h3&gt;

&lt;p&gt;Listen for media-library changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smoke testing
&lt;/h3&gt;

&lt;p&gt;Run a sequence of checks against the core APIs on a real device.&lt;/p&gt;

&lt;p&gt;That last part is especially useful for native extensions.&lt;/p&gt;

&lt;p&gt;There are a lot of things that can work in a desktop development environment and then behave differently on a real Android device.&lt;/p&gt;




&lt;h1&gt;
  
  
  A real-world use case: a Flet video downloader
&lt;/h1&gt;

&lt;p&gt;This is probably the easiest way to explain why I wanted this library to exist.&lt;/p&gt;

&lt;p&gt;Suppose we're building a Flet video downloader.&lt;/p&gt;

&lt;p&gt;The downloader creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cache/download.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/cache/download.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;download.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Movies/MyDownloader&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the file is part of the device media library.&lt;/p&gt;

&lt;p&gt;The same service can then be used to query and display it.&lt;/p&gt;

&lt;p&gt;So the application doesn't need one system for downloading and another completely separate system for managing gallery content.&lt;/p&gt;

&lt;p&gt;The workflow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Download
   ↓
Temporary file
   ↓
MediaLibrary
   ↓
MediaStore / PhotoKit
   ↓
System media library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the kind of reusable building block I wanted.&lt;/p&gt;




&lt;h1&gt;
  
  
  Another use case: camera and recording apps
&lt;/h1&gt;

&lt;p&gt;The same thing works for camera applications.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Camera capture
      ↓
temporary file
      ↓
save_image / save_video
      ↓
system gallery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for an Android voice recorder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;microphone
      ↓
WAV recording
      ↓
save_audio
      ↓
Music/FletMediaLibrary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The included demo application exercises both flows.&lt;/p&gt;




&lt;h1&gt;
  
  
  Error handling
&lt;/h1&gt;

&lt;p&gt;Native APIs fail.&lt;/p&gt;

&lt;p&gt;Permissions can be denied.&lt;/p&gt;

&lt;p&gt;Files can disappear.&lt;/p&gt;

&lt;p&gt;Users can cancel system dialogs.&lt;/p&gt;

&lt;p&gt;Some operations simply aren't available on a specific platform.&lt;/p&gt;

&lt;p&gt;So instead of returning a giant collection of ambiguous values, the package defines typed exceptions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PermissionRequiredError
PermissionDeniedError
UnsupportedError
AssetNotFoundError
AlbumNotFoundError
InvalidArgumentError
PlatformError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flet_media_library&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UnsupportedError&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_name.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;UnsupportedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rename is not supported here&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small detail, but it makes the API much easier to integrate into an actual application.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I learned while building it
&lt;/h1&gt;

&lt;p&gt;The interesting part of this project wasn't writing a few methods around a media plugin.&lt;/p&gt;

&lt;p&gt;The real work was dealing with the differences between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python
Flutter
Android
iOS
MediaStore
PhotoKit
Flet's extension system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and keeping those differences from leaking into every part of the application.&lt;/p&gt;

&lt;p&gt;A few things became especially clear during development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native capability gaps are normal
&lt;/h3&gt;

&lt;p&gt;A cross-platform Flutter package can cover most of the common functionality and still leave important platform-specific gaps.&lt;/p&gt;

&lt;p&gt;That's where an extension can add value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile storage is not a normal filesystem problem
&lt;/h3&gt;

&lt;p&gt;Especially on newer Android versions.&lt;/p&gt;

&lt;p&gt;Trying to treat everything as unrestricted file access leads to the wrong abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance matters in media applications
&lt;/h3&gt;

&lt;p&gt;Generating thumbnails is easy.&lt;/p&gt;

&lt;p&gt;Generating thousands of thumbnails efficiently is a different problem.&lt;/p&gt;

&lt;p&gt;That's why the path-based thumbnail API was worth adding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Platform differences should be explicit
&lt;/h3&gt;

&lt;p&gt;Rather than hiding limitations, I prefer capability detection and clear exceptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing on a real device matters
&lt;/h3&gt;

&lt;p&gt;Permissions, MediaStore behavior, recording paths, confirmation dialogs, and filesystem locations are all areas where a real device tells you much more than a desktop test.&lt;/p&gt;




&lt;h1&gt;
  
  
  Installing it
&lt;/h1&gt;

&lt;p&gt;The package is available on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;flet-media-library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with &lt;code&gt;uv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add flet-media-library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add it to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="py"&gt;"flet&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"flet-media-library&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package includes the Flutter extension so the native integration can be included when building the Flet application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project status
&lt;/h1&gt;

&lt;p&gt;The current release is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flet-media-library 1.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is MIT licensed and currently targets Android and iOS.&lt;/p&gt;

&lt;p&gt;Recent work has included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-media-type permissions&lt;/li&gt;
&lt;li&gt;limited-access handling&lt;/li&gt;
&lt;li&gt;cached thumbnail paths&lt;/li&gt;
&lt;li&gt;capability discovery&lt;/li&gt;
&lt;li&gt;date-range filtering&lt;/li&gt;
&lt;li&gt;Android audio saving&lt;/li&gt;
&lt;li&gt;Android move and rename support&lt;/li&gt;
&lt;li&gt;improved native concurrency handling&lt;/li&gt;
&lt;li&gt;demo recording fixes&lt;/li&gt;
&lt;li&gt;automated Android demo APK builds&lt;/li&gt;
&lt;li&gt;a larger integration-test/demo harness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is still work to do.&lt;/p&gt;

&lt;p&gt;The project roadmap includes broader device verification, more native integration tests, Dart-side tests around the media backend, and an optional original-file-path API for playback-related scenarios.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;I built &lt;code&gt;flet-media-library&lt;/code&gt; because I kept running into the same problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flet makes application development surprisingly productive, but serious mobile applications eventually need native platform capabilities.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Media handling is one of those areas.&lt;/p&gt;

&lt;p&gt;Instead of every Flet developer separately figuring out permissions, MediaStore, PhotoKit, thumbnails, public storage, media mutations, and change notifications, I'd rather have those capabilities available as a reusable service.&lt;/p&gt;

&lt;p&gt;That's what this project is trying to be.&lt;/p&gt;

&lt;p&gt;Not a replacement for Flet.&lt;/p&gt;

&lt;p&gt;Not a new UI framework.&lt;/p&gt;

&lt;p&gt;Just a practical media-library layer that lets you keep writing your application in Python while still using the native media APIs underneath.&lt;/p&gt;

&lt;p&gt;And because it's open source, you can inspect the implementation, use it in your own projects, open issues, and contribute improvements.&lt;/p&gt;

&lt;p&gt;If you're building a &lt;strong&gt;Flet gallery, video downloader, camera app, audio recorder, media editor, or another media-heavy mobile application&lt;/strong&gt;, this may save you from rebuilding the same native integration yourself.&lt;/p&gt;

</description>
      <category>fletmedialibrary</category>
      <category>fletandroidmedialibrary</category>
      <category>fletextension</category>
      <category>fletmediastore</category>
    </item>
  </channel>
</rss>
