Sparround

Cache, offline və bir neçə mənbə

Offline dəstəyi bir sinif deyil, bir sıra qərarlardır və hamısı repository-də verilir. Rəsmi offline-first sənədi oxuma üçün üç, yazma üçün iki strategiya təsvir edir.

Oxuma strategiyaları

1. Lokal fallback kimi (Future əsaslı) — əvvəlcə serverdən oxumağa cəhd edilir, alınmasa lokal bazadan verilir. Sadədir, lakin istifadəçi hər dəfə şəbəkəni gözləyir. 2. `Stream` əsaslı — sənəd bunu optimal sayır: repository iki dəyər verir, əvvəlcə lokal bazadaki sürətli məlumat, sonra serverdən gələn yenilənmiş məlumat. İstifadəçi dərhal nəsə görür. 3. Yalnız lokal — oxuma həmişə bazadan gedir, serverlə sinxronizasiya ayrı sync() metodu ilə aparılır. Ən proqnozlaşdırıla bilən variant, lakin sinxronizasiya məntiqi tələb edir.

Yazma strategiyaları

1. Yalnız online — əvvəlcə API, uğurlu olduqda baza. Məlumat həmişə sinxrondur, lakin offline yazmaq mümkün deyil. 2. Offline-first — əvvəlcə baza, sonra API. İstifadəçi offline işləyə bilir; əvəzində sənədin açıq qeyd etdiyi risk yaranır: şəbəkə çağırışı alınmasa, lokal baza və server sinxrondan çıxır.

Sonuncu halın həlli `synchronized` bayrağıdır: hər yazılan qeyd synchronized: false ilə saxlanılır, sonra fon prosesi yalnız bu qeydləri göndərir.

dart
class ProfileRepositoryOfflineFirst implements ProfileRepository {
  ProfileRepositoryOfflineFirst({
    required ApiClientService api,
    required DatabaseService database,
  })  : _api = api,
        _database = database;

  final ApiClientService _api;          // remote service
  final DatabaseService _database;      // lokal service

  // ── OXUMA: Stream əsaslı (rəsmi sənədin optimal saydığı variant) ──
  @override
  Stream<UserProfile> watchProfile() async* {
    // 1) Lokal məlumat dərhal verilir — ekran boş qalmır.
    final local = await _database.fetchUserProfile();
    if (local != null) yield local;

    // 2) Sonra serverdən yenilənmiş məlumat.
    try {
      final remote = await _api.getUserProfile();
      await _database.updateUserProfile(remote);
      yield remote;
    } catch (_) {
      // Şəbəkə yoxdur: lokal məlumat artıq verilib, xəta udulur.
      // Lokal da yoxdursa, çağıran tərəf boş axın alır və bunu idarə edir.
      if (local == null) throw const NetworkFailure();
    }
  }

  // ── YAZMA: offline-first (əvvəlcə lokal, sonra server) ──
  @override
  Future<void> updateProfile(UserProfile profile) async {
    // 1) Lokal yazı dərhal baş verir → UI cavab verir, offline işləyir.
    //    Qeyd sinxron olmayan kimi işarələnir.
    await _database.updateUserProfile(
      profile.copyWith(synchronized: false),
    );

    // 2) Serverə göndərməyə cəhd.
    try {
      await _api.putUserProfile(profile);
      // Uğurlu: bayraq qaldırılır.
      await _database.updateUserProfile(
        profile.copyWith(synchronized: true),
      );
    } catch (_) {
      // Uğursuz: qeyd `synchronized: false` qalır və sonra göndəriləcək.
      // Bu, sənədin açıq qeyd etdiyi "müvəqqəti desinxronizasiya"dır.
    }
  }

  // ── SİNXRONİZASİYA: yalnız göndərilməmiş qeydlər ──
  @override
  Future<void> sync() async {
    final pending = await _database.fetchUnsynchronizedProfiles();
    for (final profile in pending) {
      try {
        await _api.putUserProfile(profile);
        await _database.updateUserProfile(
          profile.copyWith(synchronized: true),
        );
      } catch (_) {
        // Bu qeyd növbəti dəfəyə qalır; digərlərini dayandırmır.
      }
    }
  }
}

`Stream` əsaslı oxuma və offline-first yazma. Diqqət et: iki service (lokal və remote) bir repository tərəfindən idarə olunur.

