💽Room Database

Android üzerinde SQLite yerine üretilmiş yeni db formatı RoomDB

🚴‍♂️ RoomDB'ye Giriş

  • 🤓 SQL komutları ile uğraşmadan direkt android kodları ile çalışmamızı sağlar

  • ✨ Optimize edilmiş bir veri tabanı sunar (LiveData)

  • 💨 Kotlin Flow yapısı ile RoomDB oluşturabilir (👨‍🔬 Deneysel)

🚀 Faydalı bağlantılara sayfanın en altından erişebilirsin

📢 Java örneği ile Kotlin örneği birbirinden bağımsızdır

🏗️ Projeye Dahil Etme

  • 🔄 Güncel RoomDB sürümüne Versions alanından erişebilirsin

  • ➕ RoomDB için Kotlin eklentilerine Room KTX alanından erişebilirsin

dependencies {
	// RoomDB
	implementation "androidx.work:work-runtime-ktx:2.3.0"
	implementation "androidx.room:room-ktx:2.2.3"
	kapt "androidx.room:room-compiler:2.2.3"
	androidTestImplementation "androidx.room:room-testing:2.2.3"
	
	// Lifecycle
	implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
	kapt "androidx.lifecycle:lifecycle-compiler:2.2.0"
	androidTestImplementation "androidx.arch.core:core-testing:2.1.0"

	// ViewModel
	implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
	
	// Livedata - Kotlin Flow
	implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
}

‍🧙‍♂ Detaylar için Declaring dependencies alanına bakabilirsin.

🧱 Temel Yapı

⭐ Entity Yapısı

  • 🧱 DB'ye aktarılacak sütun isimlerini temsil ederler

  • 🏷️ Annotation yapısı ile özellikleri belirlenir

  • 🔸 Tablodaki sütün isimleri entity üzerindeki değişkenlerle temsil edilir

  • 👮‍♂️ Primary key ve Entity etiketini eklemek zorunludur

package com.yemreak.depremya.db.entity

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.yemreak.depremya.db.entity.Quake.Companion.TABLE_NAME

/**
 * Deprem bilgileri
 * @see <a href="http://www.koeri.boun.edu.tr/scripts/lst0.asp">
 *     Son depremler `~ Kandilli Rasathanesi
 * </a>
 */
@Entity(tableName = TABLE_NAME)
data class Quake(
	@ColumnInfo(name = COLUMN_ID) @PrimaryKey(autoGenerate = true) val id: Int,
	@ColumnInfo(name = COLUMN_DATE) val date: String,
	@ColumnInfo(name = COLUMN_HOUR) val hour: String,
	@ColumnInfo(name = COLUMN_LAT) val lat: String,
	@ColumnInfo(name = COLUMN_LNG) val lng: String,
	@ColumnInfo(name = COLUMN_DEPTH) val depth: String,
	@ColumnInfo(name = COLUMN_MD) val md: String,
	@ColumnInfo(name = COLUMN_ML) val ml: String,
	@ColumnInfo(name = COLUMN_MW) val mw: String,
	@ColumnInfo(name = COLUMN_CITY) val city: String,
	@ColumnInfo(name = COLUMN_REGION) val region: String,
	@ColumnInfo(name = COLUMN_RESOLUTION) val resolution: String
) {
	
	companion object {
		
		const val TABLE_NAME = "quake"
		const val COLUMN_ID = "id"
		const val COLUMN_DATE = "date"
		const val COLUMN_HOUR = "hour"
		const val COLUMN_LAT = "lat"
		const val COLUMN_LNG = "lng"
		const val COLUMN_DEPTH = "depth"
		const val COLUMN_MD = "md"
		const val COLUMN_ML = "ml"
		const val COLUMN_MW = "mw"
		const val COLUMN_CITY = "city"
		const val COLUMN_REGION = "region"
		const val COLUMN_RESOLUTION = "resolution"
		
	}
	
	@Ignore
	override fun equals(other: Any?): Boolean {
		if (this === other) return true
		if (javaClass != other?.javaClass) return false
		
		other as Quake
		
		if (date != other.date) return false
		if (hour != other.hour) return false
		if (lat != other.lat) return false
		if (lng != other.lng) return false
		if (depth != other.depth) return false
		if (md != other.md) return false
		if (ml != other.ml) return false
		if (mw != other.mw) return false
		if (city != other.city) return false
		if (region != other.region) return false
		if (resolution != other.resolution) return false
		
		return true
	}

}

