• 16.08.2026 04:09:41
  • Admin Admin

Flutter mobil uygulama geliştirme projelerinde Drift, işlem günlüğü ve idempotent API tasarımıyla çevrimdışı değiğşiklikleri güvenilir biçimde senkronize etmeyi; performans sorunlarını ölçerek çözmeyi ele alın.

Flutter Mobil Uygulama Geliştirmede Offline-First Senkronizasyon

Flutter mobil uygulama geliştirmede offline-first veri sözleşmesi

Offline-first tasarımda istemci, ağ yokken de yazma işlemini kabul eder; bu nedenle SQLite tablosundaki iş kaydı (operation log), ekrandaki nesne tablosundan ayrı tutulmalıdır. Drift ile her mutasyon için değişmeyen bir operationId, sıralama için istemci üretimli bir HLC/Lamport değeri ve silmeler için tombstone alanı saklayın. Sadece updatedAt ile çatışma çözmek güvenilir değildir: cihaz saati geri kalabilir, iki cihaz aynı kaydı eşzamanlı değiştirebilir.

class Todos extends Table {
  TextColumn get id => text()();
  TextColumn get title => text()();
  BoolColumn get deleted => boolean().withDefault(const Constant(false))();
  IntColumn get revision => integer()(); // Sunucunun doğrusal revizyonu
  DateTimeColumn get updatedAt => dateTime()();
  @override
  Set get primaryKey => {id};
}

class PendingOps extends Table {
  TextColumn get operationId => text()(); // UUID v4, tekrar gönderimde aynı kalır
  TextColumn get entityId => text()();
  TextColumn get type => text()(); // upsert | delete
  TextColumn get payloadJson => text()();
  IntColumn get baseRevision => integer()();
  DateTimeColumn get createdAt => dateTime()();
  @override
  Set get primaryKey => {operationId};
}

Kullanıcı bir görevi düzenlediğinde tek bir Drift transaction içinde hem Todos satırını hem PendingOps satırını yazın. Uygulama, iki insert arasındaki anda öldürülürse yalnızca görünüm güncellenmiş ama senkronize edilemeyecek bir değişiklik üretmemiş olursunuz. Bu sınır, flutter eğitimi içeriğinde çoğu zaman atlanan ama üretimde veri kaybını belirleyen ayrıntıdır.

flutter state management katmanını senkron kuyruktan ayırmak

flutter state management için Riverpod, Bloc veya benzeri bir araç seçmek tek başına yeterli değildir; kritik sınır, widget'ın ağ sonucunu beklemeden yalnızca yerel veritabanı akışını izlemesidir. Aşağıdaki Riverpod sağlayıcısı Drift'in watch() akışını ekrana verir; komutlar ise repository üzerinden transaction başlatır. Böylece aynı görevi iki ekrandan güncellemek, geçici iki farklı UI state üretmez.

final todoRepositoryProvider = Provider((ref) => TodoRepository(ref.watch(appDbProvider)));

final todosProvider = StreamProvider.autoDispose>((ref) {
  return ref.watch(todoRepositoryProvider).watchActive();
});

class TodoRepository {
  TodoRepository(this.db);
  final AppDb db;

  Future rename(String id, String title) => db.transaction(() async {
    final current = await (db.select(db.todos)..where((t) => t.id.equals(id))).getSingle();
    final opId = const Uuid().v4();
    await db.into(db.todos).insertOnConflictUpdate(current.copyWith(
      title: title, updatedAt: DateTime.now(),
    ));
    await db.into(db.pendingOps).insert(PendingOpsCompanion.insert(
      operationId: opId, entityId: id, type: 'upsert',
      payloadJson: jsonEncode({'title': title}), baseRevision: current.revision,
      createdAt: DateTime.now(),
    ));
  });
}

Yaygın hata: AsyncNotifier içindeki listeyi ağdan gelen yanıtla doğrudan değiştirmektir. Bu yaklaşım, kuyrukta henüz gönderilmemiş bir yerel düzenlemeyi eski sunucu yanıtıyla ezebilir. Notifier yalnızca yükleme/hata durumu taşısın; liste için tek doğruluk kaynağı yerel SQLite olsun. Bir flutter kursu laboratuvarında bunu doğrulamak için uygulamayı uçak modunda açın, aynı kaydı üç kez düzenleyin, uygulamayı kapatıp açın ve üç işlemin kuyrukta kaldığını SQL sorgusuyla denetleyin.

İdempotent API ve çatışma çözümüyle cross platform mobil uygulama geliştirme

Senkronizasyon isteği, bağlantı zaman aşımında sunucu işlemi tamamlamış olsa bile istemciye yanıt dönmeyebilir. İstemci bu durumda aynı operationId ile yeniden dener; sunucu bu anahtarı benzersiz indeksle saklayıp daha önce ürettiği sonucu dönmelidir. HTTP isteğinde Idempotency-Key ve kaydın görülen son sunucu revizyonunu taşıyın; platform fark etmeksizin Android ve iOS istemcileri aynı protokolü uygular.

POST /v1/sync/operations HTTP/1.1
Authorization: Bearer 
Idempotency-Key: 8e4d7d52-7c8d-4a9f-a4fb-7d89bd3d8241
Content-Type: application/json

{
  "entityId": "todo_42",
  "type": "upsert",
  "baseRevision": 17,
  "payload": { "title": "Teknik tasarımı gözden geçir" }
}

