Performance Optimisation in Unity: Tips for Smoother and More Efficient Games

 

Unity and Performance Optimization

Hello everyone,

In this article, I will talk about performance, which is sometimes overlooked by most developers, including me, in the early stages of the game development process, but is very important. I said very important because performance is a critical factor that directly affects the player experience. In order to offer a smoother and more enjoyable experience to the players and to make our game more demanded and played, we need to develop by paying attention to details such as high frame rates (FPS) and low latency. Unity is a very powerful game engine in this sense, and when used correctly, it helps us develop high-performance games. Since I have experienced that it is so important, I have researched what we can do for performance optimisation with Unity, what are the most effective techniques and tips. Let’s get started.

1. Using Profiler

Unity Profiler is a tool for analysing game performance. Profiler monitors CPU and GPU usage, memory allocation, and other performance metrics. Thanks to this tool, we have the chance to identify the source of performance problems and find solutions for them.

How to use it?

  • First of all, we use Window => Analysis => Profiler to open the Profiler window. With this, we aim to see performance data.
  • Then we run Profiler while playing our game and wait for it to collect data in real time. By analysing the performance in different scenes, we get a feedback from the game.
  • At this point, we analyse the data we collect. For this, we can examine different tabs such as CPU, GPU, Rendering, Memory in the Profiler window and identify performance degrading points here.

2. Optimising Graphics Settings

LOD (Level of Detail) Usage

LOD allows distant objects to be rendered with less detailed models. This improves performance, especially in large scenes, by de-detailing some of the objects in the game.

  • Creating LOD Groups: In a game, we can create different levels of detail to let Unity know which objects should be rendered in more detail and which ones don’t need all the detail. We do this mostly for objects and manage it with the LOD Group component. Thus, especially in large scenes, objects that are not in sight are rendered with less detailed models and performance is increased.
  • Culling: We can improve performance by setting a game plan so that we do not need to render objects outside the camera field of view. We can increase performance by using Occlusion Culling.

Batching Techniques

  • Static Batching: Used for static (motionless) objects. It renders many objects with a single draw call.
  • Dynamic Batching: Used for dynamic (moving) objects. Reduces the number of draw calls by combining small and similar objects.

3. Physics and Collision Optimisation

Rigidbody and Collider Settings

  • Adding Rigidbody component unnecessarily may cause performance degradation. Instead, it is sufficient to add it only to objects that interact physically.
  • Another point about game performance is to use simpler Collider types if possible. Examples of simple Collider types are Box and Sphere. Using Mesh Colliders instead of more standardised ones like these can negatively affect performance.

Physics Simulation

  • Physics simulation is important to make the game realistic. However, physics calculations are often resource intensive, which affects game performance. The first thing we can do to prevent this is to set the Fixed Timestep value. The physics simulation is updated at certain time intervals, not every frame. This interval is called Fixed Timestep and we can set it in Edit => Project Settings => Time. This value is set to 0.02 (50 FPS) by default. We can adjust this value according to our game and performance requirements. A higher value means fewer physics updates, which can improve performance, but may reduce the accuracy of the physics simulation.
  • Another topic is what can be done with Physics Layers. Physics layers control which objects collide with each other. We can use these layers to prevent unnecessary collision checks. First we create new layers in Edit => Project Settings => Tags and Layers. Then we assign objects to the appropriate layers from the Inspector panel. Finally, in Edit => Project Settings => Physics, we determine which layers will collide with each other. For example, there may be collisions between player and bullet layers, but not between background objects and bullets. In this case, the unnecessary collision does not need to run in the background. And certainly not to degrade performance.

4. Memory Management

