• 7.09.2026 21:40:17
  • Admin Admin

Flutter state management katmaninda route kapanirken ve arama sorgusu degisirken HTTP isteklerini gerçekten iptal etmeyi ele aliyoruz. Dio, Riverpod, DevTools ve testlerle stale response kaynakli hatalari engelleyin.

Flutter State Management'de Route Bazli Istek Iptali Rehberi

Flutter state management icin iptal sahipligini tanimlayin

Bir istegin sahipligi, onu baslatan UI nesnesinden degil, istegin anlamsal omrunden gelmelidir. Ornegin urun detay istegi ProductPage route'una; yazarken onerilen urun listesi ise aktif query degerine aittir. flutter mobil uygulama geliştirme projelerinde repository icinde global bir CancelToken tutmak yaygin ama hatali bir yaklasimdir: iki farkli ekranda ayni repository kullanildiginda ikinci ekran birincinin istegini iptal edebilir. Her use-case cagrisi kendi token'ini almali, token'in dispose sorumlulugu ise provider, bloc veya controller scope'unda kalmalidir.

Dio ile her yeni arama sorgusunda onceki HTTP cagrisi ve eski yanitin state'i ezmesi birlikte engellenebilir. Buradaki requestId kontrolu gereklidir; iptal sinyali sokete gec ulasirsa veya mock, proxy ya da custom adapter Future'i tamamlamaya devam ederse eski sonuc yine donebilir. Token sadece tasima katmanini, monoton artan requestId ise state yazimini korur.

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';

class SearchController extends ChangeNotifier {
  SearchController(this._dio);

  final Dio _dio;
  CancelToken? _activeToken;
  int _requestId = 0;
  List<String> results = const [];
  bool loading = false;

  Future<void> search(String query) async {
    final int requestId = ++_requestId;
    _activeToken?.cancel('Superseded by a newer query');
    final token = CancelToken();
    _activeToken = token;
    loading = true;
    notifyListeners();

    try {
      final response = await _dio.get<List<dynamic>>(
        '/products/search',
        queryParameters: {'q': query},
        cancelToken: token,
      );
      if (requestId != _requestId) return;
      results = response.data!.cast<String>();
    } on DioException catch (error) {
      if (CancelToken.isCancel(error)) return;
      rethrow;
    } finally {
      if (requestId == _requestId) {
        loading = false;
        notifyListeners();
      }
    }
  }

  @override
  void dispose() {
    _activeToken?.cancel('Search page disposed');
    super.dispose();
  }
}

Riverpod autoDispose ile route omrune bagli Dio iptali

Riverpod kullanan ekiplerde FutureProvider.autoDispose, route'dan cikildiginda provider'i yok ederek iptal icin dogru omur sinirini verir. ref.onDispose icinde CancelToken.cancel cagrisi kayitli degilse autoDispose sadece Dart tarafindaki provider state'ini kaldirir; calismakta olan Dio istegi ise socket ve decode maliyetiyle devam eder. Bu ayrim, flutter eğitimi iceriklerinde siklikla atlanir.

Asagidaki provider family, her productId icin ayri bir token uretir. Ekran yeniden build edilse bile provider dinlenmeye devam ediyorsa token korunur; son listener kaldirildiginda onDispose calisir. keepAlive cagrisi bu davranisi degistirir: keepAlive kullanilan provider icin ayrica zaman asimli kapatma politikasi tanimlanmalidir.

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final dioProvider = Provider<Dio>((ref) => Dio());

final productProvider =
    FutureProvider.autoDispose.family<Map<String, dynamic>, String>(
  (ref, productId) async {
    final token = CancelToken();
    ref.onDispose(() => token.cancel('Product route left'));

    final dio = ref.read(dioProvider);
    try {
      final response = await dio.get<Map<String, dynamic>>(
        '/products/$productId',
        cancelToken: token,
      );
      return response.data!;
    } on DioException catch (error) {
      if (CancelToken.isCancel(error)) {
        throw StateError('Request cancelled because its provider was disposed');
      }
      rethrow;
    }
  },
);