Sunucu baseRevision güncel değilse körlemesine last-write-wins yapmak yerine 409 Conflict ile mevcut nesneyi ve revizyonunu döndürün. Başlık alanında LWW kabul edilebilirken, sayaçta toplama ve etiketlerde OR-Set gibi alan-bazlı kurallar gerekebilir. İstemci 409 aldığında uzak nesneyi transaction ile yazar, yerel işlemi yeni baseRevision ile yeniden üretir; eski operationId'yi tekrar kullanmaz çünkü semantiği artık değişmiştir. Bu ayrım, cross platform mobil uygulama geliştirme sürecinde 'retry' ile 'yeni komut'u karıştırmayı önler.

Flutter senkronizasyon performansını DevTools ve SQLite planıyla ölçmek

Kuyruk büyüdüğünde sorun çoğu zaman HTTP değil, indekslenmemiş yerel sorgudur. Flutter DevTools'ta Performance görünümünü açın, uçak modunda 5.000 bekleyen işlem oluşturup senkronizasyonu başlatın; UI thread'deki 16,6 ms üzeri frame'leri ve Timeline içindeki JSON serileştirme bloklarını kaydedin. Ardından Android Studio Profiler veya Xcode Instruments ile aynı senaryoda disk I/O'yu, SQLite tarafında ise EXPLAIN QUERY PLAN çıktısını karşılaştırın.

-- İndeks yoksa SQLite tüm kuyruğu tarayabilir.
EXPLAIN QUERY PLAN
SELECT * FROM pending_ops
WHERE type != 'failed'
ORDER BY created_at
LIMIT 100;

CREATE INDEX IF NOT EXISTS pending_ops_sync_idx
ON pending_ops(type, created_at);

Ölçümü 'önce/sonra' aynı cihaz, aynı 5.000 kayıt ve aynı 100'lük batch ile yapın: örneğin indeks eklemeden önce sorgu süresi ve 99. yüzdelik frame süresini, ekledikten sonra tekrar kaydedin; sayılar cihazdan cihaza değişeceği için mutlak değerden çok farkı CI artefact'ında saklayın. Büyük payload'ları ana isolate'ta jsonDecode etmek frame kaçırıyorsa ayrıştırmayı Isolate.run(() => decodeBatch(body)) içine taşıyın. Bunun mekanizması nettir: Dart ana isolate'ı hem platform mesajlarını hem raster hazırlığını yürüttüğünden, uzun CPU işi input ve build aşamasını bekletir. dart programlama eğitimi kapsamında isolate anlatılırken, isolate'a SQLite bağlantısı değil yalnızca serileştirilebilir veri gönderilmesi gerektiği de test edilmelidir.

Arka plan çalışması, token yenileme ve silinmiş kayıtlar

Arka plan gönderimi için workmanager gibi bir eklenti kullanılsa bile işletim sistemi işi tam zamanında çalıştırmayı garanti etmez; bu yüzden uygulama açılışında ve ağ geri geldiğinde de aynı SyncEngine.runOnce() çağrılmalıdır. Her çalışmada en fazla 100 operasyon ve 10 saniyelik istemci zaman aşımı kullanın; başarısızlıklarda 1, 2, 4, 8 dakika şeklinde üst sınırı olan exponential backoff uygulayın. 401 yanıtında erişim token'ını güvenli depodan yenileyin, ancak refresh token'ı Drift veritabanına veya operation payload'ına asla yazmayın; flutter_secure_storage bu ayrım için uygundur.

Silinen kayıtların tombstone'unu sunucudan ilk senkronizasyonda temizlemek eski bir cihazın 'silinmiş' nesneyi yeniden canlandırmasına yol açar. Sunucu, tüm aktif cihazların senkron cursor'ları tombstone revizyonunu geçtiğinde veya belirlenmiş bir saklama süresi sonunda fiziksel silme yapmalıdır. Test için Charles Proxy ya da mitmproxy ile yanıtı kesip istemcinin isteği tekrar gönderdiği senaryoyu çalıştırın; sunucuda aynı operationId için tek değişiklik ve istemcide tek kuyruk temizleme beklenir. Bu tür hata enjeksiyonu, flutter eğitimi projelerinde mutlu yol testinden daha değerlidir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management ile offline-first kuyruk nasıl birlikte çalışır?

Riverpod veya Bloc, Drift'ten gelen salt-okunur akışı UI'a sunmalı; yazma komutu ise tek transaction içinde nesneyi ve pending_ops kaydını oluşturmalıdır. Ağ yanıtı doğrudan widget state'ini değiştirmemeli, önce yerel veritabanına uygulanmalıdır.

Flutter mobil uygulama geliştirmede senkronizasyon çatışması nasıl test edilir?

İki emülatörü aynı kullanıcıyla açın, ikisini de çevrimdışı yapın ve aynı entity üzerinde farklı güncellemeler üretin. Sırayla çevrimiçi olduktan sonra API'nin 409 cevabını, istemcinin yeni baseRevision ile oluşturduğu işlemi ve son yerel SQLite durumunu integration_test ile doğrulayın.

Dart programlama eğitimi için Flutter isolate kullanımı ne zaman gereklidir?

DevTools Timeline'da JSON çözümleme veya şifreleme ana isolate üzerinde frame budget'ını aşıyorsa Isolate.run kullanın. Küçük payload'larda isolate başlatma ve veri kopyalama maliyeti daha pahalı olabilir; karar için aynı batch boyutunda p50/p95 sürelerini ölçün.

Cross platform mobil uygulama geliştirme için idempotency key neden zorunludur?

Mobil ağda timeout, sunucunun işlemi yapmadığını kanıtlamaz. İstemci aynı operationId'yi yeniden gönderdiğinde sunucu bunu benzersiz kayıttan tanır ve ikinci kez yazmaz; böylece çift oluşturma, iki kez para/puan ekleme veya yinelenen bildirim engellenir.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt