Flutter'da Isar Veritabanı Kullanımı: Yeni Nesil Hızlı Lokal Depolama
İlişkisel veritabanı (SQLite) mantığını anlattıktan sonra, öğrencilere güncel, yüksek performanslı ve modern bir NoSQL alternatifi olan Isar veritabanını göstermek, ufuklarını oldukça genişletecektir. Isar, tamamen Dart ve Flutter için sıfırdan yazılmış, inanılmaz hızlı ve tip güvenli (type-safe) bir veritabanıdır.
SEO Bilgileri
Meta Başlık: Flutter'da Isar Veritabanı Kullanımı: Yeni Nesil Hızlı Lokal Depolama
Meta Açıklama: Flutter projelerinizde yüksek performanslı, type-safe ve NoSQL tabanlı Isar veritabanının nasıl kullanılacağını adım adım öğrenin. Kapsamlı CRUD rehberi.
Anahtar Kelimeler: Flutter, Isar database, lokal veritabanı, NoSQL, Flutter CRUD, Dart, mobil uygulama geliştirme, Isar Inspector.
Mobil uygulama dünyasında lokal veri depolama denilince akla ilk olarak SQLite gelse de, günümüzde çok daha modern ve performanslı alternatifler mevcut. Tamamen Dart dili için optimize edilmiş, NoSQL tabanlı ve inanılmaz hızlı bir lokal veritabanı olan Isar, bu alternatiflerin en güçlüsüdür.
Eğer verilerinizi tablolar yerine koleksiyonlar (nesneler) halinde tutmak, karmaşık SQL sorguları yazmaktan kurtulmak ve uygulamanızın performansını uçuşa geçirmek istiyorsanız, Isar harika bir seçimdir. Üstelik Isar'ın sunduğu "Web Inspector" sayesinde veritabanınızı tarayıcı üzerinden anlık olarak izleyebilirsiniz.
Bu makalede, Isar kurulumunu gerçekleştirecek ve bir "Öğrenci Takip Sistemi" üzerinden temel CRUD (Oluşturma, Okuma, Güncelleme, Silme) işlemlerini adım adım uygulayacağız.
1. Gerekli Paketlerin Kurulumu
Isar, kod üretimi (code generation) kullanan bir yapıdır. Bu nedenle projeye hem ana paketleri hem de derleme aşamasında çalışacak yardımcı araçları eklememiz gerekir.
pubspec.yaml dosyanızı açın ve aşağıdaki bağımlılıkları ekleyin:
dependencies:
flutter:
sdk: flutter
isar: ^3.1.0
isar_flutter_libs: ^3.1.0 # Isar'ın çalışması için gerekli çekirdek kütüphaneler
path_provider: ^2.1.1 # Veritabanının kaydedileceği klasörü bulmak için
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.6 # Kod üretici tetikleyici
isar_generator: ^3.1.0 # Isar için gerekli kodları üreten paket
2. Veri Modelinin (Collection) Oluşturulması
Isar NoSQL tabanlı olduğu için veriler tablolar yerine Collection (Koleksiyon) olarak tutulur. Öğrenci modelimizi oluştururken sınıfın başına @collection notasyonunu ekliyoruz.
// Geliştirici: Nuri TIRAŞ
// Dosya: student.dart
import 'package:isar/isar.dart';
// Bu satır, build_runner çalıştıktan sonra otomatik oluşacak dosyayı işaret eder
part 'student.g.dart';
@collection
class Student {
// Isar'da her nesnenin benzersiz bir Id'si olmalıdır. autoIncrement ile otomatik artırıyoruz.
Id id = Isar.autoIncrement;
late String name;
// @Index ekleyerek bu alana göre arama/filtreleme işlemlerini hızlandırabiliriz
@Index()
late String grade;
late int number;
}
Kritik Adım: Kod Üretimi
student.dart dosyasını kaydettikten sonra, terminali açıp Isar'ın arka plan kodlarını üretmesi için şu komutu çalıştırıyoruz:
flutter pub run build_runner build
Bu işlem bittiğinde klasörünüzde student.g.dart adında yeni bir dosya oluştuğunu göreceksiniz. Hata veren part satırı da düzelecektir.
3. Veritabanı Servisinin (Isar Service) Yazılması
Isar veritabanı ile haberleşecek merkezi bir servis sınıfı yazmak, projenin yönetilebilirliği açısından en iyi yaklaşımdır.
// Geliştirici: Nuri TIRAŞ
// Dosya: isar_service.dart
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'student.dart';
class IsarService {
late Future<Isar> db;
IsarService() {
db = openDB();
}
// Veritabanını başlatma ve açma işlemi
Future<Isar> openDB() async {
// Eğer Isar halihazırda açıksa, mevcut olanı döndür
if (Isar.instanceNames.isEmpty) {
final dir = await getApplicationDocumentsDirectory();
return await Isar.open(
[StudentSchema], // Üretilen şema buraya eklenir
directory: dir.path,
inspector: true, // Tarayıcı üzerinden veritabanını incelemek için
);
}
return Future.value(Isar.getInstance());
}
// CREATE / UPDATE: Yeni öğrenci ekleme veya güncelleme (Isar ikisini de put ile yapar)
Future<void> saveStudent(Student newStudent) async {
final isar = await db;
// Isar'da yazma işlemleri her zaman writeTxn (Transaction) içinde yapılmalıdır
await isar.writeTxn(() async {
await isar.students.put(newStudent);
});
}
// READ: Tüm öğrencileri okuma (Numaraya göre sıralı)
Future<List<Student>> getAllStudents() async {
final isar = await db;
return await isar.students.where().sortByNumber().findAll();
}
// READ: Sınıfa göre filtreleme (Örn: Sadece 11-A sınıfını getir)
Future<List<Student>> getStudentsByGrade(String grade) async {
final isar = await db;
return await isar.students.filter().gradeEqualTo(grade).findAll();
}
// DELETE: Öğrenci silme
Future<void> deleteStudent(int id) async {
final isar = await db;
await isar.writeTxn(() async {
await isar.students.delete(id);
});
}
// Veritabanını temizleme (Tüm kayıtları siler)
Future<void> cleanDb() async {
final isar = await db;
await isar.writeTxn(() async {
await isar.clear();
});
}
}
4. Kullanıcı Arayüzü (UI) ile Entegrasyon
Artık servisimiz hazır. Bir ekran üzerinden verileri okuyup listeleyelim ve yeni kayıtlar ekleyelim.
// Geliştirici: Nuri TIRAŞ
// Dosya: student_list_screen.dart
import 'package:flutter/material.dart';
import 'isar_service.dart';
import 'student.dart';
class StudentListScreen extends StatefulWidget {
final IsarService isarService;
const StudentListScreen({Key? key, required this.isarService}) : super(key: key);
@override
State<StudentListScreen> createState() => _StudentListScreenState();
}
class _StudentListScreenState extends State<StudentListScreen> {
List<Student> _students = [];
@override
void initState() {
super.initState();
_loadStudents();
}
// Öğrencileri yükler ve UI'ı günceller
Future<void> _loadStudents() async {
final students = await widget.isarService.getAllStudents();
setState(() {
_students = students;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Isar ile Öğrenci Takibi'),
),
body: _students.isEmpty
? const Center(child: Text('Henüz öğrenci eklenmemiş.'))
: ListView.builder(
itemCount: _students.length,
itemBuilder: (context, index) {
final student = _students[index];
return ListTile(
leading: CircleAvatar(child: Text(student.grade)),
title: Text(student.name),
subtitle: Text('Okul No: ${student.number}'),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () async {
await widget.isarService.deleteStudent(student.id);
_loadStudents();
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Örnek öğrenci nesnesi oluşturma
final newStudent = Student()
..name = 'Ayşe Kaya'
..grade = '12-B'
..number = 2024;
await widget.isarService.saveStudent(newStudent);
_loadStudents();
},
child: const Icon(Icons.add),
),
);
}
}
Neden Isar Tercih Edilmeli? (Önemli İpuçları)
Performans ve Hız: Isar, C tabanlı SQLite'ın aksine Rust ve Dart kullanılarak geliştirilmiştir. Yapılan benchmark testlerinde verileri okuma ve yazma konusunda SQLite'tan çok daha hızlı sonuçlar verir.
Type-Safe Sorgular:
sqflitekullanırken SQL sorgularını metin (String) olarak yazarız (Örn:where: 'id = ?'). Bu durum yazım hatalarına davetiye çıkarır. Isar'da ise sorgular otomatik üretilen metodlarla yazılır (.filter().gradeEqualTo('11-B')), bu sayede derleme aşamasında hataları görebiliriz.Senkron ve Asenkron Destek: Gerekli durumlarda Isar ile asenkron (
await) kullanmadan senkron okuma/yazma işlemleri de yapabilirsiniz.Isar Inspector: Uygulamanız hata ayıklama (debug) modunda çalışırken terminale bir link düşer. Bu linke tıkladığınızda web tarayıcınız üzerinden veritabanınızın anlık görsel bir paneline ulaşır, verileri manuel olarak düzenleyebilirsiniz.
Isar, özellikle nesne yönelimli programlama (OOP) mantığına tam oturduğu için, modern Flutter projelerinde veri yönetimini hem kolaylaştırır hem de performansı zirveye taşır. Öğrencilerinize projelerinde rahatlıkla önerebilirsiniz.
Uygulama: Duyarlılık
Modern bir uygulamanın tüm katmanlarını (Veritabanı, Durum Yönetimi ve Arayüz) bir araya getiren "Tam Yığın" (Full-Stack) sayılabilecek bir mobil proje mimarisi kuruyoruz.
Bu projede Isar veritabanını kullanarak senaryoları kalıcı hale getireceğiz. Eğer veritabanı boşsa varsayılan 3 senaryoyu otomatik ekleyeceğiz. Öğrenciler isterlerse yeni senaryolar ekleyebilecek ve uygulama kapansa bile bu senaryolar silinmeyecek.
Isar kod üretimi (code generation) kullandığı için dosyaları düzenli ayırmamız şart. Adım adım ilerleyelim:
Adım 1: Gerekli Paketleri Ekleyin
Öncelikle projenizin terminalinde şu komutları çalıştırarak Isar, Provider ve yol bulucu paketleri indirin:
flutter pub add provider isar isar_flutter_libs path_provider
flutter pub add -d build_runner isar_generator
Adım 2: Veri Modelleri (senaryo.dart)
Isar, ilişkisel tablolar yerine nesne (Collection) tutar. Senaryonun içindeki seçenekleri ayrı bir tablo yapmak yerine @embedded (gömülü) nesne olarak tanımlayacağız. Bu NoSQL mantığını öğrencilere anlatmak için harika bir örnektir.
Projenizde lib klasörüne senaryo.dart adında bir dosya açın ve şu kodları yapıştırın:
// Geliştirici: Nuri TIRAŞ
// Dosya: lib/senaryo.dart
import 'package:isar/isar.dart';
// Bu satır Isar'ın üreteceği dosyayı işaret eder
part 'senaryo.g.dart';
@collection
class Senaryo {
Id id = Isar.autoIncrement; // Otomatik artan ID
late String baslik;
late String icerik;
// Seçenekleri ayrı tablo yerine bu senaryonun içine gömüyoruz
late List<Secenek> secenekler;
}
@embedded
class Secenek {
String? metin;
int? puan;
String? geriBildirim;
}
Kritik İşlem: Dosyayı kaydettikten sonra terminalde şu komutu çalıştırın. Bu komut, Isar'ın veritabanı şemasını (senaryo.g.dart) otomatik oluşturmasını sağlayacaktır:
flutter pub run build_runner build
Adım 3: Veritabanı Servisi (isar_service.dart)
Veritabanı bağlantısını ve kayıt ekleme/okuma işlemlerini tek bir merkezden yöneteceğimiz sınıf.
// Geliştirici: Nuri TIRAŞ
// Dosya: lib/isar_service.dart
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'senaryo.dart';
class IsarService {
late Future<Isar> db;
IsarService() {
db = openDB();
}
Future<Isar> openDB() async {
if (Isar.instanceNames.isEmpty) {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[SenaryoSchema], // Üretilen şema
directory: dir.path,
);
// Eğer veritabanı boşsa varsayılan senaryoları yükle
if (await isar.senaryos.count() == 0) {
await _varsayilanSenaryolariYukle(isar);
}
return isar;
}
return Future.value(Isar.getInstance());
}
Future<List<Senaryo>> tumSenaryolariGetir() async {
final isar = await db;
return await isar.senaryos.where().findAll();
}
Future<void> senaryoEkle(Senaryo yeniSenaryo) async {
final isar = await db;
await isar.writeTxn(() async {
await isar.senaryos.put(yeniSenaryo);
});
}
// Uygulama ilk kez açıldığında çalışacak varsayılan veriler
Future<void> _varsayilanSenaryolariYukle(Isar isar) async {
final varsayilanlar = [
Senaryo()
..baslik = 'Atölye Dersi'
..icerik = 'Sınıf arkadaşın atölyede bir bilgisayarı kullanırken zorlanıyor ve morali bozuk. Ne yaparsın?'
..secenekler = [
Secenek()..metin = 'Kendi işime odaklanırım.'..puan = -5..geriBildirim = 'Sadece izlemek onu yalnız hissettirir.',
Secenek()..metin = 'Yanına gidip yardım teklif ederim.'..puan = 10..geriBildirim = 'Harika! Akran dayanışması.',
Secenek()..metin = 'Öğretmene şikayet ederim.'..puan = 0..geriBildirim = 'Önce arkadaşınla iletişim kurmak daha iyidir.',
],
Senaryo()
..baslik = 'Okul Koridoru'
..icerik = 'Yerde buruşturulup atılmış geri dönüşüme uygun bir kağıt görüyorsun.'
..secenekler = [
Secenek()..metin = 'Görmezden gelirim.'..puan = -5..geriBildirim = 'Çevreye duyarsız kalmak hepimize zarar verir.',
Secenek()..metin = 'Alıp geri dönüşüme atarım.'..puan = 10..geriBildirim = 'Tebrikler! Doğaya katkı sağladın.',
]
];
await isar.writeTxn(() async {
await isar.senaryos.putAll(varsayilanlar);
});
}
}
Adım 4: Durum Yönetimi (duyarlilik_provider.dart)
Bu sınıf artık Isar'dan beslenecek ve arayüzü yönetecek.
// Geliştirici: Nuri TIRAŞ
// Dosya: lib/duyarlilik_provider.dart
import 'package:flutter/material.dart';
import 'isar_service.dart';
import 'senaryo.dart';
class DuyarlilikProvider with ChangeNotifier {
final IsarService _isarService = IsarService();
List<Senaryo> _senaryolar = [];
int _toplamPuan = 0;
int _aktifSenaryoIndeksi = 0;
Secenek? _secilenSecenek;
bool _oyunBitti = false;
bool _yukleniyor = true; // Veritabanı okuma durumu
List<Senaryo> get senaryolar => _senaryolar;
int get toplamPuan => _toplamPuan;
int get aktifSenaryoIndeksi => _aktifSenaryoIndeksi;
Secenek? get secilenSecenek => _secilenSecenek;
bool get oyunBitti => _oyunBitti;
bool get yukleniyor => _yukleniyor;
DuyarlilikProvider() {
_verileriYukle();
}
Future<void> _verileriYukle() async {
_yukleniyor = true;
notifyListeners();
_senaryolar = await _isarService.tumSenaryolariGetir();
_yukleniyor = false;
notifyListeners();
}
Senaryo? get aktifSenaryo {
if (_senaryolar.isEmpty) return null;
return _senaryolar[_aktifSenaryoIndeksi];
}
void secenekIsaretle(Secenek secenek) {
if (_secilenSecenek != null) return;
_secilenSecenek = secenek;
_toplamPuan += (secenek.puan ?? 0);
notifyListeners();
}
void sonrakiSenaryo() {
if (_aktifSenaryoIndeksi < _senaryolar.length - 1) {
_aktifSenaryoIndeksi++;
_secilenSecenek = null;
} else {
_oyunBitti = true;
}
notifyListeners();
}
void oyunuSifirla() {
_toplamPuan = 0;
_aktifSenaryoIndeksi = 0;
_secilenSecenek = null;
_oyunBitti = false;
_verileriYukle(); // Her ihtimale karşı güncel verileri tekrar çek
}
}
Adım 5: Ana Ekran (main.dart)
Ve son olarak kullanıcı arayüzümüz.
// Geliştirici: Nuri TIRAŞ
// Dosya: lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'duyarlilik_provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ChangeNotifierProvider(
create: (context) => DuyarlilikProvider(),
child: const DuyarlilikUygulamasi(),
),
);
}
class DuyarlilikUygulamasi extends StatelessWidget {
const DuyarlilikUygulamasi({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Isar ile Duyarlılık Simülasyonu',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueGrey),
useMaterial3: true,
),
home: const AnaEkran(),
);
}
}
class AnaEkran extends StatelessWidget {
const AnaEkran({super.key});
@override
Widget build(BuildContext context) {
final provider = context.watch<DuyarlilikProvider>();
return Scaffold(
appBar: AppBar(
title: const Text('Değerler Eğitimi'),
centerTitle: true,
backgroundColor: Colors.blueGrey[100],
),
body: provider.yukleniyor
? const Center(child: CircularProgressIndicator())
: (provider.oyunBitti ? const SonucEkrani() : const SenaryoEkrani()),
);
}
}
// Senaryo Ekranı (Öncekiyle Neredeyse Aynı)
class SenaryoEkrani extends StatelessWidget {
const SenaryoEkrani({super.key});
@override
Widget build(BuildContext context) {
final provider = context.watch<DuyarlilikProvider>();
final senaryo = provider.aktifSenaryo;
final secilenSecenek = provider.secilenSecenek;
if (senaryo == null) return const Center(child: Text('Senaryo bulunamadı.'));
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Durum ${provider.aktifSenaryoIndeksi + 1} / ${provider.senaryolar.length}',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blueGrey),
),
const SizedBox(height: 10),
Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Text(senaryo.baslik, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const Divider(),
const SizedBox(height: 10),
Text(senaryo.icerik, style: const TextStyle(fontSize: 16), textAlign: TextAlign.center),
],
),
),
),
const SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: senaryo.secenekler.length,
itemBuilder: (context, index) {
final secenek = senaryo.secenekler[index];
final isSelected = secilenSecenek == secenek;
return Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: isSelected ? Colors.blueGrey : null,
),
onPressed: secilenSecenek == null
? () => context.read<DuyarlilikProvider>().secenekIsaretle(secenek)
: null,
child: Text(
secenek.metin ?? '',
textAlign: TextAlign.center,
style: TextStyle(
color: secilenSecenek == null ? Colors.black87 : (isSelected ? Colors.white : Colors.grey),
),
),
),
);
},
),
),
if (secilenSecenek != null) ...[
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: (secilenSecenek.puan ?? 0) > 0 ? Colors.green[100] : Colors.orange[100],
borderRadius: BorderRadius.circular(10),
),
child: Text(
secilenSecenek.geriBildirim ?? '',
style: const TextStyle(fontSize: 16, fontStyle: FontStyle.italic),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.blueGrey, foregroundColor: Colors.white),
onPressed: () => context.read<DuyarlilikProvider>().sonrakiSenaryo(),
child: const Text('Sonraki Durum'),
),
],
],
),
);
}
}
// Sonuç Ekranı
class SonucEkrani extends StatelessWidget {
const SonucEkrani({super.key});
@override
Widget build(BuildContext context) {
final provider = context.watch<DuyarlilikProvider>();
final puan = provider.toplamPuan;
// Max puanı dinamik hesapla
int maxPuan = provider.senaryolar.fold(0, (toplam, s) {
int enYuksek = 0;
for(var sec in s.secenekler) {
if((sec.puan ?? 0) > enYuksek) enYuksek = sec.puan ?? 0;
}
return toplam + enYuksek;
});
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.verified, size: 100, color: Colors.blueGrey),
const SizedBox(height: 20),
const Text('Simülasyon Tamamlandı!', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Text('Toplam Puanın: $puan / $maxPuan', style: const TextStyle(fontSize: 20)),
const SizedBox(height: 40),
ElevatedButton.icon(
icon: const Icon(Icons.refresh),
label: const Text('Baştan Başla'),
onPressed: () => context.read<DuyarlilikProvider>().oyunuSifirla(),
),
],
),
),
);
}
}
Bu Mimari Öğrencilere Ne Kazandırır?
Model, Servis, Arayüz Ayrımı: Kodlar artık tek bir dosyada karmaşık bir şekilde durmuyor (MVC/Repository desenlerine harika bir giriş).
Kalıcılık (Persistence): Uygulamayı kapatıp açtıklarında senaryoların hala orada olduğunu görmek motive edicidir.
NoSQL ve Embedded Objects: Isar'ın
@embeddedyapısı sayesinde "Bir senaryonun içinde birden çok seçenek yaşar" mantığını, SQL tablolarındaki zorluJOINişlemlerine girmeden öğretebilirsiniz.
senaryoEkle fonksiyonunu kullanarak form üzerinden kendi değerler eğitimi senaryolarınızı veritabanına kaydedin."
Yorumlar
Yorum Gönder