👀 Daha fazlası için Entity ve Defining data using Room entities dokümanlarına bakabilirsin.

👀 Entity Hakkında Bir Kaç Detay

  • 💡 SQL yapısında veriler 64 bit olduğundan:

  • 🧮 32bit long değeri 64bit int değerine eş değerdir

  • 🔄 id değerlerini long olarak tutsanız da android onu int olarak tanımlanacaktır

  • 🏹 Veri tabanına eklenen verilerin id bilgileri long olarak döndürülür

🛳️ DAO Yapısı

  • 🐣 Tablolara erişmek için kullanılan yapıdır

  • 🧱 Abstract veya Interface olmak zorundadır

  • 🏷️ SQLite query metinleri metotlara Annotation yapısı ile tanımlanır

  • ✨ LiveData yapısı ile güncel verileri döndürür

📢 SQLite ile SQL Server syntax yapısı buradaki kaynağa göre farklı olabilmekte

package com.yemreak.depremya.db.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.yemreak.depremya.db.entity.Quake
import kotlinx.coroutines.flow.Flow


@Dao
abstract class QuakeDao {
	
	@Insert
	abstract suspend fun insertAll(quakes: Array<out Quake>)
	
	@Query("SELECT * FROM ${Quake.TABLE_NAME}")
	abstract fun getAll(): Flow<List<Quake>>
	
	@Query("SELECT * FROM ${Quake.TABLE_NAME} WHERE ${Quake.COLUMN_MD} > :md")
	abstract fun getAllHigherMd(md: Float): Flow<List<Quake>>
	
	@Query("DELETE FROM ${Quake.TABLE_NAME}")
	abstract suspend fun deleteAll()
	
}

👀 Daha fazlası için The DAO (data access object) dokümanına bakabilirsin.

🗂️ Room Database

  • 🧱 Abstract olmak zorundadır

  • 🏗️ Room.databaseBuilder(...) yapısı ile db tanımlanır

  • 🏷️ Database etiketi içerisinde

    • entitiesalanında tablo verilerini temsil eden Entity Class'ınızın objesi verilir

    • version alanında db'nin en son sürümünü belirtin

    • 🐛 Versiyon geçişleri arasındaki sorunları engellemek için fallbackToDestructiveMigration() özelliği eklenir

package com.yemreak.depremya.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.yemreak.depremya.db.dao.QuakeDao
import com.yemreak.depremya.db.entity.Quake

@Database(entities = [Quake::class], version = 1, exportSchema = false)
abstract class QuakeRoom : RoomDatabase() {
	
	companion object {
		
		const val DB_NAME = "quake_db"
		
		/**
		 * Singleton yapısı ile birden fazla örneğin oluşmasını engelleme
		 */
		@Volatile
		private var INSTANCE: QuakeRoom? = null
		
		fun getDatabase(context: Context): QuakeRoom {
			return when (val tempInstance = INSTANCE) {
				null -> synchronized(this) {
					val instance = Room.databaseBuilder(
						context,
						QuakeRoom::class.java,
						DB_NAME
					).fallbackToDestructiveMigration().build()
					INSTANCE = instance
					
					return instance
				}
				else -> tempInstance
			}
		}
	}
	
	abstract fun quakeDao(): QuakeDao
}

👀 Daha fazlası için Room database dokümanına bakabilirsin.

👮‍♂️ DB'yi Koruma

  • ‍🚫 Veri tabanına birden çok istek gelmesini engeller

  • 🐞 Birden çok isteğin eş zamanlı yapılmaya çalışması conflict oluşturacaktır

  • 💔 Conflict yapısı veri tabanındaki verilerin uyuşmazlığını belirtir

  • Birden fazla Thread gelmesi durumunda engellemek için synchronized anahtar kelimesi kullanılır

  • ✨ Gereksiz Thread engelinden sakınmak için, synchronized yapısı içerisinde tekrardan if kontrolü yapılmalıdır

