Ahmet Balaman LogoAhmet Balaman

Flutter'da Yaşam Döngüsü (Life Cycle) - StatefulWidget ve AppLifecycleState

personAhmet Balaman
calendar_today
FlutterLife CycleStatefulWidgetState ManagementApp Lifecycle

Flutter'da Yaşam Döngüsü (Life Cycle)

İ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.

Canlı Demo: Life Cycle Metodları

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

StatefulWidget Yaşam Döngüsü

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

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ı

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();
  }
}

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();
}

Best Practices

  1. Her zaman mounted kontrolü yapın (async işlemlerde)
  2. dispose'da temizlik yapın (timer, stream, controller)
  3. super metodları çağırmayı unutmayın
  4. initState minimal tutun (ağır işlemler için Future.delayed kullanın)
  5. build() metodunu pure tutun (yan etkisiz)

Ö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 bug-free Flutter uygulamaları yazmanın temelidir!


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

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