Flutter‘da Widget Nedir ? Widget‘ların Yaşam Döngüleri Nasıldır ?

Melisa Öztürk
Team Kraken
Published in
7 min readFeb 22, 2021

Merhaba arkadaşlar, bu ilk medium yazım. Yakın zamanda flutter öğrenmeye başladım. Öğrendiklerimi hem pekiştirmek hem de sizlere de faydası olmasını umarak sizlerle de paylaşmak istiyorum. Bu yazımda size flutter ’ın olmazsa olmazı, en temel yapı taşlarından birisi olan widget yapısından bahsetmek istiyorum.

UI’da gördüğümüz elemanlara(buton, text vb.) flutter dilinde widget diyoruz. Sizlere en basit bu şekilde ifade edebilirim. Tabi öğrenmemiz gereken onlarca widget var. 🙂

Aşağıda RaisedButton() widget yapısının kullanımını görüyorsunuz. Bu widget, standart bir buton yapısıdır ve içerisinde Text() widget ’ını da barındırıyor.

RaisedButton(
onPressed: () {
actionButton(“Raised Button Action”);
},
child: Text(“Raised Button”),
color: Colors.amber,
),
Kodu çalıştırdığımızda şu şekilde bir çıktı alırız.

Widget ’lar tek başlarına çalışmazlar. Ekranda gördüğümüz sadece bir butonun bile oluşması için farklı yapılar bir arada çalışır. Yani tüm iş bir ekip işidir. Şimdi biraz da arka planda çalışan bu gizli kahramanlarımızı tanıyalım.

Widget ve Gizli Kahramanlar

Element

Widget ile konuşur. Bizim tanımladığımız her bilgi buraya gelir. Gelen giden her bilgiyi kontrol eder. Widget bilgilerini(text font, color vb.) widget ağaç yapısında yukarıdan aşağıya taşınmasını ve sonunda render object’e iletilmesini sağlar.

Örneğin; BuildContext bir elementtir. İhtiyacımız olan verilere BuildContext nesnesine erişerek ulaşabiliriz.

Widget yapısındaki context nesnesi yardımı ile widget bilgilerine ağaç yapısı içerisinden de erişebilir ve üzerinde işlemler yapabiliriz.

class MyStatelessWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Widget Example"),
),
body: Center(
child: Column(
),),);
}}

BuildContext: Widget’ a ait elementtir. Ağaç yapısındaki üst ve alt widget’lardan bilgi getirecek olan yapılar bu element’i kullanır. Her widget oluşturduğumuzda bu context bize gelir ve gerekli bilgileri alabiliriz. İhtiyacımız olan verilere BuildContext nesnesine erişerek ulaşabiliriz.

Render object

Element nesnesinden widget için tanımlanan bilgileri alır. Buna göre widget çizim işlemleri yapılır.

Not: Widget ’taki değişiklik ile tip değişmiyorsa element ve render object aynı kalır, değişiyorsa widget eski element ve render object’i yok edip kendi element ve render object ’ini yaratır.

Örneğin; Text() widget ’tan sonra RaisedButton() widget’ı geliyorsa burada element ve render object yok edilip tekrar oluşturulur.

https://giphy.com/gifs/season-13-the-simpsons-13x9-3o6MbkB1vUMBekpMWY

State

Ekranda gördüğümüz widget ’ın o anki durumunu ifade eden bilgidir. Widget‘ın o an içerisinde bulunduğu durum olarak da ifade edebiliriz. Widget içerisinde tuttuğumuz her bilgi state ’dir. Bu bir sayı, nesne vb. herhangi bir şey olabilir. Widget state ‘e göre 2’ye ayrılır.

Stateful Widget: State‘i yani ekrandaki durumunu değiştirebilen widget’ tır. State nesnesi element ile iletişim halindedir ve bizim değişiklikleri kontrol etmemize yardımcı olur. Immutable(değiştirilemez) yapılardır. Fakat burada state mutable(değiştirilebilir) ‘dır.

Stateful widget oluştururken önce bir state yaratmamız gerekiyor. Bunun için createState() metodunu override ederiz. Yaşam döngüleri vardır.

Not: Servis ‘ten gelen verilerin gösterilmesi gibi daha dinamik bir yapı oluşturmak için kullanabiliriz.

