İçeriğe geç / Skip to content / Zum Inhalt

Flutter Yaşam Döngüsü (Life Cycle): StatefulWidget Rehberi

Ahmet Balaman

Son güncelleme:

4 dk okuma

FlutterLife CycleStatefulWidgetState ManagementApp Lifecycle
Flutter Yaşam Döngüsü (Life Cycle): StatefulWidget Rehberi

İlk kez bir API çağrısı yapmam gerektiğinde "Bunu nereye yazacağım?" diye düşünmüştüm. build() metoduna mı? Hayır, çünkü her ekran yenilendiğinde tekrar çağrılırdı. İşte burada life cycle (yaşam döngüsü) devreye giriyor.

Flutter'da iki tür widget var: StatelessWidget ve StatefulWidget. StatelessWidget'ın özel bir yaşam döngüsü yok (çünkü değişmiyor), ama StatefulWidget'ın çok zengin bir yaşam döngüsü var. İkisinin farkı ve widget ağacının nasıl kurulduğu konusunda emin değilseniz widget ağacı ve layout yazısı iyi bir başlangıç.

Canlı Demo: Life Cycle Metodları

StatefulWidget yaşam döngüsü metodlarını interaktif olarak deneyin:

💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.

StatefulWidget Yaşam Döngüsü

Bir StatefulWidget'ın hayatı şöyle ilerler:

StatefulWidget yaşam döngüsü: oluşturmada createState, initState, didChangeDependencies ve build; güncellemede setState ve build; yıkımda deactivate ve dispose

createState() → initState() → didChangeDependencies() → build()
                                         ↓
                    setState() ←→ build() ←→ didUpdateWidget()
                                         ↓
                                    dispose()

1. createState()

Widget ilk oluşturulduğunda çağrılır. State nesnesini oluşturur.

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() {
    print('createState çalıştı');
    return _MyWidgetState();
  }
}

Ne zaman çağrılır: Widget oluşturulurken, sadece bir kez.

Ne için kullanılır: State sınıfını döndürmek için. Genelde özelleştirmeye gerek yok.

2. initState()

State nesnesi oluşturulduktan hemen sonra çağrılır. İlk kurulum işlemleri burada yapılır.

class _MyWidgetState extends State<MyWidget> {
  int counter = 0;
  late Timer timer;
  
  @override
  void initState() {
    super.initState(); // Mutlaka çağırın!
    
    print('initState çalıştı');
    
    // İlk kurulum işlemleri
    counter = 0;
    _loadData();
    _startTimer();
  }
  
  void _loadData() async {
    // API çağrısı
    final data = await fetchDataFromAPI();
    setState(() {
      // Veriyi state'e kaydet
    });
  }
  
  void _startTimer() {
    timer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        counter++;
      });
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Text('Counter: $counter');
  }
}

Ne zaman çağrılır: Widget ilk oluşturulduğunda, sadece bir kez.

Ne için kullanılır:

  • Değişkenlere başlangıç değeri atama
  • API çağrıları yapma
  • Stream/Controller oluşturma
  • Timer başlatma
  • Event listener ekleme

Önemli: super.initState() çağırmayı unutmayın!

3. didChangeDependencies()

Widget'ın bağımlılıkları değiştiğinde çağrılır.

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  
  print('didChangeDependencies çalıştı');
  
  // Theme değişti mi?
  final theme = Theme.of(context);
  
  // MediaQuery değişti mi? (ekran boyutu, yönü)
  final screenSize = MediaQuery.of(context).size;
}

Ne zaman çağrılır:

  • initState()'den hemen sonra
  • InheritedWidget değiştiğinde (Theme, MediaQuery, vb.)

Ne için kullanılır:

  • Context'e bağımlı işlemler
  • Theme, Locale gibi değişimleri yakalamak

Not: initState() içinde context kullanılamaz, ama burada kullanılabilir.

4. build()

Widget'ın görünümünü oluşturur.

@override
Widget build(BuildContext context) {
  print('build çalıştı');
  
  return Scaffold(
    appBar: AppBar(title: Text('Sayfa')),
    body: Center(
      child: Text('Counter: $counter'),
    ),
  );
}

Ne zaman çağrılır:

  • didChangeDependencies() sonrası
  • setState() her çağrıldığında
  • didUpdateWidget() sonrası
  • Parent widget yeniden build olduğunda

Ne için kullanılır: UI oluşturmak.

Önemli:

  • Burada API çağrısı yapmayın!
  • Burada Timer başlatmayın!
  • Sık çağrılabilir, performansa dikkat edin.

5. didUpdateWidget()

Parent widget değiştiğinde ve yeni bir yapılandırma gönderdiğinde çağrılır.

class ParentWidget extends StatefulWidget {
  final String title;
  
  ParentWidget({required this.title});
  
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  @override
  void didUpdateWidget(ParentWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    
    print('didUpdateWidget çalıştı');
    
    // Eski ve yeni widget'ı karşılaştır
    if (oldWidget.title != widget.title) {
      print('Title değişti: ${oldWidget.title} → ${widget.title}');
      // Gerekli güncellemeleri yap
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);
  }
}

