1. Overview

2. Create Database

Create Project

room 000 create project

room 001 create project

plugins in build.gradle (:app)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs.kotlin'
}
add dependencies in build.gradle (:app)
    // 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"
add classpath for safe-args-gradle-plugin in build-gradle (RoomApp)
    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.

3. Add Navigation

  • Open Resource Manager

room 002 open resource manager

  • Choose Navigation Resource File

    • File name: my_nav

    • ok

3.1. ListFragment

  • New Destination

    • Create new destination

    • Choose Fragment (Blank)

room 003 configure fragment

  • Fragment Name: ListFragment

  • Finish

3.2. AddFragment

  • Create new Fragment in Navigation

room 004 configure add fragment

  • Fragment Name: AddFragment

  • Finish

  • connect fragments

room 005 connect fragments

4. Create Packages for ListFragment and AddFragment (INSERT)

  • remove the codes in the fragment classes except function onCreateView

AddFragment.kt
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)
    }

}
ListFragment.kt
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)

room 006 add packages

5. Add Navigation to MainActivity

  • open activity_main.xml

  • remove TextView "Hello World"

  • add NavHostFragment from palette

  • choose my_nav

  • connect constraints

room 007 connect constraints

6. ListFragment - Layout

  • open fragment_list.xml

  • remove TextView

  • Change FrameLayout to ConstraintLayout

fragment_list.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"
    tools:context=".fragments.list.ListFragment"/>
  • add RecyclerView

  • connect constraints

6.1. Add FloatingActionButton

  • add icon to resources folder

room 008 add vector asset room 009 choose add icon

  • 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
fragment_list.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>
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.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>
ListFragment.kt
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
    }
}

room 010 emulator room 011 emulator

  • 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
Now the action bar works

room 012 emulator room 013 emulator

app/src/main/res/navigation/my_nav.xml
 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>
Now the fragment labels are fine

room 014 emulator room 015 emulator

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.
After entering the values and clicking ADD the app navigates to the ListFragment

room 016 emulator room 017 emulator

7.1. View database in Android Studio

Check, if the INSERT was successfull

room 018 database inspector

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]
In the Device File Explorer you can find the database file

room 019 device file explorer

7.2. Download Database and View in IntelliJ Ultimate / Datagrip

room 020 save database

Download all three files

room 021 save database

room 022 view database local

room 023 view database local

You could also use other database clients

8. Fix the Back-Arrow

MainActivity.kt
 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

  • New  Layout Resource File

  • 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

room 024 listadapter implement members room 025 listadapter implement members

  • 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()
    }
}
remove private scope
 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
Now it works

room 026 emulator room 027 emulator

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

    • New  Fragment  Fragment (Blank)

    • Fragment Name: UpdateFragment

    • Finish

remove all codes except 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

room 028 add destination

  • 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
  • So you can create a package with an User-object

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)

room 029 add parameter to destination

  • Add

10.4.3. Add Id to ConstraintLayout of custom_row.xml

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 → Build  Rebuild Project
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
    }

}

room 030 emulator room 031 emulator

10.4.6. Persist Updated Data

Add update-function to UserDao
 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>>

}
Add update-function to UserRepository
 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)
    }
}
Add update-function to UserViewModel
 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
UpdateFragment.kt
 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())
    }
}

room 032 emulator room 033 emulator

room 034 emulator

11. DELETE from Database

  • ROOM Database #5 Video