Flutter mobil uygulama geliştirme projelerinde ETag, Cache-Control ve koşullu GET kullanarak gereksiz JSON indirmeyi azaltan, hesap izolasyonu ve stale-while-revalidate akışını ele alan uygulamalı rehber.
Flutter Mobil Uygulama Geliştirmede ETag Tabanlı HTTP Önbelleği
Flutter mobil uygulama geliştirme için HTTP önbellek sözleşmesini kurmak
Birçok istemci tarafı önbellek hatası Flutter kodundan önce HTTP sözleşmesinde başlar. API, kaynak gövdesinin kriptografik veya sürüm tabanlı temsilcisi olan ETag değerini ve tazelik süresini belirleyen Cache-Control başlığını döndürmelidir. İstemci aynı ETag ile If-None-Match gönderdiğinde sunucu değişiklik yoksa gövdesiz 304 Not Modified döner. Böylece 200 yerine yalnızca yanıt başlıkları taşınır. flutter eğitimi sırasında bu akışı yalnızca 'cache açmak' olarak değil, HTTP durum kodları ve cache key tasarımı birlikte ele alınmalıdır.
Sözleşmeyi uygulamaya geçmeden önce terminalden doğrulayın. İlk komuttaki ETag değerini ikinci komuta koyun; ikinci yanıtta 304 ve boş gövde beklenir. Sunucu 304 ile yanlışlıkla JSON gövdesi gönderirse bazı proxy katmanları bu gövdeyi atar; istemci tasarımınız 304 gövdesine hiçbir zaman bağımlı olmamalıdır.
curl -i https://api.example.com/v1/products/42
# ETag: "product-42-r18"
# Cache-Control: private, max-age=60
curl -i -H 'If-None-Match: "product-42-r18"' https://api.example.com/v1/products/42
# HTTP/2 304
Cache key yalnızca URL olmamalıdır. Ürün fiyatı para birimine, dil başlığına veya oturum sahibine göre değişiyorsa /products/42 anahtarını tek başına kullanmak başka kullanıcının verisini gösterebilir. Sunucuda bu çeşitlilik varsa Vary: Accept-Language, X-Currency üretin; istemcide ise cache key'e kullanıcı kimliği, locale ve seçili para birimini ekleyin. Yetkili yanıtlar için sunucunun Cache-Control: private göndermesi, ara proxy'lerin yanıtı ortak önbelleğe yazmamasını sağlar.
Dio ile ETag ve 304 yanıtını güvenli yönetmek
Dio varsayılan durumda 304 yanıtını başarılı 2xx aralığında görmeyebilir. Bu nedenle validateStatus fonksiyonunda 304'ü açıkça kabul edin. Aşağıdaki örnek, disk katmanını soyutlayan CacheStore ile önceki JSON ve ETag bilgisini saklar. Üretimde flutter_secure_storage erişim belirteci için uygundur, ancak çok sayıda JSON yanıtını oraya yazmak doğru değildir; yanıt gövdeleri için uygulama destek dizininde dosya veya Isar gibi bir yerel veritabanı kullanın.
import 'dart:convert';
import 'package:dio/dio.dart';
class CachedEntry {
const CachedEntry({required this.etag, required this.body, required this.savedAt});
final String? etag;
final String body;
final DateTime savedAt;
}
abstract class CacheStore {
Future<CachedEntry?> read(String key);
Future<void> write(String key, CachedEntry entry);
}
class ProductApi {
ProductApi(this._dio, this._store);
final Dio _dio;
final CacheStore _store;
Future<Map<String, dynamic>> fetchProduct({
required String productId,
required String accountId,
required String locale,
}) async {
final key = 'product:$accountId:$locale:$productId';
final cached = await _store.read(key);
final response = await _dio.get<String>(
'/v1/products/$productId',
options: Options(
responseType: ResponseType.plain,
headers: {
if (cached?.etag != null) 'If-None-Match': cached!.etag!,
'Accept-Language': locale,
},
validateStatus: (status) => status == 200 || status == 304,
),
);
if (response.statusCode == 304) {
if (cached == null) {
throw StateError('304 received but no local representation exists');
}
return jsonDecode(cached.body) as Map<String, dynamic>;
}
final body = response.data!;
await _store.write(
key,
CachedEntry(
etag: response.headers.value('etag'),
body: body,
savedAt: DateTime.now().toUtc(),
),
);
return jsonDecode(body) as Map<String, dynamic>;
}
}
Buradaki kritik edge case, 304 alındığında kaydın bulunmamasıdır. Uygulama verisi temizlenmiş, ancak bir ara katman hatalı 304 üretmiş olabilir. Bu durumda boş model döndürmek yerine hata verip sonraki isteği koşulsuz 200'e zorlayın. Ayrıca ETag değeri tırnaklarıyla saklanmalıdır: HTTP'deki "abc" ile abc aynı sözdizimsel değer değildir. dart programlama eğitimi içeriğinde bu ayrım, null-safe modelleme ve hatalı ağ durumları için iyi bir pratik örnektir.
Flutter state management ile stale-while-revalidate akışı
Ekranın diskten gelen veriyi hemen göstermesi, fakat arka planda koşullu GET çalıştırması için stale-while-revalidate akışı kullanın. Riverpod'da tek bir AsyncValue yerine kaynak bilgisini de taşıyan bir state modeli kullanmak gerekir. Aksi halde arayüz, diskten gelen eski veri ile 304 sonrası doğrulanmış veriyi ayırt edemez. flutter state management katmanının görevi HTTP kararını widget'a taşımak değil, bu iki görünür durumu modellemektir.
sealed class ProductState {
const ProductState();
}
class ProductReady extends ProductState {
const ProductReady(this.product, {required this.isRefreshing, required this.source});
final Product product;
final bool isRefreshing;
final String source; // 'disk', 'network', 'validated'
}
class ProductController extends StateNotifier<ProductState> {
ProductController(this._repository) : super(const ProductLoading());
final ProductRepository _repository;
Future<void> load(String id) async {
final local = await _repository.readLocal(id);
if (local != null) {
state = ProductReady(local, isRefreshing: true, source: 'disk');
} else {
state = const ProductLoading();
}
try {
final result = await _repository.revalidate(id);
state = ProductReady(
result.product,
isRefreshing: false,
source: result.wasNotModified ? 'validated' : 'network',
);
} catch (error, stackTrace) {
if (local == null) {
state = ProductFailure(error, stackTrace);
} else {
state = ProductReady(local, isRefreshing: false, source: 'disk');
}
}
}
}
Mutasyonlardan sonra yalnızca ilgili kaydı silmek yerine sunucunun yeni ETag değerini döndürmesini tercih edin. Örneğin PATCH /products/42 başarılı 200 dönüyorsa gövdeyi ve yanıt ETag'ini aynı veritabanı transaction'ında güncelleyin. İstemci eski ETag ile sonraki GET'i yaparsa gereksiz 200 alır. Daha önemlisi, eşzamanlı düzenlemede PATCH isteğine If-Match eklemek sunucunun 412 Precondition Failed ile kayıp güncellemeyi reddetmesini sağlar; bu durum ekranda 'yeniden yükle ve karşılaştır' akışına çevrilmelidir.
Cross platform mobil uygulama geliştirme sürecinde ölçüm ve doğrulama
Bu değişikliği 'daha hızlı hissettiriyor' diye kabul etmeyin. Android Studio veya VS Code içinden Flutter DevTools'un Network görünümünde aynı ekranı önce ETag kapalı, sonra açık halde en az 20 kez açın. Her koşulda ağ tipi, test hesabı ve API verisi sabit kalsın. Karşılaştırılacak metrikler: istek sayısı, 200 ile indirilen response body byte toplamı, 304 oranı ve ekrandaki ilk anlamlı içeriğin zamanıdır. iOS ve Android HTTP yığınları farklı olsa da Dio çağrı zincirinin ürettiği başlıklar aynı sözleşmeye göre incelenebilir; cross platform mobil uygulama geliştirme burada platforma özgü varsayım yerine ölçülebilir protokol davranışı gerektirir.
Uygulama seviyesinde de her yanıtı yapılandırılmış biçimde kaydedin. Aşağıdaki Dio interceptor'u, 304 oranını ve transfer baytını observability aracınıza göndermek için temel oluşturur. content-length sıkıştırılmış transfer boyutu olmayabilir ve chunked yanıtlarda boş gelebilir; hassas byte hesabı için proxy tarafındaki gerçek transfer metriklerini de karşılaştırın.
class CacheMetricsInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
final status = response.statusCode ?? 0;
final contentLength = int.tryParse(
response.headers.value(Headers.contentLengthHeader) ?? '',
) ?? 0;
metrics.record('http_response', {
'path': response.requestOptions.uri.path,
'status': status,
'conditional': response.requestOptions.headers.containsKey('If-None-Match'),
'etag': response.headers.value('etag') != null,
'content_length': contentLength,
});
handler.next(response);
}
}
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
..interceptors.add(CacheMetricsInterceptor());
Önce-sonra raporunda örneğin ürün detay ekranında 20 açılışın kaçında 200 yerine 304 alındığını yazın. Sadece ortalama değil p50 ve p95 ilk içerik süresini ölçün: disk okuma gecikmesi yüksek bir cihazda stale veri gösterimi p50'yi düşürürken, ilk kurulumda cache olmadığı için p95 değişmeyebilir. flutter kursu laboratuvarında bu testi Charles Proxy veya mitmproxy ile yapıp If-None-Match başlığını, 304 yanıtını ve gerçek gövde boyutunu doğrudan görmek, DevTools kaydını tek başına yorumlamaktan daha güvenilirdir.
Geçersiz kılma, süre sonu ve hesap değişimi kuralları
ETag kalıcı önbellek değildir. Oturum kapatılırken o hesaba ait cache namespace'ini silin; ortak cihazda yalnızca access token'ı silmek yeterli değildir. Key yapınız accountId:locale:resourceId biçimindeyse hesap değişiminde eski accountId önekini tarayıp silin. Ayrıca sunucu no-store gönderiyorsa yanıtı diske hiç yazmayın, no-cache gönderiyorsa her kullanım öncesi yeniden doğrulama yapın. Bu iki direktifin aynı anlama geldiğini varsaymak, kişisel verinin cihazda gereğinden uzun kalmasına yol açar.
Sunucu saati ile cihaz saati farklı olabileceği için istemci tazeliğini yalnızca DateTime.now() ile hesaplamak risklidir. Yanıta ait Date başlığını ve istemcinin istek başlangıç zamanını saklayarak sapmayı gözlemleyin; süre kritikse cihazdaki duvar saati yerine sunucu tarafındaki policy'yi otorite kabul edin. Cache-Control içindeki max-age=0, must-revalidate kombinasyonu, uygulamanın ağ yokken eski veriyi sessizce kesin bilgi gibi göstermemesi gerektiğini belirtir. UI'da bu durumda son güncelleme zamanını ve çevrimdışı rozetini kaynak bilgisi üzerinden açıkça üretin.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter mobil uygulama geliştirmede Dio ile 304 Not Modified nasıl işlenir?
Dio isteğinde validateStatus içine 304 ekleyin, ardından 304 durumunda response.data okumak yerine yerel kayıttaki JSON gövdesini çözün. Yerel kayıt yoksa StateError üretip ETag'siz bir yeniden deneme yapın; 304 yanıtının gövde taşıyacağını varsaymayın.
flutter state management katmanında stale-while-revalidate nasıl modellenir?
State'e yalnızca veri değil, isRefreshing ve source alanlarını ekleyin. Disk verisi bulunduğunda ProductReady(data, isRefreshing: true, source: 'disk') yayınlayın; koşullu GET 304 dönerse source değerini 'validated' yapın. Böylece widget ağ mantığı bilmeden güncellik bilgisini gösterebilir.
flutter eğitimi için ETag önbellek performansı nasıl ölçülür?
Flutter DevTools Network görünümünde aynı senaryoyu cache kapalı ve açık olarak 20 kez çalıştırın. 200 ve 304 sayısını, response body byte toplamını, p50-p95 ilk içerik süresini kaydedin. Charles Proxy veya mitmproxy ile If-None-Match başlığının gerçekten çıktığını ve 304 yanıtının gövdesiz olduğunu ayrıca doğrulayın.
cross platform mobil uygulama geliştirme uygulamasında ETag cache key'e kullanıcı kimliği eklenmeli mi?
Yanıt yetkiye, üyelik planına veya kişisel veriye göre değişiyorsa evet. Anahtarı accountId, locale, para birimi ve kaynak kimliğiyle oluşturun. URL tek başına kullanılırsa ortak cihazda önceki hesabın verisi sonraki hesaba gösterilebilir.
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.


