First Working Revision (#1)
App is now in working order. Reviewed-on: https://gitea.com/Hyperling/example-android-database-room/pulls/1 Co-authored-by: Hyperling <me@hyperling.com> Co-committed-by: Hyperling <me@hyperling.com>
This commit is contained in:
parent
0591682f9d
commit
bb730f2f0b
10
README.md
10
README.md
@ -1,3 +1,9 @@
|
|||||||
# example-android-database-room
|
# Example: Android-Database-Room
|
||||||
|
|
||||||
Example app of using Room functionality to handle databases.
|
Example app of using Room functionality within Android to handle databases.
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
|
||||||
|
This app was created while following this tutorial:
|
||||||
|
|
||||||
|
https://www.youtube.com/watch?v=bOd3wO0uFr8&themeRefresh=1
|
@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
|
||||||
// For "Room", see below for more details.
|
// For "Room", see below for more details.
|
||||||
kotlin("kapt")
|
kotlin("kapt")
|
||||||
@ -8,11 +9,11 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.hyperling.roomexample"
|
namespace = "com.hyperling.roomexample"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.hyperling.roomexample"
|
applicationId = "com.hyperling.roomexample"
|
||||||
minSdk = 19
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
@ -37,21 +38,27 @@ android {
|
|||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
compose = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.material)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.navigation.fragment.ktx)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.navigation.ui.ktx)
|
implementation(libs.androidx.ui.graphics)
|
||||||
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
|
implementation(libs.androidx.material3)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|
||||||
// For Room
|
// For Room
|
||||||
// https://www.youtube.com/watch?v=bOd3wO0uFr8&themeRefresh=1
|
// https://www.youtube.com/watch?v=bOd3wO0uFr8&themeRefresh=1
|
||||||
@ -61,7 +68,7 @@ dependencies {
|
|||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
// */
|
// */
|
||||||
/* build.grade.kts version */
|
/* build.grade.kts version */
|
||||||
val room_version: String = "2.5.0"
|
val room_version: String = "2.6.1"
|
||||||
implementation("androidx.room:room-ktx:$room_version")
|
implementation("androidx.room:room-ktx:$room_version")
|
||||||
kapt("androidx.room:room-compiler:$room_version")
|
kapt("androidx.room:room-compiler:$room_version")
|
||||||
// */
|
// */
|
||||||
|
@ -6,16 +6,17 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/icon_v001"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/icon_v001_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RoomExample"
|
android:theme="@style/Theme.ExampleRoomDatabase21Empty"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.RoomExample">
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.ExampleRoomDatabase21Empty">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.hyperling.roomexample
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AddContactDialog(
|
||||||
|
state: ContactState,
|
||||||
|
onEvent: (ContactEvent) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
onEvent(ContactEvent.HideDialog)
|
||||||
|
},
|
||||||
|
title = { Text(text = "Add Contact") },
|
||||||
|
text = {
|
||||||
|
Column (
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
){
|
||||||
|
TextField(
|
||||||
|
value = state.firstName,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ContactEvent.SetFirstName(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "First Name")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
value = state.lastName,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ContactEvent.SetLastName(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Last Name")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
value = state.phoneNumber,
|
||||||
|
onValueChange = {
|
||||||
|
onEvent(ContactEvent.SetPhoneNumber(it))
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Phone Number")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
onEvent(ContactEvent.SaveContact)
|
||||||
|
}) {
|
||||||
|
Text(text = "Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -9,6 +9,9 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
@Dao
|
@Dao
|
||||||
interface ContactDao {
|
interface ContactDao {
|
||||||
|
|
||||||
|
// 2024-12-28
|
||||||
|
// If these complain about return type errors, try upgrading
|
||||||
|
// the ROOM version, otherwise remove the SUSPEND keyword.
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun upsertContact(contact: Contact)
|
suspend fun upsertContact(contact: Contact)
|
||||||
|
|
||||||
@ -23,4 +26,5 @@ interface ContactDao {
|
|||||||
|
|
||||||
@Query("SELECT * FROM contact ORDER BY phoneNumber ASC")
|
@Query("SELECT * FROM contact ORDER BY phoneNumber ASC")
|
||||||
fun selectContactsPN(): Flow<List<Contact>>
|
fun selectContactsPN(): Flow<List<Contact>>
|
||||||
|
|
||||||
}
|
}
|
@ -8,5 +8,5 @@ sealed interface ContactEvent {
|
|||||||
object ShowDialog: ContactEvent
|
object ShowDialog: ContactEvent
|
||||||
object HideDialog: ContactEvent
|
object HideDialog: ContactEvent
|
||||||
data class SortContacts(val sortType: SortType): ContactEvent
|
data class SortContacts(val sortType: SortType): ContactEvent
|
||||||
data class DeleteContact(val )
|
data class DeleteContact(val contact: Contact): ContactEvent
|
||||||
}
|
}
|
108
app/src/main/java/com/hyperling/roomexample/ContactScreen.kt
Normal file
108
app/src/main/java/com/hyperling/roomexample/ContactScreen.kt
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package com.hyperling.roomexample
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ContactScreen(
|
||||||
|
state: ContactState,
|
||||||
|
onEvent: (ContactEvent) -> Unit
|
||||||
|
){
|
||||||
|
Scaffold (
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = {
|
||||||
|
onEvent(ContactEvent.ShowDialog)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = "Add Contact",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
){ padding ->
|
||||||
|
if(state.isAddingContact){
|
||||||
|
AddContactDialog(state, onEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn (
|
||||||
|
contentPadding = padding,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
){
|
||||||
|
item {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
SortType.values().forEach { sortType ->
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
onEvent(ContactEvent.SortContacts(sortType))
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
RadioButton(
|
||||||
|
selected = state.sortType == sortType,
|
||||||
|
onClick = {
|
||||||
|
onEvent(ContactEvent.SortContacts(sortType))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(text = sortType.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(state.contacts) {contact ->
|
||||||
|
Row (
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text (
|
||||||
|
text = "${contact.firstName} ${contact.lastName}",
|
||||||
|
fontSize = 20.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = contact.phoneNumber,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = {
|
||||||
|
onEvent(ContactEvent.DeleteContact(contact))
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "Delete Contact"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/java/com/hyperling/roomexample/ContactState.kt
Normal file
10
app/src/main/java/com/hyperling/roomexample/ContactState.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.hyperling.roomexample
|
||||||
|
|
||||||
|
data class ContactState(
|
||||||
|
val contacts: List<Contact> = emptyList(),
|
||||||
|
val firstName: String = "",
|
||||||
|
val lastName: String = "",
|
||||||
|
val phoneNumber: String = "",
|
||||||
|
val isAddingContact: Boolean = false,
|
||||||
|
val sortType: SortType = SortType.LAST_NAME,
|
||||||
|
)
|
@ -0,0 +1,95 @@
|
|||||||
|
package com.hyperling.roomexample
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ContactViewModel (
|
||||||
|
private val dao: ContactDao,
|
||||||
|
): ViewModel() {
|
||||||
|
|
||||||
|
private val _sortType = MutableStateFlow(SortType.LAST_NAME)
|
||||||
|
private val _state = MutableStateFlow(ContactState())
|
||||||
|
|
||||||
|
private val _contacts = _sortType
|
||||||
|
.flatMapLatest { sortType ->
|
||||||
|
when(sortType) {
|
||||||
|
SortType.FIRST_NAME -> dao.selectContactsFN()
|
||||||
|
SortType.LAST_NAME -> dao.selectContactsLN()
|
||||||
|
SortType.PHONE_NUMBER -> dao.selectContactsPN()
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|
||||||
|
val state = combine(_state, _sortType, _contacts) { state, sortType, contacts ->
|
||||||
|
state.copy(
|
||||||
|
contacts = contacts,
|
||||||
|
sortType = sortType
|
||||||
|
)
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ContactState())
|
||||||
|
|
||||||
|
fun onEvent(event: ContactEvent) {
|
||||||
|
when(event) {
|
||||||
|
is ContactEvent.DeleteContact -> {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dao.deleteContact(event.contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContactEvent.HideDialog -> {
|
||||||
|
_state.update { it.copy(
|
||||||
|
isAddingContact = false
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
ContactEvent.SaveContact -> {
|
||||||
|
val firstName = state.value.firstName
|
||||||
|
val lastName = state.value.lastName
|
||||||
|
val phoneNumber = state.value.phoneNumber
|
||||||
|
|
||||||
|
if (firstName.isBlank() || lastName.isBlank() || phoneNumber.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val contact = Contact(firstName, lastName, phoneNumber)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
dao.upsertContact(contact)
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update { it.copy(
|
||||||
|
isAddingContact = false,
|
||||||
|
firstName = "",
|
||||||
|
lastName = "",
|
||||||
|
phoneNumber = "",
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
is ContactEvent.SetFirstName -> {
|
||||||
|
_state.update { it.copy(
|
||||||
|
firstName = event.firstName
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
is ContactEvent.SetLastName -> {
|
||||||
|
_state.update { it.copy(
|
||||||
|
lastName = event.lastName
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
is ContactEvent.SetPhoneNumber -> {
|
||||||
|
_state.update { it.copy(
|
||||||
|
phoneNumber = event.phoneNumber
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
ContactEvent.ShowDialog -> {
|
||||||
|
_state.update { it.copy(
|
||||||
|
isAddingContact = true
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
is ContactEvent.SortContacts -> {
|
||||||
|
_sortType.value = event.sortType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
package com.hyperling.roomexample
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.hyperling.roomexample.databinding.FragmentFirstBinding
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple [Fragment] subclass as the default destination in the navigation.
|
|
||||||
*/
|
|
||||||
class FirstFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _binding: FragmentFirstBinding? = null
|
|
||||||
|
|
||||||
// This property is only valid between onCreateView and
|
|
||||||
// onDestroyView.
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
|
|
||||||
_binding = FragmentFirstBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding.buttonFirst.setOnClickListener {
|
|
||||||
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +1,64 @@
|
|||||||
package com.hyperling.roomexample
|
package com.hyperling.roomexample
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.activity.compose.setContent
|
||||||
import androidx.navigation.findNavController
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.navigation.ui.AppBarConfiguration
|
import androidx.activity.viewModels
|
||||||
import androidx.navigation.ui.navigateUp
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.navigation.ui.setupActionBarWithNavController
|
import androidx.compose.foundation.layout.padding
|
||||||
import android.view.Menu
|
import androidx.compose.material3.Scaffold
|
||||||
import android.view.MenuItem
|
import androidx.compose.material3.Text
|
||||||
import com.hyperling.roomexample.databinding.ActivityMainBinding
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.hyperling.roomexample.ui.theme.ExampleRoomDatabase21EmptyTheme
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
private val _db by lazy {
|
||||||
private lateinit var binding: ActivityMainBinding
|
Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
ContactDatabase::class.java,
|
||||||
|
"contacts.db"
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
private val _viewModel by viewModels<ContactViewModel> (
|
||||||
|
factoryProducer = {
|
||||||
|
object : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return ContactViewModel(_db.dao) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
setContent {
|
||||||
setContentView(binding.root)
|
ExampleRoomDatabase21EmptyTheme {
|
||||||
|
val state by _viewModel.state.collectAsState()
|
||||||
setSupportActionBar(binding.toolbar)
|
ContactScreen(
|
||||||
|
state = state,
|
||||||
val navController = findNavController(R.id.nav_host_fragment_content_main)
|
onEvent = _viewModel::onEvent
|
||||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
)
|
||||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
|
||||||
|
|
||||||
binding.fab.setOnClickListener { view ->
|
|
||||||
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
|
||||||
.setAction("Action", null)
|
|
||||||
.setAnchorView(R.id.fab).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
// Handle action bar item clicks here. The action bar will
|
|
||||||
// automatically handle clicks on the Home/Up button, so long
|
|
||||||
// as you specify a parent activity in AndroidManifest.xml.
|
|
||||||
return when (item.itemId) {
|
|
||||||
R.id.action_settings -> true
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
|
||||||
val navController = findNavController(R.id.nav_host_fragment_content_main)
|
|
||||||
return navController.navigateUp(appBarConfiguration)
|
|
||||||
|| super.onSupportNavigateUp()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* * /
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun GreetingPreview() {
|
||||||
|
ExampleRoomDatabase21EmptyTheme {
|
||||||
|
Greeting("Android")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// */
|
@ -1,44 +0,0 @@
|
|||||||
package com.hyperling.roomexample
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.hyperling.roomexample.databinding.FragmentSecondBinding
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
|
||||||
*/
|
|
||||||
class SecondFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _binding: FragmentSecondBinding? = null
|
|
||||||
|
|
||||||
// This property is only valid between onCreateView and
|
|
||||||
// onDestroyView.
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
|
|
||||||
_binding = FragmentSecondBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding.buttonSecond.setOnClickListener {
|
|
||||||
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.hyperling.roomexample.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.hyperling.roomexample.ui.theme
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExampleRoomDatabase21EmptyTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
34
app/src/main/java/com/hyperling/roomexample/ui/theme/Type.kt
Normal file
34
app/src/main/java/com/hyperling/roomexample/ui/theme/Type.kt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package com.hyperling.roomexample.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<include layout="@layout/content_main" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/fab"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_marginEnd="@dimen/fab_margin"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
app:srcCompat="@android:drawable/ic_dialog_email" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/nav_host_fragment_content_main"
|
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:defaultNavHost="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:navGraph="@navigation/nav_graph" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".FirstFragment">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button_first"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/next"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/textview_first"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textview_first"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/lorem_ipsum"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/button_first" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".SecondFragment">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button_second"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/previous"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/textview_second"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textview_second"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/lorem_ipsum"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/button_second" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,10 +0,0 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
tools:context="com.hyperling.roomexample.MainActivity">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_settings"
|
|
||||||
android:orderInCategory="100"
|
|
||||||
android:title="@string/action_settings"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
</menu>
|
|
BIN
app/src/main/res/mipmap-hdpi/icon_v001.png
Normal file
BIN
app/src/main/res/mipmap-hdpi/icon_v001.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-hdpi/icon_v001_round.png
Normal file
BIN
app/src/main/res/mipmap-hdpi/icon_v001_round.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/nav_graph"
|
|
||||||
app:startDestination="@id/FirstFragment">
|
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/FirstFragment"
|
|
||||||
android:name="com.hyperling.roomexample.FirstFragment"
|
|
||||||
android:label="@string/first_fragment_label"
|
|
||||||
tools:layout="@layout/fragment_first">
|
|
||||||
|
|
||||||
<action
|
|
||||||
android:id="@+id/action_FirstFragment_to_SecondFragment"
|
|
||||||
app:destination="@id/SecondFragment" />
|
|
||||||
</fragment>
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/SecondFragment"
|
|
||||||
android:name="com.hyperling.roomexample.SecondFragment"
|
|
||||||
android:label="@string/second_fragment_label"
|
|
||||||
tools:layout="@layout/fragment_second">
|
|
||||||
|
|
||||||
<action
|
|
||||||
android:id="@+id/action_SecondFragment_to_FirstFragment"
|
|
||||||
app:destination="@id/FirstFragment" />
|
|
||||||
</fragment>
|
|
||||||
</navigation>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<dimen name="fab_margin">48dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,7 +0,0 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Base.Theme.RoomExample" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Customize your dark theme here. -->
|
|
||||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,9 +0,0 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<style name="Theme.RoomExample" parent="Base.Theme.RoomExample">
|
|
||||||
<!-- Transparent system bars for edge-to-edge. -->
|
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<dimen name="fab_margin">200dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<dimen name="fab_margin">48dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
</resources>
|
</resources>
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<dimen name="fab_margin">16dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,46 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">RoomExample</string>
|
<string name="app_name">Room Database Testing</string>
|
||||||
<string name="action_settings">Settings</string>
|
|
||||||
<!-- Strings used for fragments for navigation -->
|
|
||||||
<string name="first_fragment_label">First Fragment</string>
|
|
||||||
<string name="second_fragment_label">Second Fragment</string>
|
|
||||||
<string name="next">Next</string>
|
|
||||||
<string name="previous">Previous</string>
|
|
||||||
|
|
||||||
<string name="lorem_ipsum">
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
|
|
||||||
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
|
|
||||||
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
|
|
||||||
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
|
|
||||||
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
|
|
||||||
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
|
|
||||||
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
|
|
||||||
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
|
|
||||||
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
|
|
||||||
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
|
|
||||||
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
|
|
||||||
bibendum, vel congue leo egestas.\n\n
|
|
||||||
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
|
|
||||||
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
|
|
||||||
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
|
|
||||||
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
|
|
||||||
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
|
|
||||||
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
|
|
||||||
est.\n\n
|
|
||||||
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
|
|
||||||
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
|
|
||||||
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
|
|
||||||
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
|
|
||||||
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
|
|
||||||
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
|
|
||||||
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
|
|
||||||
convallis.\n\n
|
|
||||||
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
|
|
||||||
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
|
|
||||||
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
|
|
||||||
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
|
|
||||||
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
|
|
||||||
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
|
|
||||||
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
|
|
||||||
</string>
|
|
||||||
</resources>
|
</resources>
|
@ -1,9 +1,5 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Base application theme. -->
|
<resources>
|
||||||
<style name="Base.Theme.RoomExample" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Customize your light theme here. -->
|
|
||||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Theme.RoomExample" parent="Base.Theme.RoomExample" />
|
<style name="Theme.ExampleRoomDatabase21Empty" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
@ -2,4 +2,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
}
|
}
|
@ -1,28 +1,32 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.7.3"
|
agp = "8.7.3"
|
||||||
kotlin = "1.9.24"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.15.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.6.1"
|
||||||
appcompat = "1.6.1"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
material = "1.10.0"
|
activityCompose = "1.8.0"
|
||||||
constraintlayout = "2.1.4"
|
composeBom = "2024.04.01"
|
||||||
navigationFragmentKtx = "2.6.0"
|
|
||||||
navigationUiKtx = "2.6.0"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,4 +1,4 @@
|
|||||||
#Tue Dec 17 13:20:05 MST 2024
|
#Sat Dec 21 14:04:35 MST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
|
BIN
media/icon_v001.png
Normal file
BIN
media/icon_v001.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
media/icon_v001.xcf
Normal file
BIN
media/icon_v001.xcf
Normal file
Binary file not shown.
BIN
media/icon_v001_round.png
Normal file
BIN
media/icon_v001_round.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -19,6 +19,6 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "RoomExample"
|
rootProject.name = "Example Room Database (SDK 21, Empty Activity)"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user