DEV Community

HMS Community
HMS Community

Posted on

Manage the Budget using Room Database in Money Management Android app (Kotlin) – Part 3

Image description
Introduction
In this article, we can learn how to manage your money using this Money Management app. User can add Income and Expenses in this app, so the data will be saved in room database, it can access in offline also. User can easily track their daily spending's and can take the preventive actions on the unnecessary expenses, so that they can save money and can invest in profitable return ways. In this app, user can add, update, delete and fetch operations.

So, I will provide the series of articles on this Money Management App, in upcoming articles I will integrate other Huawei Kits.

If you are new to this application, follow my previous articles.

Beginner: Find the introduction Sliders and Huawei Account Kit Integration in Money Management Android app (Kotlin) - Part 1

Beginner: Integration of Huawei Ads Kit and Analytics Kit in Money Management Android app (Kotlin) – Part 2

Components of Room DB

  1. Entity

  2. Dao

  3. Database

  • Entity

Represents a table within the database. Room creates a table for each class that has @Entity annotation, the fields in the class correspond to columns in the table. Therefore, the entity classes tend to be small model classes that does not contain any logic.

  • Dao

DAOs(Data Access Objects) are responsible for defining the methods that access the database. In the initial SQLite, we use the Cursor objects. With Room, we do not need all the Cursor related code and can simply define our queries using annotations in the Dao class.

  • Database

Contains the database holder and serves as the main access point for the underlying connection to your app's persisted, relational data.

To create a database, we need to define an abstract class that extends RoomDatabase. This class is annotated with @Database, lists the entities contained in the database, and the DAOs which access them.

Requirements

  1. Any operating system (MacOS, Linux and Windows).
  2. Must have a Huawei phone with HMS 4.0.0.300 or later.
  3. Must have a laptop or desktop with Android Studio, Jdk 1.8, SDK platform 26 and Gradle 4.6 and above installed.
  4. Minimum API Level 24 is required.
  5. Required EMUI 9.0.0 and later version devices.

How to integrate HMS Dependencies

  • First register as Huawei developer and complete identity verification in Huawei developers website, refer to register a Huawei ID.
  • Create a project in android studio, refer Creating an Android Studio Project.
  • Generate a SHA-256 certificate fingerprint.
  • To generate SHA-256 certificate fingerprint. On right-upper corner of android project click Gradle, choose Project Name > Tasks > android, and then click signingReport, as follows.

Image description

Note: Project Name depends on the user created name.

  • Create an App in AppGallery Connect.
  • Download the agconnect-services.json file from App information, copy and paste in android Project under app directory, as follows. Image description
  • Enter SHA-256 certificate fingerprint and click Save button, as follows.
    Image description

  • Add the below maven URL in build.gradle(Project) file under the repositories of buildscript, dependencies and allprojects, refer Add Configuration.

maven { url 'http://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.6.0.300'

Enter fullscreen mode Exit fullscreen mode
  • Add the below plugin and dependencies in build.gradle(Module) file.
apply plugin: id 'com.huawei.agconnect'
apply plugin: id 'kotlin-kapt'
// Huawei AGC
implementation 'com.huawei.agconnect:agconnect-core:1.6.0.300'
// Room Database
implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
androidTestImplementation "androidx.room:room-testing:2.4.2"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
// Recyclerview
implementation 'androidx.recyclerview:recyclerview:1.2.1'
Enter fullscreen mode Exit fullscreen mode
  • Now Sync the gradle.

Let us move to development
I have created a project on Android studio with empty activity let us start coding.
Create a Transaction.kt class annotated with @Entity to create a table for each class.

@Entity(tableName = "transactions")
data class Transaction(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val label: String,
    val amount: Double,
    val description: String): Serializable{}
Enter fullscreen mode Exit fullscreen mode

Create a TransactionDao.kt interface class annotated with @dao and responsible for defining the methods that access the database.

@Dao
interface TransactionDao {

    @Query("SELECT * from transactions")
    fun getAll(): List<Transaction>

    @Insert
    fun insertAll(vararg transaction: Transaction)

    @Delete
    fun delete(vararg transaction: Transaction)

    @Update
    fun update(vararg transaction: Transaction)

}
Enter fullscreen mode Exit fullscreen mode

Create a AppDatabase.kt abstract class that extends **RoomDatabase **annotated with @Database to lists the entities contained in the database, and the DAOs which access them.

@Database(entities = [Transaction::class], version = 2)
abstract class AppDatabase : RoomDatabase() {

    abstract fun transactionDao(): TransactionDao

}
Enter fullscreen mode Exit fullscreen mode

In the TransactionActivity.kt activity to find the business logic for entire dashboard.

class TransactionActivity : AppCompatActivity() {

    private lateinit var deletedTransaction: Transaction
    private lateinit var oldtransactions: List<Transaction>
    private lateinit var transactions: List<Transaction>
    private lateinit var transactionAdapter: TransactionAdapter
    private lateinit var linearLayoutManager: LinearLayoutManager
    private lateinit var db: AppDatabase

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

        transactions = arrayListOf()
        transactionAdapter = TransactionAdapter(transactions)
        linearLayoutManager = LinearLayoutManager(this)
        trans_recycler_view.apply {
            adapter = transactionAdapter
            layoutManager = linearLayoutManager
        }
        // Swipe to remove
        val itemTouchHelper = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT){
            override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
                return false
            }
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                deleteTransaction(transactions[viewHolder.adapterPosition])
            }
        }

        val swipeHelper = ItemTouchHelper(itemTouchHelper)
        swipeHelper.attachToRecyclerView(trans_recycler_view)

        btn_float.setOnClickListener {
            val intent = Intent(this, AddTransactionActivity::class.java)
            startActivity(intent)
        }
        // Room database
        db = Room.databaseBuilder(this,AppDatabase::class.java,"transactions").build()

    }

    @SuppressLint("SetTextI18n")
    private fun updateAmount(){
        val totalAmount: Double = transactions.map {it.amount}.sum()
        val budgetAmount: Double = transactions.filter {it.amount > 0}.map {it.amount}.sum()
        val expenseAmount: Double = totalAmount - budgetAmount
        balance.text = "RS %.2f".format(totalAmount)
        budget.text = "RS %.2f".format(budgetAmount)
        expense.text = "RS %.2f".format(expenseAmount)
    }

    // Fetch Transactions form Room Database
    private fun fetchAll(){
        GlobalScope.launch {
            transactions = db.transactionDao().getAll()
            runOnUiThread {
                updateAmount()
                transactionAdapter.setData(transactions)
            }
        }
    }

    private fun deleteTransaction(transaction: Transaction){
        deletedTransaction = transaction
        oldtransactions = transactions
        GlobalScope.launch {
           db.transactionDao().delete(transaction)
            transactions = transactions.filter {it.id != transaction.id}
            runOnUiThread {
                updateAmount()
                transactionAdapter.setData(transactions)

            }
        }
        Toast.makeText(this, "Item Deleted", Toast.LENGTH_SHORT).show()
    }

    override fun onResume() {
        super.onResume()
        fetchAll()
    }

}
Enter fullscreen mode Exit fullscreen mode

