Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách sử dụng RecyclerView kết hợp Database (SQLite) trong Android

Giới thiệu

Ví dụ này minh họa cách xây dựng ứng dụng quản lý danh bạ (Contacts) hoàn chỉnh: hiển thị danh sách trên RecyclerView, thêm/sửa/xóa dữ liệu lưu trữ trong SQLite database.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project. Thêm dependencies:

dependencies {
    implementation "androidx.recyclerview:recyclerview:1.3.2"
    implementation "androidx.cardview:cardview:1.0.0"
    implementation "com.google.android.material:material:1.11.0"
}

Bước 2: Giao diện chính (res/layout/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="12dp"
        android:text="THÊM" />

</FrameLayout>

Bước 3: Layout thêm/sửa liên lạc (res/layout/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="Nhập tên"
        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="Nhập số điện thoại"
        android:inputType="phone"
        android:maxLines="1"
        android:padding="12dp"
        android:textSize="16sp" />

</LinearLayout>

Bước 4: Layout item RecyclerView (res/layout/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="1dp"
    android:orientation="vertical">

    <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"
                android:textColor="#666" />

        </RelativeLayout>

        <ImageView
            android:id="@+id/editContact"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_gravity="center_vertical"
            android:src="@android:drawable/ic_menu_edit"
            android:contentDescription="Sửa" />

        <ImageView
            android:id="@+id/deleteContact"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:src="@android:drawable/ic_delete"
            android:contentDescription="Xóa" />

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

Bước 5: Model Contacts (Contacts.java)

package com.app.sample;

public class Contacts {
    private int id;
    private String name;
    private String phoneNumber;

    public Contacts(String name, String phno) {
        this.name = name;
        this.phoneNumber = phno;
    }

    public Contacts(int id, String name, String phno) {
        this.id = id;
        this.name = name;
        this.phoneNumber = phno;
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getPhno() { return phoneNumber; }
    public void setPhno(String phno) { this.phoneNumber = phno; }
}

Bước 6: ViewHolder (ContactViewHolder.java)

package com.app.sample;

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

class ContactViewHolder extends RecyclerView.ViewHolder {
    TextView tvName, tvPhoneNum;
    ImageView deleteContact, editContact;

    ContactViewHolder(View itemView) {
        super(itemView);
        tvName = itemView.findViewById(R.id.contactName);
        tvPhoneNum = itemView.findViewById(R.id.phoneNum);
        deleteContact = itemView.findViewById(R.id.deleteContact);
        editContact = itemView.findViewById(R.id.editContact);
    }
}

Bước 7: Database Helper (SqliteDatabase.java)

package com.app.sample;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;

public class SqliteDatabase extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "ContactsDB";
    private static final String TABLE_CONTACTS = "contacts";
    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_NAME = "contactName";
    private static final String COLUMN_NO = "phoneNumber";

    public SqliteDatabase(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String CREATE_CONTACTS_TABLE = "CREATE TABLE " + TABLE_CONTACTS + "("
                + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + COLUMN_NAME + " TEXT,"
                + COLUMN_NO + " TEXT" + ")";
        db.execSQL(CREATE_CONTACTS_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS);
        onCreate(db);
    }

    public ArrayList<Contacts> listContacts() {
        ArrayList<Contacts> storeContacts = new ArrayList<>();
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_CONTACTS, null);
        if (cursor.moveToFirst()) {
            do {
                int id = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_ID));
                String name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME));
                String phno = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NO));
                storeContacts.add(new Contacts(id, name, phno));
            } while (cursor.moveToNext());
        }
        cursor.close();
        return storeContacts;
    }

    public void addContacts(Contacts contacts) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, contacts.getName());
        values.put(COLUMN_NO, contacts.getPhno());
        db.insert(TABLE_CONTACTS, null, values);
    }

    public void updateContacts(Contacts contacts) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, contacts.getName());
        values.put(COLUMN_NO, contacts.getPhno());
        db.update(TABLE_CONTACTS, values, COLUMN_ID + " = ?", new String[]{String.valueOf(contacts.getId())});
    }

    public void deleteContact(int id) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete(TABLE_CONTACTS, COLUMN_ID + " = ?", new String[]{String.valueOf(id)});
    }
}

Bước 8: Adapter với Filter (ContactAdapter.java)

package com.app.sample;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
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.ArrayList;
import java.util.Objects;

class ContactAdapter extends RecyclerView.Adapter<ContactViewHolder> implements Filterable {
    private Context context;
    private ArrayList<Contacts> listContacts;
    private ArrayList<Contacts> mArrayList;
    private SqliteDatabase mDatabase;

    ContactAdapter(Context context, ArrayList<Contacts> listContacts) {
        this.context = context;
        this.listContacts = listContacts;
        this.mArrayList = new ArrayList<>(listContacts);
        mDatabase = new SqliteDatabase(context);
    }