Object Pooling

  • This method allows us to create a certain number of objects in advance for frequently created and destroyed objects and reuse them. Especially since continuous creation and destruction of new objects can negatively affect performance, we can reduce this cost and prevent performance degradation with object pooling. Let’s also mention a few points to be considered while doing this. It is important to determine the initial size of the pool according to the needs of our game. A pool that is too small may cause continuous creation of new objects; a pool that is too large means unnecessary memory usage. Then what we do will not have the desired effect on performance. We should also keep a good track of the state of the objects. Objects must be made suitable for reuse. For example, bullets should return to the pool when they reach the target or after a certain period of time. Finally, when returning objects to the pool, we must remember to deactivate them. This prevents the objects from performing unnecessary calculations.
  • We also need to avoid creating new objects by caching objects that are used repeatedly.

Memory Usage Monitoring

  • With Unity Profiler we can monitor memory usage and identify unnecessary memory allocations.
  • We also need to minimise GC (Garbage Collection) operations. If we can prevent unnecessary memory allocation and reduce the activation of GC, this also contributes positively to performance.

5. Code Optimisation

Efficient Code Writing

  • LINQ (Language Integrated Query) makes it easier to make queries on data, but the performance cost is high. For this reason, it may be more advantageous to use classic loops and conditions instead of LINQ in parts where we expect high performance.
  • In C#, memory management is performed by Garbage Collector (GC). Memory is allocated when new objects are created and cleared by GC when these objects are not in use. However, unnecessary memory allocations and consequent attempts to delete unnecessary created objects can negatively affect performance.

Asynchronous Operations

  • Async and Await: Performing long-running operations such as network operations or disc read/write asynchronously ensures that the main game loop runs uninterrupted.

As a result, performance optimisation in Unity is of great importance as it makes our games run smoother and more efficiently. In this article, I have tried to explain techniques and tips that will help us improve our performance. I hope that by applying these tips, we can offer a better gaming experience to our players.

Good coding to all of us.

Thanks for reading.

Selin.

Unity’de Performans Optimizasyonu: Daha Akıcı ve Verimli Oyunlar İçin İpuçları

 

Unity ve Performans Optimizasyonu

Herkese merhaba,

Bu yazımda sizlere oyun geliştirme sürecinin ilk aşamalarında ben de dahil olmak üzere çoğu geliştiriciler tarafından zaman zaman göz ardı edilen ancak çok önemli olan performans konusundan bahsedeceğim. Çok önemli dedim çünkü performans oyuncu deneyimini doğrudan etkileyen kritik bir faktör. Oyunculara daha akıcı ve zevkli bir deneyim sunmak ve oyunumuzun daha fazla talep görmesi ve oynanması için yüksek kare hızları (FPS) ve düşük gecikme süreleri gibi detaylara dikkat ederek geliştirme yapmamız gerekiyor. Unity, bu anlamda çok güçlü bir oyun motoru ve doğru kullanıldığında yüksek performanslı oyunlar geliştirmemize oldukça yardımcı oluyor. Bu kadar önemli olduğunu deneyimlediğim için Unity ile performans optimizasyonu için neler yapabiliriz, en etkili teknikler ve ipuçları nelerdir, onları araştırdım. Gelin, başlayalım.

1. Profiler Kullanımı

Unity Profiler, oyunun performansını analiz etmek için kullanılan bir araç. Profiler, CPU ve GPU kullanımını, bellek tahsisini, ve diğer performans metriklerini izliyor. Bu araç sayesinde performans sorunlarının kaynağını tespit edip buna yönelik çözüm üretme şansına sahip oluyoruz.

Nasıl Kullanılır?

  • Öncelikle Profiler’ penceresini açmak için Window => Analysis => Profiler adımlarını kullanıyoruz. Bununla performans verilerini görmeyi amaçlıyoruz.
  • Sonrasında oyunumuzu oynarken Profiler’ı çalıştırıyoruz ve gerçek zamanlı olarak verileri toplamasını bekliyoruz. Farklı sahnelerdeki performansı inceleyerek oyundan bir feedback almış oluyoruz.
  • Bu noktada topladığımız verilerle ilgili analiz yapıyoruz. Bunun için Profiler penceresinde bulunan CPU, GPU, Rendering, Memory gibi farklı sekmeleri inceleyebiliriz ve buradaki performans düşüren noktaları belirleyebiliriz.

