Gereksinimler ve Ön Hazırlık
Projeye başlamadan önce geliştirme ortamınızın güncel olduğundan emin olmalısınız. Flutter 3.x ve üzeri sürümler, modern asenkron programlama yetenekleriyle bu süreci yönetmek için oldukça elverişlidir. İhtiyacımız olan temel paketler şunlardır:
- sqflite: Yerel veritabanı işlemleri için standart kütüphane.
- connectivity_plus: İnternet bağlantı durumunu takip etmek için kullanılır.
- dio: Sunucu ile HTTP isteklerini yönetmek için tercih edilen modern istemci.
- path_provider: Veritabanı dosyasının cihazdaki konumunu bulmak için gereklidir.
Projenizin pubspec.yaml dosyasına bu bağımlılıkları ekleyerek işe başlayın. Güvenlik açısından, yerel veritabanınızda hassas veriler tutuyorsanız mutlaka sqflite_sqlcipher gibi şifreleme destekli kütüphaneleri değerlendirmelisiniz.
Adım 1: Yerel Veritabanı Mimarisini Kurma
Verilerin çevrimdışı saklanması için ilk adım, bir yerel veritabanı katmanı oluşturmaktır. SQLite, mobil cihazlarda ilişkisel verileri yönetmek için en güvenilir yöntemdir. Aşağıdaki kod bloğu, veritabanı bağlantısını başlatan temel sınıfı göstermektedir.
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class LocalDatabase {
static final LocalDatabase instance = LocalDatabase._init();
static Database? _database;
LocalDatabase._init();
Future get database async {
if (_database != null) return _database!;
_database = await _initDB('app_data.db');
return _database!;
}
Future _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(path, version: 1, onCreate: _createDB);
}
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE sync_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payload TEXT NOT NULL,
is_synced INTEGER DEFAULT 0
)
''');
}
}
Bu yapıda sync_queue tablosu, henüz sunucuya gönderilmemiş verileri tutan bir kuyruk görevi görür. is_synced sütunu, verinin senkronize edilip edilmediğini kontrol eden bir bayrak (flag) olarak çalışır.
Adım 2: Çevrimdışı Veri Ekleme ve Kuyruğa Alma
Kullanıcı bir veri girdiğinde, uygulama önce internetin olup olmadığını kontrol etmelidir. Eğer internet yoksa, veri doğrudan yerel veritabanına "beklemede" (is_synced = 0) olarak kaydedilir. Bu, kullanıcının veri kaybı yaşamasını engeller.
Future saveOfflineData(String data) async {
final db = await LocalDatabase.instance.database;
await db.insert('sync_queue', {
'payload': data,
'is_synced': 0,
});
}
Bu fonksiyon, veriyi yerel tabloya ekler. Kullanıcı arayüzünde bu işlem anında tamamlanır, böylece uygulama donmaz ve kullanıcıya "İnternet yok, bağlantı gelince gönderilecek" mesajı gösterilebilir.
Adım 3: İnternet Bağlantısını İzleme ve Tetikleme
connectivity_plus paketi, cihazın internete bağlanıp bağlanmadığını dinlememize olanak tanır. Bağlantı durumu değiştiğinde, arka planda senkronizasyon işlemini tetikleyen bir "listener" (dinleyici) kurmalıyız.
import 'package:connectivity_plus/connectivity_plus.dart';
void listenToConnection() {
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if (result != ConnectivityResult.none) {
// İnternet var, senkronizasyonu başlat
syncDataWithServer();
}
});
}
Bu dinleyici, uygulama açık olduğu sürece arka planda çalışır. İnternet kesilip geldiği anda syncDataWithServer() fonksiyonunu tetikleyerek verilerin sunucuya aktarılmasını sağlar.
Adım 4: Sunucu Senkronizasyon Mantığı
Senkronizasyon başladığında, yerel veritabanındaki is_synced = 0 olan tüm kayıtlar çekilir ve sunucuya gönderilir. Başarılı olanlar güncellenir veya silinir.
Future syncDataWithServer() async {
final db = await LocalDatabase.instance.database;
final pendingData = await db.query('sync_queue', where: 'is_synced = 0');
for (var item in pendingData) {
try {
final response = await dio.post('/api/sync', data: item['payload']);
if (response.statusCode == 200) {
await db.update('sync_queue', {'is_synced': 1}, where: 'id = ?', whereArgs: [item['id']]);
}
} catch (e) {
print("Senkronizasyon hatası: $e");
}
}
}
Burada dio kullanarak HTTP POST isteği gönderiyoruz. Başarılı olan her işlemden sonra veritabanındaki kayıt işaretlenir. Bu, mükerrer veri gönderimini önleyen en temel yöntemdir.
Veri Senkronizasyon Yöntemlerinin Karşılaştırılması
| Yöntem | Avantajı | Dezavantajı |
|---|---|---|
| Yerel Kuyruk (SQLite) | Veri kaybı sıfır, güvenli. | Karmaşık kod yapısı gerektirir. |
| Shared Preferences | Basit, hızlı. | Büyük veriler için uygun değil. |
| Firebase Firestore | Dahili çevrimdışı desteği. | Maliyetli ve bağımlı yapı. |
Kritik Uyarılar ve Güvenlik
Güvenlik Uyarısı: Yerel veritabanına kaydedilen veriler, cihazın dosya sisteminde açık metin olarak saklanabilir. Hassas veriler (şifre, kişisel bilgiler) için mutlaka şifreleme (AES-256) kullanın. Ayrıca, sunucuya gönderilen verilerde XSS ve SQL Injection saldırılarına karşı sunucu tarafında her zaman doğrulama yapın. Asla istemci tarafındaki (client-side) doğrulamaya güvenmeyin.
Sıkça Sorulan Sorular
Çevrimdışı veriler ne kadar süre saklanmalı?
Verilerin saklanma süresi uygulamanın doğasına bağlıdır. Ancak, senkronizasyon başarılı olduktan sonra yerel kayıtları silmek veya "arşivlendi" olarak işaretlemek, cihazın depolama alanını optimize etmek için önemlidir.
Uygulama kapalıyken senkronizasyon devam eder mi?
Standart bir Flutter uygulamasında uygulama kapalıyken işlem yapmak için workmanager gibi arka plan işleyici paketlerine ihtiyaç duyarsınız. Bu rehber, uygulama açıkken senkronizasyona odaklanmıştır.
Çakışan veriler (Conflict) nasıl yönetilir?
Eğer hem yerelde hem sunucuda aynı anda değişiklik yapıldıysa, "en son yapılan değişiklik geçerlidir" (last write wins) kuralı veya sunucu tarafında bir versiyon numarası takibi kullanmalısınız.
Düşük internet hızında senkronizasyon nasıl yönetilir?
Verileri parçalara (batch) ayırarak gönderin. Tek bir büyük istek yerine, 10'arlı gruplar halinde gönderim yapmak bağlantı kopmalarında veri kaybını minimize eder.
SQL Injection'dan nasıl korunurum?
sqflite kütüphanesinde sorgu parametrelerini (whereArgs) kullanarak verileri güvenli bir şekilde işleyin. Asla kullanıcıdan gelen veriyi doğrudan SQL sorgusunun içine string olarak birleştirmeyin.
İleri Seviye Hata Ayıklama ve Loglama Stratejileri
Çevrimdışı senkronizasyon sistemlerinde en büyük zorluk, kullanıcı cihazında gerçekleşen ancak sunucuya ulaşmayan verilerin izini sürmektir. Karmaşık ağ koşullarında "sessiz başarısızlıklar" yaşanabilir. Bu süreci yönetmek için yerel bir loglama mekanizması kurmak hayati önem taşır.
Senkronizasyon hatalarını takip etmek için sync_logs tablosu oluşturarak her hata durumunu kod bazında yakalayabilirsiniz:
Future logSyncError(String operation, String error, String payload) async {
final db = await database;
await db.insert('sync_logs', {
'operation': operation,
'error_message': error,
'payload': payload,
'timestamp': DateTime.now().toIso8601String(),
});
}
Bu loglar, uygulama içinde bir "Hata Raporu" ekranı oluşturarak kullanıcıdan geri bildirim almanızı veya teknik ekibin sorunu analiz etmesini sağlar. Özellikle 401 Unauthorized veya 403 Forbidden gibi yetkilendirme hatalarını bu loglardan takip ederek, token yenileme mekanizmanızın düzgün çalışıp çalışmadığını doğrulayabilirsiniz.
Performans Optimizasyonu: Batch Sync ve Delta Güncellemeleri
Veri setiniz büyüdükçe, her senkronizasyon adımında tüm veritabanını sunucuya göndermek ciddi bir bant genişliği israfına ve pil tüketimine yol açar. Profesyonel uygulamalarda "Delta Güncellemesi" (yalnızca değişen veriyi gönderme) yöntemi tercih edilir.
Performansı artırmak için uygulayabileceğiniz temel stratejiler şunlardır:
- Batch Processing: Verileri tek tek göndermek yerine, 50'li veya 100'lü paketler halinde (batch) gönderin. Bu, HTTP istek sayısını ciddi oranda azaltır.
- Pagination (Sayfalama): Sunucudan veri çekerken
limitveoffsetkullanarak sadece ihtiyaç duyulan veriyi yerel veritabanına işleyin. - Last Updated At: Her tabloya
updated_atsütunu ekleyin. Sunucuya sadece son senkronizasyondan bu yana değişen kayıtları sorgulayın.
Aşağıdaki örnek, yerel veritabanındaki "bekleyen" verileri paketler halinde sunucuya gönderen bir döngü yapısını göstermektedir:
Future syncPendingDataInBatches() async {
final db = await database;
List pendingItems = await db.query(
'sync_queue',
limit: 50
);
if (pendingItems.isNotEmpty) {
bool success = await apiService.sendBatch(pendingItems);
if (success) {
// Başarılı olanları kuyruktan temizle
final ids = pendingItems.map((e) => e['id']).toList();
await db.delete('sync_queue', where: 'id IN (${ids.join(',')})');
}
}
}
Bu yöntem, özellikle düşük internet hızına sahip bölgelerde uygulamanın donmasını engeller ve arka plan işlemlerinin daha kararlı çalışmasını sağlar. Ayrıca, WorkManager veya flutter_background_fetch gibi kütüphanelerle bu işlemi uygulama kapalıyken de belirli aralıklarla tetikleyerek kullanıcı deneyimini mükemmelleştirebilirsiniz.
Sonuç
Flutter ile çevrimdışı çalışan veri senkronizasyon sistemi kurmak, uygulamanızın profesyonellik seviyesini bir üst noktaya taşır. SQLite kullanarak verileri yerelde güvenle saklamak, bağlantı durumunu izlemek ve arka planda optimize edilmiş bir şekilde sunucuyla eşleştirmek, kullanıcıya kesintisiz bir deneyim sunar. Bir sonraki adım olarak, büyük ölçekli veriler için isar veya drift gibi daha modern ve performanslı veritabanı kütüphanelerini inceleyerek sisteminizi daha da hızlandırabilirsiniz.
Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce hata yönetimi, veri validasyonu ve güvenlik testlerini (penetrasyon testleri) mutlaka tamamlayınız.

Yorumlar (0)
Yorum Yaz