Flutter’ın Ana Bileşenleri: Widget, Element ve RenderObject

AbdullahTaş
7 min readOct 2, 2024

--

Flutter, modern ve performanslı mobil uygulamalar geliştirmek için kullanılan bir UI framework’üdür. Flutter’ın başarısının arkasındaki en önemli etkenlerden biri, widget tabanlı ve deklaratif bir UI yaklaşımı sunmasıdır. Bu makalede, Flutter’da widget’ların yeniden çizilme (rebuild) sürecini ve bu sürecin arka planda nasıl işlediğini detaylı bir şekilde ele almaya çalışacağım.

Öncelikle başlıkta belirttiğim ana bileşenlere değinmek istiyorum çünkü bu bileşenleri bilmek oldukça önemli.

1. Widget Nedir?

Widget, Flutter’ın temel yapı taşıdır. UI bileşenlerini tanımlar ve kullanıcı arayüzünün görünümünü ve davranışını belirler. Widget’lar, her şeyin (düğmeler, metinler, görseller, düzenler vb.) tanımlandığı deklaratif yapılar olarak düşünülebilir.

Özellikler

  • Değişmez (Immutable): Widget’lar oluşturulduktan sonra değiştirilemezler. Her değişiklik, yeni bir widget oluşturulmasını gerektirir.
  • Yapısal Tanım: Widget’lar, UI’ın nasıl görünmesi gerektiğini tanımlar, ancak doğrudan ekranda görünmezler.
  • Stateless ve Stateful Widget’lar:
  • Stateless Widget: Durumsuz widget’lardır. Durumlarını tutmazlar ve sadece yapılarını tanımlarlar.
  • Stateful Widget: Durumlu widget’lardır. İçsel durumlarını yönetebilir ve bu duruma bağlı olarak UI’larını güncelleyebilirler.
import ‘package:flutter/material.dart’; 

class MyButton extends StatelessWidget {

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {},
child: Text(‘Tıkla’),
);

}
}

2. Element

Element, bir widget’ın canlı bir temsilidir. Widget ile RenderObject arasında bir köprü görevi görür. Element’lar, widget’ların durumlarını ve ilişkilerini yönetir.

Özellikler

  • Widget ile RenderObject Arasında Köprü: Widget, Element üzerinden RenderObject ile etkileşime girer.
  • Durum Yönetimi: Stateful widget’larda Element, widget’ın durumunu (State) tutar.
  • Ağaç Yapısı: Element’lar, widget ağacının (Widget Tree) canlı bir temsilini oluşturur ve bu ağacı Render ağaçına (Render Tree) dönüştürür.

Çalışma Mantığı

  • Widget Ağacı: Uygulama başlatıldığında, widget ağacı oluşturulur.
  • Element Ağacı: Flutter, widget ağacını alır ve her widget için bir Element oluşturur. Bu Element’lar, widget’ların canlı örnekleridir.
  • Render Ağacı: Element’lar, render object’ları oluşturur ve bunlar, ekranda görüntülenen gerçek UI bileşenleridir.
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int counter = 0;

void increment() {
setState(() {
counter++;
});
}

@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(onPressed: increment, child: Text('Artır'))
],
);
}
}

Bu örnekte, _MyStatefulWidgetState sınıfı, widget'ın durumunu (counter) tutan bir Element olarak görev yapar.

3. RenderObject

RenderObject, UI bileşenlerinin nasıl yerleşeceğini, boyutlandırılacağını ve çizileceğini belirleyen objelerdir. RenderObject’lar, doğrudan ekranda görüntülenen unsurlardır.

Özellikler

  • Yerleşim (Layout): RenderObject’lar, kendilerine ve çocuklarına nasıl yerleşeceklerini belirler.
  • Çizim (Painting): RenderObject’lar, ekrana nasıl çizileceklerini tanımlar.
  • Hit Testing: Kullanıcı etkileşimlerini (dokunma, tıklama vb.) yönetir.
  • Performans: RenderObject’lar, yüksek performanslı ve optimize edilmiş şekilde çalışır.

