device_storage

A powerful Flutter plugin for managing device storage operations. Save, retrieve, delete, and list files in device storage including root directory and DCIM folder. Supports all file types including images, videos, audio, and documents.

Features

  • ✅ Save files to root directory or DCIM folder
  • ✅ Retrieve files by filename
  • ✅ Delete files by filename
  • ✅ List all files in a directory
  • ✅ Check if file exists
  • ✅ Support for images, videos, audio, and all file types
  • ✅ Android 11+ support with proper permissions
  • ✅ iOS support
  • ✅ Easy-to-use API

Platform Support

Platform Support
Android ✅ Yes
iOS ✅ Yes
Web ❌ No
Windows ❌ No
macOS ❌ No
Linux ❌ No

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  device_storage: ^1.0.0

Then run:

flutter pub get

Android Setup

1. Update AndroidManifest.xml

Add these permissions to android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="29" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

    <application android:requestLegacyExternalStorage="true"
    ...>

2. Update build.gradle

Ensure minimum SDK version in android/app/build.gradle:

android {
    compileSdkVersion 33
    
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 33
    }
}

iOS Setup

Add these permissions to ios/Runner/Info.plist:

<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need access to save media to your photo library</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library</string>

Usage

Import

import 'package:device_storage/device_storage.dart';

Initialize


final storage = DeviceStorage();

Request Permissions

// Request all necessary permissions
bool granted = await storage.requestPermissions();

// Check if permissions are granted
bool hasPermission = await storage.hasPermissions();

// Check if root access is available (Android 11+)
bool hasRoot = await storage.hasRootAccess();

Save Files

Save to Root Directory

// Save image to root
Uint8List imageBytes = // your image bytes
String ? path = await storage.saveToRoot(
  bytes: imageBytes,
  fileName: 'my_image.jpg',
  folderPath: 'MyApp/Images', // Optional subfolder
);

// Using convenience method
await storage.saveImage(
  imageBytes: imageBytes,
  fileName: 'photo.jpg',
  folderPath: 'MyFolder',
  saveToRoot: true,
);

Save to DCIM Folder

// Save to DCIM/MyFolder
String? path = await storage.saveToDCIM(
    bytes: imageBytes,
    fileName: 'photo.jpg',
    subfolder: 'MyFolder', // Creates DCIM/MyFolder/photo.jpg
);

// Using convenience method
await storage.saveImage(
    imageBytes: imageBytes,
    fileName: 'photo.jpg',
    folderPath: 'MyFolder',
    saveToRoot: false, // Saves to DCIM
);

Save Different File Types

// Save video
await storage.saveVideo(
  videoBytes: videoBytes,
  fileName: 'video.mp4',
  folderPath: 'Videos',
  saveToRoot: true,
);

// Save audio
await storage.saveAudio(
  audioBytes: audioBytes,
  fileName: 'audio.mp3',
  folderPath: 'Music',
  saveToRoot: true,
);

// Save any file
await storage.saveFile(
  bytes: fileBytes,
  fileName: 'document.pdf',
  folderPath: 'Documents',
  saveToRoot: true,
);

Retrieve Files

// Get file from root
Uint8List? bytes = await storage.getFromRoot(
  fileName: 'my_image.jpg',
  folderPath: 'MyApp/Images',
);

// Get file from DCIM
Uint8List? bytes = await storage.getFromDCIM(
  fileName: 'photo.jpg',
  subfolder: 'MyFolder',
);

// Using convenience method
Uint8List? bytes = await storage.getFile(
  fileName: 'photo.jpg',
  folderPath: 'MyFolder',
  fromRoot: true,
);

if (bytes != null) {
print('File size: ${bytes.length} bytes');
// Use the bytes (display image, play video, etc.)
}

Delete Files

// Delete from root
bool deleted = await storage.deleteFromRoot(
  fileName: 'my_image.jpg',
  folderPath: 'MyApp/Images',
);