Strategiyaİstifadəçi nə görürQiymətiNə vaxt seçmək
Oxuma: lokal fallback (`Future`)Şəbəkəni gözləyir; xəta olsa köhnə məlumatƏn sadə; hər açılışda gözləməMəlumat kritik dərəcədə təzə olmalıdır
Oxuma: `Stream` (lokal → remote)Dərhal məlumat, sonra sükutla yenilənməİki dəyər gəldiyi üçün UI iki dəfə qurulurƏksər siyahı və detal ekranları — rəsmi sənədin optimal saydığı variant
Oxuma: yalnız lokal + `sync()`Həmişə dərhal; təzəlik sinxronizasiyadan asılıSinxronizasiya məntiqi və konflikt həlli lazımdırTam offline işləyən tətbiqlər (qeydlər, sahə işi)
Yazma: yalnız onlineOffline yaza bilmirSadə; desinxronizasiya riski yoxdurÖdəniş, sifariş təsdiqi — server sözü sondur
Yazma: offline-first + `synchronized`Dərhal cavab, offline işləyirMüvəqqəti desinxronizasiya + sinxronizasiya koduQeydlər, favoritlər, ayarlar — konflikt riski aşağı

Kim nəyi bilir. Bu quruluşda məsuliyyət bölgüsü dəyişmir:

  • DatabaseService — SQL və ya açar-dəyər əməliyyatları. Rəsmi sənədlər bunun üçün iki ayrı dizayn pattern-i verir: kiçik məlumat üçün açar-dəyər, siyahılar üçün SQL.
  • ApiClientService — REST çağırışları.
  • Repository — hansı mənbədən oxumaq, nə vaxt yazmaq, nə vaxt sinxronlaşdırmaq. Bütün strategiya burada.
  • UI — heç nə. Ekran Stream-ə abunə olur və məlumatın hardan gəldiyini bilmir.

Sinxronizasiyanı nə tetikləyir. Rəsmi sənəd Timer ya da fon prosesləri (workmanager kimi paketlər) göstərir. Praktikada üç tetikleyici birlikdə işlədilir: tətbiq açıldıqda, şəbəkə qayıtdıqda (ConnectivityService), və istifadəçi "yenilə" hərəkəti etdikdə.

Konflikt. İki tərəf eyni qeydi dəyişdikdə kimin qazandığını bilərəkdən seçmək lazımdır: server qazanır (sadə), lokal qazanır (riskli), ya da vaxt möhürünə görə. Bu qərar da repository-dədir və onun testi yazılmalıdır — əks halda "məlumatım itdi" tipli baqlar yaranır.

Ən çox rast gəlinən səhv: offline dəstəyini UI-da qurmağa çalışmaq — hər ekranda if (hasConnection) ... else ... yazmaq. Nəticədə hər ekran öz strategiyasını daşıyır və davranış ekranlar arası fərqlənir.

Düzgün yanaşma: UI şəbəkə statusunu bilmir. O, yalnız məlumatı və (lazımdırsa) "sinxronlaşdırılmayıb" bayrağını göstərir.

Praktika. Bir feature-i offline-first-ə keçir: Stream əsaslı oxuma + synchronized bayraqlı yazma. Sonra iki test yaz: (1) şəbəkə yoxdursa oxuma lokal məlumatı verir, (2) yazma uğursuz olduqda qeyd synchronized: false qalır və sync() onu sonra göndərir.

Hazır sayılır: aviarejimdə tətbiq məlumatı göstərir və yazmağa imkan verir; şəbəkə qayıtdıqdan sonra sync() qeydi göndərir.

📚 Mənbələr və sənədlər

  • Offline-first dəstəyirəsmidocs.flutter.dev

    Bu mövzunun mənbəyi: üç oxuma, iki yazma strategiyası və `synchronized` bayrağı.

  • SQL saxlama pattern-irəsmidocs.flutter.dev

    Lokal service-in siyahı və əlaqəli məlumat üçün qurulması.

  • Açar-dəyər saxlama pattern-irəsmidocs.flutter.dev

    Ayarlar və kiçik məlumat üçün — `shared_preferences` səviyyəsində.

  • Optimistic state pattern-irəsmidocs.flutter.dev

    Yazma zamanı nəticəni gözləmədən UI-ı yeniləmək — offline-first ilə birlikdə işlədilir.