![]() |
إتقان Room وHilt: أدوات فعالة لقواعد البيانات في Android |
إتقان التعامل مع قواعد البيانات في Android باستخدام Room و Hilt بسهولة
في عالم تطوير تطبيقات Android، تُعتبر إدارة قواعد البيانات من التحديات الأساسية التي تواجه المطورين. بينما يوفر SQLite حلاً تقليدياً، إلا أن استخدامه مباشرةً قد يكون معقدًا ويستهلك الوقت. هنا يأتي دور مكتبة Room كجزء من Android Jetpack، لتُبسِّط التعامل مع قواعد البيانات، وتُقدِّم طبقة تجريدية تحمي من الأخطاء الشائعة. أما Hilt، فهو إطار عمل يُسهِّل حقن التبعيات (Dependency Injection) لتنظيم الكود وجعله أكثر كفاءة.
في هذا المقال، سنستعرض كيفية دمج Room وHilt لإنشاء قواعد بيانات قوية ومُحسَّنة في تطبيقات Android، مع خطوات عملية وأمثلة واضحة.
المكتبات اللازمه للتعامل مع البيانات المحليه room في jetpack
[versions]
roomKtx = "2.6.1"
roomCompiler = "2.6.1"
[libraries]
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
// imp
implementation(libs.androidx.room.ktx)
kapt(libs.androidx.room.compiler)
إنشاء Model للبيانات التي يتم تخزينها داخل Room
@Entity
data class RoomDataModel (
val name: String,
val description: String,
val dateAdded : Long,
@PrimaryKey(autoGenerate = true)
val id:Int = 0
)
إنشاء Model للبيانات التي يتم تخزينها داخل Room
@Entity
data class RoomDataModel (
val name: String,
val description: String,
val dateAdded : Long,
@PrimaryKey(autoGenerate = true)
val id:Int = 0
)
@Entity: يشير إلى أن هذه الفئة تمثل جدولاً في قاعدة البيانات. تصبح كل سمة في هذه الفئة عمودًا في الجدول.
RoomDataModel: هو كيان بيانات (نموذج) يحتوي على الأعمدة التالية:
dateAdded: تاريخ إضافة التعليق التوضيحي باعتباره طويلًا.
id: يتم إنشاء المفتاح الأساسي (@PrimaryKey) تلقائيًا (autoGenerate = true).
تنفيذ الاستعلامات للتعامل مع البيانات في Room - Data Access Object
@Dao
interface RoomDao {
// @Insert
@Upsert
suspend fun upsert(roomDataModel: RoomDataModel)
@Delete
suspend fun delete(roomDataModel: RoomDataModel)
@Query("SELECT * FROM RoomDataModel ORDER BY dateAdded")
fun getDataFromRoomByDate(): Flow<List<RoomDataModel>> // إزالة suspend
@Query("SELECT * FROM RoomDataModel ORDER BY name ASC")
fun getDataFromRoomByTitle(): Flow<List<RoomDataModel>> // إزالة suspend
}
كود قاعدة البيانات
@Database(
entities = [RoomDataModel::class],
version = 1
)
abstract class NotesDataBase : RoomDatabase() {
abstract val dao: RoomDao
}
الحالات التي سوف نتعامل معها في ViewModel
data class RoomState(
val notes: List<RoomDataModel> = emptyList(),
val title: MutableState<String> = mutableStateOf(""),
val description: MutableState<String> = mutableStateOf("")
)
التعامل مع الاحداث التي تحدث في Viewmodel
sealed interface RoomEvents {
object SortNote: RoomEvents
data class DeleteNote(val note: RoomDataModel): RoomEvents
data class SaveNote(val title:String, val description:String): RoomEvents
}
إنشاء ViewModel للتعامل مع البيانات
@HiltViewModel
class RoomViewModel @Inject constructor(
private val dao: RoomDao
) : ViewModel() {
private val isSortedByDateAdded = MutableStateFlow(true)
private var notes = isSortedByDateAdded.flatMapLatest { sort ->
if (sort) {
dao.getDataFromRoomByDate()
} else {
dao.getDataFromRoomByTitle()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
}
val _state = MutableStateFlow(RoomState())
val state = combine(_state, isSortedByDateAdded, notes) { state, isSortedByDateAdded, notes ->
state.copy(notes = notes)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(500), RoomState())
fun clearText() {
_state.update { currentState ->
currentState.copy(
title = mutableStateOf(""),
description = mutableStateOf("")
)
}
}
fun onEvent(event: RoomEvents) {
when (event) {
is RoomEvents.DeleteNote -> {
viewModelScope.launch {
dao.delete(event.note)
}
}
is RoomEvents.SaveNote -> {
val note = RoomDataModel(
name = state.value.title.value,
description = state.value.description.value,
dateAdded = System.currentTimeMillis()
)
viewModelScope.launch {
dao.upsert(note)
}
clearText()
}
RoomEvents.SortNote -> {
isSortedByDateAdded.value = !isSortedByDateAdded.value
}
}
}
}
كود صفحة عرض البيانات
@Composable
fun RoomScreen(
navController: NavHostController,
viewModel: RoomViewModel = hiltViewModel()
) {
val state = viewModel.state.collectAsState()
Scaffold(
topBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(MaterialTheme.colorScheme.onBackground)
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Room Database", modifier = Modifier.weight(1f))
IconButton(onClick = {
viewModel.onEvent(RoomEvents.SortNote)
}) {
Icon(Icons.Rounded.Build, contentDescription = "menu", modifier = Modifier)
}
}
},
floatingActionButton = {
FloatingActionButton(onClick = {
viewModel.clearText()
navController.navigate(Routes.AddTextScreenScreen)
}) {
Icon(Icons.Default.Add, contentDescription = "Add Note")
}
}
) { paddingValues ->
LazyColumn(
contentPadding = paddingValues,
modifier = Modifier
.fillMaxSize(1f)
.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
items(state.value.notes) { note ->
NoteItem(
onClick = { viewModel.onEvent(RoomEvents.DeleteNote(note)) },
note= note)
}
}
}
}
@Composable
fun NoteItem(onClick: () -> Unit, note: RoomDataModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(15.dp))
.background(MaterialTheme.colorScheme.onBackground.copy().copy(alpha = 0.5f))
.padding(15.dp)
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = note.name,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
10.spacerHeight()
Text(
text = note.description,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
IconButton(onClick = {
onClick()
}) {
Icon(
Icons.Rounded.Delete,
contentDescription = "delete",
modifier = Modifier.size(35.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
كود لصفحة اضافة البيانات
@Composable
fun AddNoteScreen(
navController: NavHostController,
viewModel: RoomViewModel = hiltViewModel()
) {
val state = viewModel.state.collectAsState()
Scaffold(
topBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(MaterialTheme.colorScheme.onBackground)
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Add Note", modifier = Modifier.weight(1f))
IconButton(onClick = {
navController.navigateUp()
}) {
Icon(Icons.Rounded.Build, contentDescription = "menu", modifier = Modifier)
}
}
},
floatingActionButton = {
FloatingActionButton(onClick = {
viewModel.onEvent(RoomEvents.SaveNote(state.value.title.value,state.value.description.value))
navController.navigateUp()
}) {
Icon(Icons.Default.Add, contentDescription = "Add Note")
}
}
){ pv ->
Column(
modifier = Modifier
.padding(pv)
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
TextField(
modifier = Modifier.fillMaxWidth().padding(16.dp),
textStyle = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
placeholder = {
Text("Title")
},
value = state.value.title.value,
onValueChange = {
// state.value.title.value = it
viewModel.onEvent(RoomEvents.SaveNote(
it,
state.value.description.value,
))
}
)
TextField(
modifier = Modifier.fillMaxWidth().padding(16.dp),
textStyle = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
placeholder = {
Text("Title")
},
value = state.value.description.value,
onValueChange = {
// state.value.description.value = it
viewModel.onEvent(RoomEvents.SaveNote(
state.value.title.value,
it
))
}
)
}
}
}