Çalışma Mantığı

  • Render Ağacı: Element ağacından render ağacına geçiş yapılırken, her Element ilgili RenderObject’u oluşturur.
  • Layout ve Paint: RenderObject’lar, düzen (layout) ve çizim (paint) işlemlerini gerçekleştirir. Bu işlemler, ekranda doğru ve istenen görünümü sağlar.
  • Reaktif Güncellemeler: Widget’lar değiştiğinde, ilgili RenderObject’lar yeniden düzenlenir ve yeniden çizilir.
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class MyRenderBox extends RenderBox {
@override
void performLayout() {
size = constraints.biggest;
}

@override
void paint(PaintingContext context, Offset offset) {
final paint = Paint()..color = Color(0xFF00FF00);
context.canvas.drawRect(offset & size, paint);
}
}

class MyCustomWidget extends LeafRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return MyRenderBox();
}
}

Bu örnekte, MyRenderBox sınıfı, basit bir yeşil dikdörtgen çizen bir RenderObject oluşturur.

Widget, Element ve RenderObject Arasındaki İlişki

Flutter’ın mimarisi, üç temel bileşenin birbirleriyle etkileşimini içerir:

  1. Widget Ağacı Oluşturma: Uygulama başlatıldığında, widget ağacı oluşturulur. Widget’lar, UI’ın yapısını ve görünümünü tanımlar.
  2. Element Ağacı Oluşturma: Flutter, widget ağacını alır ve her widget için bir Element oluşturur. Element’lar, widget’ların canlı temsilleridir ve durumlarını yönetir.
  3. Render Ağacı Oluşturma: Element’lar, ilgili RenderObject’ları oluşturur ve render ağacını oluştururlar. RenderObject’lar, UI bileşenlerini ekranda çizer ve kullanıcı etkileşimlerini yönetir.
  4. Güncellemeler: Widget’larda herhangi bir değişiklik olduğunda, Flutter widget ağacını yeniden oluşturur, Element ve RenderObject ağaçlarını günceller ve sadece değişen kısımları yeniden işler. Bu sayede yüksek performanslı ve verimli bir güncelleme süreci sağlanır.

Özetleyecek olursak;

Widget;

Widgetler Stateless veya Stateful olabiliyor ve sadece konfigürasyon objeleri olarak karşımıza çıkıyorlar. Yani neyin nasıl görünmesi gerektiğine karar veriyoruz. Örneğin bir container oluşturacaksak onun rengi, boyutu gibi parametleri belirliyoruz. Widgetler immutable yani değiştirilemezlerlerdir. Burada kafanız karışmasın lütfen. Hayır ben container oluşturup sonra onu text yaparım diye bir düşünceye kapılmayın :) Değişemez yani immutable olması Diff dediğimiz performans işlemi içindir. Bu işlem sadece değişen widgetin yeniden çizilmesini sağlıyor eğer immutable olmasıydı herhangi bir widgetin değişmesiyle tüm widgetlerin tekrar render olması gerekirdi. Umarım bu cümle açıklayıcı olmuştur :)

Element;

Her widget ekranda yerini bulmak için arka planda bir element oluşturur. Örneğin ekranın soluna bir container widgeti verdiyseniz element sayesinde yerini buluyor. Sadece bu mu? Elbette hayır. Elementler canlı temsil dediğimiz instance olarak widgetlerin arkasında görev alır. Bu ne demek? Yani widgetler immutable ise widgetlerin güncellenme rolünü elementler alıyor ve bu işleme destek oluyor. RendererObject ile widget arasında köprü olmasının nedenide tam olarak bu.
Yine bir container örneği verecek olursak bu widget oluşturulduğunda arkasında bir element bulunur ve bu element containere rehberlik eder. Burada dip not olarak şunuda eklemek gerek widgetlerin state’i de element üzerinden yönetilir. Yine kafa karıştırıcı bir cümle oldu sanki :) Açacak olursak ; StatefulWidget'ın görünümündeki ve davranışındaki dinamik değişiklikleri State objesi kontrol eder ve bu state'in yönetimi aslında bir Element aracılığıyla yapılır.

RenderObject;

