Gereksinimler ve Ön Hazırlık
İleri düzey bir dosya yükleme sistemi için Flutter SDK'nın güncel sürümünü kullanmalısınız. Projenize eklemeniz gereken temel kütüphaneler, performans ve güvenlik açısından kritik öneme sahiptir. "Dio" kütüphanesi, gelişmiş interceptor (araya girme) desteği ve dosya yükleme kolaylığı nedeniyle endüstri standardıdır.
- Dio: HTTP isteklerini yönetmek için.
- File_picker: Cihazdan dosya seçimi için.
- Connectivity_plus: Ağ durumunu kontrol etmek için.
- Path_provider: Dosya yollarını yönetmek için.
Projenizin pubspec.yaml dosyasına şu bağımlılıkları ekleyerek işe başlayın:
dependencies:
dio: ^5.7.0
file_picker: ^8.0.0
connectivity_plus: ^6.0.0
path_provider: ^2.1.0
Dosya Seçimi ve İzin Yönetimi
Mobil platformlarda dosya seçimi, Android ve iOS için farklı izin yapıları gerektirir. Kullanıcının galerisine veya dosya sistemine erişmeden önce, uygulamanın gerekli izinleri aldığından emin olmalısınız. Aksi takdirde, uygulama çalışma zamanında (runtime) hata verecektir.
Aşağıdaki kod bloğu, file_picker kullanarak güvenli bir dosya seçme işlemini göstermektedir:
Future dosyaSec() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'pdf', 'png'],
);
if (result != null) {
return File(result.files.single.path!);
}
return null;
}
Bu fonksiyon, sadece belirtilen uzantıdaki dosyaların seçilmesine izin vererek, sunucunuza gereksiz veya zararlı dosya tiplerinin gönderilmesini en baştan engellemiş olur.
Dio ile Çok Parçalı (Multipart) Yükleme
İleri düzey dosya yükleme işlemlerinde MultipartFile kullanımı zorunludur. Bu yöntem, dosyayı küçük parçalara bölerek sunucuya iletir ve büyük dosyaların bellek (RAM) tüketimini optimize eder. Dio kütüphanesi, bu süreci oldukça basitleştirir.
import 'package:dio/dio.dart';
Future dosyaYukle(File file) async {
String fileName = file.path.split('/').last;
FormData formData = FormData.fromMap({
"file": await MultipartFile.fromFile(file.path, filename: fileName),
});
Dio dio = Dio();
var response = await dio.post(
"https://api.ornek.com/upload",
data: formData,
onSendProgress: (int sent, int total) {
print("Yükleme durumu: ${(sent / total * 100).toStringAsFixed(0)}%");
},
);
}
onSendProgress parametresi, kullanıcıya yükleme çubuğu (progress bar) göstermek için vazgeçilmezdir. Bu geri bildirim, kullanıcı deneyimini doğrudan iyileştirir.
Dosya Yükleme Yöntemlerinin Karşılaştırılması
Aşağıdaki tablo, farklı dosya yükleme yaklaşımlarının avantaj ve dezavantajlarını özetlemektedir:
| Yöntem | Avantaj | Dezavantaj |
|---|---|---|
| Multipart/form-data | Standart, yaygın destek | Büyük dosyalarda timeout riski |
| Base64 Encoding | JSON içinde kolay iletim | Dosya boyutunu %33 artırır |
| Chunked Upload | Hata durumunda kaldığı yerden devam | Karmaşık sunucu mantığı gerektirir |
Arka Planda Yükleme ve Hata Yönetimi
Kullanıcı uygulamadan çıksa bile yüklemenin devam etmesi, "İleri Düzey" bir sistemin olmazsa olmazıdır. Bunun için flutter_background_upload gibi kütüphaneler veya işletim sisteminin sunduğu "WorkManager" benzeri servisler kullanılmalıdır. Ayrıca, ağ kesintileri için bir "Retry" (tekrar deneme) mekanizması kurmalısınız.
// Dio ile otomatik tekrar deneme mantığı
dio.interceptors.add(
InterceptorsWrapper(
onError: (DioException e, handler) async {
if (e.response?.statusCode == 500) {
// Hata durumunda 3 saniye bekleyip tekrar dene
await Future.delayed(Duration(seconds: 3));
return handler.resolve(await dio.fetch(e.requestOptions));
}
return handler.next(e);
},
),
);
Güvenlik Uyarısı: Sunucu tarafında dosya yükleme uç noktalarınızı (endpoint) korumayı unutmayın. Sadece yetkili kullanıcıların dosya yüklemesine izin verin ve yüklenen dosyaları mutlaka bir virüs taramasından geçirin. Asla kullanıcı tarafından sağlanan dosya isimlerini doğrudan dosya sisteminde kullanmayın; benzersiz ID'ler (UUID) oluşturun.
Dosya Güvenliği ve Doğrulama
Dosya yükleme sistemlerinde en büyük güvenlik açığı, kötü niyetli kullanıcıların sunucuya çalıştırılabilir dosyalar (PHP, EXE, SH gibi) göndermesidir. Bunu engellemek için sadece dosya uzantısını değil, dosyanın "MIME Type" (içerik tipi) verisini kontrol etmelisiniz.
import 'package:mime/mime.dart';
bool dosyaTipiGecerliMi(String path) {
final mimeType = lookupMimeType(path);
final izinVerilenler = ['image/jpeg', 'image/png', 'application/pdf'];
return izinVerilenler.contains(mimeType);
}
Bu kontrol, uygulamanızın güvenliğini bir üst seviyeye taşır. Sunucu tarafında da aynı kontrolü (Server-side validation) yapmanız, "Savunma Odaklı Tasarım" (Defense in Depth) prensibi gereğidir.
Sıkça Sorulan Sorular
Dosya yükleme sırasında uygulama donuyor, ne yapmalıyım?
Dosya okuma ve ağ istekleri ana iş parçacığında (UI Thread) yapılmamalıdır. Dio zaten asenkron çalışır, ancak büyük dosya işleme süreçlerinde compute fonksiyonu ile işlemleri izole bir izlek üzerinde gerçekleştirebilirsiniz.
Yükleme işlemi %99'da kalıyor, nedeni nedir?
Bu genellikle sunucu tarafındaki "Request Timeout" veya "Max Upload Size" sınırlarından kaynaklanır. Sunucu yapılandırmanızı (Nginx client_max_body_size gibi) kontrol edin.
İnternet koptuğunda yükleme nasıl devam eder?
Bunun için "Chunked Upload" (parçalı yükleme) mimarisi kullanmalısınız. Dosyayı 1MB'lık parçalara bölüp sunucuya gönderin ve her başarılı parçadan sonra sunucudan onay alın.
Dosya isimleri çakışırsa ne olur?
Sunucuya dosya gönderirken orijinal ismi kullanmayın. Uuid().v4() kullanarak rastgele ve benzersiz isimler oluşturun ve orijinal ismi veritabanında tutun.
HTTPS kullanımı zorunlu mu?
Evet, 2026 standartlarında mobil uygulamalarda tüm ağ trafiği HTTPS üzerinden şifrelenmelidir. Aksi takdirde verileriniz "Man-in-the-Middle" saldırılarına açık hale gelir.
Büyük Dosyalar İçin "Chunked Upload" (Parçalı Yükleme) Stratejisi
Mobil uygulamalarda GB seviyesindeki videoları veya yüksek çözünürlüklü görselleri tek seferde yüklemeye çalışmak, bellek (RAM) sorunlarına ve bağlantı zaman aşımlarına yol açar. Profesyonel çözüm, dosyayı küçük parçalara (chunk) bölerek sunucuya göndermektir. Bu yöntem, yükleme yarıda kesilse bile sadece son parçadan devam etmenize olanak tanır.
Parçalı Yükleme Mantığı
Dosyayı 1MB'lık parçalara bölmek için RandomAccessFile sınıfını kullanırız. Her parça gönderildiğinde sunucudan onay bekleriz. İşte bir dosyanın parçalara bölünerek gönderilme mantığı:
import 'dart:io';
Future uploadInChunks(File file, String url) async {
final int chunkSize = 1024 * 1024; // 1MB
final int fileSize = await file.length();
final List chunks = List.generate((fileSize / chunkSize).ceil(), (i) => i);
for (int chunkIndex in chunks) {
final start = chunkIndex * chunkSize;
final end = (start + chunkSize > fileSize) ? fileSize : start + chunkSize;
final List bytes = await file.openSync().readSync(end - start);
// Dio veya HTTP isteği ile parçayı gönder
await sendChunkToServer(bytes, chunkIndex, url);
}
}
Yükleme Performansını İzleme ve İstatistiksel Analiz
Kullanıcıya sadece "yükleniyor" demek yerine, profesyonel bir deneyim sunmak için anlık hız (KB/s) ve tahmini kalan süreyi (ETA) hesaplamanız gerekir. Bu veriler, uygulamanızın ağ trafiği optimizasyonu için kritik öneme sahiptir.
Anlık Hız Hesaplama Fonksiyonu
onSendProgress callback fonksiyonu içerisinden gelen verileri kullanarak bir zaman damgası (timestamp) ile hız hesaplayabilirsiniz:
double lastProgress = 0;
DateTime lastTime = DateTime.now();
void calculateSpeed(int sent, int total) {
final now = DateTime.now();
final duration = now.difference(lastTime).inSeconds;
if (duration > 0) {
final bytesSent = sent - lastProgress;
final speed = bytesSent / duration;
print("Anlık Hız: ${(speed / 1024).toStringAsFixed(2)} KB/s");
lastProgress = sent.toDouble();
lastTime = now;
}
}
Performans Optimizasyonu İçin İpuçları
- Bağlantı Türü Kontrolü: Kullanıcı hücresel veri (4G/5G) kullanıyorsa, dosya boyutunu otomatik sıkıştırarak (compression) yükleme yapın.
- Isolate Kullanımı: Dosya okuma ve parçalama işlemlerini ana UI thread'inden ayırarak uygulamanın donmasını engelleyin.
- Önbellekleme (Caching): Başarısız olan yüklemelerin parçalarını yerel bir veritabanında (SQLite veya Hive) tutarak, uygulama yeniden başlatıldığında yüklemeye devam etmesini sağlayın.
Dikkat: Parçalı yükleme yaparken sunucu tarafında parçaların doğru sırada birleştirildiğinden emin olmak için her parçaya bir "sequence number" (sıra numarası) ve dosya için benzersiz bir "session ID" atamanız şarttır.
Sonuç
Flutter ile ileri düzey dosya yükleme sistemi kurmak, ağ yönetimi, güvenlik önlemleri ve kullanıcı deneyimi optimizasyonunun bir birleşimidir. Bu rehberde öğrendiğiniz Multipart kullanımı, hata yönetimi, MIME tipi kontrolü ve asenkron işlemler, profesyonel mobil uygulamaların temelini oluşturur. Bir sonraki adım olarak, yüklenen dosyaların sunucu tarafında işlenmesi ve CDN (İçerik Dağıtım Ağı) üzerinden sunulması konularını araştırmanızı öneririm.
Yasal Sorumluluk Reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Uygulamanızın üretim ortamına (production) geçmeden önce, tüm güvenlik açıklarını (SQL injection, XSS, dosya yükleme zafiyetleri) bir güvenlik uzmanı ile gözden geçirin. Yazılım güvenliği sürekli güncellenen bir süreçtir.


Yorumlar (0)
Yorum Yaz