Back to all articles
DartFlutterProgramlamaOOPNull SafetyAsync

Dart'a Giriş: Flutter'ın Dilini Sıfırdan Öğrenin

Kurulumdan değişkenlere, null safety'den koleksiyonlara, fonksiyonlardan sınıf ve mixin'lere, records ve pattern matching'den async/await, Stream ve Isolate'lara, paket yönetiminden dosya, JSON, test ve derlemeye kadar Dart dilini örneklerle, sıfırdan ve adım adım öğrenin.

Dart'a Giriş: Flutter'ın Dilini Sıfırdan Öğrenin

Dart, Google tarafından geliştirilen; hızlı, güvenli ve öğrenmesi kolay, nesne yönelimli bir programlama dilidir. Bugün en çok Flutter'ın dili olarak tanınır: tek bir kod tabanıyla iOS, Android, web ve masaüstü uygulamaları yazmanızı sağlayan Flutter'ın arkasında Dart vardır. Ancak Dart yalnızca Flutter'dan ibaret değildir; komut satırı araçları, sunucu uygulamaları ve web projeleri de Dart ile yazılabilir.

Bu rehberde Dart'ı hiç bilmediğinizi varsayarak sıfırdan başlayacağız: kurulumdan ilk programa, değişkenlerden veri tiplerine, null safety'den koleksiyonlara, fonksiyonlardan sınıflara, kalıtım ve mixin'lerden enum ve generic'lere, records ve pattern matching'den async/await, Stream ve Isolate'lara, paket yönetiminden dosya, JSON, test ve derlemeye kadar ihtiyacınız olan her konuyu örneklerle inceleyeceğiz. Yazının sonunda tüm parçaları birleştiren eksiksiz bir komut satırı uygulaması ve hızlı başvuru listesi de bulacaksınız.

Dart Nedir?

Dart, statik tipli (değişkenlerin tipleri derleme anında bilinir), null güvenli (null hatalarını derleme anında yakalar) ve çok platformlu bir dildir. Sözdizimi C ailesine benzer; Java, C#, JavaScript, TypeScript veya Kotlin biliyorsanız Dart kodunu ilk gördüğünüzde büyük kısmını okuyabilirsiniz.

void main() {
  final names = ['Dart', 'Flutter', 'Ertu'];

  for (final name in names) {
    print('Merhaba, $name!');
  }
}

Dart'ı öne çıkaran özellikler:

  • Sağlam tip sistemi (sound type system): Tipler yalnızca bir öneri değildir; bir değişken int ise çalışma anında başka bir şey olamaz.
  • Sound null safety: Bir değişken açıkça izin vermediğiniz sürece null olamaz. Uygulamaların en sık çöktüğü hata sınıfı derleme anında ortadan kalkar.
  • Hem JIT hem AOT derleme: Geliştirirken anında yeniden yükleme (hot reload), yayında ise hızlı açılan yerel makine kodu.
  • Modern dil özellikleri: Records, pattern matching, sealed sınıflar, extension metotlar, async/await ve Stream'ler.
  • Zengin araç seti: Biçimlendirici, statik analiz, test ve paket yöneticisi tek bir dart komutunda gelir.

Dart ile Neler Yapılır?

  • Mobil uygulamalar: Flutter ile tek kod tabanından iOS ve Android uygulamaları.
  • Web uygulamaları: Flutter Web veya Dart'ın JavaScript ve WebAssembly derleyicileriyle tarayıcıda çalışan uygulamalar.
  • Masaüstü uygulamaları: Flutter ile Windows, macOS ve Linux.
  • Sunucu ve API'ler: Shelf, Dart Frog ve Serverpod gibi çatılarla backend servisleri.
  • Komut satırı araçları: Tek dosyalık, bağımlılık gerektirmeyen yerel çalıştırılabilir programlar.

Kısa Bir Tarihçe

  • 2011: Google, Lars Bak ve Kasper Lund'un öncülüğünde geliştirilen Dart'ı duyurur. İlk hedef, web için JavaScript'e alternatif olmaktır.
  • 2013: Dart 1.0 yayımlanır.
  • 2014: Dart, ECMA-408 adıyla bir ECMA standardı haline gelir.
  • 2018: Dart 2.0 ile güçlü (sound) tip sistemi zorunlu olur, new anahtar kelimesi isteğe bağlı hale gelir. Aynı yıl Flutter 1.0 çıkar ve Dart'ın yükselişi başlar.
  • 2021: Dart 2.12 ile sound null safety gelir.
  • 2023: Dart 3.0 ile null safety zorunlu olur; records, pattern matching ve sınıf değiştiricileri (sealed, final, base, interface) dile eklenir.
  • 2024–2025: Extension types, WebAssembly derleme desteği, rakam ayırıcılar (1_000_000), joker değişkenler ve koleksiyonlarda null-aware elemanlar gibi özellikler gelir.

Dart Nasıl Çalışır? JIT ve AOT

Dart'ın en akıllıca yanlarından biri, aynı kodu iki farklı şekilde çalıştırabilmesidir:

  • JIT (Just-In-Time): Geliştirme sırasında kod, Dart sanal makinesinde (Dart VM) çalışırken derlenir. Bu sayede Flutter'daki hot reload mümkün olur: kodu kaydettiğiniz anda uygulama, durumunu kaybetmeden güncellenir.
  • AOT (Ahead-Of-Time): Yayına alırken kod önceden yerel makine koduna (ARM, x64) derlenir. Uygulama hızlı açılır ve tutarlı performansla çalışır.
  • Web için: Dart kodu JavaScript'e veya WebAssembly'ye derlenerek tarayıcıda çalıştırılır.
         geliştirme                         yayın
  kaynak kod ──► Dart VM (JIT) ──► hot reload, hızlı deneme
  kaynak kod ──► AOT derleyici  ──► yerel makine kodu (mobil, masaüstü, sunucu)
  kaynak kod ──► dart2js / dart2wasm ──► JavaScript / WebAssembly (web)

Kurulum

Hiçbir Şey Kurmadan: DartPad

Dart'ı denemek için hiçbir şey kurmanız gerekmez. DartPad (dartpad.dev), tarayıcıda Dart ve Flutter kodu yazıp çalıştırabileceğiniz resmi bir ortamdır. Bu yazıdaki örneklerin çoğunu (dosya işlemleri hariç) doğrudan DartPad'e yapıştırıp çalıştırabilirsiniz.

macOS

brew tap dart-lang/dart
brew install dart

# Kurulumu kontrol edin
dart --version

Windows

En kolay yol Chocolatey paket yöneticisidir. Alternatif olarak dart.dev'den SDK'nın ZIP arşivini indirip bin klasörünü PATH'e ekleyebilirsiniz.

# Yönetici olarak açılmış PowerShell
choco install dart-sdk

dart --version

Linux (Ubuntu / Debian)

sudo apt-get update
sudo apt-get install apt-transport-https

wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg

echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list

sudo apt-get update
sudo apt-get install dart

dart --version

Flutter ile Gelen Dart

Flutter kurduysanız Dart'ı ayrıca kurmanıza gerek yoktur; Flutter SDK, uyumlu bir Dart SDK'sını içinde getirir. flutter --version çıktısında hangi Dart sürümünü kullandığınızı görebilirsiniz.

Editör Kurulumu

  • VS Code: Resmi Dart eklentisini kurun (Flutter geliştirecekseniz Flutter eklentisini de). Otomatik tamamlama, hata gösterimi, biçimlendirme ve hata ayıklama hazır gelir.
  • Android Studio / IntelliJ IDEA: Dart ve Flutter eklentileriyle aynı özellikleri sunar.

İpucu: Editörünüzde "kaydederken biçimlendir" (format on save) ayarını açın. Dart topluluğu kod biçimi konusunda tartışmaz; herkes dart format'ın çıktısını kullanır.

İlk Dart Programı

dart create ile Proje Oluşturmak

dart create merhaba
cd merhaba
dart run
Building package executable...
Built merhaba:merhaba.
Hello world: 42!

dart create varsayılan olarak bir komut satırı uygulaması şablonu oluşturur. -t parametresiyle farklı şablonlar seçebilirsiniz: console (komut satırı), package (paylaşılabilir kütüphane), server-shelf (web sunucusu), web (tarayıcı uygulaması).

Proje Yapısı

merhaba/
├── bin/
│   └── merhaba.dart          ← programın giriş noktası (main)
├── lib/
│   └── merhaba.dart          ← yeniden kullanılabilir kodlar
├── test/
│   └── merhaba_test.dart     ← testler
├── analysis_options.yaml     ← lint ve analiz kuralları
└── pubspec.yaml              ← proje adı, sürüm ve bağımlılıklar

main Fonksiyonu

Her Dart programı main fonksiyonundan başlar. Komut satırı argümanlarını almak isterseniz main bir List<String> parametresi alabilir:

// bin/merhaba.dart
void main(List<String> arguments) {
  if (arguments.isEmpty) {
    print('Merhaba, dünya!');
    return;
  }
  print('Merhaba, ${arguments.join(' ')}!');
}
dart run bin/merhaba.dart Ertuğrul Mert
# Merhaba, Ertuğrul Mert!

Temel Sözdizimi

  • Her ifade noktalı virgül (;) ile biter.
  • Kod blokları süslü parantezlerle ({ }) belirtilir; girinti yalnızca okunabilirlik içindir.
  • Dart büyük/küçük harfe duyarlıdır: name ve Name farklı şeylerdir.
  • Ekrana yazdırmak için print() kullanılır.

Yorum Satırları

// Tek satırlık yorum

/*
  Çok satırlı yorum.
  Kodun bir bölümünü geçici olarak devre dışı bırakmak için de kullanılabilir.
*/

/// Dokümantasyon yorumu: sınıf ve fonksiyonların üstüne yazılır.
/// Editörde üzerine geldiğinizde görünür, `dart doc` ile belgeye dönüşür.
/// [a] ve [b] gibi köşeli parantezler ilgili tanıma bağlantı verir.
int topla(int a, int b) => a + b;

Değişkenler

var, final ve const