// Delete from DCIM
bool deleted = await storage.deleteFromDCIM(
  fileName: 'photo.jpg',
  subfolder: 'MyFolder',
);

// Using convenience method
bool deleted = await storage.deleteFile(
  fileName: 'photo.jpg',
  folderPath: 'MyFolder',
  fromRoot: true,
);

if (deleted) {
  print('File deleted successfully');
}

List Files

// List files in root directory
List<String> files = await storage.listFiles(
  folderPath: 'MyApp/Images',
  fromRoot: true,
);

// List files in DCIM
List<String> files = await storage.listFiles(
  folderPath: 'MyFolder',
  fromRoot: false,
);

print('Found ${files.length} files');
for (String fileName in files) {
  print('File: $fileName');
}

Check File Existence

bool exists = await storage.fileExists(
  fileName: 'photo.jpg',
  folderPath: 'MyFolder',
  fromRoot: true,
);

if (exists) {
  print('File exists');
}

Save from File Path

// Save existing file to device storage
bool saved = await storage.saveImageFromPath(
  filePath: '/path/to/image.jpg',
  folderPath: 'MyFolder',
  saveToRoot: true,
);

Open App Settings

// Open app settings to manually grant permissions
await storage.openSettings();

Complete Example

import 'package:flutter/material.dart';
import 'package:device_storage/device_storage.dart';
import 'dart:typed_data';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final storage = DeviceStorage();
  List<String> fileList = [];

  @override
  void initState() {
    super.initState();
    _requestPermissions();
  }

  Future<void> _requestPermissions() async {
    await storage.requestPermissions();
  }

  Future<void> _saveFile() async {
    Uint8List bytes = Uint8List.fromList([ /* your data */
    ]);

    String? path = await storage.saveImage(
      imageBytes: bytes,
      fileName: 'test_image.jpg',
      folderPath: 'MyApp',
      saveToRoot: true,
    );

    if (path != null) {
      print('Saved to: $path');
      await _loadFiles();
    }
  }

  Future<void> _loadFiles() async {
    List<String> files = await storage.listFiles(
      folderPath: 'MyApp',
      fromRoot: true,
    );

    setState(() {
      fileList = files;
    });
  }

  Future<void> _deleteFile(String fileName) async {
    bool deleted = await storage.deleteFile(
      fileName: fileName,
      folderPath: 'MyApp',
      fromRoot: true,
    );

    if (deleted) {
      await _loadFiles();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Device Storage Example')),
        body: Column(
          children: [
            ElevatedButton(
              onPressed: _saveFile,
              child: Text('Save File'),
            ),
            ElevatedButton(
              onPressed: _loadFiles,
              child: Text('Load Files'),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: fileList.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(fileList[index]),
                    trailing: IconButton(
                      icon: Icon(Icons.delete),
                      onPressed: () => _deleteFile(fileList[index]),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Important Notes

Android 11+ (API 30+)

For root directory access on Android 11 and above, you need MANAGE_EXTERNAL_STORAGE permission. The app will prompt users to enable "All files access" in settings.

Storage Locations

  • Root Directory: /storage/emulated/0/YourFolder/
  • DCIM Directory: /storage/emulated/0/DCIM/YourFolder/

File Types

The package automatically detects MIME types based on file extensions:

  • Images: .jpg, .jpeg, .png, .gif
  • Videos: .mp4, .mov
  • Audio: .mp3, .wav
  • Documents: .pdf, .txt, .json

Troubleshooting

Permission Denied

Make sure you've:

  1. Added all required permissions to AndroidManifest.xml
  2. Called requestPermissions() before any operations
  3. For Android 11+, manually enabled "All files access" in app settings

File Not Found

  • Check if the file exists using fileExists() first
  • Verify the folder path is correct
  • Ensure the file was saved successfully

iOS Issues

  • Make sure Info.plist has the required photo library permissions
  • iOS doesn't support root directory access - files are saved to app documents

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Md. Mohi-Uddin - mohiuddin655.contact@gmail.com

Support

For issues and feature requests, please file an issue on GitHub.