Create a TransactionAdapter.kt adapter class to hold the list.

class TransactionAdapter(private var transactions: List<Transaction>):
    RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.transcation_list, parent, false)
        return TransactionViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
        val transaction: Transaction = transactions[position]
        val context: Context = holder.amount.context
        if(transaction.amount >= 0){
           holder.amount.text = " RS %.2f".format(transaction.amount)
           holder.amount.setTextColor(ContextCompat.getColor(context,R.color.Green))
        } else {
           holder.amount.text = " RS %.2f".format(transaction.amount)
           holder.amount.setTextColor(ContextCompat.getColor(context,R.color.Red))
        }
        holder.label.text = transaction.label

        holder.itemView.setOnClickListener {
           val intent = Intent(context, DetailedActivity::class.java)
           intent.putExtra("transaction", transaction)
           context.startActivity(intent)
        }
    }

    override fun getItemCount(): Int {
        return transactions.size
    }

   inner class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val label: TextView = itemView.findViewById(R.id.txt_label)
        val amount: TextView = itemView.findViewById(R.id.txt_amount)

    }

    fun setData(transactions: List<Transaction>){
        this.transactions = transactions
        notifyDataSetChanged()
    }

}
Enter fullscreen mode Exit fullscreen mode

In the AddTransactionActivity.kt activity to find the business logic to add items.

class AddTransactionActivity : AppCompatActivity() {

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

