Android uygulamaları geliştirirken veri depolama, uygulamanın temel ihtiyaçlarından biridir. Kullanıcı ayarları, uygulama içeriği veya çevrimdışı erişim gerektiren her türlü veri için güvenilir ve kalıcı bir depolama çözümü şarttır. Android, bu ihtiyacı karşılamak için hafif ve gömülü bir ilişkisel veritabanı yönetim sistemi olan SQLite’ı varsayılan olarak sunar. Bu rehberde, Android uygulamalarınızda SQLite veritabanını nasıl oluşturacağınızı, yöneteceğinizi ve CRUD (Create, Read, Update, Delete) işlemlerini nasıl gerçekleştireceğinizi adım adım öğreneceksiniz.
SQLite, sunucusuz (serverless), kendi kendine yeten (self-contained) ve sıfır yapılandırma (zero-configuration) gerektiren bir veritabanıdır. Bu özellikleri sayesinde mobil cihazlar için ideal bir seçimdir. Veritabanı tümüyle bir dosya içinde depolanır ve uygulama içinden doğrudan erişilebilir. Android SDK, SQLite veritabanlarıyla etkileşim kurmak için güçlü API’ler sağlar ve bu sayede geliştiriciler kolayca veritabanı işlemleri yapabilirler.
SQLite Veritabanı Temelleri ve Android Entegrasyonu
Android’de SQLite veritabanı ile çalışmanın merkezinde SQLiteOpenHelper sınıfı bulunur. Bu sınıf, veritabanının oluşturulması, sürüm kontrolü ve yükseltilmesi gibi karmaşık görevleri basitleştirir. Uygulamanız ilk kez çalıştırıldığında veya veritabanı sürümünü değiştirdiğinizde, SQLiteOpenHelper sizin için gerekli işlemleri otomatik olarak yönetir.
Temel Kavramlar:
- Veritabanı (Database): Verilerin depolandığı ana yapıdır. Genellikle bir
.dbdosyası olarak saklanır. - Tablo (Table): Verilerin düzenli bir şekilde saklandığı yapılardır. Her tablo, belirli bir türdeki verileri içerir (örneğin, “Kullanıcılar” tablosu, “Ürünler” tablosu).
- Sütun (Column): Tablodaki her bir veri parçasının özelliğini tanımlar (örneğin, “Kullanıcı Adı”, “E-posta”).
- Satır (Row): Tablodaki tek bir veri kaydını temsil eder (örneğin, bir kullanıcının tüm bilgileri).
- CRUD İşlemleri: Veritabanı ile etkileşim kurmanın dört temel işlemidir:
- Create (Oluşturma): Yeni veri ekleme.
- Read (Okuma): Mevcut verileri alma.
- Update (Güncelleme): Mevcut verileri değiştirme.
- Delete (Silme): Mevcut verileri kaldırma.
SQLiteOpenHelper sınıfı, veritabanı oluşturulduğunda veya yükseltildiğinde çağrılan iki önemli metot içerir:
onCreate(SQLiteDatabase db): Veritabanı ilk kez oluşturulduğunda çağrılır. Tablolarınızı oluşturmak için SQLCREATE TABLEkomutlarını burada yürütürsünüz.onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion): Veritabanı sürümü yükseltildiğinde çağrılır. Bu metot, mevcut verileri kaybetmeden tablo yapısını güncellemek için kullanılır. Genellikle eski tabloları silip yenilerini oluşturma veyaALTER TABLEkomutlarını kullanma gibi işlemler yapılır.
Uygulama Yapısı ve Veritabanı Sınıfı Oluşturma
Bir Android projesinde SQLite veritabanı ile çalışmak için öncelikle bir yardımcı sınıf oluşturmamız gerekir. Bu sınıf, SQLiteOpenHelper‘dan türetilecektir. Örnek olarak, basit bir “Yapılacaklar Listesi” uygulaması için görevleri (tasks) depolayacağımız bir veritabanı oluşturacağız.
Proje Kurulumu
Yeni bir Android Studio projesi oluşturun ve boş bir etkinlik (Empty Activity) seçin. Projeye “SQLiteTutorial” gibi bir isim verebilirsiniz.
Veritabanı Yardımcı Sınıfı Oluşturma
app/java/com.example.sqlitetutorial klasöründe DatabaseHelper.java adında yeni bir Java sınıfı oluşturun.
package com.example.sqlitetutorial;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
// Veritabanı adı ve sürümü
private static final String DATABASE_NAME = "task_manager.db";
private static final int DATABASE_VERSION = 1;
// Görevler tablosu adı ve sütunları
public static final String TABLE_TASKS = "tasks";
public static final String COLUMN_ID = "_id"; // Genellikle _id kullanılır
public static final String COLUMN_TASK_NAME = "task_name";
public static final String COLUMN_IS_COMPLETED = "is_completed"; // 0: false, 1: true
// Tablo oluşturma SQL sorgusu
private static final String CREATE_TABLE_TASKS = "CREATE TABLE " + TABLE_TASKS + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_TASK_NAME + " TEXT NOT NULL,"
+ COLUMN_IS_COMPLETED + " INTEGER DEFAULT 0"
+ ");";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_TASKS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Veritabanı sürümü yükseltildiğinde eski tabloyu silip yenisini oluştur
// Gerçek uygulamalarda ALTER TABLE kullanmak daha iyidir, veri kaybını önler
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TASKS);
onCreate(db);
}
}
Yukarıdaki kodda:
DATABASE_NAME: Uygulamanızın veritabanı dosyasının adıdır.DATABASE_VERSION: Veritabanının sürüm numarasıdır. Şema değişiklikleri yaptığınızda bu numarayı artırmanız gerekir.TABLE_TASKSveCOLUMN_...sabitleri, tablo adını ve sütun adlarını tanımlar. Bu, yazım hatalarını önlemek ve kodu daha okunabilir hale getirmek için iyi bir pratiktir.CREATE_TABLE_TASKSSQL sorgusu,tasksadında bir tablo oluşturur. Bu tablo, otomatik artan birincil anahtar (_id), görev adı (task_name) ve tamamlanma durumu (is_completed) sütunlarını içerir.onCreate()metodu, veritabanı ilk kez oluşturulduğunda tablo oluşturma sorgusunu çalıştırır.onUpgrade()metodu, veritabanı sürümü değiştiğinde çağrılır. Bu örnekte basitçe eski tabloyu silip yenisini oluşturuyoruz. Gerçek bir uygulamada, mevcut verileri korumak içinALTER TABLEgibi daha sofistike SQL komutları kullanmanız önerilir.
CRUD İşlemlerini Gerçekleştirme
Şimdi DatabaseHelper sınıfımıza görevleri eklemek, okumak, güncellemek ve silmek için metotlar ekleyelim.
1. Create (Ekleme) İşlemi
Yeni bir görev eklemek için insert() metodunu kullanırız. Bu metot, eklenecek verileri anahtar-değer çiftleri olarak tutan bir ContentValues nesnesi alır.
// ... DatabaseHelper sınıfının içine eklenecek metotlar ...
import android.content.ContentValues;
import android.database.Cursor; // Cursor'ı da import edin
public class DatabaseHelper extends SQLiteOpenHelper {
// ... (önceki kodlar) ...
/
* Yeni bir görev ekler.
* @param taskName Eklenecek görevin adı.
* @return Ekleme başarılıysa yeni satırın ID'si, başarısızsa -1.
*/
public long addTask(String taskName) {
SQLiteDatabase db = this.getWritableDatabase(); // Yazılabilir veritabanı örneği al
ContentValues values = new ContentValues();
values.put(COLUMN_TASK_NAME, taskName);
values.put(COLUMN_IS_COMPLETED, 0); // Varsayılan olarak tamamlanmamış
// Veriyi tabloya ekle
long newRowId = db.insert(TABLE_TASKS, null, values);
db.close(); // Veritabanı bağlantısını kapat
return newRowId;
}
}
2. Read (Okuma) İşlemi
Mevcut görevleri okumak için query() metodunu kullanırız. Bu metot, sorgunun sonuçlarını içeren bir Cursor nesnesi döndürür. Cursor, veritabanı sonuç kümesinde satır satır gezinmemizi sağlar.
// ... DatabaseHelper sınıfının içine eklenecek metotlar ...
/
* Tüm görevleri okur.
* @return Görevleri içeren bir Cursor.
*/
public Cursor getAllTasks() {
SQLiteDatabase db = this.getReadableDatabase(); // Okunabilir veritabanı örneği al
return db.query(
TABLE_TASKS, // Sorgulanacak tablo
new String[]{COLUMN_ID, COLUMN_TASK_NAME, COLUMN_IS_COMPLETED}, // Döndürülecek sütunlar
null, // WHERE koşulu (null tüm satırları getirir)
null, // WHERE koşulunun argümanları
null, // GROUP BY
null, // HAVING
COLUMN_ID + " ASC" // ORDER BY (ID'ye göre artan sıralama)
);
}
Pratik Bilgi: Cursor nesnesini kullandıktan sonra her zaman cursor.close() metodunu çağırarak kaynakları serbest bırakmalısınız. Aksi takdirde bellek sızıntılarına neden olabilirsiniz.
3. Update (Güncelleme) İşlemi
Mevcut bir görevi güncellemek için update() metodunu kullanırız. Bu metot da ContentValues ve bir WHERE koşulu alır.
// ... DatabaseHelper sınıfının içine eklenecek metotlar ...
/
* Bir görevin tamamlanma durumunu günceller.
* @param taskId Güncellenecek görevin ID'si.
* @param isCompleted Görevin yeni tamamlanma durumu (0 veya 1).
* @return Güncellenen satır sayısı.
*/
public int updateTaskStatus(long taskId, int isCompleted) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_IS_COMPLETED, isCompleted);
// WHERE koşulu: sadece belirli bir ID'ye sahip görevi güncelle
String selection = COLUMN_ID + " = ?";
String[] selectionArgs = { String.valueOf(taskId) };
int count = db.update(
TABLE_TASKS,
values,
selection,
selectionArgs
);
db.close();
return count;
}
4. Delete (Silme) İşlemi
Mevcut bir görevi silmek için delete() metodunu kullanırız. Bu metot sadece bir WHERE koşulu alır.
// ... DatabaseHelper sınıfının içine eklenecek metotlar ...
/
* Bir görevi siler.
* @param taskId Silinecek görevin ID'si.
* @return Silinen satır sayısı.
*/
public int deleteTask(long taskId) {
SQLiteDatabase db = this.getWritableDatabase();
// WHERE koşulu: sadece belirli bir ID'ye sahip görevi sil
String selection = COLUMN_ID + " = ?";
String[] selectionArgs = { String.valueOf(taskId) };
int deletedRows = db.delete(
TABLE_TASKS,
selection,
selectionArgs
);
db.close();
return deletedRows;
}
} // DatabaseHelper sınıfının kapanışı
Kullanıcı Arayüzü ile Entegrasyon ve Pratik İpuçları
Veritabanı işlemlerini DatabaseHelper sınıfında tanımladıktan sonra, bu metotları etkinliklerinizden (Activities) veya parçalarınızdan (Fragments) çağırabilirsiniz. Basit bir örnek olarak, MainActivity içinde bir görev ekleyip tüm görevleri listeleyelim.
MainActivity.java
package com.example.sqlitetutorial;
import androidx.appcompat.app.AppCompatActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private DatabaseHelper dbHelper;
private EditText etTaskName;
private Button btnAddTask;
private Button btnListTasks;
private TextView tvTaskList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new DatabaseHelper(this); // DatabaseHelper örneğini oluştur
etTaskName = findViewById(R.id.etTaskName);
btnAddTask = findViewById(R.id.btnAddTask);
btnListTasks = findViewById(R.id.btnListTasks);
tvTaskList = findViewById(R.id.tvTaskList);
btnAddTask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String taskName = etTaskName.getText().toString().trim();
if (!taskName.isEmpty()) {
long newRowId = dbHelper.addTask(taskName);
if (newRowId != -1) {
Toast.makeText(MainActivity.this, "Görev eklendi: " + taskName, Toast.LENGTH_SHORT).show();
etTaskName.setText(""); // EditText'i temizle
listAllTasks(); // Görevleri tekrar listele
} else {
Toast.makeText(MainActivity.this, "Görev eklenirken hata oluştu.", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(MainActivity.this, "Görev adı boş olamaz.", Toast.LENGTH_SHORT).show();
}
}
});
btnListTasks.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listAllTasks();
}
});
// Uygulama başladığında görevleri listele
listAllTasks();
}
private void listAllTasks() {
Cursor cursor = dbHelper.getAllTasks();
List taskList = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
// Sütun indekslerini al
int idIndex = cursor.getColumnIndex(DatabaseHelper.COLUMN_ID);
int nameIndex = cursor.getColumnIndex(DatabaseHelper.COLUMN_TASK_NAME);
int completedIndex = cursor.getColumnIndex(DatabaseHelper.COLUMN_IS_COMPLETED);
// Verileri al
long id = cursor.getLong(idIndex);
String name = cursor.getString(nameIndex);
int isCompleted = cursor.getInt(completedIndex);
taskList.add(id + ". " + name + " (" + (isCompleted == 1 ? "Tamamlandı" : "Bekliyor") + ")");
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close(); // Cursor'ı kapatmayı unutmayın!
}
if (taskList.isEmpty()) {
tvTaskList.setText("Henüz görev yok.");
} else {
tvTaskList.setText(String.join("\n", taskList));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Uygulama kapanırken veritabanı bağlantısını kapatmak iyi bir pratiktir.
// Ancak DatabaseHelper'daki her metot sonrası close() çağrıldığı için burada zorunlu değil.
// Eğer DatabaseHelper singleton olarak yönetilseydi, burada kapatmak daha uygun olabilirdi.
// dbHelper.close(); // Bu örnekte her işlem sonrası kapattığımız için yorum satırı.
}
}
activity_main.xml (Layout Dosyası)
Pratik İpuçları:
- Arka Plan İş Parçacıkları: Veritabanı işlemleri (özellikle okuma ve yazma) zaman alıcı olabilir ve ana iş parçacığında (UI thread) yapıldığında uygulamanızın donmasına (ANR – Application Not Responding) neden olabilir. Bu nedenle, veritabanı işlemlerini her zaman arka plan iş parçacıklarında (örneğin,
AsyncTask, Kotlin Coroutines, JavaExecutorservisleri) yapmanız şiddetle tavsiye edilir. - Singleton Modeli:
DatabaseHelpersınıfının birden fazla örneğini oluşturmak kaynak israfına ve potansiyel hatalara yol açabilir.DatabaseHelperiçin bir Singleton modeli uygulamak, uygulama genelinde tek bir veritabanı bağlantısı olmasını sağlar. - Kaynakları Kapatma:
SQLiteDatabaseveCursornesnelerini kullandıktan sonra her zamanclose()metodunu çağırarak kapatmayı unutmayın. Bu, bellek sızıntılarını ve kilitlenmeleri önler. - Hata Yönetimi: Veritabanı işlemleri sırasında oluşabilecek hataları (örneğin, veritabanı kilitlenmesi, disk dolu) uygun
try-catchblokları ile yönetin. - Veritabanı İncelemesi: Geliştirme sırasında veritabanınızın içeriğini incelemek için Android Studio’nun App Inspection aracını veya
adb shellkomutlarını kullanabilirsiniz.adb shellüzerinden uygulamanızın veritabanı dosyasını bulupsqlite3komutuyla inceleyebilirsiniz. Genellikle/data/data/com.your.package.name/databases/your_database_name.dbyolundadır.
Sonuç ve Sıkça Sorulan Sorular
Bu rehberde, Android’de SQLite veritabanı ile çalışmanın temel adımlarını öğrendik. SQLiteOpenHelper sınıfını kullanarak bir veritabanı yardımcı sınıfı oluşturduk, görevler için bir tablo tanımladık ve CRUD işlemlerini gerçekleştiren metotları yazdık. Son olarak, bu veritabanı işlemlerini bir Android etkinliğinde nasıl kullanacağımızı gösterdik ve bazı pratik ipuçları verdik.
SQLite, Android uygulamalarında yerel veri depolama için güçlü ve esnek bir çözümdür. Ancak daha büyük ve karmaşık uygulamalar için Google’ın önerdiği Room Persistence Library gibi soyutlama katmanlarını kullanmak, veritabanı işlemlerini daha güvenli, daha test edilebilir ve daha kolay yönetilebilir hale getirecektir. Room, SQLite’ın üstüne kurulmuştur ve SQL sorgularını doğrudan yazmak yerine POJO (Plain Old Java Object) ve anotasyonlar kullanarak veritabanı işlemlerini basitleştirir.
Sıkça Sorulan Sorular (SSS)
1. SQLite mı Room Persistence Library mi kullanmalıyım?
Basit ve küçük uygulamalar için doğrudan SQLite kullanmak yeterli olabilir. Ancak orta ve büyük ölçekli uygulamalar, karmaşık sorgular veya daha iyi test edilebilirlik gerektiren durumlar için Room Persistence Library’yi kullanmanız şiddetle tavsiye edilir. Room, SQLite’ın üzerine bir soyutlama katmanı ekler, derleme zamanı SQL sorgu doğrulaması, LiveData ve RxJava desteği gibi avantajlar sunar.
2. Veritabanı işlemlerini ana iş parçacığında (UI thread) yapmak güvenli midir?
Hayır, veritabanı işlemleri (özellikle yoğun okuma/yazma) ana iş parçacığında yapılmamalıdır. Bu, uygulamanızın donmasına (ANR) neden olabilir. Her zaman arka plan iş parçacıklarında (örn. Kotlin Coroutines, Java Executor, AsyncTask) yapılmalıdır.
3. Veritabanı dosyasını cihazda nerede bulabilirim?
Uygulamanızın veritabanı dosyası genellikle cihazın dahili depolamasında /data/data/com.your.package.name/databases/your_database_name.db yolunda bulunur. Bu konuma köklü (rooted) cihazlarda doğrudan veya Android Studio’nun “Device File Explorer” ya da “App Inspection” araçlarını kullanarak erişebilirsiniz.
4. Veritabanı sürümü (DATABASE_VERSION) neden önemlidir?
DATABASE_VERSION, veritabanı şemanızın (tablo yapınızın) versiyonunu belirtir. Uygulamanızın yeni bir versiyonunda veritabanı yapısını değiştirdiğinizde (yeni bir tablo ekleme, sütun ekleme/silme vb.), bu sürüm numarasını artırmanız gerekir. Sürüm numarasını artırdığınızda, SQLiteOpenHelper‘ın onUpgrade() metodu çağrılır ve bu metot içinde eski şemadan yeni şemaya geçiş mantığını uygulayabilirsiniz. Bu, mevcut kullanıcıların verilerini kaybetmeden uygulamanızı güncellemenizi sağlar.