Ne zaman çağrılır: Parent widget değişip yeniden build olduğunda.

Ne için kullanılır:

  • Eski ve yeni widget'ı karşılaştırma
  • Değişen parametrelere göre güncelleme yapma

6. setState()

State'i güncelleyip widget'ı yeniden build etmek için.

class _CounterState extends State<Counter> {
  int count = 0;
  
  void _increment() {
    setState(() {
      count++; // State değişikliği
    });
    // Bu noktada build() tekrar çağrılır
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          child: Text('Artır'),
          onPressed: _increment,
        ),
      ],
    );
  }
}

Ne zaman çağrılır: Manuel olarak çağırırsınız.

Ne için kullanılır: State değişikliklerini Flutter'a bildirmek.

Önemli:

  • Async işlemlerde dikkatli kullanın
  • Gereksiz setState çağrılarından kaçının
  • Widget dispose olduktan sonra çağırmayın

7. dispose()

Widget silinmeden önce çağrılır. Temizlik işlemleri burada yapılır.

class _MyWidgetState extends State<MyWidget> {
  late Timer timer;
  late StreamSubscription subscription;
  TextEditingController controller = TextEditingController();
  
  @override
  void initState() {
    super.initState();
    
    timer = Timer.periodic(Duration(seconds: 1), (timer) {
      // ...
    });
    
    subscription = someStream.listen((data) {
      // ...
    });
  }
  
  @override
  void dispose() {
    print('dispose çalıştı - temizlik yapılıyor');
    
    // Timer'ı durdur
    timer.cancel();
    
    // Stream'i kapat
    subscription.cancel();
    
    // Controller'ı temizle
    controller.dispose();
    
    super.dispose(); // Mutlaka en sonda çağırın!
  }
  
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Ne zaman çağrılır: Widget widget ağacından kaldırılmadan önce.

Ne için kullanılır:

  • Timer'ları durdurmak
  • Stream'leri kapatmak
  • Controller'ları temizlemek
  • Event listener'ları kaldırmak
  • Memory leak'i önlemek

Önemli: super.dispose() en sonda çağırılmalı!

Pratik Örnek: Life Cycle ile API Çağrısı

Aşağıdaki kalıp elle yazılmış bir yükleme akışı: veriyi initState içinde çekip setState ile ekrana basıyoruz. Aynı işi daha az kodla yapmak istiyorsanız FutureBuilder yükleme, hata ve sonuç durumlarını sizin için yönetir; sürekli akan veri varsa StreamBuilder daha uygun.

class UserProfilePage extends StatefulWidget {
  final int userId;
  
  UserProfilePage({required this.userId});
  
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  bool isLoading = true;
  String? userName;
  String? errorMessage;
  
  @override
  void initState() {
    super.initState();
    print('1. initState - API çağrısı yapılıyor');
    _loadUserData();
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('2. didChangeDependencies - Context hazır');
  }
  
  @override
  void didUpdateWidget(UserProfilePage oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('3. didUpdateWidget - userId değişti mi?');
    
    // UserId değiştiyse yeniden yükle
    if (oldWidget.userId != widget.userId) {
      setState(() {
        isLoading = true;
        errorMessage = null;
      });
      _loadUserData();
    }
  }
  
  Future<void> _loadUserData() async {
    try {
      print('API çağrısı başladı: userId=${widget.userId}');
      
      // Simüle edilmiş API çağrısı
      await Future.delayed(Duration(seconds: 2));
      
      // Widget hala mounted mi kontrol et
      if (!mounted) return;
      
      setState(() {
        userName = 'Kullanıcı ${widget.userId}';
        isLoading = false;
      });
      
      print('API çağrısı tamamlandı');
    } catch (e) {
      if (!mounted) return;
      
      setState(() {
        errorMessage = 'Hata: $e';
        isLoading = false;
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    print('4. build - UI oluşturuluyor');
    
    return Scaffold(
      appBar: AppBar(title: Text('Profil')),
      body: Center(
        child: isLoading
            ? CircularProgressIndicator()
            : errorMessage != null
                ? Text(errorMessage!)
                : Text('Merhaba, $userName!'),
      ),
    );
  }
  
  @override
  void dispose() {
    print('5. dispose - Widget temizleniyor');
    super.dispose();
  }
}

Dikkat edin: bu state sayfa pop edildiğinde dispose ile birlikte yok olur. Birden fazla ekranın paylaştığı ve sayfa geçişlerinde yaşaması gereken veriler için widget state'i yerine Provider veya Riverpod gibi bir çözüm kullanın.

Uygulama Yaşam Döngüsü (App Life Cycle)

Widget yaşam döngüsünden farklı olarak, uygulamanın kendisinin de bir yaşam döngüsü var.

AppLifecycleState

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    
    switch (state) {
      case AppLifecycleState.resumed:
        print('Uygulama aktif - Kullanıcı uygulamada');
        // Veri güncelleme, animasyon başlatma
        break;
        
      case AppLifecycleState.inactive:
        print('Uygulama pasif - Geçici durum');
        // Telefon görüşmesi, bildirim çekmesi
        break;
        
      case AppLifecycleState.paused:
        print('Uygulama arka planda');
        // Veri kaydetme, işlemleri durdurma
        break;
        
      case AppLifecycleState.detached:
        print('Uygulama sonlandırılıyor');
        // Final temizlik
        break;
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HomePage());
  }
}

Durum Açıklamaları

