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.
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ür | Qiyməti | Nə 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ır | Tam offline işləyən tətbiqlər (qeydlər, sahə işi) |
| Yazma: yalnız online | Offline yaza bilmir | Sadə; desinxronizasiya riski yoxdur | Ödəniş, sifariş təsdiqi — server sözü sondur |
| Yazma: offline-first + `synchronized` | Dərhal cavab, offline işləyir | Müvəqqəti desinxronizasiya + sinxronizasiya kodu | Qeydlə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.