var city = 'İstanbul';   // tip çıkarımı: String
city = 'Afyon';          // ✅ var ile tanımlanan değişken değiştirilebilir
// city = 42;            // ❌ tipi String, sayı atanamaz

String name = 'Ertu';    // tip açıkça yazılabilir
int age = 20;

final today = DateTime.now(); // bir kez atanır, sonra değiştirilemez
// today = DateTime.now();    // ❌ hata

const pi = 3.14159;           // derleme anında bilinen sabit
const maxUsers = 100;

Hangisini ne zaman kullanacağınızı özetleyen tablo:

Anahtar kelimeYeniden atanabilir mi?Değer ne zaman belli olur?Örnek
varEvetÇalışma anındaSayaçlar, değişen durum
finalHayırÇalışma anında (bir kez)DateTime.now(), API yanıtı
constHayırDerleme anındaMatematik sabitleri, sabit metinler

İpucu: Varsayılan olarak final kullanın; yalnızca değeri gerçekten değişecek değişkenler için var'a geçin. Kodunuzu okuyan kişi, hangi değerlerin değişebileceğini bir bakışta anlar.

final ile const Arasındaki Fark

final değişkenin kendisini sabitler, const ise değerin tamamını derleme anında dondurur:

final skills = ['Dart', 'Flutter'];
skills.add('Firebase');        // ✅ değişken aynı listeyi gösteriyor, liste değişebilir
// skills = [];                // ❌ değişkene yeni liste atanamaz

const colors = ['kırmızı', 'mavi'];
// colors.add('yeşil');        // ❌ çalışma anında hata: liste değiştirilemez

// const nesneler aynı değere sahipse bellekte tek bir kopya olarak tutulur
print(identical(const [1, 2], const [1, 2])); // true

late ile Geç Başlatma

late, bir değişkene değerin daha sonra ama kullanılmadan önce verileceğini derleyiciye söyler. Ayrıca değeri ilk kullanıldığı ana kadar hesaplamayı erteler (lazy initialization):

late String description; // şimdi değil, sonra atanacak

void setup() {
  description = 'Ayarlar yüklendi';
}

class ReportPage {
  // İlk erişildiğinde hesaplanır; hiç erişilmezse hiç çalışmaz
  late final report = buildExpensiveReport();
}

late bir değişkeni değer atamadan okursanız çalışma anında LateInitializationError alırsınız; bu yüzden yalnızca değerin kesinlikle atanacağından emin olduğunuz durumlarda kullanın.

Kullanılmayan Değişkenler: _

Bir değeri almanız gereken ama kullanmayacağınız yerlerde _ (joker, wildcard) kullanılır. Dart 3.7'den itibaren _ gerçek bir değişken oluşturmaz, aynı kapsamda birden fazla kez kullanılabilir:

list.forEach((_) => print('bir eleman daha'));

void onTap(_, _) {} // iki parametre de kullanılmıyor

Veri Tipleri

Dart'ta her şey bir nesnedir; sayılar, fonksiyonlar ve hatta null bile. Tüm tipler Object sınıfından türer (null hariç). Temel tipler şunlardır:

TipAçıklamaÖrnek
intTam sayı (yerel platformlarda 64 bit)42, -7, 0xFF
double64 bit ondalıklı sayı3.14, 1e6
numint ve double'ın ortak üst tipinum x = 5;
StringUTF-16 metin'Merhaba'
boolMantıksal değertrue, false
List, Set, MapKoleksiyonlar[1, 2], {1, 2}, {'a': 1}
RecordSabit sayıda, farklı tipte değer grubu('Ertu', 20)
NullYalnızca null değerini taşırnull

Sayılar: int, double ve num

int a = 17;
double b = 2.5;
num c = 10;        // int veya double olabilir
c = 10.5;          // ✅

print(a + b);      // 19.5
print(a / 5);      // 3.4   (bölme her zaman double döner)
print(a ~/ 5);     // 3     (tam sayı bölmesi)
print(a % 5);      // 2     (mod, kalan)
print(-a.abs());   // -17

final population = 85_000_000;   // rakam ayırıcı (Dart 3.6+), okunabilirlik için
const hexColor = 0xFF0175C2;

print(3.14159.toStringAsFixed(2)); // "3.14"
print(7.5.round());                // 8
print(7.5.floor());                // 7
print(2.1.ceil());                 // 3
print(10.clamp(0, 5));             // 5

Matematik fonksiyonları için dart:math kütüphanesi kullanılır:

import 'dart:math';

void main() {
  print(sqrt(16));          // 4.0
  print(pow(2, 10));        // 1024
  print(max(3, 9));         // 9
  print(pi);                // 3.141592653589793
  print(Random().nextInt(6) + 1); // 1-6 arası zar
}

String'ler

Metinler tek veya çift tırnakla yazılabilir; Dart topluluğunda tek tırnak yaygındır. Değişkenleri metnin içine $degisken, ifadeleri ${ifade} ile yerleştirirsiniz (string interpolation):

const name = 'Ertuğrul';
const age = 20;

print('Merhaba, $name!');                       // değişken
print('Gelecek yıl ${age + 1} yaşında olacak.'); // ifade
print('Adın ${name.length} harfli.');

// Birleştirme: yan yana yazılan string'ler otomatik birleşir
const message = 'Dart '
    've '
    'Flutter';

// Çok satırlı metin
const poem = '''
Satır bir
Satır iki
''';

// Ham (raw) string: ters bölü ve $ olduğu gibi kalır
const path = r'C:\Users\ertu\$HOME';

// Özel karakterler
print('Satır 1\nSatır 2\tSekme \$ işareti');

Sık kullanılan String metotları:

const text = '  Dart çok güzel bir dil  ';

text.trim();                    // 'Dart çok güzel bir dil'
text.toUpperCase();             // '  DART ÇOK GÜZEL BIR DIL  '
text.contains('güzel');         // true
text.trim().startsWith('Dart'); // true
text.trim().split(' ');         // ['Dart', 'çok', 'güzel', 'bir', 'dil']
text.replaceAll('güzel', 'hızlı');
'Dart'.substring(1, 3);         // 'ar'
'7'.padLeft(3, '0');            // '007'
'Dart'.indexOf('r');            // 2
'ab' * 3;                       // 'ababab'
'Dart'.isEmpty;                 // false

// Çok sayıda birleştirme için StringBuffer verimlidir
final buffer = StringBuffer();
for (var i = 1; i <= 3; i++) {
  buffer.write('Satır $i\n');
}
print(buffer.toString());

Dikkat: toUpperCase() ve toLowerCase() dile duyarlı değildir: 'istanbul'.toUpperCase() sonucu 'ISTANBUL' olur, 'İSTANBUL' değil. Türkçe metinlerde doğru dönüşüm için intl paketi gibi yerelleştirme araçlarına ya da i/İ ve ı/I harflerini önceden değiştiren küçük bir yardımcı fonksiyona ihtiyaç duyarsınız.

bool

final isLoggedIn = true;
final hasItems = [1, 2].isNotEmpty;

// Dart'ta yalnızca true ve false mantıksaldır; 0, '' veya null "yanlış" sayılmaz
// if (1) {}       // ❌ hata: koşul bool olmalı
if (hasItems) print('Sepette ürün var');

Tip Dönüşümleri

int.parse('42');              // 42
double.parse('3.14');         // 3.14
int.tryParse('abc');          // null (hata fırlatmaz)
double.tryParse('3,14');      // null (ondalık ayırıcı nokta olmalı)

42.toString();                // '42'
3.14159.toStringAsFixed(2);   // '3.14'
10.toDouble();                // 10.0
9.99.toInt();                 // 9 (ondalık kısım atılır)

// Tip kontrolü ve dönüştürme
Object value = 'Merhaba';
if (value is String) {
  print(value.length);        // tip yükseltme: burada value bir String
}
final text = value as String; // tip yanlışsa çalışma anında hata

dynamic ve Object?

dynamic, tip denetimini kapatır: üzerinde istediğiniz metodu çağırabilirsiniz, hatalar ancak çalışma anında ortaya çıkar. Object? ise "her şey olabilir" der ama kullanmadan önce tipini kontrol etmeniz gerekir:

dynamic a = 'Dart';
a.foo();                 // derlenir ama çalışırken NoSuchMethodError fırlatır

Object? b = 'Dart';
// b.length;             // ❌ derleme hatası: Object'te length yok
if (b is String) print(b.length); // ✅ önce kontrol, sonra kullanım

Kural olarak dynamic yerine Object? tercih edin; dynamic yalnızca JSON gibi yapısı önceden bilinmeyen verilerle çalışırken geçici olarak kabul edilebilir.

Null Safety

Null safety, Dart'ın en önemli özelliklerinden biridir. Varsayılan olarak hiçbir değişken null olamaz; bir değişkenin null olabileceğini tipinin sonuna ? ekleyerek açıkça belirtirsiniz. Böylece "null bir değerin özelliğine erişmeye çalışma" hataları uygulama çalışırken değil, kodu yazarken yakalanır.

Nullable ve Non-nullable Tipler

String name = 'Ertu';
// name = null;           // ❌ derleme hatası: String null olamaz

String? nickname;         // null olabilir, varsayılan değeri null
nickname = 'ertucuk';
nickname = null;          // ✅

int? findUserAge(String id) {
  return id == '1' ? 20 : null; // kullanıcı bulunamazsa null
}

Null-aware Operatörler

String? nickname;

// ?.  : nesne null değilse eriş, null ise sonuç da null
print(nickname?.length);          // null

// ??  : sol taraf null ise sağdakini kullan
print(nickname ?? 'Misafir');     // 'Misafir'

// ??= : yalnızca null ise ata
nickname ??= 'ertucuk';

// ?[] : null olabilen listede güvenli erişim
List<int>? scores;
print(scores?[0]);                // null

// !   : "bu değer kesinlikle null değil" diyerek null'ı kabul etmeyen tipe çevirir
String? fromServer = 'veri';
print(fromServer!.length);       // fromServer null olsaydı çalışma anında hata

