Local Data Storage with Flutter
Herkese Selamlar,
Bu yazımda mobil uygulamaların olmazsa olmazı veri depolama işlemlerinden bahsedeceğim. Kullandığım teknolojileri bol teknik kod dahilinde sizlere aktarmaya çalışacağım. Keyifli okumalar dilerim.
Anlatacağım konuların başlıkları ile başlayalım🔥
- Path Provider
- Shared Prefeferences
- Secure Storage
- Hive
- SQLite
- Floor
görüldüğü gibi harika 6 adet konu başlığımız bulunuyor. Haydi başlayalım..
Path Provider
Bu oluşturduğum başlıklar aslında pub.dev içerisinde bulunan paket isimleridir. Flutter ile direkt olarak dosya sistemine erişemiyoruz. Bu yüzden bu tarz plugin ve paketlerden yararlanıyoruz. Bu paketi de Flutter ekibi geliştirmektedir.
Bu paketin genel amacı dosya sistemine erişmek, veri yazıp , veri okumaktır. Bu paket ile internet üzerinden pdf,mp4 dosyaları indirebilirsiniz. Biz ise bu paket ile dosya dizininde bir txt dosyayı oluşturup bunun içine veri yazmayı ve yazdığımız veriyi okumayı göreceğiz.
Lets code🔥
İlk olarak paket ekleyerek başlayalım. Vs code kullanıyorsanız benim çok beğendiğim bir paket ekleme yöntemi var. Sizlerle de bunu paylaşmak istiyorum. Editör üzerinde ctrl+shift+p
tuşlarına basın.
Burada Dart:Add Dependency
kısmını seçiyoruz.
Burada istediğimiz paketin adını seçiyoruz ve paketi pubspec.yaml
içerisine ekliyor o da yetmezmiş gibi flutter pub get
komutunu da çalıştırıyor ve paket kullanıma hazır hale geliyor. 🚀
Android Studio veya IntelliJ kullananlar için bilindik yöntemler ile paketi projeye dahil ediyoruz.
dependencies:
path_provider: ^2.0.9
flutter pub get
komutunu çalıştırınız.
Path provider ile platform bağımsız bir şekilde dosya dizinine erişebiliyoruz. Android ve IOS için 2 farklı kısıma da erişim sağlanabiliyor. Temporary Directory
cache olarak da bildiğimiz önbellek dosyalarının bulunduğu kısım diyebiliriz. Document Directory
ise yalnızca uygulamanın erişebildiği dizindir. Biz Document Directory
`e veri yazacağız.
Path provider ile dosyaya veri yazarken ve okurken 4 adımda ilerliyoruz.
1- Dosyayı yazacağımız yerel dizine ulaşmak
2- Dosyanın bulunduğu kısımdan bir referans oluşturmak
3- Veriyi yazmak
4- Yazdığımız veriyi okumak
Bu şekilde yerel dizine ulaşıyoruz. directory.path
bize dosya yolunu veriyor.
Bu kısımda ise yukarıda oluşturduğumuz fonksiyonu da kulanarak o dosya dizininde bize bir referans oluşturuyoruz. Bu referansı kullanarak dosyaya yazma ve okuma işlemlerini gerçekleştireceğiz.
Burada kullandığımız
File
veDirectory
sınıflarıdart:io
kitaplığına ait sınıflardır.
Buradaki örnekte Textfield
üzerinden aldığım String
değeri dosyaya yazıyoruz.
Asenkron işlemler yaptığımız yerlerde
try-catch
bloklarını kullanıyoruz.
Son olarak da bu yazığımız veriyi okumalıyız.
Okuma işlemini gerçekleştirirken yine referansımızı kullanarak okuyoruz ve okuduğum değeri de ekranda Text
widget’a yazdırıyorum. Örneğin tamamını görmeniz için github reposuna tıklayınız.
Shared Prefeferences
Bu konunun en çok kullanılan paketlerinden biri ile devam ediyoruz. Gördüğüm bir çok projede bu yapıya yer veriliyor. Basit verileri key-value
çiftleri olarak kaydediyor.
int, double, String ,bool , List<String> tipinde verileri saklar.
Örnek vermek gerekirse bir onboard ekranımız mevcut ve biz bunu sadece uygulama ilk açıldığında göstermek istiyoruz. Bunu uygulamanın ilk kez açıldığını bizim bir yerde tutmamız gerekiyor. Firebase veya database tarafında bunu tutmamız çok doğru olmaz bunun yerine SharedPreferences
içerisinde tutmamız daha kolay olacaktır.
Uygulama için kritik verilerin saklanması önerilmez.⚠️
Platformlara bağlı olarak kayıt ettiği lokasyonlar şu şekilde;
Android-Shared Preferences
IOS-NSUserDefaults
Web-Local Storage
Windows- AppData Directory
Native olarak bu alanlarda çalışmış kişiler için bu lokasyonlar pek tanıdık gelecektir.
Shared Preferences için sevdiğim bir kullanım yöntemi mevcut ve bunu sizinle de paylaşmak istiyorum. İlk olarak Helper yazmamız gerekiyor. Ben helper diyorum fakat siz bunu manager olarak da duymuş olabilirsiniz veya util olarak da duymuş olabilirsiniz.
Bu yapıyı üzerinde anlatılacak çok güzel konular bulunuyor. İlk olarak Singleton pattern kullanarak bu sınıfı oluşturdum. Peki nedir bu singleton pattern?
Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
Refactoring.guru sitesinden alıntıladığım bu cümlede de dediği gibi oluşturduğumuz sınıfın tek bir erişim noktası olmasını istiyorsak ve bu sınıfı birçok yerde kullanacaksak yapmamız gereken şey tam olarak budur. Ayrıntı için inceleyiniz.
Shared Preferences kullanımına gelirsek bu sınıfın içerisinde getInstance()
metodunu çağırmamız gerekiyor ve geri dönen Shared Preferences nesnesi ile de işlemlerimizi yapabiliriz. Bu metodu da Helper sınıfının isimlendirilmiş kurucusunda(named constructor) çağırıyoruz.
Sırada ise kayıt edilecek ve okunacak veriler için bize uygun fonksiyonları yazmaya.. Bunun için bir interface yazıp bu fonksiyonları içerinde belirtip Helper sınıfına kalıtım verebiliriz veya bu şekilde de kullanabiliriz. Bu örneği de yine github reposu üzerinden iceleyebilirsiniz.
Secure Storage
Secure Storage , Shared ile çok benzer yapıdadır. Adından da anlaşıldığı gibi güvenlidir. Şifreleme ile verileri saklar. IOS için Keychain serivisini, Android için ise Keystore servisini kullanır. Secure storage 5.0.0 ile birlikte Android için EncryptedSharedPreferences özelliğininde kullanabiliyoruz.
Şimdi paketi projemize dahil edelim.
dependencies:
flutter_secure_storage: ^5.0.2
flutter pub get
komutunu çalıştırınız.Keystore Android 4.3(API 18)’den önceki sürümler için çalışmaz.
Bunun için proje dizini içinde android/app/build.gradle
içerisinde bulunan minSdkVersion
18 olmalıdır.
android {
...
defaultConfig {
...
minSdkVersion 18
...
}
}
minSdkVersion’u 18 yaptıktan sonra projeyi tekrar başlatmayı unutmayınız.
Linux ve Web için farklı configurasyonlar da bulunmaktadır. Daha fazla detay için paket sayfasına gidiniz.
Secure Storage
için helper sınıfımızı yazıyoruz. Shared Preferences
’a benzer bir yapı kuracağız.
Secure Storage için FlutterSecureStorage
‘den nesne üretiyoruz ve bunu yine sınıfımızın isimlendirilmiş kurucusu(named constructor) içerisinde yapıyoruz.
Secure storage içerisinde sadece String veriler tutuluyor.
Kullanımları anlatırken sadece okuma ve yazma işlemlerinden bahsediyorum fakat silme, hepsini silme, hepsini okuma gibi fonksiyonlar da bulunuyor. Bunları da deneyerek öğrenebilir ve kullanabilirsiniz. 🤞
Örnek kullanımları github reposu üzerinden incelemeyi unutmayınız.
Hive
Yukarda anlattığım yapıları veri saklama olarak düşünebiliriz. Hive’e geldiğimizde ise daha çok database gibi düşünebiliriz. NoSQL olan Hive key-value
çiftlerini tutmaktadır.
Öne çıkaran özelliklerden birisi ise saf Dart dili ile yazılmış olmasıdır. Bu yüzden hafif ve hızlı bir veritabanıdır.
Hive sadece bir değil birçok paketten oluşmaktadır. Eğer Dart uygulaması yapıyorsanız hive, Flutter uygulaması yapıyorsanız hive_flutter ve yardımcı paket olarakta hive_generator’ı kullabilirsiniz.
bağımlılıklarımızı pubspec.yaml
içerisine ekleyelim.
dependencies:
hive_flutter: ^1.1.0dev_dependencies:
hive_generator: ^1.1.2
build_runner: ^2.1.7
flutter pub get
komutunu çalıştırınız.Hive int, String, Map, Datetime gibi primitive tipteki verileri saklayabilir.
Hive kullanmadan önce init fonksiyonunu çağırmamız gerekmektedir.
Bunun için Helper metodu içierisinde kurucu içerisinde bu fonksiyonu çağırıyoruz.
await Hive.initFlutter('/hive');
Burada fonksiyon içinde verdiğimiz String parametre zorunlu alan değildir. Buraya dosya dizini içinde yeni dosya yolu vermek için kullanıyoruz.
İnit fonksiyonunu çağıdıktan sonra box
yapılarına geçelim. Veritabanına veri yazmak için box
açmamız gerekmektedir. Bunu SQL veritabanında bulunan tablo gibi düşünebiliriz. box
yapılarında generic olarak içerisine hangi türde veri geleceğini belirtebiliyoruz.
Helper sınıfı içerisinde openBox()
fonksiyonunu yazıyoruz. Açtığımız box ile işlemler yapmamız gerekiyor ve bunun için helper içinde initialBox
adından bir box
nesnesi oluşturuyorum ve changeInitialBox()
fonksiyonu ile de açığımız box
‘ı initialBox
nesnesi içine atıyoruz ve bu box
için yapılacak işlemleri de helper sınıfı içerisine yazalım.
Eğer box
ile başka bir işiniz kalmadıysa onu close()
metodu ile kapatın.
Yukarıdaki helper sınıfını detaylı inceleyerek daha ayrıntılı anlayabiliriz. Hive içinde verileri kaydederken Shared Preferences
‘de olduğu gibi tipe göre kaydetmiyoruz, put()
metodu dynamic
değerler alabilmektedir ama bu şekilde kullanmak, tipe göre fonksiyon yazmak işimizi kolaylaştıracaktır.
Bu sınıfı kullanarak bir örnek gerçekleştirelim.
Bu şekilde çok basit bir tasarım yaptım. İsterseniz siz daha güzel ekranlar hazırlayıp orada da deneyebilirsiniz.
Stateful widget oluşturuyoruz ve sayfanın tasarımını yapıyoruz.
Textfield, ElevatedButton ve sonrasında da bir Divider ekliyoruz. Tekrar ElevatedButton ve Text widgetlarını sırası ile bir Column içinde yerleştiriyoruz.
Write
butonu ile Textfield
içerisinde girilen String
değeri açtığımız bir box
içerisine kaydediyoruz ve Read
butonu ile de veriyi aşağıda bulunan Text
içine yerleştiriyoruz.
late final HiveHelper hiveHelper;late final TextEditingController tfController;String data = '';
Bu tanımlamaları yapıyoruz ve initState()
fonksiyonu içerisinde de atamalarını yapıyoruz.
@overridevoid initState() {hiveHelper = HiveHelper.instance;tfController = TextEditingController();super.initState();}
Şimdi ekranımızı yazalım. Scaffold içinde body kısmına geliyoruz.
Write
butonu içinde “names” isimli box
nesnesini açıyoruz ve initialBox
olarak bu box
‘ı seçiyoruz. Şimdi de Textfield
içerisinden gelecek olan değeri key
değeri ile kaydediyoruz.
Read
butonunda ise verdiğimiz key
ile kayıt edilen veriyi alıyoruz ve ekrana yazdırıyoruz.
Yazdığımız veriyi Text
içinde görüntüledik.🎉
Hive için çok fazla anlatılacak başlık bulunuyor fakat daha fazla ayrıntıya inerek bu kısımı çok uzatmak istemiyorum. Ayrıntılı bilgi için hive dökümanını incelemenizde fayda var.
SQLite
SQLite Flutter ile kullanabileceğimiz SQL özelliklerini de içerisinde barındıran bir veritabanıdır. Insert, query, update, delete sorguları yapabilmekteyiz. Hızlı ve güvenilirdir. Android , IOS ve MacOS desteklemektedir.
Güncel olarak 20.02.2022 tarihinde web desteği bulunmamaktadır.
Daha önce SQL dili kullandıysanız bildiğimiz sorgular basit bir proje geliştireceğiz. Haydi paketimizi dahil edelim.
dependencies:
sqflite: ^2.0.2
path: ^1.8.1
flutter pub get
komutunu çalıştırınız.
Path paketini de helper sınıfımız içerisinde join()
metodu için kullanıyoruz.
import 'dart:async';import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
sonrasında Helper sınıfımızın bulunduğu klasöre importlarımızı ekliyoruz.
Şimdi yapacağımız örnekten biraz konuşalım. Student adında bir modelimiz olacak ve biz Student modeli için ad, okul adı ve yaş bilgisini alıp veritabanına kayıt edeceğiz. Kayıt ettikten sonrada tekrardan kayıtlı olan veriyi çekip kontrolleri sağlayacağız.
Student sınıfımızı aşağıda göründüğü gibi oluşturalım.
toMap
fonksiyonu ve fromMap
kurucu fonksiyonu ile modelimizi veritabanına kayıt edebileceğimiz Map<>
formatına dönüştürmekteyiz.
Factory constructor hakkında detaylı bilgi için linke tıklayınız.
toString()
fonksiyonu ile de sınıfımızı String
türünde yazdırabilmekteyiz.
Şimdi de Student sınıfımız için Helper sınıfı yazalım ve kullanımı kolaylaştıralım.
Buradaki kodları biraz detaylandıralım. İlk olarak yine Singleton bir sınıf oluşturduk ve kurucu fonksiyon içerisinde _init()
fonksiyonu yazdık. Burada bunu yapmamın amacı kurucu fonksiyon içerisinde await
işlemini gerçekleştiremiyoruz. Bunun için _init()
içerisinde openDatabase()
fonksiyonunu çağırdık.
openDatabase()
içerisinde String
dosya uzantısı , ve onCreate()
fonksiyonlarını kullandık. Path paketini burada kullanıyoruz.
Join
fonksiyonu verilenString
değerleri birleştirmektedir.
onCreate()
fonksiyonu ile veritabanımızda SQL sorguları ile tablomuzu oluşturduk.
SQLite içerisinde veri tipleri INTEGER, TEXT, REAL, NULL’dır.
Veritabanı hazır olduğuna göre insert, select, update ve delete fonksiyonlarını hazırlayalım.
Şimdi örneğimizi hazırlayalım. Öreğimizde 3 adet Textfield
içerisinden ad, okul adı, yaş bilgilerini alıp ElevatedButton
yardımı ile veritabanına kayıt edeceğiz. Kayıt ettiğimiz verileri ElevatedButton
ile çekip Snackbar
ile ekranda göstereceğiz.
Bu şeklilde yine basit bir ekran tasarlıyoruz ve anlattığım kurgu ile bu işlemleri yapmaya çalışınız. Yapamadığınız yerde github reposu içinde proje ile karşılaştırmalar yapabilirsiniz.
Kayıt edip, tekrar okuduktan sonra da şu şekilde bir görüntü ortaya çıkmaktadır.
Sqlite için daha fazla detaylı bilgiye de şu kaynaklardan ulaşabilirsiniz.
Floor
Floor Flutter için geliştirilmiş bir SQLite kütüphanesidir. Eğer bir modeli veritabanına kaydetmek istiyorsanız Floor bu iş için uygunudur. Native Android geliştirmesi yaptıysanız Room ile benzer mantığa sahiptir.
Floor, Entity
DAO
ve Database
yapılarından oluşmaktadır. Entity
modellerimizi kapsar. DAO
ise modellerimize erişimi sağlar(Database Access Object). Database
ise verilerimizi sakladığımız yer olarak düşünebiliriz.
Önce paketlerimizi pubspec.yaml
içerisine ekleyelim.
dependencies:
floor: ^1.2.0dev_dependencies:
floor_generator: ^1.2.0
build_runner: ^2.1.7
flutter pub get
komutunu çalıştırınız.
Geliştireceğimiz uygulamada çalışan sınıfı olacak. Bu sınıfı veritabanına kayıt etmeyi ve kayıtlı verileri çekmeyi anlatacağım.
Şimdi çalışan sınıfımızı oluşturalım.
Çalışanların isim ve cinsiyet tiplerini tutacağız. Farklı bir kullanımı göstermek için cinsiyeti enum tipinde tanımladım. Floor içerisinde enum tipinde veriler tutamayız bunun için enum’ın index değerini int olarak tutabiliriz. Şimdi bu sınıfı nasıl Entity
işaretliyebileceğimizi göstereyim.
Burada belirli annotationlar kullanıyoruz. @entity
sınıfı işaretler.
@PrimaryKey
ise sınıfın primaryKey
‘ini işaretler ve otomatik arttırabiliyoruz.
@ignore
ile de veritabanına kaydedemeyeceğimiz tipleri işaretleriz.
Enum, Datetime gibi yapılar veritabanında saklanamaz.
Sınıfımız veritabanına kayıt edilmek için hazırlandı. Bu sınıf için Dao
‘yu oluşturalım.
Dao
içerisinde sınıf ile ilgili yapacağımıc CRUD fonksiyonlarını yazıyoruz. Query yazıldığı gibi delete
, insert
, update
gibi annotationlar da vardır.
Future fonksiyonlar ile bunları yaparken Stream ile de sürekli verileri dinleyebiliyoruz.
Sırada Database’i oluşturmak var. Database oluşurken build runner’ı çalıştırıp code generation yapacağız. Haydi hazırlayalım..
Database için dart dosyası açtıktan sonra bu import içeriğini sayfanın en üstüne ekleyin.
part satırında proje hata verecektir ama sıkntı yok. Part içinde database yazan yeri kendi database.dart dosyanızın ismi ile değiştirmeyi unutmayınız.
Bu eklemelerden sonra Database sınıfımızı oluşturalım.
Database’i olşutururken yine @Database
annotation
`u ile işaretliyoruz. Bu işaretlemede version için 1 değerini giriyoruz. entities içine ise Entitiy
olarak işaretlediğimiz sınıfları ekliyoruz.
Database içine başka Dao’ları da ekleyebiliriz.
Sırada build runner ile code generation yapmamız gerekiyor. Bunun için hazır bir kod bulunuyor.
flutter pub run build_runner build --delete-conflicting-outputs
Kendi IDE terminalinize bu kodu yapıştırıp,çalıştırınız.
Build runner çalıştığında çok fazla sorunla karşılaşabilirsiniz :) isimlendirmeleri, annotationları, importları eksiksiz yaptığınızıdan emin olun.
Yapılacak son bir adım kaldı. Database’i oluşturmak için bir helper sınıfı yazacağız. Database nesnesine bu sınıfın instance’ı üzerinden erişeceğiz.
Burada oluşturduğumuz database yapısına erişimek için LocalDatabase
tipinde nesnemiz bulunuyor. Bu nesneyi Helper snıfının constructor’ında dolduruyoruz. Bu işlem Sqlite içinde bulunana openDatabase
fonksiyonuna karşılık gelmektedir. Bu işlemi de tamamladıktan sonra geriye ekran tasarımlarını da halledip, deneyimlemek kalıyor.
Ekranda sayfanın üzerinde kayıt alanı ve sayfanın altında Listview
içerisinde verilerin gösterildiği bir alan bulunacak. Verileri kayıt edip, Listview
içerisinde görmeyi bekliyoruz.
Burada Save butonu ile çalışan kayıt ediyoruz. Read butonu ile de kayıttaki verileri çekiyoruz.
Burada getEmployee()
fonkisyonu ile verileri çekiyoruz.
Scaffold içinde body kısmını aşağıda bulunan kodlar ile doldurunuz.
Floor ile ilgili anlataklarım bu kadardı. Ayrıntılı bilgi için linke tıklayınız.
Ekranların çiziminin ayrınıtısı için github reposunu incelemeyi unutmayınız.
Umarım anlaşılır ve güzel bir içerik olmuştur. Sonraki yazılarda görüşmek üzere.. 💙