Görsel render süreci için kullanılır. Widgetlerin ekranda nasıl çizileceğini belirtir ve bunu yönetir. Bunun yanında yine widgetlerle nasıl etkileşime girileceğinide RenderObject tanımlar. Yine container üzerinden örnek verecek olursak ekranda rengi, boyutu, pozisyonu vb. belirlediğimiz widgetlerin ekranda belirlediğimiz şekilde çizilmesinden sorumlu.

Bu kavramları kabaca öğrenip anlayabildiğinizi varsayıyorum. Ve şimdi adım adım Çalışma mantığını anlatmak istiyorum;

Adım Adım Süreç

Adım 1: Uygulama Başlangıcı ve Widget Ağacı Oluşturma

  • Başlangıç: main fonksiyonu çalışır ve runApp çağrılır.
  • Widget Ağacı Oluşumu: runApp, verilen widget ile widget ağacını oluşturur. Bu ağaç, uygulamanın tüm UI bileşenlerini hiyerarşik bir yapıda temsil eder.

Adım 2: Widget Ağacının Element Ağacına Dönüştürülmesi

Element Ağacı Oluşumu: Flutter, widget ağacını alır ve her widget için bir Element oluşturur.

  • Stateless Widget: Her stateless widget için bir StatelessElement oluşturulur.
  • Stateful Widget: Her stateful widget için bir StatefulElement oluşturulur. StatefulElement, widget'ın durumunu (State) yönetir.

Adım 3: RenderObject Ağacının Oluşturulması

RenderObject’lar: Element’lar, widget’ların görsel temsilini oluşturmak için RenderObject'ları üretirler.

  • Leaf RenderObject: UI’da doğrudan çizim yapılmayan, fakat diğer RenderObject’lara sahip olmayan öğelerdir.
  • Container RenderObject: Diğer RenderObject’ları içinde barındıran ve düzenleyen öğelerdir.
    Adım 4: Layout (Yerleşim) ve Paint (Çizim) Süreçleri

Adım 4: Layout (Yerleşim) ve Paint (Çizim) Süreçleri

  • Layout (Yerleşim): RenderObject’lar, kendilerine ve çocuklarına uygun boyutları ve konumları belirler.
  • Paint (Çizim): RenderObject’lar, belirlenen düzenlemelere göre UI bileşenlerini ekrana çizerler.

Adım 5: Kullanıcı Etkileşimleri ve Güncellemeler

  • Hit Testing: Kullanıcı bir ekrana dokunduğunda, Flutter hangi RenderObject’ın bu etkileşimi alacağını belirlemek için hit testing yapar.
  • Güncellemeler ve Rebuild: Widget’larda herhangi bir değişiklik olduğunda, ilgili Element’lar güncellenir ve RenderObject’lar yeniden düzenlenir ve çizilir.

Adım 6: Performans Optimizasyonları

  • Widget Yeniden Kullanımı: Değişmeyen widget’lar yeniden kullanılabilir.
  • Reaktif Güncellemeler: Flutter, yalnızca değişen bölümleri yeniden işler.
  • Asenkron İşlemler: Layout ve paint işlemleri mümkün olduğunca asenkron olarak gerçekleştirilir.
  • Layering: RenderObject’lar, katmanlar halinde organize edilerek çizim işlemleri optimize edilir.

Kabaca adım adım çalışma sürecinden bahsettim. Esasında derinine inilecek ve anlatılacak çok fazla kavram var ama kabacada olsa bunları en azından duymuş görmüş olmak bu aşamada yeterli olacaktır.
Değerli okurlar ve geliştirici arkadaşlar bu bilgileri bilmek ne işimize yaracak dediğinizi duyar gibiyim cevaben şunları söylemek isterim;

Bu Bilgiler Ne İşimize Yarayacak?

1. Performans Optimizasyonu ve Gereksiz Yeniden Çizimleri Önleme

Flutter’ın çalışma prensiplerini, özellikle Widget, Element, ve RenderObject arasındaki ilişkiyi anlamak, uygulamanızda gereksiz rebuild işlemlerini önlemenize yardımcı olur. Aksi takdirde, her küçük değişiklikte widget ağacınızın tamamını yenileyebilir ve performans sorunlarıyla karşılaşabilirsiniz.

