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:
Hyperling 2024-12-29 14:53:12 +00:00 committed by Hyperling
parent 0591682f9d
commit bb730f2f0b
39 changed files with 504 additions and 406 deletions

View File

@ -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

View File

@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
// For "Room", see below for more details.
kotlin("kapt")
@ -8,11 +9,11 @@ plugins {
android {
namespace = "com.hyperling.roomexample"
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "com.hyperling.roomexample"
minSdk = 19
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
@ -37,21 +38,27 @@ android {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
compose = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
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
// https://www.youtube.com/watch?v=bOd3wO0uFr8&themeRefresh=1
@ -61,7 +68,7 @@ dependencies {
kapt "androidx.room:room-compiler:$room_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")
kapt("androidx.room:room-compiler:$room_version")
// */

View File

@ -6,16 +6,17 @@
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:icon="@mipmap/icon_v001"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/icon_v001_round"
android:supportsRtl="true"
android:theme="@style/Theme.RoomExample"
android:theme="@style/Theme.ExampleRoomDatabase21Empty"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.RoomExample">
android:label="@string/app_name"
android:theme="@style/Theme.ExampleRoomDatabase21Empty">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -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")
}
}
}
)
}

View File

@ -9,6 +9,9 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface ContactDao {
// 2024-12-28
// If these complain about return type errors, try upgrading
// the ROOM version, otherwise remove the SUSPEND keyword.
@Upsert
suspend fun upsertContact(contact: Contact)
@ -23,4 +26,5 @@ interface ContactDao {
@Query("SELECT * FROM contact ORDER BY phoneNumber ASC")
fun selectContactsPN(): Flow<List<Contact>>
}

View File

@ -8,5 +8,5 @@ sealed interface ContactEvent {
object ShowDialog: ContactEvent
object HideDialog: ContactEvent
data class SortContacts(val sortType: SortType): ContactEvent
data class DeleteContact(val )
data class DeleteContact(val contact: Contact): ContactEvent
}

View 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"
)
}
}
}
}
}
}

View 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,
)

View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -1,59 +1,64 @@
package com.hyperling.roomexample
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.hyperling.roomexample.databinding.ActivityMainBinding
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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 lateinit var binding: ActivityMainBinding
private val _db by lazy {
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?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navController = findNavController(R.id.nav_host_fragment_content_main)
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()
enableEdgeToEdge()
setContent {
ExampleRoomDatabase21EmptyTheme {
val state by _viewModel.state.collectAsState()
ContactScreen(
state = state,
onEvent = _viewModel::onEvent
)
}
}
}
}
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
/* * /
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ExampleRoomDatabase21EmptyTheme {
Greeting("Android")
}
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()
}
}
}
// */

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
)
}

View 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
)
*/
)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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>

View File

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
</resources>

View File

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="white">#FFFFFFFF</color>
</resources>

View File

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@ -1,46 +1,3 @@
<resources>
<string name="app_name">RoomExample</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>
<string name="app_name">Room Database Testing</string>
</resources>

View File

@ -1,9 +1,5 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.RoomExample" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.RoomExample" parent="Base.Theme.RoomExample" />
<style name="Theme.ExampleRoomDatabase21Empty" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -2,4 +2,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

View File

@ -1,28 +1,32 @@
[versions]
agp = "8.7.3"
kotlin = "1.9.24"
coreKtx = "1.10.1"
kotlin = "2.0.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.10.0"
constraintlayout = "2.1.4"
navigationFragmentKtx = "2.6.0"
navigationUiKtx = "2.6.0"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.04.01"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
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-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
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]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

View File

@ -1,4 +1,4 @@
#Tue Dec 17 13:20:05 MST 2024
#Sat Dec 21 14:04:35 MST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip

BIN
media/icon_v001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
media/icon_v001.xcf Normal file

Binary file not shown.

BIN
media/icon_v001_round.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -19,6 +19,6 @@ dependencyResolutionManagement {
}
}
rootProject.name = "RoomExample"
rootProject.name = "Example Room Database (SDK 21, Empty Activity)"
include(":app")