2. Grafik Ayarlarını Optimize Etmek

LOD (Level of Detail) Kullanımı

LOD, uzaktaki nesnelerin daha az detaylı modellerle render edilmesini sağlar. Bu, oyundaki nesnelerin bazılarını ayrıntılarından arındırarak özellikle büyük sahnelerde performansı artırır.

  • LOD Grupları Oluşturma: Bir oyunda hangi nesnelerin daha detaylı gösterileceği, hangilerinin tüm detaylarına ihtiyaç duyulmayacağını Unity’e bildirebilmek için farklı detay seviyeleri oluşturabiliriz. Bunu çoğunlukla nesneler için yaparız ve LOD Grubu bileşeni ile yönetiriz Böylelikle özellikle büyük sahnelerde göz önünde olmayan nesneler daha az detaylı modeli ile render edilir ve performans artışı sağlanır.
  • Culling: Kamera görüş alanı dışındaki nesneleri render etmemize gerek olmayacak şekilde bir oyun planı koyarak performans artışı konusunda gelişme sağlayabiliriz. Occlusion Culling kullanarak performansı artırabiliriz.

Batching Teknikleri

  • Static Batching: Statik (hareketsiz) nesneler için kullanılır. Birçok nesneyi tek bir draw call ile render eder.
  • Dynamic Batching: Dinamik (hareketli) nesneler için kullanılır. Küçük ve benzer nesneleri birleştirerek draw call sayısını azaltır.

3. Fizik ve Çarpışma Optimizasyonu

Rigidbody ve Collider Ayarları

  • Gereksiz yere Rigidbody bileşeni eklemek performans düşüşüne sebep olabilir. Bunun yerine sadece fiziksel etkileşime giren nesnelere eklemek yeterlidir.
  • Mümkünse daha basit Collider türlerini kullanmak oyun performansı ile ilgili bir başka nokta. Basit Collider türlerine Box ve Sphere’i örnek verebiliriz. Bu gibi daha standardize edilmişlerin yerine Mesh Collider kullanmak performansı olumsuz etkileyebilir.

Fizik Simülasyonu

  • Fizik simülasyonu oyunu gerçekçi kılmak için önemli. Ancak, fizik hesaplamaları çoğu zaman yoğun kaynak kullanımı gerektiriyor ve bu da oyun performansını etkiliyor. Bunu önlemek için yapabileceğimiz ilk şey Fixed Timestep değerini ayarlamak. Fizik simülasyonu, her çerçevede değil, belirli zaman aralıklarında güncelleniyor. Bu aralık Fixed Timestep olarak adlandırılıyor ve bunu Edit => Project Settings => Time adımlarından ayarlayabiliriz. Bu değer varsayılan olarak 0.02 (50 FPS) olarak ayarlanmıştır. Bu değeri, oyunumuza ve performans gereksinimlerimize göre ayarlayabiliriz. Daha yüksek bir değer daha az fizik güncellemesi anlamına gelir ve bu da performansı artırabilir, ancak fizik simülasyonunun doğruluğunu azaltabilir.
  • Bir diğer konu fizik katmanları (Physics Layers) ile yapılabilecekler. Fizik katmanları, hangi nesnelerin birbirleriyle çarpışacağını kontrol eder. Gereksiz çarpışma kontrollerini önlemek için bu katmanları kullanabiliriz. Önce Edit => Project Settings => Tags and Layers adımlarından yeni katmanlar oluştururuz. Sonrasında nesneleri Inspector panelinden uygun katmanlara atama yaparız. Son olarak Edit => Project Settings => Physics adımlarından hangi katmanların birbirleriyle çarpışacağını belirleriz. Örneğin, oyuncu ve mermi katmanları arasında çarpışma olabilir, ancak arka plan nesneleriyle mermiler arasında çarpışma olmayabilir. Bu durumda gereksiz olan çarpışmanın arka planda çalışmasına gerek yoktur. Ve tabi performansı düşürmesine de.