👀 Detaylar için Multi-threading alanına bakabilirsin.

🏗️ Repository Yapısı

  • 🌃 Alt katmanda olan tüm sınıfları tek bir sınıfmış gibi gösterir

    • 😏 Bu sayede ViewModel üzerinden birden fazla sınıfla uğraşmak zorunda kalmayız

    • 🚧 DB üzerinde yapılacak olan tüm işlemlerinde burada metot olarak tanımlanması lazımdır

  • LiveData yapısı sayesinde verileri otomatik günceller

    • 🦄 Verilerin aktarımı bir defaya mahsus Constructor üzerinde yapılır

  • 🌠 Verilerin aktarılması asenkron olması gerektiğinden AsyncTask yapısı kullanılır

package com.yemreak.depremya

import com.yemreak.depremya.db.dao.QuakeDao
import com.yemreak.depremya.db.entity.Quake
import kotlinx.coroutines.flow.Flow

class QuakeRepository(private val quakeDao: QuakeDao) {
	
	val allQuakes: Flow<List<Quake>> = quakeDao.getAll()
	
	suspend fun insert(quakes: Array<out Quake>) {
		quakeDao.insertAll(quakes)
	}
	
	suspend fun deleteAll() {
		quakeDao.deleteAll()
	}
	
}

🛍️ ViewModel

  • 🧱 Yapılandırma değişikliklerine karşı dayanıklıdır

  • 🐣 Repository ile DB'ye erişir

  • 🎳 Activity context objesi gönderilmez, çok maliyetlidir

  • 🥚 Context verisi miras alınmalıdır

  • 📝 UI ile alakalı bilgilerin kaydı ile uğraşır

package com.yemreak.depremya.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.yemreak.depremya.QuakeRepository
import com.yemreak.depremya.db.QuakeRoom
import com.yemreak.depremya.db.entity.Quake
import kotlinx.coroutines.launch

class QuakeViewModel(application: Application) : AndroidViewModel(application) {
	
	private val repository: QuakeRepository
	
	val allQuakes: LiveData<List<Quake>>
	
	init {
		val quakeDao = QuakeRoom.getDatabase(application.applicationContext).quakeDao()
		repository = QuakeRepository(quakeDao)
		allQuakes = repository.allQuakes.asLiveData()
	}
	
	// UI threadi bloklamadan çalışır (viewModelScope)
	fun refreshQuakes(quakes: List<Quake>) = viewModelScope.launch {
		repository.deleteAll()
		repository.insert(quakes.toTypedArray())
	}
}

✨ LiveData

  • 🔄 Verileri güncel tutmak için kullanılır

  • 📈 Performansı artırır

  • 🧱 Yapılandırma değişikliklerine karşı dayanıklıdır

    • 📳 Telefonu çevirme vs.

  • 🍱 Tüm katmanlardaki metotlar kapsüllenmelidir

🚀 Main Activity

class MainActivity : AppCompatActivity() {
	
	private var quakes: List<Quake> = emptyList()
	private var selectedMag: Int = 0
	
	private lateinit var quakeViewModel: QuakeViewModel
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		
		setContentView(R.layout.activity_main)
		// ...
		
		quakeViewModel = ViewModelProvider(this).get(QuakeViewModel::class.java)
		quakeViewModel.allQuakes.observe(this, Observer {
			it?.let {
				quakes = it
				/*
				// Varsa recycleview objesine aktarılır
				(quake_recycler_view.adapter as QuakeAdapter).setQuakesAndNotify(quakes)
				*/
			}
		})
		
		/*
		// İsteğe bağlı refresh layout kullanımı
		quake_refresh_layout.setOnRefreshListener {
			// ...
			quake_refresh_layout.isRefreshing = false
		}
		*/
		
	}
	
	//...
	
}

‍🧙‍♂ Detaylar için RecycleView alanına bakabilirsiniz.

🔗 Faydalı Bağlantılar

🚀 Bu alandaki bağlantılar YEmoji ~Bağlantılar yapısına uygundur

🎃 Kotlin

☕ Java

Last updated

© 2024 ~ Yunus Emre Ak ~ yEmreAk