পূর্ব প্রস্তুতি
এই সম्पূর্ণ উদাহরণটি 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 দিয়ে ডেটাবেস অপারেশন ব্যাকগ্রাউন্ডে করুন।