  • resumed: Uygulama görünür ve kullanıcı etkileşimde
  • inactive: Uygulama görünür ama etkileşim yok (telefon görüşmesi)
  • paused: Uygulama arka planda, görünmüyor
  • detached: Uygulama sonlandırılıyor

Pratik Kullanım: Video Player

class VideoPlayerPage extends StatefulWidget {
  @override
  _VideoPlayerPageState createState() => _VideoPlayerPageState();
}

class _VideoPlayerPageState extends State<VideoPlayerPage>
    with WidgetsBindingObserver {
  late VideoPlayerController videoController;
  
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    
    videoController = VideoPlayerController.network('video_url');
    videoController.initialize();
    videoController.play();
  }
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      // Uygulama arka plana alındı - videoyu duraklat
      videoController.pause();
    } else if (state == AppLifecycleState.resumed) {
      // Uygulama aktif - videoyu devam ettir
      videoController.play();
    }
  }
  
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    videoController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AspectRatio(
          aspectRatio: videoController.value.aspectRatio,
          child: VideoPlayer(videoController),
        ),
      ),
    );
  }
}

Deactivate() Metodu

Nadiren kullanılır, widget geçici olarak ağaçtan kaldırıldığında çağrılır.

@override
void deactivate() {
  super.deactivate();
  print('Widget deactivate edildi');
  // Geçici temizlik işlemleri
}

Ne zaman çağrılır:

  • Navigator ile sayfa değişiminde
  • Widget yeniden konumlandırıldığında

Sık Yapılan Hatalar

1. initState'te Context Kullanımı

// ❌ Yanlış
@override
void initState() {
  super.initState();
  final theme = Theme.of(context); // HATA!
}

// ✅ Doğru
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  final theme = Theme.of(context); // Doğru
}

2. dispose Sonrası setState

// ❌ Yanlış
Future<void> loadData() async {
  final data = await api.fetchData();
  setState(() {
    this.data = data; // Widget dispose olmuş olabilir!
  });
}

// ✅ Doğru
Future<void> loadData() async {
  final data = await api.fetchData();
  if (mounted) { // mounted kontrolü
    setState(() {
      this.data = data;
    });
  }
}

3. build() İçinde Yan Etkiler

// ❌ Yanlış
@override
Widget build(BuildContext context) {
  fetchData(); // Her build'de çağrılır!
  return Text('...');
}

// ✅ Doğru
@override
void initState() {
  super.initState();
  fetchData(); // Bir kez çağrılır
}

4. super Çağrısını Unutmak

// ❌ Yanlış
@override
void initState() {
  // super.initState() yok!
  loadData();
}

// ✅ Doğru
@override
void initState() {
  super.initState(); // Mutlaka çağırın!
  loadData();
}

En İyi Uygulamalar

  1. Async işlemlerden sonra her zaman mounted kontrolü yapın.
  2. dispose içinde timer, stream aboneliği ve controller'ları temizleyin.
  3. super metodlarını çağırmayı unutmayın.
  4. initState'i minimal tutun; ağır işleri sonraya bırakın.
  5. build() metodunu yan etkisiz tutun.

Özet

StatefulWidget Life Cycle:

  1. createState() - State oluştur
  2. initState() - İlk kurulum
  3. didChangeDependencies() - Bağımlılık değişimi
  4. build() - UI oluştur
  5. didUpdateWidget() - Parent değişimi
  6. setState() - State güncelleme
  7. dispose() - Temizlik

App Life Cycle:

  • resumed - Aktif
  • inactive - Geçici pasif
  • paused - Arka plan
  • detached - Sonlandırılıyor

Life cycle metodlarını anlamak, performanslı ve hatasız Flutter uygulamaları yazmanın temelidir.


Life cycle konusunda kafanız karıştı mı?

Bir sonraki yazıda görüşmek üzere! 🚀

Sık Sorulan Sorular

API çağrısını initState'te mi yapmalıyım?

Evet, tek seferlik veri yüklemesi için doğru yer initState. build() her yeniden çizimde çalıştığı için oraya asla koymayın. initState içinde await kullanmak yerine async bir metodu çağırın ve sonucu setState ile yazın.

dispose metodunda tam olarak neyi temizlemeliyim?

Kendiniz oluşturduğunuz her şeyi: TextEditingController, AnimationController, ScrollController, Timer nesneleri ve StreamSubscription abonelikleri. Bunlar temizlenmezse widget ağaçtan çıktıktan sonra da çalışmaya devam eder ve bellek sızıntısına yol açar.

"setState() called after dispose()" hatasını nasıl önlerim?

Bu hata, asenkron bir işlem tamamlandığında widget'ın artık ağaçta olmamasından kaynaklanır. setState çağırmadan önce if (!mounted) return; kontrolü ekleyin; mümkünse dispose içinde işlemi ya da aboneliği iptal edin.

Yorumlar