Overview
KlugGallery saves pictures and videos from your AIR app to the photo gallery of the device: a BitmapData such as a snapshot, or a photo or video file your app has recorded or downloaded.
It only asks for what saving needs. On iPhone and iPad it requests add-only access, so your app never sees the user's library. On Android 10 and later saving needs no permission at all; on Android 6 to 9 KlugGallery asks for storage access at runtime.
Files land where users look for them: in Photos on iOS, and in Pictures/<app name> or Movies/<app name> on Android.
KlugGallery is in daily use in our apps on iOS and Android, our proof of concept.
Features
- Self-contained: one ANE, no core library or dependency ANEs to add
- Save a BitmapData as PNG or JPEG, with quality and file name
- Copy a photo or video file into the gallery (mp4, mov and m4v are saved as videos)
- iOS: add-only photo access, the user's library stays private
- Android 10 and later: no permission needed; Android 6 to 9: runtime storage permission
- Own album folder per app on Android
- Permission status and a shortcut to the app settings after a denial
- One event per result, with the error message when something fails
Supported platforms
On other platforms isSupported returns false, so the same code can stay in a cross-platform app.
Getting started
Add the extension to your application descriptor:
<extensions>
<extensionID>com.klug.gallery</extensionID>
</extensions>
iOS: explain in the InfoAdditions why your app saves to Photos:
<key>NSPhotoLibraryAddUsageDescription</key> <string>Saves your snapshots and videos to Photos.</string>
Android: only needed for Android 6 to 9; newer versions ignore it.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<application>
<activity android:name="com.klug.gallery.KlugGalleryPermissionActivity" android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
</application>
Code examples
Three complete flows, taken from the API that runs in our apps. Every call returns at once; the result arrives as a GalleryEvent. Full documentation →
import com.klug.gallery.Gallery; import com.klug.gallery.GalleryEvent; import flash.display.BitmapData; var gallery:Gallery = Gallery.getInstance(); gallery.addEventListener( GalleryEvent.ADD_COMPLETE, function( e:GalleryEvent ):void { showToast( "Saved to your gallery" ); // your app function }); gallery.addEventListener( GalleryEvent.ADD_ERROR, function( e:GalleryEvent ):void { trace( "could not save: " + e.status ); // the error message }); function saveSnapshot( picture:BitmapData ):void { if ( !gallery.isSupported ) return; // "png" or "jpg"; quality 0..1 counts for jpg gallery.addBitmapData( picture, "jpg", 0.9, "snapshot.jpg" ); }
import flash.filesystem.File; gallery.addEventListener( GalleryEvent.ADD_FILE_COMPLETE, function( e:GalleryEvent ):void { showToast( "Video saved" ); }); gallery.addEventListener( GalleryEvent.ADD_FILE_ERROR, function( e:GalleryEvent ):void { trace( "could not save the video: " + e.status ); }); // a File or a native path; mp4, mov and m4v are saved as videos, everything else as a photo var clip:File = File.applicationStorageDirectory.resolvePath( "clips/last.mp4" ); gallery.addFile( clip );
var pending:BitmapData; function save( picture:BitmapData ):void { var status:String = gallery.authorisationStatus(); if ( status == Gallery.AUTHORISED || status == Gallery.LIMITED ) gallery.addBitmapData( picture, "png" ); else if ( status == Gallery.DENIED || status == Gallery.RESTRICTED ) askToOpenSettings(); // your dialog, then gallery.openDeviceSettings() else { pending = picture; // not asked yet: show the system prompt gallery.requestAuthorisation(); } } gallery.addEventListener( GalleryEvent.AUTHORISATION, function( e:GalleryEvent ):void { if ( e.status == Gallery.AUTHORISED && pending != null ) gallery.addBitmapData( pending, "png" ); pending = null; });
API overview
| Gallery.getInstance() | Returns the single instance. |
| isSupported | True on iOS and Android, false elsewhere. |
| authorisationStatus() | AUTHORISED, DENIED, NOT_DETERMINED, SHOULD_EXPLAIN, RESTRICTED, LIMITED or UNKNOWN. |
| requestAuthorisation() | Shows the system prompt while the user has not decided yet. The answer arrives as GalleryEvent.AUTHORISATION. |
| addBitmapData(bitmapData, format, quality, filename) | Saves the picture as "png" or "jpg". Result: ADD_COMPLETE or ADD_ERROR. |
| addFile(file) | Copies a photo or video file (File or native path) into the gallery. Result: ADD_FILE_COMPLETE or ADD_FILE_ERROR. |
| canOpenDeviceSettings | True where the app settings can be opened. |
| openDeviceSettings() | Opens the settings page of your app. |
Events
GalleryEvent: the type names what happened, status carries the details.
| GalleryEvent.AUTHORISATION | status = the new permission status |
| GalleryEvent.ADD_COMPLETE | the picture was saved |
| GalleryEvent.ADD_ERROR | status = the error message |
| GalleryEvent.ADD_FILE_COMPLETE | the file was saved |
| GalleryEvent.ADD_FILE_ERROR | status = the error message |
Buy KlugGallery
A yearly licence for one AIR application, with all updates for 12 months. After payment you receive the extension, the ActionScript library and the documentation by e-mail, usually within one business day. Licence and terms
- Licence for one AIR application
- All updates for 12 months
- Answer by e-mail within 7 days
- Licence for one AIR application
- All updates for 12 months
- Answer by e-mail within 48 hours
Requirements
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 13 or later (add-only access from iOS 14) |
| Android | Android 6.0 (API 23) or later |
| iOS descriptor | NSPhotoLibraryAddUsageDescription in InfoAdditions |
| Android descriptor | Permission activity; storage permission for Android 6 to 9 |