Takip et

SQLite Veritabanı Temelleri ve Android Entegrasyonu

Android uygulamaları geliştirirken veri depolama, uygulamanın temel ihtiyaçlarından biridir. Kullanıcı ayarları, uygulama içeriği veya çe

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 .db dosyası 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 SQL CREATE TABLE komutları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 veya ALTER TABLE komutları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_TASKS ve COLUMN_... 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_TASKS SQL sorgusu, tasks adı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çin ALTER TABLE gibi 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, Java Executor servisleri) yapmanız şiddetle tavsiye edilir.
  • Singleton Modeli: DatabaseHelper sınıfının birden fazla örneğini oluşturmak kaynak israfına ve potansiyel hatalara yol açabilir. DatabaseHelper için bir Singleton modeli uygulamak, uygulama genelinde tek bir veritabanı bağlantısı olmasını sağlar.
  • Kaynakları Kapatma: SQLiteDatabase ve Cursor nesnelerini kullandıktan sonra her zaman close() 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-catch blokları 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 shell komutlarını kullanabilirsiniz. adb shell üzerinden uygulamanızın veritabanı dosyasını bulup sqlite3 komutuyla inceleyebilirsiniz. Genellikle /data/data/com.your.package.name/databases/your_database_name.db yolundadı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.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.