Flutter state management katmanında eski HTTP yanıtlarının yeni ekran durumunu ezmesini önlemek için sıra numarası, immutable state, seçici dinleme ve profil tabanlı doğrulama tekniklerini uygulayın.
Flutter State Management'de Asenkron Yarışları Güvenli Yönetmek
Flutter state management'de stale response problemi
Bir ürün detay ekranında kullanıcı A ürününden B ürününe geçtiğinde, A isteğinin B isteğinden sonra dönmesi klasik bir stale response hatasıdır. Sorun yalnızca ağ gecikmesi değildir: state katmanı, sonucu hangi kullanıcı niyetinin başlattığını kaybettiğinde eski veri yeni state'e yazılır. Flutter mobil uygulama geliştirme akışında bunu görünür kılmak için repository çağrılarına istek kimliği ve zaman damgası ekleyin; Dio kullanıyorsanız interceptor ile her isteğe X-Request-Id başlığı koyup istemci loglarıyla sunucu loglarını eşleştirebilirsiniz.
final requestId = '${productId}-${DateTime.now().microsecondsSinceEpoch}';
final response = await dio.get(
'/products/$productId',
options: Options(headers: {'X-Request-Id': requestId}),
);
debugPrint('product=$productId request=$requestId status=${response.statusCode}');Bu hatayı sadece CancelToken ile çözmek yeterli değildir. İptal sinyali sunucuya geç ulaşabilir, HTTP katmanı isteği tamamlamış olabilir veya repository sonucu disk önbelleğinden dönebilir. Bu nedenle state'e yazma anında bir sahiplik kontrolü gerekir: sonucu yalnızca halen geçerli olan kullanıcı niyetinin üretmesine izin verin. Bu yaklaşım Android, iOS ve web hedeflerinde aynı Dart koduyla çalıştığı için cross platform mobil uygulama geliştirme projelerinde platforma özel yaşam döngüsü farklarını state katmanına taşımadan uygulanabilir.
Sıra numarasıyla son isteğin kazanmasını sağlamak
En basit güvenli protokol, her yüklemede artan bir epoch üretmek ve await sonrasında epoch'un hala geçerli olup olmadığını denetlemektir. Aşağıdaki controller, ağ isteğini fiziksel olarak iptal etmese bile eski sonucun state'i değiştirmesini engeller. Kritik ayrıntı, epoch değerinin await öncesinde yerel değişkene alınmasıdır; aksi halde tamamlanan her istek güncel sayaç değerini görür ve koruma etkisiz kalır.
sealed class ProductState {
const ProductState();
}
final class ProductLoading extends ProductState {
const ProductLoading(this.productId);
final String productId;
}
final class ProductReady extends ProductState {
const ProductReady(this.product);
final Product product;
}
final class ProductFailure extends ProductState {
const ProductFailure(this.error);
final Object error;
}
class ProductController {
ProductController(this._repository);
final ProductRepository _repository;
int _epoch = 0;
bool _disposed = false;
ProductState state = const ProductLoading('');
Future<void> load(String productId) async {
final int requestEpoch = ++_epoch;
state = ProductLoading(productId);
try {
final product = await _repository.fetch(productId);
if (_disposed || requestEpoch != _epoch) return;
state = ProductReady(product);
} catch (error) {
if (_disposed || requestEpoch != _epoch) return;
state = ProductFailure(error);
}
}
void dispose() {
_disposed = true;
_epoch++;
}
}Bu modeli Riverpod, Bloc veya kendi ChangeNotifier katmanınıza taşıyabilirsiniz; korunması gereken kural framework'ten bağımsızdır: yalnızca en güncel komut state commit edebilir. Arama ekranında ise her karakter değişiminde istek atmak yerine 250 ms debounce uygulayın, fakat debounce'u doğruluk mekanizması sanmayın. Debounce istek sayısını azaltır; epoch kontrolü ise sırası bozuk tamamlanmaları engeller. Bu ayrım, dart programlama eğitimi kapsamında Future ve event loop anlatılırken özellikle önemlidir, çünkü hata Future'ın kendisinden değil, paylaşılan mutable state'e geç yazılmasından doğar.
Flutter state management'de seçici dinleme ve immutable sınırlar
Asenkron tutarlılık çözüldükten sonra ikinci maliyet, tek bir alan değiştiğinde geniş widget ağacının yeniden build edilmesidir. Riverpod kullanıyorsanız select ile widget'ın gerçekten çizdiği alanı dinleyin. Aşağıdaki örnekte fiyat değişimi ürün başlığını kullanan Text widget'ını yeniden build etmez; seçilen String değeri önceki değerle == üzerinden karşılaştırılır.
class ProductHeader extends ConsumerWidget {
const ProductHeader({required this.productId, super.key});
final String productId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final title = ref.watch(
productProvider(productId).select((state) => state.product?.title),
);
return Text(title ?? 'Yukleniyor');
}
}Buradaki edge case, state içindeki List veya Map nesnesini yerinde değiştirmektir. Örneğin state.items.add(item) yaptıktan sonra aynı liste referansını yeniden yayımlamak, select kullanan tüketicilerde değişimin algılanmamasına veya beklenmedik sayıda rebuild'e yol açar. Yeni bir sınır üretin: items: List.unmodifiable([...state.items, item]). Freezed gibi immutable model araçları kullanıyorsanız copyWith ile yeni state üretin ve model alanlarında eşitlik semantiğini bilin. Bu, flutter kursu projelerinde sık görülen 'liste güncelleniyor ama UI bazen değişmiyor' hatasının doğrudan nedenidir.
Önce-sonra ölçümü: DevTools ile rebuild ve frame maliyeti
Seçici dinlemenin etkisini debug modda tahmin etmeyin. Aynı cihazda flutter run --profile ile uygulamayı çalıştırın, Flutter DevTools Performance görünümünde aynı kullanıcı senaryosunu iki kez kaydedin: örneğin 100 ürünlük ekranda 30 fiyat güncellemesi üretin. Önce tüm ekranın provider'ı dinlediği sürümü, sonra select kullanan sürümü ölçün. Karşılaştırmada UI thread frame sürelerinin P95 değerini, 16.67 ms eşiğini aşan frame sayısını ve Inspector'daki Track Widget Rebuilds çıktısındaki rebuild sayılarını kaydedin.
# Profil derlemesiyle fiziksel cihazda çalıştırın.
flutter run --profile
# Debug modunda yalnızca rebuild teşhisi için etkinleştirin.
# main() fonksiyonunda:
debugPrintRebuildDirtyWidgets = true;debugPrintRebuildDirtyWidgets yalnızca debug teşhisinde kullanılmalıdır; yoğun log yazımı frame zamanlarını bozar ve profil sonucu üretmez. Profile ölçümünde ayrıca ağ yanıtlarını sabitleyin: repository'yi test JSON'u döndüren bir fake ile besleyin veya Charles Proxy gibi bir araçla gecikmeyi sabit tutun. Aksi halde CDN gecikmesindeki farkı state değişikliğinin sonucu sanabilirsiniz. Hedefiniz 'daha hızlı' gibi belirsiz bir ifade değil, aynı senaryoda P95 UI frame süresi ile rebuild sayısının önce-sonra kaydı olmalıdır.
Yarış koşullarını deterministik widget ve birim testlerle yakalamak
Gerçek ağ ile yarış testi yazmak flaky olur. Bunun yerine repository'nin iki çağrısını iki ayrı Completer ile kontrol edin: önce B sonucunu, sonra kasıtlı olarak eski A sonucunu tamamlayın ve state'in B kaldığını doğrulayın. Bu test, refactor sırasında epoch kontrolünün yanlışlıkla kaldırılmasını CI aşamasında yakalar.
test('eski yanit yeni statei ezemez', () async {
final first = Completer<Product>();
final second = Completer<Product>();
final repository = FakeRepository([first.future, second.future]);
final controller = ProductController(repository);
final loadA = controller.load('A');
final loadB = controller.load('B');
second.complete(Product(id: 'B', title: 'Yeni urun'));
await loadB;
first.complete(Product(id: 'A', title: 'Eski urun'));
await loadA;
expect((controller.state as ProductReady).product.id, 'B');
});Widget testinde aynı senaryoyu arama alanına iki hızlı metin girişiyle genişletin ve son render edilen sonuçların son sorguya ait olduğunu kontrol edin. Bu testler, flutter eğitimi materyallerinde genellikle atlanan bir noktayı kapsar: state management doğruluğu yalnızca loading, success ve error ekranlarının varlığı değildir; olayların farklı sırada tamamlandığında state geçişlerinin halen geçerli olmasıdır.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter state management içinde eski API yanıtı yeni veriyi neden eziyor?
İki Future başlatıldığı sıra ile tamamlanmak zorunda değildir. İlk isteğin sonucu await sonrasında doğrudan state'e yazılırsa, daha yeni isteğin state'i üzerine yazabilir. Her komuta artan bir epoch verin ve commit öncesinde requestEpoch != currentEpoch kontrolü yapın.
Flutter mobil uygulama geliştirme projesinde Riverpod select ne zaman kullanılmalı?
Bir widget yalnızca state'in tek bir türev alanını kullanıyorsa select kullanın. Örneğin yalnızca product.title çizen başlık widget'ı tüm ProductState'i dinlememelidir. Seçilen değer immutable olmalı veya anlamlı == eşitliğine sahip olmalıdır; mutable List'i yerinde değiştirmek seçici dinlemeyi yanıltabilir.
Flutter kursu projelerinde rebuild performansı nasıl ölçülür?
Ölçümü flutter run --profile ile fiziksel cihazda yapın. DevTools Performance görünümünde aynı etkileşim kaydının P95 UI frame süresini ve 16.67 ms üstü frame sayısını önce-sonra karşılaştırın. Debug modda debugPrintRebuildDirtyWidgets ile hangi widget'ların dirty işaretlendiğini teşhis edin, ancak bu logları performans metriği olarak kullanmayın.
Cross platform mobil uygulama geliştirme için istek iptali epoch kontrolünün yerine geçer mi?
Hayır. Dio CancelToken veya platform HTTP iptali bant genişliğini azaltabilir, ancak yanıt iptal anından önce tamamlanmış olabilir. State katmanındaki epoch veya request id kontrolü, Android, iOS ve web üzerinde sonucu yazma yetkisini belirleyen son savunma hattıdır.
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.



