Cloud Storage — رفع الملفات وتنزيلها
Cloud Storage — رفع الملفات وتنزيلها
يوفر Firebase Cloud Storage خدمة تخزين كائنات قابلة للتوسع لحفظ المحتوى الذي ينشئه المستخدمون، مثل الصور ومقاطع الفيديو والصوت والبيانات الثنائية العامة. في Flutter، تعرض حزمة firebase_storage واجهة برمجية نظيفة بلغة Dart تتيح لك رفع الملفات ومتابعة التقدم في الوقت الفعلي واسترداد روابط التنزيل العامة لعرض المحتوى في واجهة المستخدم.
firebase_storage إلى pubspec.yaml وتهيئة Firebase في main(). كما يجب أن تسمح قواعد التخزين في وحدة تحكم Firebase بالقراءة والكتابة للمسارات التي تنوي استخدامها.الحصول على مرجع التخزين
تبدأ كل عملية بـ Reference — وهو مؤشر إلى مسار محدد في حاوية التخزين الخاصة بك. احصل عليه من النسخة الفردية FirebaseStorage.instance:
import 'package:firebase_storage/firebase_storage.dart';
// مرجع الجذر
final storageRef = FirebaseStorage.instance.ref();
// مرجع لمسار متداخل
final imageRef = storageRef.child('users/uid123/avatar.jpg');
// أو بناء المسار الكامل مباشرةً
final fileRef = FirebaseStorage.instance.ref('uploads/documents/resume.pdf');
المرجع Reference مجرد واصف مسار — لا يجلب أي بيانات. تستخدمه كمقبض لاستدعاء أساليب الرفع والتنزيل والبيانات الوصفية.
رفع ملف باستخدام putFile()
استخدم putFile() عندما يكون لديك كائن File محلي (مثل صورة مختارة من المعرض عبر image_picker). تعيد هذه الدالة كائن UploadTask وهو في نفس الوقت Future وStream لأحداث التقدم.
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
Future<String> uploadImageFile(File imageFile, String userId) async {
// بناء مسار تخزين فريد
final fileName = '${DateTime.now().millisecondsSinceEpoch}.jpg';
final ref = FirebaseStorage.instance.ref('avatars/$userId/$fileName');
// تعيين بيانات وصفية اختيارية
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'uploadedBy': userId},
);
// بدء الرفع — تعيد UploadTask
final uploadTask = ref.putFile(imageFile, metadata);
// انتظار الاكتمال والحصول على TaskSnapshot
final snapshot = await uploadTask;
// استرداد رابط التنزيل العام
final downloadUrl = await snapshot.ref.getDownloadURL();
return downloadUrl;
}
رفع بيانات خام باستخدام putData()
عندما يكون لديك Uint8List — مثل مخزن مؤقت لصورة مقصوصة من لوحة رسم، أو بيانات مجلوبة من تدفق شبكي — استخدم putData() بدلاً من putFile(). الواجهة البرمجية متطابقة باستثناء نوع المعامل:
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
Future<String> uploadBytes(Uint8List bytes, String path) async {
final ref = FirebaseStorage.instance.ref(path);
final metadata = SettableMetadata(contentType: 'image/png');
final uploadTask = ref.putData(bytes, metadata);
final snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
}
تتبع تقدم الرفع باستخدام UploadTask
يكشف UploadTask عن تدفق snapshotEvents يُصدر TaskSnapshot عند كل نبضة تقدم. تحتوي كل لقطة على bytesTransferred وtotalBytes، مما يجعل عرض مؤشر التقدم أمراً بسيطاً.
class UploadProgressWidget extends StatefulWidget {
final File file;
const UploadProgressWidget({super.key, required this.file});
@override
State<UploadProgressWidget> createState() => _UploadProgressWidgetState();
}
class _UploadProgressWidgetState extends State<UploadProgressWidget> {
double _progress = 0.0;
String _status = 'Idle';
String? _downloadUrl;
Future<void> _startUpload() async {
final ref = FirebaseStorage.instance
.ref('uploads/${DateTime.now().millisecondsSinceEpoch}.jpg');
final task = ref.putFile(widget.file);
// الاستماع إلى snapshotEvents لمتابعة التقدم
task.snapshotEvents.listen((TaskSnapshot snapshot) {
if (!mounted) return;
setState(() {
_progress = snapshot.bytesTransferred / snapshot.totalBytes;
_status = snapshot.state == TaskState.running
? 'جاري الرفع...'
: snapshot.state.name;
});
});
// انتظار المهمة للحصول على اللقطة النهائية
final finalSnapshot = await task;
final url = await finalSnapshot.ref.getDownloadURL();
if (mounted) {
setState(() {
_downloadUrl = url;
_status = 'تم';
});
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('الحالة: $_status'),
const SizedBox(height: 8),
LinearProgressIndicator(value: _progress),
const SizedBox(height: 8),
if (_downloadUrl != null)
Image.network(_downloadUrl!, height: 200),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _startUpload,
child: const Text('رفع الملف'),
),
],
);
}
}
if (!mounted) return; قبل استدعاء setState() داخل رد نداء غير متزامن أو مستمع تدفق. ربما يكون الودجت قد تخلص منه بحلول وقت إطلاق الحدث، واستدعاء setState() على ودجت غير مركّب يرمي استثناءً.تنزيل الملفات واسترداد الروابط
لعرض الصور أو ربط الملفات في واجهة المستخدم، أسهل نهج هو استرداد رابط تنزيل دائم وتمريره إلى NetworkImage أو فتحه في متصفح. استخدم getDownloadURL() على أي مرجع موجود:
Future<void> loadAvatar(String userId) async {
try {
final ref = FirebaseStorage.instance.ref('avatars/$userId/avatar.jpg');
final url = await ref.getDownloadURL();
// url هو رابط HTTPS كامل — استخدمه في أي مكان
print('رابط الصورة الشخصية: $url');
} on FirebaseException catch (e) {
if (e.code == 'object-not-found') {
print('لم يتم رفع صورة شخصية بعد.');
} else {
rethrow;
}
}
}
لتنزيل البيانات الخام مباشرةً إلى الذاكرة (مثلاً للمعالجة)، استخدم getData():
Future<Uint8List?> downloadBytes(String storagePath) async {
final ref = FirebaseStorage.instance.ref(storagePath);
// تعيد null إذا لم يكن الملف موجوداً؛ مرر حد أقصى للبايتات
final Uint8List? data = await ref.getData(10 * 1024 * 1024); // حد 10 ميغابايت
return data;
}
معالجة الأخطاء وحالات المهمة
يمكن أن تكون مهام الرفع في إحدى الحالات المكشوفة عبر TaskState: running وpaused وsuccess وcanceled وerror. لف مهمتك في try/catch وافحص FirebaseException.code للحصول على رسائل خطأ قابلة للتصرف:
storage/unauthorized— رفضت قواعد التخزين العمليةstorage/object-not-found— المسار المرجعي غير موجودstorage/quota-exceeded— تم الوصول إلى حصة الحاويةstorage/canceled— تم إلغاء المهمة صراحةًstorage/unknown— حدث خطأ غير متوقع في الخادم
الملخص
يتكامل Firebase Cloud Storage بشكل طبيعي مع Flutter من خلال حزمة firebase_storage. سير العمل الرئيسي هو: إنشاء Reference يشير إلى مسار تخزين، استدعاء putFile() أو putData() للحصول على UploadTask، الاشتراك في snapshotEvents لمتابعة التقدم المباشر، واستدعاء getDownloadURL() على اللقطة المُحلَّلة للحصول على رابط جاهز لواجهة المستخدم. تعامل دائماً مع FirebaseException واحترم قواعد الأمان لحماية بيانات المستخدمين.