-
Source:
-
github-repo for project
2. Create Database
Create Project
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
}
// Material Design
implementation 'com.google.android.material:material:1.3.0'
// Navigation Component
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// Room components
implementation "androidx.room:room-runtime:2.3.0"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:2.3.0"
implementation "androidx.room:room-ktx:2.3.0"
androidTestImplementation "androidx.room:room-testing:2.3.0"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
// DataBinding
kapt "com.android.databinding:compiler:3.2.0"
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
2.1. Create the entity class
-
create a package
data
-
create a Kotlin class
User
package at.htl.roomapp.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "user_table")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int,
val firstName: String,
val lastName: String,
val age: Int
)
2.2. Create the Dao
-
Create a interface
package at.htl.roomapp.data
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user: User)
@Query("SELECT * FROM user_table ORDER BY id ASC")
fun readAllData(): LiveData<List<User>>
}
2.3. Create the database
package at.htl.roomapp.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class UserDatabase: RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE:UserDatabase? = null
fun getDatabase(context: Context):UserDatabase{
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
2.4. Create a Repository
package at.htl.roomapp.data
import androidx.lifecycle.LiveData
class UserRepository(private val userDao: UserDao) {
val readAlldata: LiveData<List<User>> = userDao.readAllData()
suspend fun addUser(user: User) {
userDao.addUser(user)
}
}
2.5. Create ViewModel
The ViewModel’s role is to provide data to the UI and survive configuration changes. A ViewModel acts as a communication center between the Repository and the UI.
package at.htl.roomapp.data
import android.app.Application
import androidx.annotation.NonNull
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class UserViewModel(application: Application) : AndroidViewModel(application) { (1)
private val readAllData: LiveData<List<User>>
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao)
readAllData = repository.readAlldata
}
fun addUser(user: User) {
viewModelScope.launch(Dispatchers.IO){
repository.addUser(user)
}
}
}
1 | A AndroidViewModel is a subclass of ViewModel and includes the application context. |
4. Create Packages for ListFragment and AddFragment (INSERT)
-
remove the codes in the fragment classes except function
onCreateView
package at.htl.roomapp
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class AddFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_add, container, false)
}
}
package at.htl.roomapp
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class ListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_list, container, false)
}
}
-
Add packages and move the fragments into them (refactoring)
5. Add Navigation to MainActivity
-
open activity_main.xml
-
remove TextView "Hello World"
-
add NavHostFragment from palette
-
choose my_nav
-
connect constraints
6. ListFragment - Layout
-
open fragment_list.xml
-
remove TextView
-
Change FrameLayout to ConstraintLayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.list.ListFragment"/>
-
add RecyclerView
-
connect constraints
6.1. Add FloatingActionButton
-
add icon to resources folder
-
Ok
-
rename icon to:
ic_add
-
Next
-
Finsish
-
Add FloatingActionButton from palette
-
Choose ic_add - icon
-
Ok
-
-
Connect constraints to right and bottom with 24dp
-
add to FloatingAction Button
android:focusable="true" android:tint="@android:color/white"
fragment_list.xml and fragment_add.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.list.ListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:clickable="true"
android:focusable="true"
android:tint="@android:color/white"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.add.AddFragment"
android:padding="24dp">
<EditText
android:id="@+id/addFirstName_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:ems="10"
android:hint="First Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/addLastName_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="Last Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/addFirstName_et" />
<EditText
android:id="@+id/addAge_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="Age"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/addLastName_et" />
<Button
android:id="@+id/add_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Add"
app:layout_constraintTop_toBottomOf="@+id/addAge_et"
tools:layout_editor_absoluteX="147dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
package at.htl.roomapp.fragments.list
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 at.htl.roomapp.R
import kotlinx.android.synthetic.main.fragment_list.view.*
class ListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list, container, false)
view.floatingActionButton.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return view
}
}
-
Die Navigation funktioniert, allerdings ändert sich die ActionBar nicht
6.2. Add NavController to ActionBar
package at.htl.roomapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupActionBarWithNavController(findNavController(R.id.fragment)) (1)
}
}
1 | The fragment in activity_main.xml is called fragment |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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/my_nav"
app:startDestination="@id/listFragment">
<fragment
android:id="@+id/listFragment"
android:name="at.htl.roomapp.fragments.list.ListFragment"
android:label="List"
tools:layout="@layout/fragment_list" >
<action
android:id="@+id/action_listFragment_to_addFragment"
app:destination="@id/addFragment" />
</fragment>
<fragment
android:id="@+id/addFragment"
android:name="at.htl.roomapp.fragments.add.AddFragment"
android:label="Add"
tools:layout="@layout/fragment_add" >
<action
android:id="@+id/action_addFragment_to_listFragment"
app:destination="@id/listFragment" />
</fragment>
</navigation>
7. Implement the AddFragment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package at.htl.roomapp.fragments.add
import android.os.Bundle
import android.text.Editable
import android.text.TextUtils
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import at.htl.roomapp.R
import at.htl.roomapp.data.User
import at.htl.roomapp.data.UserViewModel
import kotlinx.android.synthetic.main.fragment_add.*
import kotlinx.android.synthetic.main.fragment_add.view.*
class AddFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_add, container, false)
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java) (1)
view.add_btn.setOnClickListener {
insertDataToDatabase()
}
return view
}
private fun insertDataToDatabase() {
val firstName = addFirstName_et.text.toString()
val lastName = addLastName_et.text.toString()
val age = addAge_et.text
if (inputCheck(firstName, lastName, age)) {
// Create User Object
val user = User(0, firstName, lastName, Integer.parseInt(age.toString())) (2)
// Add Data to Database
mUserViewModel.addUser(user)
Toast.makeText(requireContext(), "successfully added!", Toast.LENGTH_LONG).show()
// Navigate back
findNavController().navigate(R.id.action_addFragment_to_listFragment)
} else {
Toast.makeText(requireContext(), "Please fill out all fields.", Toast.LENGTH_LONG)
.show()
}
}
private fun inputCheck(firstName: String, lastName: String, age: Editable): Boolean {
return !(TextUtils.isEmpty(firstName) && TextUtils.isEmpty(lastName) && age.isEmpty())
}
}
1 | Initialize the ViewModel. We are using the default ViewModelProvider. |
2 | We have to pass 0 for the id. But the database will use the auto-generated key. |
7.1. View database in Android Studio
1 | open the Database Inspector |
2 | choose the device / emulator and app |
3 | all tables of the app are shown automatically - choose one |
4 | you can see the contents - NOTICE: the id is set to 1, even 0 was passed as parameter |
The room_master_table stores an unique identity_hash for each version of the database [source]
|
7.2. Download Database and View in IntelliJ Ultimate / Datagrip
You could also use other database clients
8. Fix the Back-Arrow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package at.htl.roomapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupActionBarWithNavController(findNavController(R.id.fragment))
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.fragment)
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
9. Implement the ListFragment (READ from Database)
-
ROOM Database #3 Video
9.1. Create Row Item Layout for ListFragment
-
Right click res/layout
-
-
File name:
custom_row
-
Root element:
androidx.constraintlayout.widget.ConstraintLayout
-
To the ConstraintLayout-Element
-
add
android:padding="24dp"
-
change to
android:layout_height="wrap_content"
-
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp">
<TextView
android:id="@+id/id_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="40dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/firstName_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:text="John"
android:textSize="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/id_txt"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/lastName_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="Doe"
android:textSize="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/firstName_txt"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/age_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="(25)"
android:textSize="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/lastName_txt"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
9.2. Create ListAdapter
-
Create class
fragments/list/ListAdapter.kt
-
derivate from
RecyclerView.Adapter<ListAdapter.MyViewHolder>
-
and create MyViewHolder-class
class ListAdapter:RecyclerView.Adapter<ListAdapter.MyViewHolder> {
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
}
}
-
and implement the member methods
-
add userList
package at.htl.roomapp.fragments.list
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import at.htl.roomapp.R
import at.htl.roomapp.data.User
import kotlinx.android.synthetic.main.custom_row.view.*
class ListAdapter:RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var userList = emptyList<User>()
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.custom_row,parent,false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = userList[position]
holder.itemView.id_txt.text = currentItem.id.toString()
holder.itemView.firstName_txt.text = currentItem.firstName
holder.itemView.lastName_txt.text = currentItem.lastName
holder.itemView.age_txt.text = currentItem.age.toString()
}
override fun getItemCount(): Int {
return userList.size
}
fun setData(users: List<User>) {
this.userList = users
notifyDataSetChanged()
}
}
1
2
3
4
5
6
7
8
9
10
package at.htl.roomapp.data
import ...
class UserViewModel(application: Application) : AndroidViewModel(application) {
val readAllData: LiveData<List<User>>
private val repository: UserRepository
...
}
9.3. Add RecyclerView and UserViewModel to ListFragment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package at.htl.roomapp.fragments.list
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import at.htl.roomapp.R
import at.htl.roomapp.data.UserViewModel
import kotlinx.android.synthetic.main.fragment_list.view.*
class ListFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list, container, false)
// RecyclerView
val adapter = ListAdapter()
val recyclerView = view.recyclerview
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
// UserViewModel
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
mUserViewModel.readAllData.observe(viewLifecycleOwner, Observer { user ->
adapter.setData(user) (1)
})
view.floatingActionButton.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return view
}
}
1 | setData notifies the observers |
10. UPDATE the Database
-
ROOM Database #4 Video
10.1. Reorganize the Packages
-
create a package
model
-
move
User.kt
into this package -
create a package
viewmodel
-
move
UserViewModel.kt
into this package -
create a package
repository
-
move
UserRepository.kt
into this package
10.2. Create a new Fragment UpdateFragment
-
create a new package
update
-
insert a new fragment
-
right click on package
update
-
-
Fragment Name:
UpdateFragment
-
Finish
-
onCreateView
package at.htl.roomapp.fragments.update import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import at.htl.roomapp.R class UpdateFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_update, container, false) } }
-
change the layout from fragment_update.xml to ConstraintLayout
-
Copy the view-elements from fragment_add.xml to fragment_update.xml
-
Rename the elements from add… to uUpdate…
-
Change the text of the button to "Update"
fragment_update.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.update.UpdateFragment"
android:padding="24dp">
<EditText
android:id="@+id/updateFirstName_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:ems="10"
android:hint="First Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<EditText
android:id="@+id/updateLastName_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="Last Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/updateFirstName_et" />
<EditText
android:id="@+id/updateAge_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="Age"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/updateLastName_et" />
<Button
android:id="@+id/update_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Update"
app:layout_constraintTop_toBottomOf="@+id/updateAge_et"
tools:layout_editor_absoluteX="147dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
10.3. Add the Fragment to the Navigation
-
open
my_nav.xml
-
add "New Destination"
fragment_update
-
add connections
-
change the label of the updateFragment to "Update" in my_nav.xml
10.4. Add a Parameter for invoking UpdateFragment
10.4.1. Derivate the Entity Class from Parcelable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package at.htl.roomapp.model
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Parcelize (1)
@Entity(tableName = "user_table")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int,
val firstName: String,
val lastName: String,
val age: Int
): Parcelable (2)
1 | add @Parcelize and |
2 | extends Parcelable
|
10.4.2. Add the Parameters to the Navigation-Destination
-
Click on updateFragment in my_nav.xml
-
Click on + next to Arguments
-
Name:
currentUser
-
Choose Type:
Custom Parcelable …
-
Select Class:
User (at.htl.roomapp.model)
-
Add
10.4.3. Add Id to ConstraintLayout of custom_row.xml
1
2
3
4
5
6
7
8
9
10
11
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
android:id="@+id/rowLayout">
...
</androidx.constraintlayout.widget.ConstraintLayout>
10.4.4. Add onClickListener to ListAdapter
-
When you click on a row, the click-Lsitener is invoked
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ListAdapter:RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
...
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = userList[position]
holder.itemView.id_txt.text = currentItem.id.toString()
holder.itemView.firstName_txt.text = currentItem.firstName
holder.itemView.lastName_txt.text = currentItem.lastName
holder.itemView.age_txt.text = currentItem.age.toString()
holder.itemView.rowLayout.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItem) (1)
holder.itemView.findNavController().navigate(action) (2)
}
}
...
}
1 | This is the connection in the navigation. ListFragmentDirection is auto-generated. So you have to rebuild the project, to get access to the actionListFragmentToUpdateFragment-function → |
2 | opens the update-fragment after clicing on the row |
10.4.5. Fill the Update-Fragment with Data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package at.htl.roomapp.fragments.update
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.navArgs
import at.htl.roomapp.R
import kotlinx.android.synthetic.main.fragment_add.view.*
class UpdateFragment : Fragment() {
private val args by navArgs<UpdateFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_update, container, false)
view.updateFirstName_et.setText(args.currentUser.firstName)
view.updateLastName_et.setText(args.currentUser.lastName)
view.updateAge_et.setText(args.currentUser.age.toString())
return view
}
}
10.4.6. Persist Updated Data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package at.htl.roomapp.data
import androidx.lifecycle.LiveData
import androidx.room.*
import at.htl.roomapp.model.User
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user: User)
@Update
suspend fun updateUser(user: User)
@Query("SELECT * FROM user_table ORDER BY id ASC")
fun readAllData(): LiveData<List<User>>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package at.htl.roomapp.repository
import androidx.lifecycle.LiveData
import at.htl.roomapp.data.UserDao
import at.htl.roomapp.model.User
class UserRepository(private val userDao: UserDao) {
val readAlldata: LiveData<List<User>> = userDao.readAllData()
suspend fun addUser(user: User) {
userDao.addUser(user)
}
suspend fun updateUser(user: User) {
userDao.updateUser(user)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package at.htl.roomapp.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import at.htl.roomapp.data.UserDatabase
import at.htl.roomapp.model.User
import at.htl.roomapp.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class UserViewModel(application: Application) : AndroidViewModel(application) {
val readAllData: LiveData<List<User>>
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao)
readAllData = repository.readAlldata
}
fun addUser(user: User) {
viewModelScope.launch(Dispatchers.IO){
repository.addUser(user)
}
}
fun updateUser(user: User) {
viewModelScope.launch(Dispatchers.IO) { (1)
repository.updateUser(user)
}
}
}
1 | Dispatchers.IO means, the update is running asynchronous |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package at.htl.roomapp.fragments.update
import android.os.Bundle
import android.text.Editable
import android.text.TextUtils
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import at.htl.roomapp.R
import at.htl.roomapp.model.User
import at.htl.roomapp.viewmodel.UserViewModel
import kotlinx.android.synthetic.main.fragment_update.*
import kotlinx.android.synthetic.main.fragment_update.view.*
class UpdateFragment : Fragment() {
private val args by navArgs<UpdateFragmentArgs>()
private lateinit var mUserViewModel: UserViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_update, container, false)
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
view.updateFirstName_et.setText(args.currentUser.firstName)
view.updateLastName_et.setText(args.currentUser.lastName)
view.updateAge_et.setText(args.currentUser.age.toString())
view.update_btn.setOnClickListener {
updateItem()
}
return view
}
private fun updateItem() {
val firstName = updateFirstName_et.text.toString()
val lastName = updateLastName_et.text.toString()
val age = Integer.parseInt(updateAge_et.text.toString())
if (inputCheck(firstName, lastName, updateAge_et.text)) {
// Create User Object
val updatedUser = User(args.currentUser.id, firstName,lastName, age)
// Update Current User
mUserViewModel.updateUser(updatedUser)
Toast.makeText(requireContext(),"Updated Successfully!", Toast.LENGTH_SHORT).show()
// Navigate Back
findNavController().navigate(R.id.action_updateFragment_to_listFragment)
} else {
Toast.makeText(requireContext(),"Please fill out all fields.", Toast.LENGTH_SHORT).show()
}
}
private fun inputCheck(firstName: String, lastName: String, age: Editable): Boolean {
return !(TextUtils.isEmpty(firstName) && TextUtils.isEmpty(lastName) && age.isEmpty())
}
}