কম্পিউটার টিউটোরিয়াল

Kotlin-এ Android-এ RecyclerView এবং ডেটাবেস (SQLite) একসাথে ব্যবহার করার পদ্ধতি

পূর্ব প্রস্তুতি

এই সম्पূর্ণ উদাহরণটি SQLite ডেটাবেস, RecyclerView, CardView, এবং AlertDialog ব্যবহার করে একটি কন্টাক্ট ম্যানেজমেন্ট অ্যাপ তৈরি করার পদ্ধতি দেখায়।

ধাপ ১: নতুন প্রজেক্ট তৈরি করুন

Android Studio-তে File → New Project নির্বাচন করুন। build.gradle-এ Material Components নির্ভরতা যোগ করুন:

dependencies {
    implementation "androidx.recyclerview:recyclerview:1.2.1"
    implementation "androidx.cardview:cardview:1.0.0"
    implementation "com.google.android.material:material:1.4.0"
}

ধাপ ২: activity_main.xml লেআউট

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/myContactList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="50dp"
        android:paddingBottom="8dp" />

    <Button
        android:id="@+id/btnAdd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="4dp"
        android:padding="2dp"
        android:text="ADD" />

</FrameLayout>

ধাপ ৩: MainActivity.kt

import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {
    private lateinit var dataBase: SqliteDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "KotlinApp"

        val contactView: RecyclerView = findViewById(R.id.myContactList)
        contactView.layoutManager = LinearLayoutManager(this)
        contactView.setHasFixedSize(true)

        dataBase = SqliteDatabase(this)
        loadContacts()

        val btnAdd: Button = findViewById(R.id.btnAdd)
        btnAdd.setOnClickListener { addTaskDialog() }
    }

    private fun loadContacts() {
        val allContacts = dataBase.listContacts()
        val contactView: RecyclerView = findViewById(R.id.myContactList)

        if (allContacts.isNotEmpty()) {
            contactView.visibility = View.VISIBLE
            val adapter = ContactAdapter(this, allContacts)
            contactView.adapter = adapter
        } else {
            contactView.visibility = View.GONE
            Toast.makeText(this, "No contacts. Add new one.", Toast.LENGTH_LONG).show()
        }
    }

    private fun addTaskDialog() {
        val inflater = LayoutInflater.from(this)
        val subView = inflater.inflate(R.layout.add_contacts, null)
        val nameField: EditText = subView.findViewById(R.id.enterName)
        val noField: EditText = subView.findViewById(R.id.enterPhoneNum)

        AlertDialog.Builder(this)
            .setTitle("Add new CONTACT")
            .setView(subView)
            .setPositiveButton("ADD CONTACT") { _, _ ->
                val name = nameField.text.toString()
                val phoneNum = noField.text.toString()
                if (TextUtils.isEmpty(name)) {
                    Toast.makeText(this, "Check input values", Toast.LENGTH_LONG).show()
                } else {
                    dataBase.addContacts(Contacts(name, phoneNum))
                    recreate() // লিস্ট রিফ্রেশ
                }
            }
            .setNegativeButton("CANCEL") { _, _ -> Toast.makeText(this, "Cancelled", Toast.LENGTH_SHORT).show() }
            .show()
    }

    override fun onDestroy() {
        super.onDestroy()
        dataBase.close()
    }
}

ধাপ ৪: ডেটা ক্লাস ও হেল্পার ক্লাসেস

Contacts.kt

class Contacts {
    var id = 0
    var name: String = ""
    var phno: String = ""

    constructor(name: String, phno: String) {
        this.name = name
        this.phno = phno
    }

    constructor(id: Int, name: String, phno: String) {
        this.id = id
        this.name = name
        this.phno = phno
    }
}

ContactViewHolder.kt

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ContactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var tvName: TextView = itemView.findViewById(R.id.contactName)
    var tvPhoneNum: TextView = itemView.findViewById(R.id.phoneNum)
    var deleteContact: ImageView = itemView.findViewById(R.id.deleteContact)
    var editContact: ImageView = itemView.findViewById(R.id.editContact)
}

ContactAdapter.kt

import android.app.Activity
import android.content.Context
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Filter
import android.widget.Filterable
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import java.util.*

internal class ContactAdapter(
    private val context: Context,
    private var listContacts: ArrayList<Contacts>
) : RecyclerView.Adapter<ContactViewHolder>(), Filterable {

    private val mArrayList = ArrayList<Contacts>()
    private val mDatabase: SqliteDatabase

    init {
        mArrayList.addAll(listContacts)
        mDatabase = SqliteDatabase(context)
    }

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

    override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
        val contact = listContacts[position]
        holder.tvName.text = contact.name
        holder.tvPhoneNum.text = contact.phno

        holder.editContact.setOnClickListener { editTaskDialog(contact) }
        holder.deleteContact.setOnClickListener {
            mDatabase.deleteContact(contact.id)
            (context as Activity).recreate()
        }
    }

    override fun getFilter(): Filter = object : Filter() {
        override fun performFiltering(charSequence: CharSequence): FilterResults {
            val charString = charSequence.toString()
            val filtered = if (charString.isEmpty()) {
                mArrayList
            } else {
                mArrayList.filter { it.name.toLowerCase().contains(charString.toLowerCase()) }.toCollection(ArrayList())
            }
            return FilterResults().apply { values = filtered }
        }

        override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) {
            listContacts = filterResults.values as ArrayList<Contacts>
            notifyDataSetChanged()
        }
    }

    override fun getItemCount() = listContacts.size

    private fun editTaskDialog(contact: Contacts) {
        val inflater = LayoutInflater.from(context)
        val subView = inflater.inflate(R.layout.add_contacts, null)
        val nameField: EditText = subView.findViewById(R.id.enterName)
        val contactField: EditText = subView.findViewById(R.id.enterPhoneNum)
        nameField.setText(contact.name)
        contactField.setText(contact.phno)

        AlertDialog.Builder(context)
            .setTitle("Edit contact")
            .setView(subView)
            .setPositiveButton("EDIT CONTACT") { _, _ ->
                val name = nameField.text.toString()
                val phNo = contactField.text.toString()
                if (TextUtils.isEmpty(name)) {
                    Toast.makeText(context, "Check input", Toast.LENGTH_LONG).show()
                } else {
                    mDatabase.updateContacts(Contacts(contact.id, name, phNo))
                    (context as Activity).recreate()
                }
            }
            .setNegativeButton("CANCEL") { _, _ -> Toast.makeText(context, "Cancelled", Toast.LENGTH_LONG).show() }
            .show()
    }
}