    @Override
    public ContactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_list_layout, parent, false);
        return new ContactViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ContactViewHolder holder, int position) {
        Contacts contacts = listContacts.get(position);
        holder.tvName.setText(contacts.getName());
        holder.tvPhoneNum.setText(contacts.getPhno());

        holder.editContact.setOnClickListener(v -> editTaskDialog(contacts));
        holder.deleteContact.setOnClickListener(v -> {
            mDatabase.deleteContact(contacts.getId());
            ((Activity) context).recreate(); // Tải lại Activity
        });
    }

    @Override
    public int getItemCount() { return listContacts.size(); }

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String charString = charSequence.toString().toLowerCase().trim();
                ArrayList<Contacts> filteredList = new ArrayList<>();
                if (charString.isEmpty()) {
                    filteredList.addAll(mArrayList);
                } else {
                    for (Contacts contacts : mArrayList) {
                        if (contacts.getName().toLowerCase().contains(charString)) {
                            filteredList.add(contacts);
                        }
                    }
                }
                FilterResults results = new FilterResults();
                results.values = filteredList;
                return results;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                listContacts.clear();
                listContacts.addAll((ArrayList<Contacts>) filterResults.values);
                notifyDataSetChanged();
            }
        };
    }

    private void editTaskDialog(final Contacts contacts) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View subView = inflater.inflate(R.layout.add_contacts, null);
        EditText nameField = subView.findViewById(R.id.enterName);
        EditText contactField = subView.findViewById(R.id.enterPhoneNum);

        if (contacts != null) {
            nameField.setText(contacts.getName());
            contactField.setText(contacts.getPhno());
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Sửa liên lạc");
        builder.setView(subView);
        builder.setPositiveButton("LƯU", (dialog, which) -> {
            String name = nameField.getText().toString().trim();
            String ph_no = contactField.getText().toString().trim();
            if (TextUtils.isEmpty(name)) {
                Toast.makeText(context, "Tên không được trống", Toast.LENGTH_SHORT).show();
            } else {
                mDatabase.updateContacts(new Contacts(contacts.getId(), name, ph_no));
                ((Activity) context).recreate();
            }
        });
        builder.setNegativeButton("HỦY", (dialog, which) -> Toast.makeText(context, "Đã hủy", Toast.LENGTH_SHORT).show());
        builder.show();
    }
}

Bước 9: MainActivity (src/MainActivity.java)

package com.app.sample;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.DialogInterface;
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 java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    private SqliteDatabase mDatabase;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView contactView = findViewById(R.id.myContactList);
        contactView.setLayoutManager(new LinearLayoutManager(this));
        contactView.setHasFixedSize(true);

        mDatabase = new SqliteDatabase(this);
        loadContacts();

        Button btnAdd = findViewById(R.id.btnAdd);
        btnAdd.setOnClickListener(v -> addTaskDialog());
    }

    private void loadContacts() {
        ArrayList<Contacts> allContacts = mDatabase.listContacts();
        RecyclerView contactView = findViewById(R.id.myContactList);
        if (allContacts.size() > 0) {
            contactView.setVisibility(View.VISIBLE);
            ContactAdapter mAdapter = new ContactAdapter(this, allContacts);
            contactView.setAdapter(mAdapter);
        } else {
            contactView.setVisibility(View.GONE);
            Toast.makeText(this, "Chưa có liên lạc. Nhấn THÊM để bắt đầu.", Toast.LENGTH_LONG).show();
        }
    }

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

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Thêm liên lạc mới");
        builder.setView(subView);
        builder.setPositiveButton("THÊM", (dialog, which) -> {
            String name = nameField.getText().toString().trim();
            String ph_no = noField.getText().toString().trim();
            if (TextUtils.isEmpty(name)) {
                Toast.makeText(MainActivity.this, "Tên không được trống", Toast.LENGTH_LONG).show();
            } else {
                mDatabase.addContacts(new Contacts(name, ph_no));
                recreate();
            }
        });
        builder.setNegativeButton("HỦY", (dialog, which) -> Toast.makeText(MainActivity.this, "Đã hủy", Toast.LENGTH_SHORT).show());
        builder.show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDatabase != null) mDatabase.close();
    }
}

Bước 10: Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">
    <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>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Nhấn nút THÊM để thêm liên lạc, nhấn icon sửa/xóa trên mỗi item.

Cách sử dụng RecyclerView kết hợp Database (SQLite) trong Android

Cách sử dụng RecyclerView kết hợp Database (SQLite) trong Android

Cải tiến cho ứng dụng thực tế

  • Dùng Room Database (Android Jetpack) thay vì SQLite thuần - type-safe, compile-time check, LiveData/Flow support.
  • Dùng ViewBinding hoặc DataBinding thay vì findViewById.
  • Triển khai DiffUtil trong Adapter để animation mượt mà khi cập nhật danh sách.
  • Thêm tìm kiếm (SearchView) trên Toolbar kết hợp Filterable.
  • Xử lý permission READ_CONTACTS nếu muốn đồng bộ với danh bạ hệ thống.