Örneğin:

  • StatefulWidget ile çalışırken sadece gerekli durumlarda setState() çağrısı yapmanız gerektiğini bilirsiniz. setState() çağrısı tüm build metodunu yeniden çalıştırdığı için bu noktada gereksiz yeniden çizimleri engellemek önemlidir.
  • Belirli bir UI parçasını sık sık güncellemeniz gerekiyorsa, bu kısmı ayrı bir widget olarak soyutlayabilirsiniz. Böylece ana widget ağacınızın geri kalanı etkilenmez ve performans kaybı yaşamazsınız.

2. Daha İyi State Yönetimi ve Yerinde Değişiklikler

Flutter’da state yönetimi önemli bir konudur. Widget ağacı, element ağacı ve render ağacı arasındaki ilişkiyi anlamak, uygulamanızda state’i doğru katmanda tutmanızı sağlar.

  • Bir state’i hangi widget seviyesinde tutmanız gerektiğini daha kolay belirlersiniz.
  • StatelessWidget ve StatefulWidget arasında doğru bir seçim yapmanıza yardımcı olur; gereksiz StatefulWidget kullanmaktan kaçınarak uygulamanızın sadeliğini ve performansını koruyabilirsiniz.

Bu, özellikle büyük ve karmaşık projelerde gereksiz karmaşıklıktan kaçınmanıza ve state’in doğru yerde tutulmasını sağlayarak kodunuzun daha kolay yönetilmesini ve debug edilmesini sağlar.

3. RenderObject’ü Bilmekle Optimize Layouts

RenderObject'ün widget'ların nasıl çizildiğini kontrol ettiğini bilmek, layout ve çizim konularında optimizasyon yapmanızı sağlar.

  • Performansı artırmak için kendi özel RenderObject'lerinizi tanımlayabilirsiniz. Bu, özellikle performans kritik animasyonlar veya özel çizim gerektiren durumlarda işe yarar.
  • RenderObject ağacının nasıl çalıştığını bilmek, pahalı layout ve paint işlemlerinden kaçınmanıza yardımcı olur. Böylece uygulamanızda hızı etkileyebilecek potansiyel sorunları daha iyi analiz edebilir ve çözebilirsiniz.

4. Debugging ve Hata Ayıklama Kolaylığı

Bu kavramları bilmek, karşılaşacağınız birçok sorunu daha hızlı anlamanıza ve çözmenize yardımcı olur.

  • Mesela, bir widget ekranda güncellenmiyorsa, bunun neden kaynaklandığını anlamak için Widget'ın state’ini, Element’in güncellenip güncellenmediğini ve RenderObject’ün doğru şekilde render edilip edilmediğini analiz edebilirsiniz.
  • Flutter DevTools gibi araçlarla Widget Inspector veya Performance Overlay kullanarak uygulamanızın hangi noktada performans kaybı yaşadığını ve hangi widget'ların gereksiz yeniden oluşturulduğunu kolayca görebilirsiniz.

5. Daha İyi Animasyon ve UI Tasarımı

Animasyonlar sırasında, Widget ve Element kavramlarını anlamak, hangi kısmın yeniden çizildiğini kontrol etmenize olanak tanır. Gereksiz animasyonlar oluşturmak yerine sadece ihtiyaç duyulan kısımları yeniden render ederek daha pürüzsüz ve verimli animasyonlar oluşturabilirsiniz.

Örneğin:

  • AnimatedBuilder gibi animasyon widget'ları kullanırken, animasyonun sadece hedeflediğiniz kısmı etkilediğinden emin olabilirsiniz.
  • Gereksiz layout ve paint işlemlerinden kaçınarak animasyonun performansını korursunuz.

Evet, umarım sizler için faydalı olacak bir makalenin daha sonuna gelmiş bulunmaktayım. Detaylarını parça parça bir araya getirip aktarmaya çalıştığım bu yazının ilgilileri için faydalı olmasını temenni ediyor, çalışmalarınızda başarılar diliyorum.

--

--

AbdullahTaş
AbdullahTaş

Written by AbdullahTaş

Bilgisayar Programıcısı-AÖF Yönetim Bilişim Sistemleri | Flutter Developer |

No responses yet