💽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'
}
🧱 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
}
}
👀 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()
}
🗂️ Room Database
🧱 Abstract olmak zorundadır
🏗️
Room.databaseBuilder(...)
yapısı ile db tanımlanır🏷️ Database etiketi içerisinde
entities
alanında tablo verilerini temsil eden Entity Class'ınızın objesi verilirversion
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
}
👮♂️ 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

🏗️ 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
}
*/
}
//...
}
🔗 Faydalı Bağlantılar
🚀 Bu alandaki bağlantılar YEmoji ~Bağlantılar yapısına uygundur
🎃 Kotlin
☕ Java
Last updated
Was this helpful?