Flutter'da Yaşam Döngüsü (Life Cycle) - StatefulWidget ve AppLifecycleState
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ığındadidUpdateWidget()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
- Her zaman mounted kontrolü yapın (async işlemlerde)
- dispose'da temizlik yapın (timer, stream, controller)
- super metodları çağırmayı unutmayın
- initState minimal tutun (ağır işlemler için Future.delayed kullanın)
- build() metodunu pure tutun (yan etkisiz)
Özet
StatefulWidget Life Cycle:
createState()- State oluşturinitState()- İlk kurulumdidChangeDependencies()- Bağımlılık değişimibuild()- UI oluşturdidUpdateWidget()- Parent değişimisetState()- State güncellemedispose()- Temizlik
App Life Cycle:
resumed- Aktifinactive- Geçici pasifpaused- Arka plandetached- 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! 🚀