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
intise çalışma anında başka bir şey olamaz. - Sound null safety: Bir değişken açıkça izin vermediğiniz sürece
nullolamaz. 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
dartkomutunda 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,
newanahtar 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:
nameveNamefarklı ş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 kelime | Yeniden atanabilir mi? | Değer ne zaman belli olur? | Örnek |
|---|---|---|---|
var | Evet | Çalışma anında | Sayaçlar, değişen durum |
final | Hayır | Çalışma anında (bir kez) | DateTime.now(), API yanıtı |
const | Hayır | Derleme anında | Matematik sabitleri, sabit metinler |
İpucu: Varsayılan olarak
finalkullanın; yalnızca değeri gerçekten değişecek değişkenler içinvar'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:
| Tip | Açıklama | Örnek |
|---|---|---|
int | Tam sayı (yerel platformlarda 64 bit) | 42, -7, 0xFF |
double | 64 bit ondalıklı sayı | 3.14, 1e6 |
num | int ve double'ın ortak üst tipi | num x = 5; |
String | UTF-16 metin | 'Merhaba' |
bool | Mantıksal değer | true, false |
List, Set, Map | Koleksiyonlar | [1, 2], {1, 2}, {'a': 1} |
| Record | Sabit sayıda, farklı tipte değer grubu | ('Ertu', 20) |
Null | Yalnızca null değerini taşır | null |
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()vetoLowerCase()dile duyarlı değildir:'istanbul'.toUpperCase()sonucu'ISTANBUL'olur,'İSTANBUL'değil. Türkçe metinlerde doğru dönüşüm içinintlpaketi 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
| Grup | Operatörler | Örnek |
|---|---|---|
| Aritmetik | + - * / ~/ % | 17 ~/ 5 → 3 |
| Artırma / azaltma | ++ -- | count++ |
| Karşılaştırma | == != > < >= <= | age >= 18 |
| Mantıksal | && || ! | isAdmin || isOwner |
| Tip testi | is is! as | value is String |
| Atama | = += -= *= /= ??= | total += price |
| Koşullu | koşul ? a : b, a ?? b | age >= 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,takegibi metotlar tembeldir (lazy): birIterabledö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)yerinecreateUser(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:
=='ı eziphashCode'u ezmezseniz, eşit görünen nesnelerSetveMapiç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ştirici | Nesne oluşturulabilir mi? | Dışarıda extends? | Dışarıda implements? | Ne için? |
|---|---|---|---|---|
| (yok) | Evet | Evet | Evet | Varsayılan, her şeye açık |
abstract | Hayır | Evet | Evet | Kısmi uygulama içeren sözleşmeler |
base | Evet | Evet | Hayır | Uygulamanın her zaman miras alınmasını garanti etmek |
interface | Evet | Hayır | Evet | Yalnızca arayüz olarak kullanılmak |
final | Evet | Hayır | Hayır | Hiçbir şekilde alt tipi oluşturulamasın |
sealed | Hayır | Hayır | Hayır | Alt tipleri bilinen kapalı aileler (exhaustive switch) |
mixin class | Evet | Evet | Evet | Hem 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
fromJsonvetoJsonmetotlarını elle yazmak yerinejson_serializableya dafreezedpaketleriyle 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 run | Yok (JIT ile çalıştırır) | Geliştirme |
dart compile exe | Tek dosyalık yerel çalıştırılabilir | CLI araçları, sunucular |
dart compile aot-snapshot | Platforma özel AOT dosyası | Daha küçük dağıtımlar |
dart compile jit-snapshot | JIT ön ısıtmalı dosya | Hızlı açılan Dart VM programları |
dart compile js | JavaScript | Web |
dart compile wasm | WebAssembly | Modern 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'dakiMAX_RETRIESbiç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 şeyconstolsun. - 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 formatkodu 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_futureslint kuralını açın. forEachiçindeasyncfonksiyon kullanmak:list.forEach((x) async { await ... })hiçbir işlemi beklemez. Sırayla beklemek içinfor (final x in list) { await ... }, paralel içinFuture.waitkullanın.=='ı eziphashCode'u unutmak: Set ve Map'lerde beklenmedik sonuçlar doğurur.constbir listeyi değiştirmeye çalışmak: Çalışma anındaUnsupportedErroralırsınız.- JSON'dan gelen listeyi doğrudan tip dönüştürmek:
json['tags'] as List<String>hata verir, çünkü gelen listeList<dynamic>'tir.(json['tags'] as List).cast<String>()veyaList<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
int64 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:
- Pratik: Bu yazıdaki görev yöneticisini genişletin: görevlere son tarih, öncelik ve etiket ekleyin, testlerini yazın.
- Flutter: Widget'lar, durum yönetimi (setState, Provider, Riverpod veya Bloc) ve navigasyonla ilk mobil uygulamanızı yazın.
- Async derinleşme: Stream'ler ve isolate'larla gerçek zamanlı bir uygulama (örneğin sohbet veya canlı fiyat takibi) geliştirin.
- Backend: Shelf veya Dart Frog ile küçük bir REST API yazıp Flutter uygulamanıza bağlayın.
- Kod kalitesi: Effective Dart rehberini okuyun, projelerinizde
dart analyzeçıktısını sıfır uyarıda tutun.
Faydalı kaynaklar:
- Dart Dil Turu: Dilin tüm özellikleri için resmi rehber.
- Effective Dart: Stil, dokümantasyon ve tasarım kuralları.
- DartPad: Tarayıcıda Dart ve Flutter kodu çalıştırın.
- Dart API Referansı: Çekirdek kütüphanelerin tüm sınıf ve metotları.
- pub.dev: Dart ve Flutter paket deposu.
- Flutter Dokümantasyonu: Dart'tan sonraki doğal adım.
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.