        btn_add.setOnClickListener {
            val label = edt_label.text.toString()
            val amount = edt_amount.text.toString().toDoubleOrNull()
            val description = edt_desc.text.toString()

            if(label.isBlank()) {
               Toast.makeText(this, "Enter the label", Toast.LENGTH_SHORT).show()
            }
            else if(amount == null) {
                Toast.makeText(this, "Enter the valid amount", Toast.LENGTH_SHORT).show()
            }
            else {
                 val transaction = Transaction(0, label, amount, description)
                 insert(transaction)
                Toast.makeText(this, "Saved Content", Toast.LENGTH_SHORT).show()
            }
        }

    }

    private fun insert(transaction: Transaction) {
        val db: AppDatabase = Room.databaseBuilder(this,AppDatabase::class.java,"transactions").build()
        GlobalScope.launch {
             db.transactionDao().insertAll(transaction)
             finish()
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

In the DetailedActivity.kt activity to find the business logic for updating the items.

class DetailedActivity : AppCompatActivity() {

   private lateinit var transaction: Transaction

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

        transaction = intent.getSerializableExtra("transaction") as Transaction
        update_label.setText(transaction.label)
        update_amount.setText(transaction.amount.toString())

        btn_update.setOnClickListener {
            val label = update_label.text.toString()
            val amount = update_amount.text.toString().toDoubleOrNull()
            val description = update_desc.text.toString()

            if(label.isBlank()) {
                Toast.makeText(this, "Enter the label", Toast.LENGTH_SHORT).show()
            }
            else if(amount == null) {
                Toast.makeText(this, "Enter the valid amount", Toast.LENGTH_SHORT).show()
            }
            else {
                val transaction = Transaction(transaction.id, label, amount, description)
                update(transaction)
                Toast.makeText(this, "Saved Content", Toast.LENGTH_SHORT).show()
            }
        }

    }

    private fun update(transaction: Transaction) {
        val db: AppDatabase = Room.databaseBuilder(this,AppDatabase::class.java,"transactions").build()
        GlobalScope.launch {
            db.transactionDao().update(transaction)
            finish()
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

In the activity_transcation.xml we can create the UI screen for Dashboard.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".room.TransactionActivity">

    <LinearLayout
        android:id="@+id/balance_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:layout_marginStart="10dp"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Total Balance:"
            android:textAllCaps="false"
            android:textSize="20sp"
            android:textStyle="bold" />
        <TextView
            android:id="@+id/balance"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="RS.xx"
            android:textColor="@color/black"
            android:textSize="20sp"
            android:textStyle="bold" />
    </LinearLayout>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/cardview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        app:cardCornerRadius="12dp"
        android:layout_below="@+id/balance_layout">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:layout_weight="0.5"
                android:layout_gravity="center">
                <TextView
                    android:id="@+id/budget"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="RS.xx"
                    android:textAllCaps="false"
                    android:textSize="24sp"
                    android:textColor="@color/Green"
                    android:textStyle="bold" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Budget "
                    android:textSize="16sp"
                    android:textStyle="bold" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:layout_gravity="center"
                android:layout_weight="0.5">
                <TextView
                    android:id="@+id/expense"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="RS.xx"
                    android:textColor="@color/Red"
                    android:textAllCaps="false"
                    android:textSize="24sp"
                    android:textStyle="bold" />
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Expense "
                    android:textSize="16sp"
                    android:textStyle="bold" />
            </LinearLayout>
        </LinearLayout>
    </com.google.android.material.card.MaterialCardView>

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Transcations"
        android:textAllCaps="false"
        android:textSize="18sp"
        android:layout_marginTop="14dp"
        android:layout_marginStart="10dp"
        android:layout_below="@+id/cardview"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/trans_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/title"
        android:layout_marginTop="10dp"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/btn_float"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="10dp"
        android:layout_marginBottom="10dp"
        android:backgroundTint="@color/Red"
        android:clickable="true"
        android:contentDescription="TODO"
        app:borderWidth="0dp"
        app:srcCompat="@android:drawable/ic_input_add" />

</RelativeLayout>
Enter fullscreen mode Exit fullscreen mode

In the activity_add_transaction.xml we can create the UI screen for adding items.

<?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=".room.AddTransactionActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:ignore="MissingConstraints">
        <EditText
            android:id="@+id/edt_label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:hint="Label "
            app:layout_constraintTop_toTopOf="parent" />
        <EditText
            android:id="@+id/edt_amount"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Amount: "
            android:inputType="numberSigned"
            app:layout_constraintTop_toBottomOf="@id/edt_label" />
        <EditText
            android:id="@+id/edt_desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Description: "
            app:layout_constraintTop_toBottomOf="@id/edt_amount" />
        <Button
            android:id="@+id/btn_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:textSize="18sp"
            android:layout_gravity="center"
            android:text="Add"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/edt_desc" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Enter fullscreen mode Exit fullscreen mode

In the activity_detailed.xml we can create the UI screen for updating items.

<?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=".room.DetailedActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:ignore="MissingConstraints">
        <EditText
            android:id="@+id/update_label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:hint="Label "
            app:layout_constraintTop_toTopOf="parent" />
        <EditText
            android:id="@+id/update_amount"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Amount: "
            android:inputType="numberSigned"
            app:layout_constraintTop_toBottomOf="@id/edt_label" />
        <EditText
            android:id="@+id/update_desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Description: "
            app:layout_constraintTop_toBottomOf="@id/edt_amount" />
        <Button
            android:id="@+id/btn_update"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:textSize="18sp"
            android:layout_gravity="center"
            android:text="Update"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/edt_desc" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Enter fullscreen mode Exit fullscreen mode

In the transcation_list.xml we can create the UI screen for customized items.

<?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="40dp"
    android:layout_gravity="center_vertical">

    <TextView
        android:id="@+id/txt_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Food"
        android:textAllCaps="false"
        android:layout_marginStart="6dp"
        android:textSize="20sp"
        android:layout_weight="1"/>
    <TextView
        android:id="@+id/txt_amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RS.12"
        android:layout_marginEnd="12dp"
        android:textSize="20sp"/>

</LinearLayout>
Enter fullscreen mode Exit fullscreen mode

Demo
Image description

Image description

Tips and Tricks

  1. Make sure you are already registered as Huawei developer.
  2. Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.
  3. Make sure you have added the agconnect-services.json file to app folder.
  4. Make sure you have added SHA-256 fingerprint without fail.
  5. Make sure all the dependencies are added properly.

Conclusion
In this article, we have learned the integration of Room database. Also we have learnt about the room database and its components such as DAO, Entity and Database. How to create, read, update and delete the content in room database and which helps the user to access the data when they are in offline.

I hope you have read this article. If you found it is helpful, please provide likes and comments.

Reference
Room Database

Top comments (0)