UI katmaninda AsyncValue.error durumunu koruyan bir hata ekrani olarak gostermek yerine iptal hatasini gozlemlenebilirlik katmaninda ayirmak daha temizdir. Sentry veya OpenTelemetry interceptor'iniz varsa CancelToken.isCancel(error) olan olaylari error severity ile gondermeyin; aksi halde kullanicinin geri tusuna basmasi yapay hata orani uretir. Buna karsilik timeout, 401 ve 5xx durumlarini ayri tag'lerle kaydetmek icin Dio Interceptor kullanin.

Debounce, switchMap ve gercek HTTP iptali farki

Debounce sadece yeni bir istek baslatmayi geciktirir, baslatilmis istegi durdurmaz. RxDart'taki switchMap ise eski stream subscription'ini iptal eder, fakat icerde await edilen Dio Future'ine CancelToken verilmemisse TCP baglantisi veya HTTP/2 stream'i devam edebilir. cross platform mobil uygulama geliştirme yapan ekipler bu farki hem Android hem iOS agirlikli ag kosullarinda test etmelidir; platform fark etmeksizin uygulama state'i eski yaniti kabul etmemelidir.

Bloc kullaniyorsaniz bloc_concurrency paketindeki restartable transformer ile event handler omrunu yonetin, ancak network iptalini acikca baglayin. Asagidaki ornekte operation yerel degiskene atanir; _operation alanini await sonrasinda okumak hatalidir, cunku o alan bu sirada yeni sorgunun operation'i olabilir.

import 'package:async/async.dart';
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:dio/dio.dart';

on<QueryChanged>((event, emit) async {
  await _operation?.cancel();
  final token = CancelToken();
  final operation = CancelableOperation<List<String>>.fromFuture(
    api.search(event.query, cancelToken: token),
    onCancel: () => token.cancel('New query event'),
  );
  _operation = operation;

  final value = await operation.valueOrCancellation();
  if (value != null && identical(_operation, operation)) {
    emit(SearchReady(value));
  }
}, transformer: restartable());

Buradaki edge case, bos sorgudur. Bos sorguda sadece UI listesini temizlemek yerine aktif token'i de iptal edin; aksi halde kullanici arama alanini temizledikten sonra gec kalan onceki sonuc tekrar gorunur. API katmaninda minimum sorgu boyu 2 ise controller'da da ayni esigi uygulayin ve token iptalinden sonra emit edilen loading state'ini requestId veya identical kontroluyle kapatin.

Flutter mobil uygulama gelistirmede iptal maliyetini olcun

Istek iptalini performans iyilestirmesi olarak kabul etmeden once iki profil build'iyle ayni senaryoyu olcun: arama alanina 150 ms araliklarla 12 karakter girin, sonra route'u geri tusuyla kapatin. flutter run --profile ile uygulamayi calistirin, Flutter DevTools Performance ekraninda TimelineTask isaretlerini arayin ve Dio loglarindan baslatilan, tamamlanan ve iptal edilen istek sayilarini alin. Once durumunda tamamlanan istek sayisi, p95 yanit boyutu ve JSON decode suresi; sonra durumunda ayni uc metriyi karsilastirin. Sadece build sayisini olcmek yeterli degildir, cunku asil tasarruf genellikle gereksiz response body transferi ve deserialize edilmemis veridedir.

Dio interceptor'i ve dart:developer TimelineTask birlikte kullanildiginda DevTools zaman cizelgesinde her endpoint gorunur. production loglarinda query metnini yazmak yerine uzunluk veya hash kullanin; arama metni kisisel veri icerebilir.

import 'dart:developer';
import 'package:dio/dio.dart';

class NetworkTimelineInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final task = TimelineTask(filterKey: 'network');
    task.start('GET ${options.path}', arguments: {'requestId': options.hashCode});
    options.extra['timelineTask'] = task;
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    (response.requestOptions.extra['timelineTask'] as TimelineTask?)?.finish();
    handler.next(response);
  }

  @override
  void onError(DioException error, ErrorInterceptorHandler handler) {
    final task = error.requestOptions.extra['timelineTask'] as TimelineTask?;
    task?.finish(arguments: {'cancelled': CancelToken.isCancel(error)});
    handler.next(error);
  }
}