// Zincirleme
final city = user?.address?.city ?? 'Bilinmiyor';

Dikkat: ! operatörü null safety'yi devre dışı bırakır; değer null çıkarsa uygulama çöker. Mümkün olduğunda ??, ?. ya da aşağıdaki tip yükseltme yöntemlerini tercih edin.

Tip Yükseltme (Type Promotion)

Bir yerel değişkenin null olmadığını kontrol ettiğinizde, Dart o bloğun içinde değişkeni otomatik olarak null olmayan tipe yükseltir:

void greet(String? name) {
  if (name == null) {
    print('Merhaba, misafir!');
    return;
  }
  // Buradan sonra name bir String, ? veya ! gerekmez
  print('Merhaba, $name! Adın ${name.length} harfli.');
}

Tip yükseltme yerel değişkenlerde ve parametrelerde çalışır. Sınıf alanlarında ise başka bir kod alanı arada değiştirebileceği için çalışmaz; yalnızca özel (_ ile başlayan) final alanlarda Dart 3.2'den beri desteklenir. Alanlar için en temiz çözüm, değeri önce yerel bir değişkene almaktır:

class Profile {
  String? bio;

  void printBio() {
    final bio = this.bio;     // yerel kopya
    if (bio != null) {
      print(bio.toUpperCase()); // ✅ yükseltme çalışır
    }
  }
}

Operatörler

GrupOperatörlerÖrnek
Aritmetik+ - * / ~/ %17 ~/ 5 → 3
Artırma / azaltma++ --count++
Karşılaştırma== != > < >= <=age >= 18
Mantıksal&& || !isAdmin || isOwner
Tip testiis is! asvalue is String
Atama= += -= *= /= ??=total += price
Koşullukoşul ? a : b, a ?? bage >= 18 ? 'yetişkin' : 'çocuk'
Kaskad.. ?..list..add(1)..add(2)
Yayma (spread)... ...?[...a, ...b]

Kaskad Operatörü (..)

Kaskad, aynı nesne üzerinde art arda işlem yapmanızı sağlar ve her seferinde nesnenin kendisini döndürür. Nesne oluşturup ayarlarken kodu çok sadeleştirir:

// Kaskad olmadan
final buffer = StringBuffer();
buffer.write('Dart');
buffer.write(' ');
buffer.write('Flutter');

// Kaskad ile
final buffer2 = StringBuffer()
  ..write('Dart')
  ..write(' ')
  ..write('Flutter');

final numbers = [5, 3, 8]
  ..add(1)
  ..sort();          // [1, 3, 5, 8]

Kontrol Akışı

if / else

final score = 78;

if (score >= 90) {
  print('AA');
} else if (score >= 70) {
  print('BB');
} else {
  print('Tekrar çalış');
}

// Tek satırlık koşullu ifade
final result = score >= 50 ? 'Geçti' : 'Kaldı';

switch Deyimi ve switch İfadesi

Dart 3 ile switch çok daha güçlü hale geldi: case'ler artık birbirine akmaz (her birinin sonuna break yazmanız gerekmez) ve switch bir değer üreten ifade (expression) olarak da kullanılabilir:

final day = 'Cumartesi';

// switch deyimi (statement)
switch (day) {
  case 'Cumartesi' || 'Pazar':
    print('Hafta sonu!');
  case 'Cuma':
    print('Az kaldı');
  default:
    print('Hafta içi');
}

// switch ifadesi (expression): değer döndürür
final statusCode = 404;
final message = switch (statusCode) {
  200 => 'Başarılı',
  404 => 'Bulunamadı',
  >= 500 => 'Sunucu hatası',
  _ => 'Bilinmeyen durum',   // _ : diğer tüm durumlar
};

switch'in pattern'larla birlikte kullanımını ilerideki "Records ve Pattern Matching" bölümünde ayrıntılı göreceğiz.

Döngüler

// Klasik for
for (var i = 0; i < 3; i++) {
  print('i = $i');
}

// for-in: koleksiyon elemanlarını gezer
const languages = ['Dart', 'Kotlin', 'Swift'];
for (final language in languages) {
  print(language);
}

// forEach
languages.forEach(print);

// while: koşul doğru olduğu sürece
var count = 3;
while (count > 0) {
  print(count--);
}

// do-while: en az bir kez çalışır
var attempt = 0;
do {
  attempt++;
} while (attempt < 3);

// break ve continue
for (var i = 1; i <= 10; i++) {
  if (i % 2 == 0) continue; // çiftleri atla
  if (i > 7) break;         // 7'den sonra dur
  print(i);                 // 1, 3, 5, 7
}

assert

assert, geliştirme sırasında bir koşulun doğru olduğunu doğrular. Koşul yanlışsa hata fırlatır. Yayın (production) derlemelerinde tamamen yok sayılır, bu yüzden performans maliyeti yoktur:

void setVolume(int level) {
  assert(level >= 0 && level <= 100, 'Ses seviyesi 0-100 arasında olmalı');
  // ...
}

Koleksiyonlar

Dart'ın üç temel koleksiyon tipi vardır: sıralı ve tekrar edebilen List, benzersiz elemanlardan oluşan Set ve anahtar–değer çiftlerinden oluşan Map.

List

final fruits = ['elma', 'armut'];          // List<String>
final List<int> scores = [90, 75, 82];
final empty = <String>[];                  // boş listede tipi belirtin

fruits.add('muz');                          // sona ekle
fruits.addAll(['kiraz', 'çilek']);
fruits.insert(0, 'kayısı');                 // belirli konuma ekle
fruits.remove('armut');                     // değere göre sil
fruits.removeAt(0);                         // konuma göre sil

print(fruits[0]);                           // ilk eleman
print(fruits.first);
print(fruits.last);
print(fruits.length);
print(fruits.contains('muz'));              // true
print(fruits.indexOf('kiraz'));

scores.sort();                              // [75, 82, 90]
scores.sort((a, b) => b.compareTo(a));      // büyükten küçüğe
final reversed = scores.reversed.toList();

// Hazır liste oluşturma
final squares = List.generate(5, (i) => i * i); // [0, 1, 4, 9, 16]
final zeros = List.filled(3, 0);                // [0, 0, 0]

Set

final tags = {'dart', 'flutter', 'dart'}; // {'dart', 'flutter'}: tekrar yok
final Set<int> ids = {};                   // boş set (tipi belirtilmeli, yoksa Map olur)

tags.add('mobil');
print(tags.contains('dart'));              // çok hızlı arama

final a = {1, 2, 3};
final b = {2, 3, 4};
print(a.union(b));                         // {1, 2, 3, 4}
print(a.intersection(b));                  // {2, 3}
print(a.difference(b));                    // {1}

// Listedeki tekrarları temizlemek için
final unique = [1, 1, 2, 3, 3].toSet().toList(); // [1, 2, 3]

Map

final user = {
  'name': 'Ertuğrul',
  'city': 'İstanbul',
};                                          // Map<String, String>

final Map<String, int> stock = {'klavye': 5, 'mouse': 12};

print(user['name']);                        // 'Ertuğrul'
print(user['phone']);                       // null: anahtar yoksa null döner

user['role'] = 'Developer';                 // ekle veya güncelle
user.remove('city');

print(stock.containsKey('klavye'));         // true
print(stock.keys);                          // (klavye, mouse)
print(stock.values);                        // (5, 12)

stock.update('klavye', (value) => value - 1);   // 4
stock.putIfAbsent('monitör', () => 0);          // yoksa ekle

for (final entry in stock.entries) {
  print('${entry.key}: ${entry.value} adet');
}

// Map'i dönüştürmek
final prices = {'klavye': 1500, 'mouse': 500};
final discounted = prices.map((key, value) => MapEntry(key, value * 0.9));

Koleksiyon İçinde if, for ve Spread

Dart, koleksiyon tanımının içinde koşul ve döngü kullanmanıza izin verir. Flutter'da arayüz ağaçları kurarken çok sık kullanılır:

final isAdmin = true;
final base = ['Ana Sayfa', 'Profil'];
final extra = ['Ayarlar'];
List<String>? maybeMore;

final menu = [
  ...base,                       // spread: listeyi içine aç
  ...?maybeMore,                 // null olabilen liste için null-aware spread
  if (isAdmin) 'Yönetim Paneli', // koleksiyon if
  for (final item in extra) item.toUpperCase(), // koleksiyon for
];
// ['Ana Sayfa', 'Profil', 'Yönetim Paneli', 'AYARLAR']

// Dart 3.8+: null-aware eleman. Değer null ise listeye hiç eklenmez
String? coupon;
final codes = ['HOSGELDIN', ?coupon];  // ['HOSGELDIN']

Iterable Metotları: map, where, reduce...

Koleksiyonları döngü yazmadan dönüştürmek, filtrelemek ve özetlemek için fonksiyonel metotlar kullanılır:

final prices = [120, 45, 300, 80, 15];

prices.map((p) => p * 2);                  // her elemanı dönüştür
prices.where((p) => p > 50);               // filtrele
prices.firstWhere((p) => p > 100);         // koşula uyan ilk eleman
prices.any((p) => p > 250);                // en az biri uyuyor mu? true
prices.every((p) => p > 10);               // hepsi uyuyor mu? true
prices.reduce((a, b) => a + b);            // 560
prices.fold<int>(0, (sum, p) => sum + p);  // 560 (başlangıç değeriyle)
prices.take(2);                            // (120, 45)
prices.skip(3);                            // (80, 15)

// Zincirleme: 50'den büyük fiyatların KDV'li toplamı
final total = prices
    .where((p) => p > 50)
    .map((p) => p * 1.2)
    .fold<double>(0, (sum, p) => sum + p);

// İç içe listeleri düzleştirmek
final nested = [[1, 2], [3], [4, 5]];
final flat = nested.expand((list) => list).toList(); // [1, 2, 3, 4, 5]

İpucu: map, where, take gibi metotlar tembeldir (lazy): bir Iterable döndürür ve siz sonucu kullanana kadar hiçbir şey hesaplanmaz. Sonucu birden fazla kez kullanacaksanız .toList() ile bir kez listeye çevirin, yoksa her kullanımda yeniden hesaplanır.

