x_storage_core
XStorage is a Flutter package that provides a unified interface for handling different storage services (Firebase Storage, local file system, AWS S3, etc.). This package provides the core functionality of XStorage.
Why XStorage?
File storage management in app development can be complex and time-consuming. Some common challenges include:
- Using multiple storage services: Having to use different APIs when you need both local and cloud storage in your app
- Switching storage providers: Need for extensive code changes when changing storage providers during development or production
- Testing difficulties: Complexity in creating mocks and building test environments
- Error handling: Implementing consistent error handling across different storage services
XStorage solves these challenges by providing a unified interface. It abstracts the type of storage and allows file operations through a consistent URI-based API.
Key Features
- Unified storage interface: Operate different storage services through the same API via
XStorageProvider - URI-based file management: Handle files with a consistent path format regardless of storage type using
XUri - Multiple provider support: Register and use multiple storage services simultaneously
- Basic file operations: Support for read, write, delete, existence check, and other basic operations
- Robust error handling: Safe error handling using the
Resulttype - Extensibility: Easy creation of custom storage providers
- File transfer between providers: Support for transferring files between different storage services
Understanding XUri
XUri is a core concept in XStorage that provides a unified way to reference files across different storage providers. It consists of:
scheme://path/to/file.ext
Where:
- scheme: Identifies the storage provider (e.g., 'file', 'firebase', 's3')
- path: The path to the file within that storage system
Examples:
// Local file reference
final localUri = XUri.create('file', 'documents/report.pdf');
// Firebase Storage reference
final firebaseUri = XUri.create('firebase', 'user_uploads/profile.jpg');
// S3 reference (using a presigned URL provider)
final s3Uri = XUri.create('s3', 'bucket/images/banner.png');
XUri allows your application to reference files in a storage-agnostic way. This means you can change storage providers without changing file references throughout your code.
Available Storage Providers
The XStorage ecosystem offers these storage providers:
- FileStorageProvider: For local file system (
x_storage_filepackage) - FirebaseStorageProvider: For Firebase Storage (
x_storage_firebasepackage) - PresignedUrlStorageProvider: For storage services that use presigned URLs like AWS S3 (
x_storage_presigned_urlpackage) - AssetStorageProvider: For Flutter assets (included in this package)
Getting Started
Installation
Add dependencies to your pubspec.yaml:
dependencies:
x_storage_core: ^0.0.1
# Add additional provider packages as needed
x_storage_file: ^0.0.1 # For file system
x_storage_firebase: ^0.0.1 # For Firebase Storage
x_storage_presigned_url: ^0.0.1 # For presigned URL storage
Basic Usage
import 'package:x_storage_core/x_storage_core.dart';
import 'package:x_storage_file/x_storage_file.dart';
import 'package:x_storage_firebase/x_storage_firebase.dart';
void main() async {
// Create XStorage instance
final storage = XStorage();
// Register providers
storage.registerProvider(FileStorageProvider());
storage.registerProvider(FirebaseStorageProvider(
firebaseStorage: FirebaseStorage.instance,
));
// Save a file (example: saving to local storage)
final saveResult = await storage.saveFile(
XUri.create('file', 'documents/myfile.txt'),
Uint8List.fromList(utf8.encode('Hello, World!')),
);
// Error handling with Result type
if (saveResult.isSuccess) {
print('File saved successfully');
} else {
print('Error: ${saveResult.failure.message}');
}
// Load a file
final loadResult = await storage.loadFile(
XUri.create('file', 'documents/myfile.txt'),
);
if (loadResult.isSuccess) {
final content = utf8.decode(loadResult.success);
print('File content: $content');
} else {
print('Load error: ${loadResult.failure.message}');
}
// Check if file exists
final exists = await storage.exists(
XUri.create('file', 'documents/myfile.txt'),
);
print('Does file exist? $exists');
// Delete a file
final deleteResult = await storage.deleteFile(
XUri.create('file', 'documents/myfile.txt'),
);
if (deleteResult.isSuccess) {
print('File deleted');
} else {
print('Delete error: ${deleteResult.failure.message}');
}
}
Transferring Files Between Storage Providers
XStorage makes it easy to transfer files between different storage services:
// Example: Download from Firebase Storage to local storage
Future<void> downloadFromFirebaseToLocal() async {
final storage = XStorage();
// Register both providers
storage.registerProvider(FileStorageProvider());
storage.registerProvider(FirebaseStorageProvider(
firebaseStorage: FirebaseStorage.instance,
));
// Download file from Firebase URL
final downloadResult = await storage.downloadFile(
XUri.create('firebase', 'images/photo.jpg'),
onProgress: (received, total) {
final progress = (received / total * 100).toStringAsFixed(2);
print('Download progress: $progress%');
},
);
if (downloadResult.isSuccess) {
final localUri = downloadResult.success;
print('File saved locally: ${localUri.toString()}');
} else {
print('Download error: ${downloadResult.failure.message}');
}
}
Creating Custom Storage Providers
To support a new storage service, extend XStorageProvider and implement the required methods:
class CustomStorageProvider extends XStorageProvider with NetworkProviderMixin {
@override
String get scheme => 'custom';
@override
String get rootUrl => 'https://custom-storage.example.com';
@override
Future<Result<void, XStorageException>> saveFile(XUri uri, Uint8List data) async {
try {
// Implementation for uploading to your custom storage service
// ...
return Result.success(null);
} catch (e) {
return Result.failure(UnknownException(e));
}
}
@override
Future<Result<Uint8List, XStorageException>> loadFile(XUri uri) async {
try {
// Implementation for downloading from your custom storage service
// ...
if (/* file found */) {
return Result.success(data);
} else {
return Result.failure(FileNotFoundException(uri));
}
} catch (e) {
return Result.failure(UnknownException(e));
}
}
@override
Future<Result<void, XStorageException>> deleteFile(XUri uri) async {
try {
// Implementation for deleting from your custom storage service
// ...
return Result.success(null);
} catch (e) {
return Result.failure(UnknownException(e));
}
}
@override
Future<bool> exists(XUri uri) async {
try {
// Implementation for checking file existence
// ...
return /* boolean indicating existence */;
} catch (e) {
return false;
}
}
}
Available Mixins and Their Benefits
XStorage provides mixins to facilitate implementation of different storage service types:
-
NetworkProviderMixin: For network-based storage
- Provides conversion from URI to network URL through the
getNetworkUrlmethod - Ideal for implementing cloud storage services (Firebase, S3, etc.)
- Provides conversion from URI to network URL through the
-
FileProviderMixin: For file system-based storage
- Provides conversion from URI to local file path through the
getFilePathmethod - Ideal for implementing local storage in your app
- Provides conversion from URI to local file path through the
-
AssetProviderMixin: For Flutter assets
- Provides functionality to access assets in Flutter apps
- Ideal for loading resources bundled with the application
Error Handling
XStorage uses the Result<T, E> type for concise and safe error handling:
// Example of file loading
final result = await storage.loadFile(XUri.create('file', 'document.txt'));
// Error handling using switch expression
switch (result) {
case Success(value: final data):
final text = utf8.decode(data);
print('File content: $text');
break;
case Failure(value: final error):
switch (error) {
case FileNotFoundException():
print('File not found');
break;
case AccessDeniedException():
print('Access denied');
break;
default:
print('An error occurred: ${error.message}');
break;
}
break;
}
License
This project is licensed under the MIT License - see the LICENSE file for details.