4. Bellek Yönetimi

Nesne Havuzlama (Object Pooling)

  • Bu yöntem, sıkça oluşturulan ve yok edilen nesneler için önceden belirli sayıda nesne yaratıp bunları yeniden kullanmayı sağlıyor. Özellikle sürekli olarak yeni nesne oluşturma ve yok etme işlemleri performansı olumsuz etkileyebileceğinden, nesne havuzlama ile bu maliyeti azaltabiliyor ve performansın düşmesini engelleyebiliyoruz. Bunu yaparken dikkat edilmesi gereken birkaç noktaya da değinelim. Havuzun başlangıç boyutunu oyunumuzun ihtiyaçlarına göre iyi belirlememiz önemli. Çok küçük bir havuz, sürekli yeni nesne oluşturulmasına neden olabilir; çok büyük bir havuz ise gereksiz bellek kullanımı anlamına gelir. O zaman yaptığımız işlemin performansa istenen yönde bir etkisi olmaz. Ayrıca nesnelerin durumunu da iyi takip etmeliyiz. Nesneler mutlaka tekrar kullanıma uygun hale getirilmeli. Örneğin, mermiler hedefe ulaştığında veya belirli bir süre geçtikten sonra havuza geri dönmeli. Son olarak da nesneleri havuza iade ederken, bunları devre dışı bırakmayı unutmamamız gerekiyor. Bu, nesnelerin gereksiz yere hesaplama yapmasını engeller.
  • Tekrar tekrar kullanılan nesneleri önbelleğe alarak yeni nesne oluşturma işlemlerinden de kaçınmamız gerekiyor.

Bellek Kullanımı İzleme

  • Unity Profiler ile bellek kullanımını izleyip gereksiz bellek tahsislerini belirleyebiliriz.
  • GC (Garbage Collection) işlemlerini de minimize etmemiz gerekir. Gereksiz bellek tahsisini önleyerek GC’nin devreye girmesini azaltabilirsek, bu da performansa olumlu yönde katkı sağlar.

5. Kod Optimizasyonu

Verimli Kod Yazımı

  • LINQ (Language Integrated Query), veriler üzerinde sorgular yapmayı kolaylaştırıyor ancak performans maliyeti yüksek.Bu sebeple yüksek performans beklediğimiz kısımlarda LINQ yerine klasik döngüleri ve koşulları kullanmak daha avantajlı olabilir.
  • C# dilinde bellek yönetimi, Garbage Collector (GC) tarafından gerçekleştiriliyor. Yeni nesneler oluşturulduğunda bellek tahsis ediliyor ve bu nesneler kullanılmadığında GC tarafından temizleniyor. Ancak, gereksiz bellek tahsisleri ve buna bağlı olarak gereksiz oluşturulan nesnelerin silinmeye çalışılması performansı olumsuz etkileyebilir.

Asenkron İşlemler

  • Async ve Await: Ağ işlemleri veya disk okuma/yazma gibi uzun süren işlemleri asenkron olarak gerçekleştirmek ana oyun döngüsünün kesintisiz çalışmasını sağlar.

Sonuç olarak Unity’de performans optimizasyonu, oyunlarımızın daha akıcı ve verimli çalışmasını sağladığından büyük öneme sahip. Ben de bu makalede performansımızı artırmamıza yardımcı olacak teknikler ve ipuçlarını anlatmaya çalıştım. Bu ipuçlarını uygulayarak, oyuncularımıza daha iyi bir oyun deneyimi sunabileceğimizi umuyorum.

Hepimize iyi kodlamalar.

Teşekkürler.

Selin.

Simple ToDo Application with Provider

 