Değiştirilemez Koleksiyonlar

import 'dart:collection';

const weekdays = ['Pzt', 'Sal', 'Çar'];       // derleme anında sabit
final snapshot = List.unmodifiable(fruits);   // çalışma anında kopyası alınmış, değiştirilemez
final view = UnmodifiableListView(fruits);    // dart:collection: orijinali gösteren salt okunur görünüm

Fonksiyonlar

Fonksiyon Tanımlamak

// Dönüş tipi, ad, parametreler, gövde
int add(int a, int b) {
  return a + b;
}

// Tek ifadeden oluşan fonksiyonlar için ok (arrow) sözdizimi
int multiply(int a, int b) => a * b;

// Değer döndürmeyen fonksiyon
void log(String message) => print('[LOG] $message');

bool isEven(int n) => n % 2 == 0;

Parametre Türleri

Dart'ta üç tür parametre vardır: zorunlu konumsal (positional), isteğe bağlı konumsal ve isimli (named) parametreler:

// İsteğe bağlı konumsal parametreler köşeli parantez içinde
String greet(String name, [String? title]) {
  return title == null ? 'Merhaba $name' : 'Merhaba $title $name';
}
greet('Ertu');            // Merhaba Ertu
greet('Ertu', 'Sayın');   // Merhaba Sayın Ertu

// İsimli parametreler süslü parantez içinde; çağırırken adıyla verilir
void createUser({
  required String name,       // required: verilmesi zorunlu
  int age = 18,               // varsayılan değer
  String? email,              // isteğe bağlı, null olabilir
}) {
  print('$name, $age, ${email ?? 'e-posta yok'}');
}

createUser(name: 'Ertu');
createUser(age: 20, name: 'Ertu', email: 'ertu@example.com'); // sıra önemsiz

İpucu: Bir fonksiyonun ikiden fazla parametresi varsa ya da parametrelerin anlamı çağrıdan anlaşılmıyorsa isimli parametre kullanın. createUser('Ertu', 20, true) yerine createUser(name: 'Ertu', age: 20, isAdmin: true) okumak çok daha kolaydır. Flutter'daki widget'ların neredeyse tamamı bu yüzden isimli parametre kullanır.

Fonksiyonlar Birer Değerdir

Dart'ta fonksiyonlar birinci sınıf nesnelerdir: değişkene atanabilir, başka bir fonksiyona parametre olarak verilebilir ve fonksiyondan döndürülebilir:

// Anonim fonksiyon (lambda) bir değişkene atanır
final square = (int x) => x * x;
print(square(4)); // 16

// Fonksiyonu parametre olarak alan fonksiyon (higher-order function)
void repeat(int times, void Function(int) action) {
  for (var i = 0; i < times; i++) {
    action(i);
  }
}

repeat(3, (i) => print('Tekrar $i'));

// Var olan fonksiyonu doğrudan vermek (tear-off)
[1, 2, 3].forEach(print);

Closure'lar

Bir fonksiyon, tanımlandığı kapsamdaki değişkenleri "hatırlar" ve onlara daha sonra da erişebilir:

int Function() makeCounter() {
  var count = 0;
  return () => ++count; // count değişkenini yakalar
}

final counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 3

typedef ile Fonksiyon Tipleri

typedef Validator = String? Function(String value);
typedef JsonMap = Map<String, dynamic>;

final Validator notEmpty = (value) => value.isEmpty ? 'Bu alan zorunlu' : null;
final Validator emailRule = (value) => value.contains('@') ? null : 'Geçersiz e-posta';

String? validate(String value, List<Validator> rules) {
  for (final rule in rules) {
    final error = rule(value);
    if (error != null) return error;
  }
  return null;
}

print(validate('', [notEmpty, emailRule]));      // Bu alan zorunlu
print(validate('ertu', [notEmpty, emailRule]));  // Geçersiz e-posta

Sınıflar ve Nesneler

Dart tamamen nesne yönelimli bir dildir. Sınıflar; veriyi (alanlar, fields) ve o veri üzerinde çalışan davranışları (metotlar) bir arada tutar.

Sınıf Tanımlamak

class BankAccount {
  BankAccount(this.owner, this.balance); // kısa constructor sözdizimi

  final String owner;
  double balance;

  void deposit(double amount) {
    balance += amount;
  }

  bool withdraw(double amount) {
    if (amount > balance) return false;
    balance -= amount;
    return true;
  }

  @override
  String toString() => '$owner: ${balance.toStringAsFixed(2)} ₺';
}

void main() {
  final account = BankAccount('Ertu', 500); // new anahtar kelimesi gerekmez
  account.deposit(250);
  account.withdraw(100);
  print(account); // Ertu: 650.00 ₺
}

Constructor Türleri

class Point {
  // 1) Üretici (generative) constructor, kısa sözdizimiyle
  const Point(this.x, this.y);

  // 2) İsimli (named) constructor: aynı sınıfı farklı yollarla oluşturmak için
  const Point.origin() : x = 0, y = 0;

  // 3) Initializer list: gövde çalışmadan önce alanlara değer atar
  Point.fromJson(Map<String, num> json)
      : x = json['x']!.toDouble(),
        y = json['y']!.toDouble();

  // 4) Yönlendiren (redirecting) constructor
  const Point.onXAxis(double x) : this(x, 0);

  final double x;
  final double y;
}

final a = Point(3, 4);
final b = Point.origin();
final c = Point.fromJson({'x': 1, 'y': 2});
const d = Point(1, 1); // tüm alanları final olan sınıflarda const nesne

factory constructor her seferinde yeni bir nesne üretmek zorunda değildir; önbellekten nesne döndürebilir ya da bir alt sınıf seçebilir:

class Logger {
  static final _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  Logger._internal(this.name); // özel (private) constructor

  final String name;

  void log(String message) => print('[$name] $message');
}

final l1 = Logger('api');
final l2 = Logger('api');
print(identical(l1, l2)); // true: aynı nesne

Getter ve Setter

class Rectangle {
  Rectangle(this.width, this.height);

  double width;
  double height;

  // Hesaplanan özellik: parantez olmadan çağrılır
  double get area => width * height;
  bool get isSquare => width == height;

  // Değer atarken doğrulama
  set size(double value) {
    if (value <= 0) throw ArgumentError('Boyut pozitif olmalı');
    width = value;
    height = value;
  }
}

final r = Rectangle(3, 4);
print(r.area);   // 12.0
r.size = 5;
print(r.isSquare); // true

Gizlilik: _ ile Başlayan İsimler

Dart'ta private veya public anahtar kelimeleri yoktur. Adı alt çizgi (_) ile başlayan her şey (alan, metot, sınıf, fonksiyon) yalnızca tanımlandığı kütüphanenin (genellikle dosyanın) içinden erişilebilir:

class Wallet {
  int _balance = 0;            // dışarıdan erişilemez

  int get balance => _balance; // yalnızca okunabilir olarak dışarı açılır

  void add(int amount) {
    if (amount <= 0) return;
    _balance += amount;
  }
}

static Üyeler

class AppConfig {
  static const appName = 'Portfolio';
  static int instanceCount = 0;

  static String greeting(String user) => 'Merhaba $user, $appName\'a hoş geldin!';
}

print(AppConfig.appName);           // nesne oluşturmadan erişim
print(AppConfig.greeting('Ertu'));

Eşitlik: == ve hashCode

Varsayılan olarak iki nesne yalnızca aynı nesne ise eşittir. Değerlerine göre karşılaştırmak için == operatörünü ve onunla birlikte hashCode'u ezmelisiniz (override). Aynı şekilde + gibi operatörleri de kendi sınıflarınız için tanımlayabilirsiniz:

class Money {
  const Money(this.amount, this.currency);

  final int amount;       // kuruş cinsinden
  final String currency;

  Money operator +(Money other) {
    assert(currency == other.currency);
    return Money(amount + other.amount, currency);
  }

  @override
  bool operator ==(Object other) =>
      other is Money && other.amount == amount && other.currency == currency;

  @override
  int get hashCode => Object.hash(amount, currency);

  @override
  String toString() => '${(amount / 100).toStringAsFixed(2)} $currency';
}

print(const Money(1000, 'TRY') == const Money(1000, 'TRY')); // true
print(const Money(1000, 'TRY') + const Money(250, 'TRY'));   // 12.50 TRY

Dikkat: =='ı ezip hashCode'u ezmezseniz, eşit görünen nesneler Set ve Map içinde farklı kabul edilir. İkisini her zaman birlikte ezin.

Kalıtım, Arayüzler ve Mixin'ler

extends ile Kalıtım

class Animal {
  Animal(this.name);

  final String name;

  void speak() => print('$name bir ses çıkarıyor.');
}

class Dog extends Animal {
  Dog(super.name, this.breed); // super parametresi: üst sınıfa iletilir

  final String breed;

  @override
  void speak() {
    super.speak();             // üst sınıfın metodunu da çağır
    print('$name havlıyor! ($breed)');
  }
}

final dog = Dog('Karabaş', 'Kangal');
dog.speak();

Dart'ta bir sınıf yalnızca tek bir sınıftan kalıtım alabilir (single inheritance). Birden fazla kaynaktan davranış almak için arayüzler ve mixin'ler kullanılır.

Soyut (abstract) Sınıflar

Soyut sınıflardan doğrudan nesne oluşturulamaz; alt sınıfların uygulaması gereken bir sözleşme tanımlarlar:

abstract class Shape {
  double get area;               // gövdesiz: alt sınıf uygulamak zorunda
  String describe() => 'Alanı ${area.toStringAsFixed(1)} olan bir şekil';
}

class Circle extends Shape {
  Circle(this.radius);
  final double radius;

  @override
  double get area => 3.14159 * radius * radius;
}

// final s = Shape();            // ❌ soyut sınıftan nesne oluşturulamaz
print(Circle(2).describe());      // Alanı 12.6 olan bir şekil

implements: Her Sınıf Bir Arayüzdür