Aşağıdaki örnekte RaisedButton ‘a tıkladığımızda navigation bar başlığının değiştiğini görürüz. Bunu yapmak için de setState() metodunu kullandık. Böylece widget ’ımızın state(durum) ‘ ini değiştirdik.

Butona basılmadan önce
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String navBarTitle = "Widget Example";

void actionButton(String text) {
setState(() {
navBarTitle = text;
});}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(navBarTitle),
),
body: Center(
child:
RaisedButton(
onPressed: () {
actionButton("Raised Button Action");
},
child: Text("Raised Button"),
color: Colors.amber,
),),);}}

Stateful Widget Yaşam Döngüsü:

Stateful widget ‘ın yaşam döngüsü 7 adımdan oluşur. Stateful widget yaşamı boyunca widget tekrar tekrar çizilebilir. Bu nedenle build() metodu birden fazla kez çağırılır.

  1. createState(): Stateful widget için state nesnesini oluşturur. Her stateful widget ’ın state ‘i olmalıdır.
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

}

Mounted(true/false): State ‘i init etmeden önce state ve buildContext arasında bağlantı kuran yapıdır. Her widget için true ya da false ‘tur. Yani bir widget ‘a bir buildContext atanmış ya da atanmamıştır. Widget ile buildContext arasında bağlantı kurulduğunda ise true olur.

Not: Bu yapı biraz daha arka planda çalışır. Yaşam döngüsüne dahil olmamakla birlikte bilinmesi gereken küçük bir detaydır. setState() metodunu çağırmadan önce state ’in oluşup oluşmadığını kontrol etmek için kullanılabilir.

2. initState(): State nesnesi oluşturulduğunda çalışan ilk metottur. Sadece bir kere çağırılır. Sadece çağırdığımızda çalışmasını istediğimiz değerler için kullanırız. Örneğin; widget içerisinde değişkenlik gösterecek değerler ya da bir nesnenin oluştuğundan emin olmak için kullanabiliriz.

@override
void initState() {
super.initState();
// TODO: implement initState
}

3. didChangeDependencies(): Bir state değişikliği varsa ve initState() ve setState() ‘den hemen sonra çağırılır. Bu metodu, bu iki case arasında yapmak istediğimiz işlemler için kullanırız. Bu metottan sonra build metodu çağılır. Örneğin; build metodunu ağırlaştırmamak için API işlemlerini burada yapabiliriz.

@override
void didChangeDependencies() {
super.didChangeDependencies();
}

4. build(): Her ne zaman widget state değiştirmek istese çağırılır. Yani yaşam döngüsü boyunca defalarca çağırılabilir. (Örneğin; didUpdateWidget() ya da setState() metotlarından sonra çağırılır.) Widget ağaç yapının oluştuğu, döndürüldüğü yerdir. Bu metot olmadan state nesnesini oluşturamayız.

@override
Widget build(BuildContext context) {
return Scaffold(
);}

5. didUpdateWidget(): Parent widget ‘ta bir state değişikliği varsa bununla ilgili olarak state değişikliği yapılması gereken subWidget ‘lar için kullanırız. Widget aynı runtime ‘da rebuilt edilir. Bu metottan sonra her zaman build() metodu çağırılarak widget tekrar oluşturulur.

Widget değişikliğinde emin olmak istediğimiz bir durum varsa ya da widget update oldu mu kontrol ederek gerekli işlemleri yapmak için kullanabiliriz. Örneğin; bir widget içerisinde custom bir widget kullanmışsak, widget ‘taki değişikliklere bağlı olarak custom widget ’ta da değişiklik olması gerekiyorsa buradaki state değişikliklerini yapmak için kullanabiliriz.

@override
void didUpdateWidget(StatefulWidget oldWidget) {
if (oldWidget.variable != widget.variable) {
_init();
}}
@override
void didUpdateWidget(StateBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this);
}

6. setState(): UI ‘ı etkileyebilecek bir durum değişikliğini bildirmek için kullanılır. Sonrasında build() metodu çağırılır ve widget tekrar çizilir.

setState(() {
// implement setState
});

7. deactivate(): Widget ağaç yapısından bir widget ya da state silindiği zaman state inactive duruma geçer. UI işlemleri tamamlandıktan sonra widget tekrar oluşturamamış yani hala inactive durumda ise kesin olarak silinir. Inactive durumdaki widget‘lar ekranda görünmez.