Hello,
Today I would like to talk about and illustrate a structure that I found very difficult, complex and took a lot of time to understand during my Flutter learning process. Our topic is the Provider structure in Flutter. I have previously talked about the “State” logic in Flutter, the differences between stateful and stateless widgets. I leave a reminder link below:

Flutter and Widget Concept

We use Stateful Widgets if there will be changes in the application as a result of user interaction or for other reasons. That is, at points that should contain a State object. And with very simple logic, if there is a “State” in the middle, it needs to be managed, that is, we need to choose and use one of the State Management methods so that we can track the state of the State and instantly show the changing interface to the user as a result of interaction. I want to talk about the Provider structure, which is one of these methods. Let’s start if we are ready.

What is Provider ?

Provider is a package used for state management in Flutter, and is used to manage and share state in specific regions or across the application. Provider provides a simple and efficient way to recreate, update and share state.

The encyclopaedic information I used above can be found everywhere. I will show how to use it with a short and simple todo application.

When our project is completed, it will look like the following:

Basic ToDoApp with Provider

Step 1:

First, we create and name a new Flutter application. After the project is created, we open the pubspec.yaml file and add the Provider package here.

dependencies:
flutter:
sdk: flutter
provider: ^6.0.0 //The version number may change
//according to the date you created the application.

Step 2:

The second step is to create a model representing the state. Since our application is TodoApp, we can create a Todo class:

class Todo {
//We will check the todo items with checkbox.
String title;
bool isDone;

Todo({
required this.title,
this.isDone = false, //checkbox will initially come unchecked
});

void toggleDone() {
isDone = !isDone; //method to add tick if there is no tick
//when checkbox is clicked and delete if there is tick
}
}

Step 3:

In the next stage, we need to create a class that manages State and extend it with “ChangeNotifier”. ChangeNotifier is a class that listens and notifies state changes. By calling the notifyListeners() method of this class, we notify this change to all components (widgets) that listen for changes. If we go from our example, let’s say the user wanted to cross out an item he added to the Todo list and ticked the checkbox, thanks to this class, the change is notified to the user interface instantly and the checkbox tick appears and the item is crossed out.

class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];

List<Todo> get todos => _todos;

void addTodo(String title) { //Method to add from CRUD operations
_todos.add(Todo(title: title));
notifyListeners(); //method that allows changes to be reflected
//to the interface instantly
}

void toggleTodoStatus(int index) {
_todos[index].toggleDone();
notifyListeners();
}
}

Step 4:

In this step, we integrate the Provider structure into our main.dart file. ChangeNotifierProvider creates an instance of the ChangeNotifier class (we can think of it as getting an instance) and declares State to subcomponents.

void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TodoProvider(),
child: MyApp(),
),
);
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoListScreen(),
);
}
}

Step 5:

Finally, we create another file called todoListScreen.dart and redirect the MaterialApp’s home page to it.

class TodoListScreen extends StatelessWidget {
final TextEditingController _controller = TextEditingController();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('To-Do List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'New To-Do'),
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
if (_controller.text.isNotEmpty) {
Provider.of<TodoProvider>(context, listen: false)
.addTodo(_controller.text);
_controller.clear();
}
},
),
],
),
),
Expanded(
child: Consumer<TodoProvider>(
builder: (context, todoProvider, child) {
return ListView.builder(
itemCount: todoProvider.todos.length,
itemBuilder: (context, index) {
final todo = todoProvider.todos[index];
return ListTile(
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
trailing: Checkbox(
value: todo.isDone,
onChanged: (value) {
todoProvider.toggleTodoStatus(index);
},
),
);
},
);
},
),
),
],
),
);
}
}

There are two uses I need to explain here. The first one is Consumer.

Expanded widget with Consumer is the place where todo items will be displayed. Here, the Consumer allows us to display the todo items right below when the user adds an item. Because Consumer listens to the provider (TodoProvider) using the builder method and automatically rebuilds the widget when the state changes.