Dart'ta ayrı bir interface anahtar kelimesine gerek yoktur; her sınıf aynı zamanda örtük bir arayüzdür. implements ile bir sınıfın tüm üyelerini kendiniz uygulamayı taahhüt edersiniz. Bir sınıf birden fazla arayüzü uygulayabilir:

abstract class Storage {
  Future<void> save(String key, String value);
  Future<String?> read(String key);
}

class MemoryStorage implements Storage {
  final _data = <String, String>{};

  @override
  Future<void> save(String key, String value) async {
    _data[key] = value;
  }

  @override
  Future<String?> read(String key) async => _data[key];
}

// Kod Storage'a bağımlı olduğu için test ederken MemoryStorage,
// gerçek uygulamada dosya veya veritabanı kullanan bir sınıf verilebilir.

mixin ve with

Mixin'ler, kalıtım zinciri kurmadan birden fazla sınıfa ortak davranış eklemenin yoludur:

mixin Loggable {
  void log(String message) => print('[${runtimeType}] $message');
}

mixin Timestamped {
  final createdAt = DateTime.now();
}

class Order with Loggable, Timestamped {
  Order(this.id);
  final int id;

  void complete() => log('Sipariş #$id tamamlandı');
}

Order(42).complete(); // [Order] Sipariş #42 tamamlandı

// on ile mixin'in yalnızca belirli sınıflara eklenmesi sağlanabilir
mixin Barking on Animal {
  void bark() => print('$name: Hav!');
}

Sınıf Değiştiricileri (Dart 3)

Dart 3 ile bir sınıfın kütüphane dışında nasıl kullanılabileceğini kontrol eden değiştiriciler geldi. Özellikle paket yazarken API'nizi korumak için kullanılır:

DeğiştiriciNesne oluşturulabilir mi?Dışarıda extends?Dışarıda implements?Ne için?
(yok)EvetEvetEvetVarsayılan, her şeye açık
abstractHayırEvetEvetKısmi uygulama içeren sözleşmeler
baseEvetEvetHayırUygulamanın her zaman miras alınmasını garanti etmek
interfaceEvetHayırEvetYalnızca arayüz olarak kullanılmak
finalEvetHayırHayırHiçbir şekilde alt tipi oluşturulamasın
sealedHayırHayırHayırAlt tipleri bilinen kapalı aileler (exhaustive switch)
mixin classEvetEvetEvetHem sınıf hem mixin olarak kullanılmak

Bu kısıtlamalar yalnızca tanımlandığı kütüphanenin dışında geçerlidir; aynı dosyanın içinde sınıfı istediğiniz gibi genişletebilirsiniz. En sık kullanılanı sealed'dır ve pattern matching bölümünde neden bu kadar faydalı olduğunu göreceğiz.

Enum'lar

Enum'lar, sabit sayıda seçenekten oluşan değerleri temsil eder:

enum Status { todo, inProgress, done }

final s = Status.inProgress;
print(s.name);                         // 'inProgress'
print(Status.values);                  // tüm değerler
print(Status.values.byName('done'));   // Status.done

switch (s) {
  case Status.todo:
    print('Başlanmadı');
  case Status.inProgress:
    print('Devam ediyor');
  case Status.done:
    print('Tamamlandı');
} // tüm durumlar ele alındığı için default gerekmez

Gelişmiş (Enhanced) Enum'lar

Enum'lar alan, constructor, metot ve getter içerebilir; hatta arayüz uygulayabilir:

enum Planet {
  mercury(diameterKm: 4879, hasRings: false),
  earth(diameterKm: 12742, hasRings: false),
  saturn(diameterKm: 116460, hasRings: true);

  const Planet({required this.diameterKm, required this.hasRings});

  final int diameterKm;
  final bool hasRings;

  bool get isGiant => diameterKm > 50000;

  String describe() => '$name: $diameterKm km${hasRings ? ', halkalı' : ''}';
}

for (final planet in Planet.values) {
  print(planet.describe());
}
print(Planet.saturn.isGiant); // true

Generic'ler

Generic'ler, farklı tiplerle çalışabilen ama tip güvenliğini koruyan kod yazmanızı sağlar. List<int> ve Map<String, int> zaten generic tiplerdir:

// Generic sınıf
class Box<T> {
  Box(this.value);
  T value;

  Box<R> map<R>(R Function(T value) transform) => Box(transform(value));
}

final intBox = Box(42);                       // Box<int>
final textBox = intBox.map((v) => 'Sayı: $v'); // Box<String>

// Generic fonksiyon
T firstOrDefault<T>(List<T> items, T fallback) =>
    items.isEmpty ? fallback : items.first;

// Sınırlandırılmış (bounded) tip: T yalnızca num veya alt tipi olabilir
T largest<T extends num>(List<T> values) => values.reduce((a, b) => a > b ? a : b);

print(largest([3, 9, 4]));     // 9
print(largest([1.5, 0.2]));    // 1.5
// largest(['a', 'b']);        // ❌ String, num değil

Gerçek hayattan bir örnek, bir işlemin sonucunu ya da hatasını taşıyan Result tipi:

sealed class Result<T> {
  const Result();
}

final class Success<T> extends Result<T> {
  const Success(this.value);
  final T value;
}

final class Failure<T> extends Result<T> {
  const Failure(this.message);
  final String message;
}

Result<int> parseAge(String input) {
  final age = int.tryParse(input);
  if (age == null || age < 0) return const Failure('Geçerli bir yaş girin');
  return Success(age);
}

Extension Metotlar ve Extension Types

Extension Metotlar

Kaynak koduna erişiminiz olmayan sınıflara (String, int, List gibi) yeni metotlar eklemenizi sağlar:

extension StringExtensions on String {
  String get capitalized =>
      isEmpty ? this : this[0].toUpperCase() + substring(1);

  bool get isValidEmail => contains('@') && contains('.');

  String truncate(int maxLength) =>
      length <= maxLength ? this : '${substring(0, maxLength)}…';
}

extension DurationFormat on int {
  Duration get seconds => Duration(seconds: this);
  Duration get minutes => Duration(minutes: this);
}

print('dart'.capitalized);                        // Dart
print('ertu@example.com'.isValidEmail);           // true
print('Uzun bir blog başlığı'.truncate(10));      // Uzun bir b…
await Future.delayed(2.seconds);                  // async bir fonksiyonun içinde

Extension Types

Dart 3.3 ile gelen extension type'lar, var olan bir tipi çalışma anında hiçbir ek maliyet olmadan yeni bir tipmiş gibi sarmalar. Örneğin bir int'in kullanıcı ID'si mi yoksa ürün ID'si mi olduğunu karıştırmanızı derleyici seviyesinde engeller:

extension type UserId(int value) {
  bool get isValid => value > 0;
}

extension type ProductId(int value) {}

void deleteUser(UserId id) => print('Kullanıcı ${id.value} siliniyor');

final userId = UserId(7);
final productId = ProductId(7);

deleteUser(userId);        // ✅
// deleteUser(productId);  // ❌ derleme hatası: ProductId, UserId değil
// deleteUser(7);          // ❌ düz int de kabul edilmez

Records ve Pattern Matching

Dart 3'ün en büyük yeniliği records ve pattern matching'dir. Birlikte kullanıldıklarında veriyi parçalarına ayırmayı, doğrulamayı ve dallara ayırmayı çok kısa ve güvenli hale getirirler.

Records: Birden Fazla Değer Döndürmek

Record, sabit sayıda ve farklı tiplerde değeri bir arada tutan, değiştirilemez, hafif bir yapıdır. Sınıf tanımlamadan birden fazla değer döndürmek için idealdir:

// Konumsal alanlar: $1, $2 ile erişilir
(String, int) user = ('Ertuğrul', 20);
print(user.$1); // Ertuğrul
print(user.$2); // 20

// İsimli alanlar
({String name, int age}) person = (name: 'Ertu', age: 20);
print(person.name);

// Fonksiyondan birden fazla değer döndürmek
(int min, int max) minMax(List<int> numbers) {
  var min = numbers.first, max = numbers.first;
  for (final n in numbers) {
    if (n < min) min = n;
    if (n > max) max = n;
  }
  return (min, max);
}

// Records değerleriyle karşılaştırılır
print((1, 'a') == (1, 'a')); // true

Destructuring: Parçalarına Ayırmak

final (min, max) = minMax([4, 9, 1, 7]);
print('En küçük: $min, en büyük: $max');

final (:name, :age) = person;          // isimli alanları aynı adlı değişkenlere aç
print('$name, $age');

// İki değişkenin yerini değiştirmek
var a = 1, b = 2;
(a, b) = (b, a);

// Map ve listeleri döngüde açmak
final scores = {'Ertu': 92, 'Utku': 85};
for (final MapEntry(:key, :value) in scores.entries) {
  print('$key: $value');
}

final [first, second, ...rest] = [10, 20, 30, 40];
print(rest); // [30, 40]

switch ile Pattern Matching

Pattern'lar, bir değerin şeklini kontrol eder ve eşleşirse parçalarını değişkenlere atar:

String describe(Object value) => switch (value) {
  int n when n < 0 => 'negatif sayı',      // tip + koşul (guard)
  0 => 'sıfır',                             // sabit
  int n => 'pozitif sayı: $n',
  String s when s.isEmpty => 'boş metin',
  String s => '${s.length} harfli metin',
  (int x, int y) => 'nokta ($x, $y)',       // record pattern
  [] => 'boş liste',                         // liste pattern
  [var only] => 'tek elemanlı liste: $only',
  [var head, ...] => '"$head" ile başlayan liste',
  _ => 'bilinmeyen değer',
};

String grade(int score) => switch (score) {
  >= 90 => 'AA',
  >= 80 && < 90 => 'BA',
  >= 70 => 'BB',
  _ => 'Tekrar',
};

sealed Sınıflar ve Eksiksiz (Exhaustive) switch

sealed bir sınıfın tüm alt tipleri aynı dosyada tanımlanmak zorundadır; bu yüzden derleyici olası tüm alt tipleri bilir. switch içinde bir alt tipi unutursanız kod derlenmez. Yeni bir durum eklediğinizde, onu ele almadığınız her yer hata olarak işaretlenir:

import 'dart:math' as math;

sealed class Shape {}

class Circle extends Shape {
  Circle(this.radius);
  final double radius;
}

class Square extends Shape {
  Square(this.side);
  final double side;
}

class Triangle extends Shape {
  Triangle(this.base, this.height);
  final double base;
  final double height;
}

double area(Shape shape) => switch (shape) {
  Circle(:final radius) => math.pi * radius * radius,   // nesne pattern'ı
  Square(:final side) => side * side,
  Triangle(:final base, :final height) => base * height / 2,
  // Bir alt tipi unutursanız: "The type 'Shape' is not exhaustively matched" hatası
};

Bu desen, önceki bölümdeki Result tipiyle birleştiğinde hata yönetimini çok güvenli hale getirir:

final output = switch (parseAge('20')) {
  Success(:final value) => 'Yaşınız $value',
  Failure(:final message) => 'Hata: $message',
};

if-case ile JSON Doğrulamak

Pattern'lar if içinde de kullanılabilir. Dışarıdan gelen JSON verisinin hem şeklini hem tiplerini tek satırda doğrulayıp parçalarına ayırmak için mükemmeldir:

final json = {
  'name': 'Ertuğrul',
  'age': 20,
  'skills': ['Dart', 'Next.js'],
};

if (json case {'name': String name, 'age': int age, 'skills': List skills}) {
  print('$name ($age) - ${skills.length} yetenek');
} else {
  print('Geçersiz kullanıcı verisi');
}

Hata Yönetimi

try, catch, on ve finally

void main() {
  try {
    final age = int.parse('yirmi');     // FormatException fırlatır
    print(age);
  } on FormatException catch (e) {      // belirli bir hata tipini yakala
    print('Sayı bekleniyordu: ${e.message}');
  } on RangeError {
    print('Aralık dışı');
  } catch (e, stackTrace) {             // geri kalan her şey + yığın izi
    print('Beklenmeyen hata: $e');
    print(stackTrace);
  } finally {
    print('Bu blok her durumda çalışır'); // kaynakları kapatmak için
  }
}

Hata Fırlatmak ve Özel Hata Sınıfları

class InsufficientFundsException implements Exception {
  InsufficientFundsException(this.needed, this.available);

  final double needed;
  final double available;

  @override
  String toString() =>
      'Yetersiz bakiye: ${needed - available} ₺ eksik';
}

void withdraw(double amount, double balance) {
  if (amount > balance) {
    throw InsufficientFundsException(amount, balance);
  }
}

try {
  withdraw(500, 200);
} on InsufficientFundsException catch (e) {
  print(e); // Yetersiz bakiye: 300.0 ₺ eksik
}

// Yakaladığınız hatayı işledikten sonra yukarıya iletmek için
try {
  riskyOperation();
} catch (e) {
  log('Hata kaydedildi');
  rethrow;
}

Dart'ta iki tür hata vardır: Exception, programın öngörüp ele alması gereken durumlar içindir (geçersiz kullanıcı girdisi, ağ hatası). Error ise programcı hatalarını temsil eder (RangeError, TypeError, StateError); bunları yakalamak yerine kodu düzeltmeniz gerekir.

Asenkron Programlama

Ağ istekleri, dosya okuma veya veritabanı sorguları zaman alır. Dart bu işlemleri beklerken programı dondurmaz; tek iş parçacığında çalışan bir olay döngüsü (event loop) sayesinde başka işleri yapmaya devam eder. Flutter'da arayüzün akıcı kalmasının sebebi budur.

Future ve async/await

Future, "ileride hazır olacak bir değer" demektir. async ile işaretlenen fonksiyonlar bir Future döndürür; await ise sonuç gelene kadar o fonksiyonun devamını bekletir (ama programı değil):

Future<String> fetchUsername() async {
  await Future.delayed(const Duration(seconds: 1)); // ağ isteğini taklit eder
  return 'ertucuk';
}

Future<void> main() async {
  print('İstek gönderiliyor...');
  final username = await fetchUsername();
  print('Kullanıcı: $username');
}

// await olmadan: then ile zincirleme (eski stil)
fetchUsername()
    .then((name) => print(name))
    .catchError((error) => print('Hata: $error'));

Asenkron Hata Yönetimi ve Zaman Aşımı

import 'dart:async'; // TimeoutException için

Future<void> loadProfile() async {
  try {
    final name = await fetchUsername().timeout(const Duration(seconds: 5));
    print(name);
  } on TimeoutException {
    print('Sunucu çok geç yanıt verdi');
  } catch (e) {
    print('Profil yüklenemedi: $e');
  }
}

Birden Fazla Future'ı Paralel Çalıştırmak

Future<String> fetchUser() async => 'Ertu';
Future<List<String>> fetchPosts() async => ['Dart', 'MongoDB'];
Future<int> fetchFollowers() async => 128;

Future<void> main() async {
  // ❌ Sırayla: toplam süre = üç isteğin toplamı
  final user = await fetchUser();
  final posts = await fetchPosts();

  // ✅ Paralel: toplam süre = en yavaş istek
  final results = await Future.wait([fetchUser(), fetchFollowers()]);

  // ✅ Dart 3: farklı tipleri record ile paralel beklemek
  final (name, postList, followers) =
      await (fetchUser(), fetchPosts(), fetchFollowers()).wait;
  print('$name, ${postList.length} yazı, $followers takipçi');
}

Stream'ler

Future tek bir değer getirirken, Stream zaman içinde art arda gelen birden çok değeri temsil eder: kullanıcı tıklamaları, WebSocket mesajları, dosyadan okunan satırlar veya bir geri sayım gibi.

// async* ve yield ile Stream üreten fonksiyon
Stream<int> countdown(int from) async* {
  for (var i = from; i >= 0; i--) {
    await Future.delayed(const Duration(seconds: 1));
    yield i;
  }
}

Future<void> main() async {
  // await for: Stream'deki her değeri sırayla işler
  await for (final n in countdown(3)) {
    print(n); // 3, 2, 1, 0
  }

  // Stream'ler de map, where, take gibi metotlara sahiptir
  final evens = countdown(10).where((n) => n.isEven).map((n) => 'Çift: $n');

  // listen ile dinlemek
  final subscription = evens.listen(
    print,
    onError: (error) => print('Hata: $error'),
    onDone: () => print('Bitti'),
  );

  // Artık ilgilenmiyorsanız aboneliği iptal edin (bellek sızıntısını önler)
  await Future.delayed(const Duration(seconds: 3));
  await subscription.cancel();
}

Kendi olaylarınızı yayınlamak için StreamController kullanılır:

import 'dart:async';

class CartNotifier {
  final _controller = StreamController<int>.broadcast(); // birden çok dinleyici
  var _count = 0;

  Stream<int> get itemCount => _controller.stream;

  void addItem() => _controller.add(++_count);

  void dispose() => _controller.close();
}

final cart = CartNotifier();
cart.itemCount.listen((count) => print('Sepette $count ürün'));
cart.addItem(); // Sepette 1 ürün
cart.addItem(); // Sepette 2 ürün

Isolate'lar: Ağır İşleri Arka Plana Almak

Olay döngüsü tek iş parçacığında çalıştığı için, büyük bir JSON dosyasını çözümlemek ya da bir görseli işlemek gibi uzun süren CPU işleri her şeyi dondurur. Dart'ta paralel çalışma isolate'larla yapılır: her isolate'ın kendi belleği ve olay döngüsü vardır, belleği paylaşmazlar, mesajlaşarak haberleşirler.

import 'dart:isolate';