@override
void deactivate() {
super.deactivate();
_state.deactivate();
// TODO: implement deactivate
}

8. dispose(): State widget ağaç yapısından kalıcı olarak silindiğinde çağırılır. Nesnemize ait hafıza, listener vb. bağlantılı olduğu yapılar varsa bağlantıları kopartılır.

@override
void dispose() {
super.dispose();
// TODO: implement dispose
}

Not: dispose() metodu çalıştıktan sonra mounted false olur. Çünkü artık ağaç yapımızda bir state nesnesi bulunmuyor.

Stateless Widget: Tamamen değiştirilemez bir nesnedir. Bir kere oluşturunca olduğu yerde o şekilde kalır. State(durum) değiştiremez. Bu nedenle statik ekranlar için daha uygundur. Immutable(değiştirilemez) ‘dırlar. Yaşam döngüleri yoktur. Çünkü sadece bir kere çizilir ve sonrasında sabit kalır. Herhangi bir değişikliği olmaz, tekrar tekrar çizilmez. Önce element’i oluşturur. Sonrasında da build metodundaki işlemleri yapar

Not: Kalabalık, büyük kod bloklarını parçalamak(metotlara bölmek) için kullanabiliriz. Böylecek tekrarlardan kurtuluruz, kod daha okunur ve performanslı hale gelir.

Aşağıdaki örnekte RaisedButton ‘a tıkladığımızda stateful widget’ta olduğu gibi navigation bar başlığı değişmez. Çünkü stateless widget içerisinde state set edemiyoruz. Ancak bir görselin gösterilmesi gibi daha statik işlemler için kullanabiliriz.

class MyStatelessWidget extends StatelessWidget {
/* Stateless widget içerisinde stateful widget'ta olduğu gibi aşağıdaki şekilde state set edemeyiz.
void actionButton(String text) {
setState(() {
navBarTitle = text;
});}*/
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Widget Example"),
),
body: Center(
child: Column(
children: [
Container(
child: Image.asset("images/pengu.jpeg", fit: BoxFit.fill),
width: 400,
height: 500,
),
RaisedButton(
onPressed: () {
//State tanımlayamadığımız için böyle bir tanımlama da yapamayız. Bu nedenle navigation bar başlığı değişmez.
// actionButton("Raised Button Action");
},
child: Text("Raised Button"),
color: Colors.amber,
),],),),);}}

Custom Widget

Diğer dillerde olduğu gibi burada da ui element ’lerini kişiselleştirebiliyoruz. Bir widget ‘ı ihtiyacımız doğrultusunda düzenleyip, geliştirerek ileride aynı özelliklere sahip bir widget ‘a daha ihtiyaç duyduğumuzda, daha önce yarattığımız widget ‘ı tekrar kullanabiliriz.

Aşağıdaki örnekte text, color, background color değişkenlerini dışarıdan alarak bir custom widget oluşturduk.

class CustomWidget extends StatelessWidget {
final String text;
final Color textColor;
final Color textBackgroundColor;
CustomWidget({@required this.text, @required this.textColor, @required this.textBackgroundColor}); @override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Text("Custom Widget Text");
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)
),
child: Text("Custom Widget Raised Button", style: TextStyle(fontSize: 20.0, color: textColor, backgroundColor: textBackgroundColor),),
color: Colors.amber,
);}}
class MyCustomWidgetExample extends StatefulWidget {
@override
_MyCustomWidgetExampleState createState() => _MyCustomWidgetExampleState();
}
class _MyCustomWidgetExampleState extends State<MyCustomWidgetExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Custom Widget"),
),
body: Center(
child: CustomWidget(
text: "Custom Widget Example Test",
textColor: Colors.amber,
textBackgroundColor: Colors.deepPurple)),
);}}

TeamKraken olarak düzenli bir şekilde paylaşımlarımıza devam edeceğiz. Bizi takip etmeyi unutmayın.

Örnek projeye GitHub ‘dan erişebilirsiniz. Bu yazdığım ilk yazı, herhangi bir eksiğim, hatam ya da daha iyi yapabileceğim bir şey var ise bilgilendirseniz çok sevinirim. Yorumlarınızı bekliyor olacağım. İlginiz için teşekkürler. 🙏

--

--