The second use is the Provider.of<T>(context) construct. This method accesses the provider of the specified type, which in our example is TodoProvider. Here we have a method called addTodo and using this method new todo items can be added to the list. Here we want to access only the state without listening to the state change, so we give the listen parameter as false. If we give true, it also listens to these changes, but it may cause performance problems because it is unnecessary here.

Finally, we have experienced state management by combining the simple To-Do List application with the Provider structure. We can further develop this project, add persistent data storage or more advanced user interface features.

I tried to explain the Provider structure in a very short and simple way. I hope it was useful.

Good coding to all of us.

Selin.

Provider ile Basit ToDo Uygulaması

 

Merhaba,

Bugün sizlere Flutter öğrenme sürecimde beni epey zorlayan, karmaşık bulduğum ve anlamak için oldukça fazla zaman ayırdığım bir yapıdan bahsetmek ve bunu örneklemek istiyorum. Konumuz Flutter’da Provider yapısı. Daha önce Flutter’da “State” mantığından, stateful ve stateless widget’ların farklılıklarından söz etmiştim. Aşağıya hatırlatma linki bırakıyorum:

Flutter ve Widget Kavramı

Stateful Widget’ları uygulamada kullanıcı etkileşimi sonucu veya başka sebeplerle değişiklik olacaksa kullanıyoruz. Yani State nesnesi içermesi gereken noktalarda. Ve çok basit mantıkla eğer ortada bir “State” var ise, bunun yönetilmesi gerekir yani State Management yöntemlerinden birini seçmemi ve kullanmamız gerekir ki State’in durumunu takip edebilelim ve etkileşim sonucu değişen arayüzü kullanıcıya anlık olarak gösterebilelim. Ben size bu yöntemlerden biri olan Provider yapısından bahsetmek istiyorum. Hazırsak başlayalım.

Provider Nedir?

Provider, Flutter’da state management için kullanılan bir paket olup, uygulamanın belirli bölgelerinde veya genelinde durumu (state) yönetmek ve bu durumu paylaşmak için kullanılır. Provider, durumun yeniden oluşturulması, güncellenmesi ve paylaşılması konusunda basit ve etkili bir yol sunar.

Yukarıda kullandığım ansiklopedik bir bilgi, her yerde bulunabilir. Ben kısa ve basit bir todo uygulaması ile açıklamalı olarak bunun nasıl kullanılacağını göstereceğim.

Projemiz tamamlandığında aşağıdaki gibi görünecek:

Provider ile Basit ToDoApp

1. Adım:

İlk olarak yeni bir Flutter uygulaması oluşturup isimlendiriyoruz. Proje oluştuktan sonra pubspec.yaml dosyasını açarak buraya Provider paketini ekliyoruz.

dependencies:
flutter:
sdk: flutter
provider: ^6.0.0 //versiyon numarası
//sizin uygulamayı oluşturduğunuz
//tarihe göre değişebilir.

2. Adım:

İkinci adım olarak durumu (state) temsil eden bir model oluşturuyoruz. Uygulamamız TodoApp olduğu için bir Todo sınıfı oluşturabiliriz:

class Todo {
//todo maddelerini checkbox ile kontrol edeceğiz.
String title;
bool isDone;

Todo({
required this.title,
this.isDone = false, //checkbox başlangıçta işaretlenmemiş olarak gelecek
});

void toggleDone() {
isDone = !isDone; //checkbox tıklandığında tick yok ise eklenmesini,
//var ise silinmesini sağlayan metod
}
}

3. Adım:

Bir sonraki aşamada State’i yöneten bir sınıf oluşturmamız gerekiyor ve “ChangeNotifier” ile genişletiyoruz. ChangeNotifier, durum değişikliklerini dinleyen ve bildiren bir sınıftır. Bu sınıfa ait notifyListeners() isimli metodunu çağırarak d,a değişiklikleri dinleyen tüm bileşenlere (widget'lara) bu değişikliği bildirmiş oluyoruz. Örneğimizden gidecek olursak, kullanıcı Todo listesine eklediği bir maddenin üzerini çizmek istedi ve checkbox’ı işaretledi diyelim, bu sınıf sayesinde kullanıcı arayüzüne değişiklik anında bildiriliyor ve checkbox tick’i beliriyor ve maddenin üzeri çiziliyor.


class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];

List<Todo> get todos => _todos;

void addTodo(String title) { //CRUD işlemlerinden eklemeyi sağlayan metot
_todos.add(Todo(title: title));
notifyListeners(); //değişikliklerin anlık olarak arayüze yansıtılmasını
//sağlayan metot
}

void toggleTodoStatus(int index) {
_todos[index].toggleDone();
notifyListeners();
}
}

4. Adım:

Bu adımda main.dart dosyamıza Provider yapısını entegre ediyoruz. ChangeNotifierProvider, ChangeNotifier sınıfının bir örneğini oluşturuyor (instance almak gibi düşünebiliriz) ve alt bileşenlere State bildirimi yapıyor.

void main() {
runApp(
ChangeNotifierProvider(
create: (context) => TodoProvider(),
child: MyApp(),
),
);
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TodoListScreen(),
);
}
}

5. Adım:

Son olarak todoListScreen.dart adında bir dosya daha oluşturuyoruz ve MaterialApp’in home sayfasını buna yönlendiriyoruz.

class TodoListScreen extends StatelessWidget {
final TextEditingController _controller = TextEditingController();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('To-Do List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'New To-Do'),
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
if (_controller.text.isNotEmpty) {
Provider.of<TodoProvider>(context, listen: false)
.addTodo(_controller.text);
_controller.clear();
}
},
),
],
),
),
Expanded(
child: Consumer<TodoProvider>(
builder: (context, todoProvider, child) {
return ListView.builder(
itemCount: todoProvider.todos.length,
itemBuilder: (context, index) {
final todo = todoProvider.todos[index];
return ListTile(
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
trailing: Checkbox(
value: todo.isDone,
onChanged: (value) {
todoProvider.toggleTodoStatus(index);
},
),
);
},
);
},
),
),
],
),
);
}
}

Burada açıklamam gereken iki kullanım var. Birincisi Consumer.

Consumer’ın yer aldığı Expanded widget’ı todo maddelerinin görüntüleneceği yeri oluşturuyor. Burada Consumer bize kullanıcı bir madde eklediğinde hemen altında görüntülenmesini sağlıyor. Çünkü Consumer, builder metodunu kullanarak, sağlayıcıyı (TodoProvider) dinliyor ve durum değiştiğinde otomatik olarak widget'ı yeniden oluşturuyor.

İkinci kullanım ise Provider.of<T>(context) yapısı. Bu metod belirtilen türdeki provider’a ulaşıyor, ki bu bizim örneğimizde TodoProvider. Burada addTodo isimli bir metodumuz var ve bu metodu kullanarak listeye yeni todo maddeleri eklenebiliyor. Burada durum değişikliğini dinlemeden sadece duruma erişmek istediğimizden listen parametresini false olarak veriyoruz. true verirsek bu değişiklikleri de dinler ancak burada gereksiz olduğundan performans sorunlarına yol açabilir.

Son olarak, basit To-Do Listesi uygulaması ile Provider yapısını birleştirerek state management (durum yönetimi) konusunu deneyimlemiş olduk. Bu projeyi daha da geliştirebilir, kalıcı veri depolama veya daha gelişmiş kullanıcı arayüzü özellikleri ekleyebiliriz.

Provider yapısını oldukça kısa ve basit şekliyle anlatmaya çalıştım. Umarım faydası olmuştur.

Hepimize iyi kodlamalar.

Selin.