int sumOfPrimes(int limit) {
  var sum = 0;
  for (var n = 2; n <= limit; n++) {
    var isPrime = true;
    for (var d = 2; d * d <= n; d++) {
      if (n % d == 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) sum += n;
  }
  return sum;
}

Future<void> main() async {
  // Ağır hesaplama ayrı bir isolate'ta çalışır, ana isolate donmaz
  final result = await Isolate.run(() => sumOfPrimes(5_000_000));
  print(result);
}

Flutter'da aynı iş için compute() fonksiyonu da kullanılabilir.

Kütüphaneler, Paketler ve pub

import ve export

import 'dart:math';                         // Dart'ın çekirdek kütüphaneleri
import 'dart:convert';

import 'package:http/http.dart' as http;    // pub paketi, ön ekle
import 'package:merhaba/utils.dart';        // kendi paketinizin lib/ klasöründen

import 'src/helpers.dart' show slugify;     // yalnızca slugify'ı al
import 'src/legacy.dart' hide oldFunction;  // oldFunction hariç her şeyi al

// lib/merhaba.dart: paketin dışarıya açtığı API'yi tek dosyadan dağıtmak
export 'src/models/user.dart';
export 'src/services/api.dart' show ApiClient;

Sık kullanılan çekirdek kütüphaneler: dart:core (otomatik gelir), dart:async, dart:math, dart:convert (JSON, UTF-8), dart:io (dosya, ağ, süreç; web'de yoktur), dart:collection, dart:isolate.

pubspec.yaml

Her Dart projesinin kalbi pubspec.yaml dosyasıdır; projenin adını, sürümünü, desteklenen SDK aralığını ve bağımlılıklarını tanımlar:

name: gorev_yoneticisi
description: Komut satırından görevleri yöneten küçük bir uygulama.
version: 1.0.0

environment:
  sdk: ^3.5.0

dependencies:
  http: ^1.2.0

dev_dependencies:
  lints: ^5.0.0
  test: ^1.25.0

^1.2.0 gösterimi "1.2.0 veya üzeri, ama 2.0.0'dan küçük" anlamına gelir (semantic versioning). Büyük sürüm değişiklikleri geriye uyumsuz olabileceği için otomatik olarak alınmaz.

pub Komutları ve pub.dev

dart pub add http            # bağımlılık ekle (pubspec.yaml'ı günceller)
dart pub add --dev test      # yalnızca geliştirmede kullanılan bağımlılık
dart pub remove http         # bağımlılık kaldır
dart pub get                 # bağımlılıkları indir
dart pub upgrade             # izin verilen en yeni sürümlere yükselt
dart pub outdated            # güncellenebilecek paketleri listele

Paketler pub.dev adresinde yayımlanır. Bir paketi seçerken pub.dev'deki likes, pub points (kalite puanı) ve popularity değerlerine, son güncelleme tarihine ve yayıncının doğrulanmış (verified publisher) olup olmadığına bakın. Sık kullanılan paketlerden bazıları: http ve dio (ağ istekleri), json_serializable ve freezed (kod üretimi), intl (tarih, sayı biçimlendirme ve çeviri), path, shelf (sunucu).

Dosya, JSON ve HTTP

Dosya Okuma ve Yazma

import 'dart:io';

Future<void> main() async {
  final file = File('notlar.txt');

  await file.writeAsString('İlk not\n');                          // yaz (üzerine)
  await file.writeAsString('İkinci not\n', mode: FileMode.append); // sona ekle

  if (await file.exists()) {
    final content = await file.readAsString();  // tamamını oku
    final lines = await file.readAsLines();     // satır satır
    print('${lines.length} satır:\n$content');
  }

  // Klasör işlemleri
  final dir = Directory('yedekler');
  await dir.create(recursive: true);
  await for (final entity in Directory.current.list()) {
    print(entity.path);
  }

  // Kullanıcıdan girdi almak
  stdout.write('Adınız: ');
  final name = stdin.readLineSync();
  print('Merhaba, ${name ?? 'misafir'}!');
}

JSON ile Çalışmak

dart:convert kütüphanesindeki jsonDecode metni Dart nesnelerine, jsonEncode ise Dart nesnelerini metne çevirir. Gerçek projelerde JSON'u tip güvenli model sınıflarına dönüştürmek en iyi uygulamadır:

import 'dart:convert';

class User {
  const User({required this.name, required this.age, this.skills = const []});

  final String name;
  final int age;
  final List<String> skills;

  factory User.fromJson(Map<String, dynamic> json) => User(
        name: json['name'] as String,
        age: json['age'] as int,
        skills: (json['skills'] as List<dynamic>? ?? []).cast<String>(),
      );

  Map<String, dynamic> toJson() => {'name': name, 'age': age, 'skills': skills};
}

void main() {
  const source = '{"name": "Ertuğrul", "age": 20, "skills": ["Dart", "MongoDB"]}';

  final user = User.fromJson(jsonDecode(source) as Map<String, dynamic>);
  print(user.skills.first); // Dart

  final encoded = jsonEncode(user); // toJson otomatik çağrılır
  print(encoded);

  // Okunabilir (girintili) çıktı
  print(const JsonEncoder.withIndent('  ').convert(user));
}

İpucu: Büyük projelerde fromJson ve toJson metotlarını elle yazmak yerine json_serializable ya da freezed paketleriyle otomatik ürettirin; hem hata yapma ihtimali azalır hem de alan eklemek tek satıra iner.

HTTP İsteği Göndermek

dart pub add http
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> main() async {
  final uri = Uri.parse('https://api.github.com/users/ertucuk');
  final response = await http.get(uri, headers: {'Accept': 'application/json'});

  if (response.statusCode != 200) {
    print('İstek başarısız: ${response.statusCode}');
    return;
  }

  final data = jsonDecode(response.body) as Map<String, dynamic>;
  print('${data['login']} kullanıcısının ${data['public_repos']} açık reposu var.');

  // POST isteği
  final created = await http.post(
    Uri.parse('https://example.com/api/notes'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'title': 'Dart notları'}),
  );
  print(created.statusCode);
}

Test Yazmak

Dart'ın resmi test paketiyle birim testleri yazmak çok kolaydır. Testler test/ klasöründe, adı _test.dart ile biten dosyalarda tutulur:

// lib/calculator.dart
int add(int a, int b) => a + b;

double divide(int a, int b) {
  if (b == 0) throw ArgumentError('Sıfıra bölünemez');
  return a / b;
}
// test/calculator_test.dart
import 'package:merhaba/calculator.dart';
import 'package:test/test.dart';

void main() {
  group('add', () {
    test('iki pozitif sayıyı toplar', () {
      expect(add(2, 3), 5);
    });

    test('negatif sayılarla çalışır', () {
      expect(add(-2, -3), equals(-5));
    });
  });

  group('divide', () {
    test('ondalıklı sonuç döndürür', () {
      expect(divide(7, 2), 3.5);
    });

    test('sıfıra bölmede hata fırlatır', () {
      expect(() => divide(1, 0), throwsA(isA<ArgumentError>()));
    });
  });
}
dart test                       # tüm testleri çalıştır
dart test test/calculator_test.dart
dart test --name "sıfıra"       # adında "sıfıra" geçen testler

Dart Araçları ve Derleme

Analiz, Biçimlendirme ve Otomatik Düzeltme

dart analyze            # tip hataları, uyarılar ve lint kurallarını kontrol et
dart format .           # tüm dosyaları resmi stile göre biçimlendir
dart fix --dry-run      # otomatik düzeltilebilecek sorunları listele
dart fix --apply        # ve düzelt (örneğin eski API'leri yenileriyle değiştirir)
dart doc                # /// yorumlarından HTML dokümantasyon üret

Lint kuralları analysis_options.yaml dosyasında ayarlanır. dart create, Dart ekibinin önerdiği kuralları içeren lints paketini otomatik ekler:

include: package:lints/recommended.yaml

linter:
  rules:
    - prefer_final_locals     # değişmeyen yerel değişkenler final olsun
    - avoid_print             # üretim kodunda print yerine loglama kullan
    - unawaited_futures       # await edilmeyen Future'ları uyar

analyzer:
  language:
    strict-casts: true        # dynamic'ten örtük dönüşümleri yasakla

Derleme Hedefleri

KomutÇıktıNe için?
dart runYok (JIT ile çalıştırır)Geliştirme
dart compile exeTek dosyalık yerel çalıştırılabilirCLI araçları, sunucular
dart compile aot-snapshotPlatforma özel AOT dosyasıDaha küçük dağıtımlar
dart compile jit-snapshotJIT ön ısıtmalı dosyaHızlı açılan Dart VM programları
dart compile jsJavaScriptWeb
dart compile wasmWebAssemblyModern tarayıcılarda daha yüksek performans
dart compile exe bin/gorev.dart -o gorev
./gorev liste   # Dart kurulu olmayan bir bilgisayarda da çalışır (aynı işletim sistemi ve mimaride)

Dart Kod Stili (Effective Dart)

Dart ekibinin yayımladığı Effective Dart rehberi, topluluğun ortak yazım kurallarını belirler. En önemlileri:

  • UpperCamelCase: Sınıflar, enum'lar, typedef'ler, extension'lar (UserProfile, HttpStatus).
  • lowerCamelCase: Değişkenler, fonksiyonlar, parametreler ve sabitler (maxRetries; Java'daki MAX_RETRIES biçimi kullanılmaz).
  • lowercase_with_underscores: Dosya, klasör ve paket adları (user_profile.dart).
  • Değişmeyen her şey final, derleme anında bilinen her şey const olsun.
  • Tip açıkça belli olan yerel değişkenlerde tipi tekrar yazmayın: final names = <String>[]; yeterli.
  • Çok satırlı parametre listelerinde sona virgül (trailing comma) koyun; dart format kodu daha okunabilir biçimde böler.
  • Herkese açık API'leri /// ile belgeleyin.

Sık Yapılan Hatalar

  • Her yerde ! kullanmak: Null safety'nin sağladığı güvenceyi ortadan kaldırır; değer null çıktığında uygulama çöker.
  • dynamic'e fazla güvenmek: Hatalar derleme yerine çalışma anına kayar. JSON'u en kısa sürede model sınıflarına çevirin.
  • Future'ları await etmemek: İşlem bitmeden sonraki satıra geçilir, hatalar sessizce kaybolur. unawaited_futures lint kuralını açın.
  • forEach içinde async fonksiyon kullanmak: list.forEach((x) async { await ... }) hiçbir işlemi beklemez. Sırayla beklemek için for (final x in list) { await ... }, paralel için Future.wait kullanın.
  • =='ı ezip hashCode'u unutmak: Set ve Map'lerde beklenmedik sonuçlar doğurur.
  • const bir listeyi değiştirmeye çalışmak: Çalışma anında UnsupportedError alırsınız.
  • JSON'dan gelen listeyi doğrudan tip dönüştürmek: json['tags'] as List<String> hata verir, çünkü gelen liste List<dynamic>'tir. (json['tags'] as List).cast<String>() veya List<String>.from(...) kullanın.
  • Türkçe metinlerde toUpperCase()'a güvenmek: "i" harfi "I" olur, "İ" değil.
  • Stream aboneliklerini iptal etmemek: Bellek sızıntısına ve kapanmış ekranlarda çalışan kodlara yol açar.
  • Web'de büyük tam sayılar: Yerel platformlarda int 64 bittir; ancak JavaScript'e derlenen kodda sayılar JavaScript sayılarıdır ve 2^53'ten büyük tam sayılar hassasiyet kaybeder.

Hepsi Bir Arada: Komut Satırı Görev Yöneticisi

Şimdi öğrendiklerimizi birleştirip görevleri bir JSON dosyasında saklayan küçük bir komut satırı uygulaması yazalım. Bu örnekte enhanced enum, factory constructor, JSON dönüşümü, sealed sınıflar, liste pattern'ları, exhaustive switch, async dosya işlemleri ve hata yönetimi bir arada kullanılıyor:

dart create gorev_yoneticisi
cd gorev_yoneticisi
# bin/gorev_yoneticisi.dart dosyasının içeriğini aşağıdaki kodla değiştirin
import 'dart:convert';
import 'dart:io';
import 'dart:math';

/// Görevin durumu ve listede gösterilecek simgesi.
enum Status {
  todo('[ ]'),
  done('[x]');

  const Status(this.icon);
  final String icon;
}

class Task {
  Task({required this.id, required this.title, this.status = Status.todo, DateTime? createdAt})
      : createdAt = createdAt ?? DateTime.now();

  final int id;
  final String title;
  Status status;
  final DateTime createdAt;

  factory Task.fromJson(Map<String, dynamic> json) => Task(
        id: json['id'] as int,
        title: json['title'] as String,
        status: Status.values.byName(json['status'] as String),
        createdAt: DateTime.parse(json['createdAt'] as String),
      );

  Map<String, dynamic> toJson() => {
        'id': id,
        'title': title,
        'status': status.name,
        'createdAt': createdAt.toIso8601String(),
      };

  @override
  String toString() => '${status.icon} #$id $title';
}

/// Komut satırından gelebilecek tüm komutlar. sealed olduğu için
/// switch içinde birini unutursak program derlenmez.
sealed class Command {
  const Command();

  static Command parse(List<String> args) => switch (args) {
        ['ekle', ...final words] when words.isNotEmpty => Add(words.join(' ')),
        ['bitir', final id] => Complete(int.parse(id)),
        ['sil', final id] => Remove(int.parse(id)),
        ['liste'] || [] => const ListAll(),
        _ => const Help(),
      };
}

final class Add extends Command {
  const Add(this.title);
  final String title;
}

final class Complete extends Command {
  const Complete(this.id);
  final int id;
}

final class Remove extends Command {
  const Remove(this.id);
  final int id;
}

final class ListAll extends Command {
  const ListAll();
}

final class Help extends Command {
  const Help();
}

/// Görevleri JSON dosyasında saklar.
class TaskStore {
  TaskStore(this.file);
  final File file;

  Future<List<Task>> load() async {
    if (!await file.exists()) return [];
    final data = jsonDecode(await file.readAsString()) as List<dynamic>;
    return [for (final item in data) Task.fromJson(item as Map<String, dynamic>)];
  }

  Future<void> save(List<Task> tasks) async {
    const encoder = JsonEncoder.withIndent('  ');
    await file.writeAsString(encoder.convert(tasks));
  }
}

Future<void> main(List<String> args) async {
  final store = TaskStore(File('gorevler.json'));
  final tasks = await store.load();

  try {
    switch (Command.parse(args)) {
      case Add(:final title):
        final id = tasks.isEmpty ? 1 : tasks.map((t) => t.id).reduce(max) + 1;
        tasks.add(Task(id: id, title: title));
        print('Eklendi: #$id $title');
      case Complete(:final id):
        tasks.firstWhere((t) => t.id == id).status = Status.done;
        print('Tamamlandı: #$id');
      case Remove(:final id):
        final before = tasks.length;
        tasks.removeWhere((t) => t.id == id);
        print(before == tasks.length ? '#$id bulunamadı' : 'Silindi: #$id');
      case ListAll():
        if (tasks.isEmpty) {
          print('Henüz görev yok. Eklemek için: ekle <başlık>');
        }
        tasks.forEach(print);
        final done = tasks.where((t) => t.status == Status.done).length;
        print('\n$done/${tasks.length} görev tamamlandı');
      case Help():
        print('Kullanım: dart run bin/gorev_yoneticisi.dart [ekle <başlık> | bitir <id> | sil <id> | liste]');
    }
    await store.save(tasks);
  } on StateError {
    stderr.writeln('Bu ID ile bir görev bulunamadı.');
    exitCode = 1;
  } on FormatException {
    stderr.writeln('ID bir sayı olmalı.');
    exitCode = 1;
  }
}
$ dart run bin/gorev_yoneticisi.dart ekle Dart yazısını bitir
Eklendi: #1 Dart yazısını bitir
$ dart run bin/gorev_yoneticisi.dart ekle Flutter projesine başla
Eklendi: #2 Flutter projesine başla
$ dart run bin/gorev_yoneticisi.dart bitir 1
Tamamlandı: #1
$ dart run bin/gorev_yoneticisi.dart liste
[x] #1 Dart yazısını bitir
[ ] #2 Flutter projesine başla

1/2 görev tamamlandı

Uygulamayı dart compile exe bin/gorev_yoneticisi.dart -o gorev komutuyla tek dosyalık bir programa çevirip ./gorev liste şeklinde, Dart kurulu olmadan da çalıştırabilirsiniz.

Hızlı Başvuru Listesi

  • Değişkenler: var, final, const, late, _
  • Temel tipler: int, double, num, String, bool, List, Set, Map, record, Object, dynamic
  • Null safety: Tip?, ?., ??, ??=, ?[], !, ...?, required
  • Operatörler: ~/, %, is, is!, as, .., ..., ? :
  • Kontrol akışı: if, switch (deyim ve ifade), for, for-in, while, do-while, break, continue, assert
  • Koleksiyon metotları: map, where, firstWhere, any, every, reduce, fold, expand, take, skip, toList, toSet
  • Fonksiyonlar: =>, [isteğe bağlı], {isimli}, required, varsayılan değerler, closure, typedef
  • Sınıflar: constructor, named / factory / const constructor, initializer list, get, set, static, _gizli, operator
  • Kalıtım: extends, super, @override, abstract, implements, mixin, with, on
  • Sınıf değiştiricileri: base, interface, final, sealed, mixin class
  • Diğer tipler: enum, generic'ler (<T>, extends), extension, extension type
  • Pattern'lar: record, liste, map, nesne, sabit, ilişkisel (>=), mantıksal (||, &&), when, if-case, _
  • Hatalar: try, on, catch, finally, throw, rethrow, Exception, Error
  • Asenkron: Future, async, await, Future.wait, .wait, timeout, Stream, async*, yield, await for, StreamController, Isolate.run
  • Komutlar: dart create, dart run, dart pub add, dart test, dart analyze, dart format, dart fix, dart compile

Sıkça Sorulan Sorular

Dart sadece Flutter için mi kullanılır?

Hayır, ama en yaygın kullanım alanı kesinlikle Flutter'dır. Dart ile komut satırı araçları, sunucu uygulamaları (Shelf, Dart Frog, Serverpod) ve web uygulamaları da yazılabilir. Flutter ile mobil uygulama yazan bir ekibin backend'i de Dart ile yazması, modelleri ve iş mantığını iki taraf arasında paylaşmayı mümkün kılar.

Flutter'a başlamadan önce Dart öğrenmeli miyim?

Evet, en azından bu yazıdaki temel konuları (değişkenler, null safety, koleksiyonlar, fonksiyonlar, sınıflar ve async/await) öğrenmeniz çok faydalı olur. Flutter'daki zorlukların büyük bölümü aslında Dart'tan kaynaklanır; dili bildiğinizde Flutter'ın kendisine odaklanabilirsiniz.

Dart mı, JavaScript/TypeScript mi?

İkisi farklı ekosistemlerde parlar. Web ön yüzü ve Node.js dünyası için JavaScript/TypeScript; tek kod tabanıyla mobil, web ve masaüstü uygulamaları için Dart ve Flutter öne çıkar. TypeScript biliyorsanız Dart'ı çok hızlı öğrenirsiniz: sınıflar, generic'ler, async/await ve tip sistemi birbirine çok benzer. En büyük farklar Dart'ın sound null safety'si ve tiplerin çalışma anında da geçerli olmasıdır.

Dart öğrenmek ne kadar sürer?

Daha önce başka bir dil kullandıysanız temel sözdizimini birkaç günde öğrenip küçük programlar yazmaya başlayabilirsiniz. Programlamaya Dart ile başlıyorsanız temeller birkaç hafta sürebilir. Pattern matching, Stream'ler ve isolate'lar gibi ileri konularda rahatlamak ise gerçek projelerde geçen zamanla gelir.

Dart zor bir dil mi?

Hayır. Dart, okunabilirlik ve öğrenme kolaylığı düşünülerek tasarlanmış bir dildir. Tutarlı sözdizimi, açıklayıcı hata mesajları, hazır gelen biçimlendirici ve DartPad sayesinde yeni başlayanlar için en rahat dillerden biridir.

Sonraki Adımlar ve Kaynaklar

Dart temellerini öğrendikten sonra izleyebileceğiniz yol:

  1. Pratik: Bu yazıdaki görev yöneticisini genişletin: görevlere son tarih, öncelik ve etiket ekleyin, testlerini yazın.
  2. Flutter: Widget'lar, durum yönetimi (setState, Provider, Riverpod veya Bloc) ve navigasyonla ilk mobil uygulamanızı yazın.
  3. Async derinleşme: Stream'ler ve isolate'larla gerçek zamanlı bir uygulama (örneğin sohbet veya canlı fiyat takibi) geliştirin.
  4. Backend: Shelf veya Dart Frog ile küçük bir REST API yazıp Flutter uygulamanıza bağlayın.
  5. Kod kalitesi: Effective Dart rehberini okuyun, projelerinizde dart analyze çıktısını sıfır uyarıda tutun.

Faydalı kaynaklar:

Sonuç

Dart; okunabilir sözdizimi, sağlam tip sistemi, sound null safety'si ve hem JIT hem AOT derleme yeteneğiyle modern uygulama geliştirme için son derece üretken bir dildir. Bu yazıda kurulumdan değişkenlere, veri tiplerinden null safety'ye, koleksiyonlardan fonksiyonlara, sınıf ve mixin'lerden enum, generic ve extension'lara, records ve pattern matching'den async/await, Stream ve isolate'lara, paket yönetiminden dosya, JSON, test ve derlemeye kadar Dart'ın neredeyse tüm temel konularını ele aldık.

Akılda tutulması gereken en önemli fikir şu: Derleyiciyi düşman değil, ekip arkadaşı olarak görün. Dart'ın tip sistemi, null safety'si ve exhaustive switch'leri hataları siz programı çalıştırmadan önce yakalamak için tasarlanmıştır; onlarla savaşmak yerine onlardan yararlandığınızda çok daha az hata içeren kod yazarsınız.

Bu temel bilgilerle artık kendi Dart programlarınızı yazmaya başlayabilirsiniz. Bir sonraki adım, Flutter ile bu bilgileri ekranda görünen, dokunulabilen bir uygulamaya dönüştürmek olacak.