SqliteDatabase.kt

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import java.util.*

class SqliteDatabase(context: Context?) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    override fun onCreate(db: SQLiteDatabase) {
        val sql = "CREATE TABLE $TABLE_CONTACTS ($COLUMN_ID INTEGER PRIMARY KEY, $COLUMN_NAME TEXT, $COLUMN_NO TEXT)"
        db.execSQL(sql)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("DROP TABLE IF EXISTS $TABLE_CONTACTS")
        onCreate(db)
    }

    fun listContacts(): ArrayList<Contacts> {
        val list = ArrayList<Contacts>()
        val db = readableDatabase
        val cursor = db.rawQuery("SELECT * FROM $TABLE_CONTACTS", null)
        if (cursor.moveToFirst()) {
            do {
                val id = cursor.getInt(0)
                val name = cursor.getString(1)
                val phno = cursor.getString(2)
                list.add(Contacts(id, name, phno))
            } while (cursor.moveToNext())
        }
        cursor.close()
        return list
    }

    fun addContacts(contact: Contacts) {
        val values = ContentValues().apply {
            put(COLUMN_NAME, contact.name)
            put(COLUMN_NO, contact.phno)
        }
        writableDatabase.insert(TABLE_CONTACTS, null, values)
    }

    fun updateContacts(contact: Contacts) {
        val values = ContentValues().apply {
            put(COLUMN_NAME, contact.name)
            put(COLUMN_NO, contact.phno)
        }
        writableDatabase.update(TABLE_CONTACTS, values, "$COLUMN_ID = ?", arrayOf(contact.id.toString()))
    }

    fun deleteContact(id: Int) {
        writableDatabase.delete(TABLE_CONTACTS, "$COLUMN_ID = ?", arrayOf(id.toString()))
    }

    companion object {
        private const val DATABASE_VERSION = 1
        private const val DATABASE_NAME = "ContactsDB"
        private const val TABLE_CONTACTS = "Contacts"
        private const val COLUMN_ID = "_id"
        private const val COLUMN_NAME = "contactName"
        private const val COLUMN_NO = "phoneNumber"
    }
}

ধাপ ৫: লেআউট রিসোর্স ফাইলসমূহ

add_contacts.xml

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

    <EditText
        android:id="@+id/enterName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter Name"
        android:inputType="text"
        android:maxLines="1"
        android:padding="12dp"
        android:textSize="16sp" />

    <EditText
        android:id="@+id/enterPhoneNum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="Enter Number"
        android:inputType="phone"
        android:maxLines="1"
        android:padding="12dp"
        android:textSize="16sp" />

</LinearLayout>

contact_list_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="12dp">

        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1">

            <TextView
                android:id="@+id/contactName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/phoneNum"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/contactName"
                android:textSize="14sp" />

        </RelativeLayout>

        <ImageView
            android:id="@+id/editContact"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_gravity="center_vertical"
            android:src="@drawable/ic_baseline_edit_24" />

        <ImageView
            android:id="@+id/deleteContact"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="8dp"
            android:src="@drawable/ic_baseline_delete_24" />

    </LinearLayout>
</androidx.cardview.widget.CardView>

ধাপ ৬: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.recyclerviewdb">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

পরীক্ষা করুন

অ্যাপ চালান। "ADD" বাটনে ক্লিক করে কন্টাক্ট যোগ করুন। লিস্টে দেখাবে। প্রতিটি আইটেমে এডিট/ডিলিট আইকন থাকবে। উপরে সার্চ বক্স যোগ করলে `Filterable` কাজ করবে।

সর্বোত্তম অনুশীলন

  • Room Database ব্যবহার করুন (SQLite-এর আধুনিক আবস্ট্রাকশন)।
  • ViewBinding বা DataBinding ব্যবহার করুন।
  • Coroutine/Flow দিয়ে ডেটাবেস অপারেশন ব্যাকগ্রাউন্ডে করুন।
  1. RecyclerView-এ ConstraintLayout ব্যবহারের পদ্ধতি

  2. RecyclerView-এ CardView ব্যবহারের পদ্ধতি

  3. অ্যান্ড্রয়েডে ডাটাবেস সহ RecyclerView কীভাবে ব্যবহার করবেন?

  4. Android অ্যাপ্লিকেশনে SQLite ডেটাবেস ব্যবহারের পূর্ণাঙ্গ গাইড