Flutter state management katmanında rebuild yayılımını DevTools ile ölçmeyi, Riverpod select kullanarak widget bağımlılıklarını daraltmayı ve yan etkileri güvenli biçimde yönetmeyi uygulamalı olarak inceleyin.
Flutter State Management ile Seçici Yeniden Çizim Tasarımı
Flutter State Management Sorununu Ölçerek Başlatın
Flutter mobil uygulama geliştirme sürecinde state yönetimi tartışmasına doğrudan paket seçerek başlamayın. Önce hedef akışta kaç widget'ın rebuild edildiğini ölçün. Uygulamayı profile modda başlatın:
flutter run --profile Ardından Flutter DevTools içindeki Inspector sekmesinden "Track widget rebuilds" seçeneğini açın ve kritik ekranı 10-20 kez gezinin. Performance görünümündeki UI thread karelerini, değişiklikten önce ve sonra aynı fiziksel cihazda karşılaştırın. Emulator sonuçları, özellikle düşük güç modundaki CPU frekans dalgalanmaları nedeniyle yanıltıcı olabilir.Örneğin ürün listesi ekranında sepete ürün eklemek yalnızca sepet rozeti ve ilgili ürün satırını değiştirmelidir. Bu işlem tüm listeyi rebuild ediyorsa, her satırın global sepet state'ini doğrudan dinlemesi muhtemeldir. DevTools Widget rebuild stats çıktısında önce ProductList, tüm ProductTile'lar ve CartBadge görünürken, düzeltmeden sonra sadece CartBadge ile değişen ProductTile görünmelidir. Ölçüm hedefini "rebuild sayısı azalsın" diye değil, 500 satırlı sentetik listede sepet ekleme başına yeniden çizilen ProductTile sayısı 500'den 1'e insin şeklinde tanımlayın.
Bir flutter eğitimi veya flutter kursu içeriğinde sık görülen hata, debug moddaki zaman çizelgesini performans kanıtı saymaktır. Debug derlemesinde assert'ler, service extension'lar ve JIT davranışı ölçümü bozar. Ölçüm için profile APK veya IPA üretin; Android tarafında örneğin
flutter build apk --profile
adb install -r build/app/outputs/flutter-apk/app-profile.apk komutlarıyla aynı derlemeyi test cihazına kurun. Bu yaklaşım, state değişikliğinin gerçek raster ve UI thread maliyetini ayırmayı sağlar.Riverpod select ile Bağımlılık Yüzeyini Daraltın
Riverpod kullanırken ref.watch(cartProvider) çağrısını ekranın en üstünde yapmak, sepetin her alanını ekranın tüm alt ağacına bağımlı hale getirebilir. Bunun yerine widget'ın gerçekten kullandığı türetilmiş alanı seçin. Aşağıdaki CartBadge, ürün satırları veya kupon bilgisi değiştiğinde değil, yalnızca itemCount değiştiğinde rebuild edilir.
final cartProvider = NotifierProvider<CartController, CartState>(
CartController.new,
);
class CartBadge extends ConsumerWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(
cartProvider.select((cart) => cart.itemCount),
);
return Badge(
label: Text('$count'),
child: const Icon(Icons.shopping_cart_outlined),
);
}
}select ancak seçilen değer anlamlı eşitlik davranışına sahipse işe yarar. Örneğin selector içinde her build'de yeni bir List oluşturmak, listenin referans eşitliği nedeniyle gereksiz bildirim üretebilir:
// Sorunlu: Her state değişiminde yeni List üretilir.
final ids = ref.watch(
cartProvider.select((cart) => cart.items.map((e) => e.id).toList()),
);
// Daha güvenli: Stabil, scalar bir değer seçilir.
final total = ref.watch(
cartProvider.select((cart) => cart.totalCents),
); Liste seçmek zorundaysanız immutable state kullanın ve Freezed gibi bir araçla içerik tabanlı equality üretin. Mutable List'i state içinde yerinde değiştirmek ise daha sinsi bir hatadır: notifier yeni bir state atamıyorsa dinleyiciler hiç güncellenmeyebilir.Dart programlama eğitimi bağlamında bu ayrımın temel nedeni nesne referansı ve değer eşitliğidir. `select`, önceki ve yeni seçili sonuçları karşılaştırarak dinleyiciyi bildirir; selector'ın maliyeti de provider değiştiğinde çalışır. Bu nedenle `items.where(...).fold(...)` gibi O(n) bir hesaplamayı 30 ayrı widget selector'ına koymak yerine, toplamı notifier içinde veya ayrı bir türetilmiş provider'da bir kez hesaplayın. DevTools CPU Profiler'da `CartState.totalCents` hesaplamasının çağrı sayısını önce-sonra karşılaştırın.
Yan Etkileri Build Fonksiyonundan Ayırın
State değiştiğinde snackbar göstermek, oturum süresi dolunca login ekranına gitmek veya analytics olayı göndermek render işlemi değildir. Bunları `build` içine koyarsanız parent rebuild'i aynı yan etkiyi tekrar tetikleyebilir. Riverpod'da `ref.listen` kullanın ve state geçişini filtreleyin. Aşağıdaki örnekte hata snackbar'ı yalnızca yeni hata önceki hatadan farklıysa gösterilir.
class CheckoutPage extends ConsumerStatefulWidget {
const CheckoutPage({super.key});
@override
ConsumerState<CheckoutPage> createState() => _CheckoutPageState();
}
class _CheckoutPageState extends ConsumerState<CheckoutPage> {
@override
void initState() {
super.initState();
ref.listenManual(checkoutProvider, (previous, next) {
final oldError = previous?.error;
final newError = next.error;
if (newError != null && newError != oldError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(newError.message)),
);
}
});
}
@override
Widget build(BuildContext context) => const CheckoutForm();
}Buradaki edge case `BuildContext` yaşam döngüsüdür. Ağ isteği tamamlandığında sayfa kapatılmış olabilir; `await` sonrasında navigation veya dialog açacaksanız `if (!context.mounted) return;` kontrolü yapın. Ayrıca `ref.listenManual` dönüşündeki subscription, ConsumerState yaşam döngüsünde otomatik kapanır; bunu sıradan bir `StreamSubscription` ile karıştırmayın. Bir `StreamSubscription` oluşturduysanız `dispose` içinde iptal etmeniz gerekir.
Yan etki sayısını doğrulamak için testte fake observer veya mock analytics kullanın. Bir butona iki kez basıldığında state loading -> success -> loading -> success akışını doğrulayın ve her success için tam bir event bekleyin. `build` içinden event gönderilen eski tasarımda parent tema değişimi gibi ilgisiz bir rebuild, event sayısını artırabilir. Bu hata cross platform mobil uygulama geliştirme projelerinde iOS ve Android analitik verilerinin birbirinden sapmasına yol açar.
Async State İçin İptal, Yarış ve Eski Yanıt Koruması
Arama ekranında kullanıcı "flu" yazdıktan hemen sonra "flutter" yazarsa, kısa sorgunun HTTP yanıtı uzun sorgudan sonra dönebilir. Sadece son gelen yanıtı state'e yazmak eski sonuçları ekrana basar. Dart'ta `CancelableOperation` için `async` paketi kullanılabilir, ancak HTTP istemcisinin gerçekten iptal edilmesi için kullandığınız istemcinin iptal API'sine bakın. Dio kullanıyorsanız `CancelToken` ile önceki isteği iptal edin.
class SearchController extends AutoDisposeNotifier<AsyncValue<List<Product>>> {
CancelToken? _cancelToken;
int _requestId = 0;
@override
AsyncValue<List<Product>> build() {
ref.onDispose(() => _cancelToken?.cancel('screen disposed'));
return const AsyncData([]);
}
Future<void> search(String query) async {
_cancelToken?.cancel('new query');
final token = _cancelToken = CancelToken();
final requestId = ++_requestId;
state = const AsyncLoading();
try {
final response = await dio.get('/products', queryParameters: {'q': query}, cancelToken: token);
if (requestId != _requestId) return;
state = AsyncData(parseProducts(response.data));
} on DioException catch (error) {
if (!CancelToken.isCancel(error)) state = AsyncError(error, StackTrace.current);
}
}
}`autoDispose` her durumda doğru tercih değildir. Sekmeler arasında geçerken arama state'inin korunması gerekiyorsa provider'ın her görünmez oluşta kapanması ağ isteğini ve cache'i gereksizce sıfırlar. Riverpod'un `ref.keepAlive()` mekanizmasını yalnızca başarılı sonuç için kullanabilir veya repository katmanında TTL tabanlı bellek cache'i kurabilirsiniz. Örneğin başarılı yanıt sonrası 30 saniyelik cache için anahtarı `query + locale + activeFilters` bileşiminden üretin; yalnızca query kullanmak, farklı filtrelerin aynı sonuç kümesini paylaşmasına neden olur.
Flutter Mobil Uygulama Geliştirme İçin State Sınırlarını Test Edin
State katmanını widget testlerinden bağımsız doğrulamak için repository'yi provider override ile değiştirin. Bu, gerçek ağ çağrısı yerine kontrollü gecikme ve hata üretmenizi sağlar. Aşağıdaki test, ikinci aramanın sonucunun ilk aramayı ezmediğini denetler; bu senaryo elle testte nadiren yakalanır.
test('latest search result wins', () async {
final container = ProviderContainer(
overrides: [searchRepositoryProvider.overrideWithValue(FakeSearchRepository())],
);
addTearDown(container.dispose);
final controller = container.read(searchProvider.notifier);
final first = controller.search('flu');
final second = controller.search('flutter');
await Future.wait([first, second]);
final value = container.read(searchProvider).requireValue;
expect(value.single.name, 'Flutter Book');
});Bu testin güvenilir olması için FakeSearchRepository, "flu" isteğini bilinçli olarak daha uzun geciktirmelidir. Rastgele `Future.delayed` kullanmak flaky test üretir; `Completer` ile yanıt sırasını testin kontrol etmesi daha doğrudur. Widget seviyesinde ise `ProviderScope` override kullanın, `pumpAndSettle()` çağrısını sürekli stream veya animasyon içeren ekranda körlemesine kullanmayın. Bunun yerine beklenen state değişimine kadar kontrollü `pump(Duration...)` yapın.
Cross platform mobil uygulama geliştirme ekiplerinde state modeli platform kanallarından gelen veriyi de normalize etmelidir. Örneğin Android'in geri dönüş sinyali ile iOS'taki sheet kapatma olayı aynı `CheckoutCancelled` domain event'ine dönüştürülürse, UI provider'ı platform ayrıntısı taşımaz. Bu sınırı sözleşme testiyle koruyun: native adapter'ın ürettiği event'i repository testinde doğrulayın, Flutter widget testinde ise yalnızca domain event'in state sonucunu test edin.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter state management performansı DevTools ile nasıl ölçülür?
Uygulamayı `flutter run --profile` ile gerçek cihazda çalıştırın, DevTools Inspector'da Track widget rebuilds seçeneğini açın ve aynı kullanıcı akışını değişiklikten önce-sonra uygulayın. Karşılaştırmada etkilenen widget sayısını, UI thread frame sürelerini ve CPU Profiler'daki selector çağrı sayılarını kaydedin.
Flutter kursu projelerinde Riverpod select ne zaman kullanılmalı?
Bir widget büyük bir state nesnesinin yalnızca scalar veya stabil eşitlikli küçük bir parçasını kullanıyorsa `select` kullanın. `toList()` gibi her çağrıda yeni koleksiyon oluşturan selector'lardan kaçının; immutable değer nesnesi, Freezed equality veya ayrı bir türetilmiş provider tercih edin.
Dart programlama eğitimi kapsamında async arama yarışları nasıl önlenir?
Her isteğe artan bir requestId verin ve yanıt state'e yazılmadan önce kimliği son istekle karşılaştırın. Dio kullanılıyorsa önceki isteği `CancelToken` ile iptal edin. Sadece iptal yeterli değildir; iptal anında tamamlanmış eski yanıtlar için requestId koruması da gerekir.
Flutter mobil uygulama geliştirme sürecinde snackbar build içinde neden sorun çıkarır?
Build fonksiyonu parent rebuild, tema değişimi veya ekran boyutu değişimi nedeniyle tekrar çağrılabilir. Bu durumda aynı hata için birden fazla snackbar oluşur. State geçişini `ref.listen` ile izleyin ve önceki-yeni hata değerlerini karşılaştırarak yan etkiyi bir kez tetikleyin.
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.