Sunucu etkisini de ayri olcun. Dio iptali istemci tarafinda Future'i hata ile bitirir ve tasima katmanini kesmeye calisir, fakat backend handler'i veritabani sorgusunu coktan baslatmis olabilir. API gateway loglarinda request ID ile baglantisi kapanan istekleri, backend tracing'de ise DB span'larini eslestirin. Uzun sorgular icin PostgreSQL tarafinda statement_timeout, backend tarafinda request context cancellation ve idempotent yazma endpoint'lerinde Idempotency-Key kullanimi ayri savunma katmanlaridir.

Dart programlama egitimi seviyesinde yarismayi test edin

Gercek ag ile yazilan widget testleri yarismayi deterministik bicimde uretmez. Repository'yi Future donen bir arayuz olarak soyutlayin, iki Completer ile once eski sonra yeni sorguyu baslatin ve eski Future'i en son tamamlayin. Beklenen sonuc eski listenin ekranda gorunmemesidir. Bu test, dart programlama eğitimi alan gelistiricilerin ogrendigi async siralama varsayiminin neden yanlis oldugunu somutlastirir: Future'ler baslama sirasiyla tamamlanmaz.

Asagidaki unit test, token iptalini degil state yazma kapisini test eder. Her ikisi gereklidir: token ag kaynagini azaltir, gate ise iptal edilemeyen adapter veya backend gecikmesinde veri tutarliligini korur.

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';

class LatestOnly<T> {
  int _version = 0;
  T? value;

  Future<void> run(Future<T> future) async {
    final version = ++_version;
    final result = await future;
    if (version == _version) value = result;
  }
}

void main() {
  test('late old response cannot overwrite new response', () async {
    final oldResponse = Completer<String>();
    final newResponse = Completer<String>();
    final gate = LatestOnly<String>();

    final oldRun = gate.run(oldResponse.future);
    final newRun = gate.run(newResponse.future);
    newResponse.complete('new');
    await newRun;
    oldResponse.complete('old');
    await oldRun;

    expect(gate.value, 'new');
  });
}

Bir flutter kursu icin code review kontrol listesine su iki maddeyi eklemek pratiktir: dispose, ref.onDispose veya Bloc.close icinde aktif subscription ve CancelToken kapatiliyor mu; her sonucun state'e yazilmasindan once route veya request versiyonu dogrulaniyor mu? Ozellikle pagination'da tek global token kullanmayin. Bir sonraki sayfa istegi ile pull-to-refresh isteginin iptal semantigi farklidir; refresh yeni bir liste olustururken sayfalama ayni listenin devamidir ve ayri operation anahtarlari gerektirir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management icinde Dio CancelToken kullanmak stale response sorununu tek basina cozer mi?

Hayir. CancelToken Dio istegini iptal etmeye calisir, ancak eski Future adapter tarafinda tamamlanabilir veya iptal yeni istekten sonra yarismaya girebilir. Her istege artan requestId verin ya da operation referansini identical ile kontrol edin; state sadece en guncel istekten yazilmalidir.

Flutter mobil uygulama geliştirme projesinde Riverpod autoDispose HTTP istegini otomatik iptal eder mi?

autoDispose provider state'ini yok eder, Dio istegini kendiliginden bilmez. Provider icinde CancelToken olusturup ref.onDispose(() => token.cancel()) kaydi ekleyin. keepAlive kullaniyorsaniz provider dispose olmayacagi icin token'in omrunu ayrica tasarlayin.

Cross platform mobil uygulama geliştirme icin debounce mi switchMap mi kullanmaliyim?

Metin aramasinda debounce yeni istek sayisini azaltir, switchMap eski event handler sonucunu gecersiz kilmaya yardim eder. Aktif HTTP istegini de sonlandirmak istiyorsaniz switchMap veya restartable akisini Dio CancelToken.cancel ile baglayin. DevTools Timeline ve Dio interceptor loglariyla tamamlanan istek sayisini once-sonra olcun.

Flutter eğitimi sirasinda iptal edilen HTTP isteklerini hata olarak raporlamali miyim?

Hayir. DioException yakalandiginda CancelToken.isCancel(error) kontrolu yapin ve bu olayi Sentry veya OpenTelemetry'ye application error olarak gondermeyin. Timeout, TLS, 401 ve 5xx hatalarini ise endpoint, status code ve request ID etiketleriyle ayri raporlayin.

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.

Opendart Akademi llms.txt