Android Architecture Components - Room - Kotlin


Introduction:

Google's Android Architecture components became stable now. It is nothing but a collection of libraries that ease our Android Development.

Android Architecture Components:


Let's see about Room in this post. 

Room is a new way to create a database in your Android Apps. 

SQLite vs Room:

The following are some of the advantages of Room over SQLite.

(a) In SQLite, there is no compile-time verification of SQL queries, whereas Room verifies it.
(b) We need to write lots of boilerplate codes to convert SQL queries to Java Objects.
(c) We need to update the affected SQL queries manually, while the schema changes in SQLite. Whereas, Room will take care of it automatically.

Room Architecure and Components:

Three components are there in Room:

(1) Database is a holder class that uses annotation to define the list of entities (i.e., tables) and database version. 
(2) Entity - represents a table within the database
(3) DAO (Database Access Objects) - Contains methods used for accessing database.

Code:

build.gradle (module level gradle):

Include the following in your module - level gradle.


apply plugin: 'kotlin-kapt'

compile 'com.android.support:recyclerview-v7:27.0.2'
compile 'com.android.support:cardview-v7:27.0.2'
compile "android.arch.persistence.room:runtime:1.0.0"
kapt "android.arch.persistence.room:compiler:1.0.0"

dialog.xml:

Design a layout to show as dialog while adding/inserting data to the created Table in Database.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.design.widget.TextInputEditText
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Book Name" />
    </android.support.design.widget.TextInputLayout>

    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.design.widget.TextInputEditText
            android:id="@+id/author"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Author" />
    </android.support.design.widget.TextInputLayout>


    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">

        <android.support.design.widget.TextInputEditText
            android:id="@+id/genre"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Genre" />
    </android.support.design.widget.TextInputLayout>


    <TextView
        android:id="@+id/submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:layout_marginTop="20dp"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:padding="15dp"
        android:text="Submit"
        android:textColor="@color/white" />

</LinearLayout>

db_layout.xml:

Let's design a layout to list the stored data in Recyclerview and a FloatingActionButton to add new data to the database.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/add"
        app:elevation="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

Book.kt (Entity - Class)

Entity is nothing but Table in Database. In Kotlin, we can define the Entity class as same as data class with so much ease. By default, the class name "Book" will be considered as Table name. If you need to assign a different name, you have define that as follows:


@Entity(tableName = "book_table")

data class Book(var bookName : String)


Likewise, if we need to change columnName of Table ,

data class Book(@ColumnInfo(name = "book_name")var bookName : String)



Each table should contain one PrimaryKey, it can be added manually or auto generated. In this example, we are adding that autogenerate.

So, Our Book.kt class will be like as follows:

@Entity
data class Book(var bookName : String, var author : String,
                var genre : String){
    @PrimaryKey(autoGenerate = true)
    var id : Long? = null
}

BookDao.kt (Dao - Interface)

Dao interface contains all queries that will be used to process Database.

@Dao
interface BookDao {
    @Query("SELECT * FROM Book")
    fun getBookInfo() : MutableList<Book>

    @Insert
    fun addBook(book : Book)

    @Update(onConflict = REPLACE)
    fun updateBook( book: Book)

    @Delete
    fun deleteBook( book: Book)
}

MyDatabase.kt (Database - Class)

Database class should be declared once like Singleton class and can be accessed throughout the app. It should include array of entities and version of Database. In Kotlin, there is no static keyword. But it can be replaced with companion object. Also, do remember that every database operations should be done only in Background thread. Otherwise the app will crash.

@Database(entities = arrayOf(Book::class), version = 1)
abstract class MyDatabase : RoomDatabase() {
    abstract fun bookDao(): BookDao

    companion object {
        var INSTANCE: MyDatabase? = null

        fun getInstance(context: Context): MyDatabase {

            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context.applicationContext, MyDatabase::class.java, "Books.db").build()
            }
            return INSTANCE as MyDatabase;
        }

        @SuppressLint("StaticFieldLeak")
        fun insertData(mydata: MyDatabase, book: Book) {
            object : AsyncTask<Void, Void, Void>() {
                override fun doInBackground(vararg voids: Void): Void? {
                    mydata.bookDao().addBook(book)
                    return null
                }
            }.execute()
        }

        @SuppressLint("StaticFieldLeak")
        fun getData(mydata: MyDatabase): MutableList<Book> {
            lateinit var lists: MutableList<Book>

            return object : AsyncTask<Void, Void, MutableList<Book>>() {
                override fun doInBackground(vararg voids: Void): MutableList<Book>? {
                    lists = mydata.bookDao().getBookInfo()
                    return lists
                }
            }.execute().get()
        }
    }
}

RecyclerAdapter.kt (Adapter - Class)


class RecyclerAdapter(var context: Context, var data : List<Book>) : RecyclerView.Adapter<RecyclerAdapter.Holder>() {
    override fun onBindViewHolder(holder: Holder?, position: Int) {
        holder?.bindItems(data.get(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
        val v = LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false)
        return Holder(v)
    }

    override fun getItemCount(): Int = data.size

    class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        fun bindItems(book: Book){
            itemView.name.text = book.bookName
            itemView.author.text = book.author
            itemView.genre.text = book.genre
        }

    }
}

DbActivity.kt

Finally, we can fetch data from Database and add it to our list and set it to our recyclerview's adapter. In FloatingActionButton's click event, a dialog will be opened. There the details of the book can be added and stored into the database. Now its done.

class DbActivity : AppCompatActivity() {

    lateinit var adapter: RecyclerAdapter
    lateinit var list: MutableList<Book>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.db_layout)

        list = MyDatabase.getData(MyDatabase.getInstance(this))

        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = RecyclerAdapter(this, list)
        recyclerView.adapter = adapter

        add.setOnClickListener {
            openDialog()
        }

    }

    private fun openDialog() {
        val dialog = Dialog(this)
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
        dialog.setContentView(R.layout.dialog)
        val lp: WindowManager.LayoutParams = WindowManager.LayoutParams().apply {
            copyFrom(dialog.window.attributes)
            width = WindowManager.LayoutParams.MATCH_PARENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
        }

        val submit = dialog.findViewById<View>(R.id.submit) as TextView
        val name = dialog.findViewById<View>(R.id.name) as EditText
        val author = dialog.findViewById<View>(R.id.author) as EditText
        val genre = dialog.findViewById<View>(R.id.genre) as EditText

        submit.setOnClickListener {
            when {
                name.length() == 0 || author.length() == 0 || genre.length() == 0 ->
                    Toast.makeText(this@DbActivity, "Please fill all the fields"
                            , Toast.LENGTH_SHORT).show()

                else -> {
                    val book = Book(name.text.toString(), author.text.toString(), genre.text.toString())
                    MyDatabase.insertData(MyDatabase.getInstance(this), book)
                    dialog.dismiss()
                    Handler().postDelayed({
                        list.clear()
                        list.addAll(MyDatabase.getData(MyDatabase.getInstance(this)))
                        adapter.notifyDataSetChanged()
                    }, 1000)
                }
            }
        }
        dialog.show()
        dialog.getWindow().setAttributes(lp)
    }
}

Run Application:




Comments

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

RecyclerView with different number of columns using SpanSizeLookup

Using Camera in Android - Kotlin

Databinding in RecyclerView - Android - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Room with LiveData, ViewModel - Android Architecture Components - Kotlin

Stripe Integration in Android - Kotlin

Braintree Integration in Android - Kotlin

Exploring Android Slices - JetPack

Android JetPack - Scheduling Tasks with WorkManager