lOMoARcPSD| 60752940
Thực hành ngày 09/092025
Xây dựng ứng dụng Quản lý Danh sách Ghi chú (Notes App) với Android Jetpack
Mục tiêu
Làm quen với kiến trúc MVVM trong Android Jetpack.
Thực hành các thành phần: Room, ViewModel, LiveData, RecyclerView.
Biết cách tách biệt UI – ViewModel – Repository – Database.
Yêu cầu chức năng
1. Người dùng nhập ghi chú mới (Note).
2. Lưu ghi chú vào Room Database.
3. Hiển thị toàn bộ ghi chú bằng RecyclerView.
4. Dữ liệu hiển thị tự động cập nhật khi thêm/sửa/xóa nhờ LiveData.
5. Có nút Xóa toàn bộ ghi chú. Cấu trúc Project app/
├── MainActivity.java // UI chính: nhập ghi chú, hiển thị danh sách
├── adapter/
│ └── NoteAdapter.java // Adapter cho RecyclerView
├── data/
│ ├── Note.java // Entity
│ ├── NoteDao.java // DAO
│ ├── NoteDatabase.java // Room Database
│ └── NoteRepository.java // Repository
└── viewmodel/
└── NoteViewModel.java // ViewModel + LiveData
1. Layouts activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:padding="16dp">
lOMoARcPSD| 60752940
<EditText android:id="@+id/edtNote"
android:hint="Nhập ghi chú..."
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/btnAdd"
android:text="Thêm ghi chú"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/btnClear"
android:text="Xóa tất cả"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/> </LinearLayout>
2. Tạo Entity (Note.java)
@Entity(tableName = "notes") public
class Note {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "content")
public String content;
lOMoARcPSD| 60752940
public Note(String content) {
this.content = content;
}
}
3. Tạo DAO (NoteDao.java)
@Dao
public interface NoteDao {
@Insert void
insert(Note note);
@Query("SELECT * FROM notes ORDER BY id DESC")
LiveData<List<Note>> getAllNotes();
@Query("DELETE FROM notes")
void deleteAll();
}
4. Room Database (NoteDatabase.java)
@Database(entities = {Note.class}, version = 1)
public abstract class NoteDatabase extends RoomDatabase { private
static NoteDatabase instance; public abstract NoteDao noteDao();
public static synchronized NoteDatabase getInstance(Context context) {
if (instance == null) { instance =
Room.databaseBuilder(context.getApplicationContext(),
NoteDatabase.class, "note_database")
.fallbackToDestructiveMigration()
.build();
lOMoARcPSD| 60752940
} return
instance;
}
}
5. Repository (NoteRepository.java)
public class NoteRepository { private
NoteDao noteDao; private
LiveData<List<Note>> allNotes;
public NoteRepository(Application application) {
NoteDatabase db = NoteDatabase.getInstance(application);
noteDao = db.noteDao(); allNotes = noteDao.getAllNotes();
}
public void insert(Note note) {
Executors.newSingleThreadExecutor().execute(() -> noteDao.insert(note));
}
public void deleteAll() {
Executors.newSingleThreadExecutor().execute(() -> noteDao.deleteAll());
}
public LiveData<List<Note>> getAllNotes() {
return allNotes;
}
}
6. ViewModel (NoteViewModel.java) public class
NoteViewModel extends AndroidViewModel { private
lOMoARcPSD| 60752940
NoteRepository repository; private
LiveData<List<Note>> allNotes;
public NoteViewModel(@NonNull Application application) {
super(application); repository = new
NoteRepository(application); allNotes =
repository.getAllNotes();
}
public void insert(Note note) {
repository.insert(note);
}
public void deleteAll() {
repository.deleteAll();
}
public LiveData<List<Note>> getAllNotes() {
return allNotes;
}
}
7. Adapter (NoteAdapter.java) public class NoteAdapter extends
RecyclerView.Adapter<NoteAdapter.NoteViewHolder> { private List<Note> notes = new
ArrayList<>();
public void setNotes(List<Note> notes) {
this.notes = notes;
notifyDataSetChanged();
lOMoARcPSD| 60752940
}
@NonNull @Override public NoteViewHolder onCreateViewHolder(@NonNull
ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,
parent, false); return new NoteViewHolder(v);
}
@Override public void onBindViewHolder(@NonNull NoteViewHolder holder,
int position) { holder.textView.setText(notes.get(position).content);
}
@Override public int
getItemCount() { return
notes.size();
}
static class NoteViewHolder extends RecyclerView.ViewHolder {
TextView textView;
NoteViewHolder(View itemView) {
super(itemView); textView =
itemView.findViewById(android.R.id.text1);
}
}
}
8. MainActivity.java public class MainActivity extends
AppCompatActivity { private NoteViewModel
lOMoARcPSD| 60752940
noteViewModel; private EditText edtNote; private
NoteAdapter adapter;
@Override protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtNote = findViewById(R.id.edtNote);
Button btnAdd = findViewById(R.id.btnAdd);
Button btnClear = findViewById(R.id.btnClear);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
adapter = new NoteAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
noteViewModel.getAllNotes().observe(this, notes -> adapter.setNotes(notes));
btnAdd.setOnClickListener(v -> {
String content = edtNote.getText().toString().trim();
if (!content.isEmpty()) {
noteViewModel.insert(new Note(content));
edtNote.setText("");
}
});
lOMoARcPSD| 60752940
btnClear.setOnClickListener(v -> noteViewModel.deleteAll());
}
}
Chú ý: Các thư viện cần thiết
1. Room (Database) def room_version = "2.6.1" implementation
"androidx.room:room-runtime:$room_version" annotationProcessor
"androidx.room:room-compiler:$room_version" // Nếu dùng Kotlin: kapt
"androidx.room:room-compiler:$room_version" implementation
"androidx.room:room-ktx:$room_version"
2. Lifecycle (ViewModel + LiveData) def lifecycle_version =
"2.6.2" implementation "androidx.lifecycle:lifecycle-
viewmodel:$lifecycle_version" implementation
"androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-
runtime:$lifecycle_version"
3. RecyclerView implementation
"androidx.recyclerview:recyclerview:1.3.2" 4. Material Design (UI,
FloatingActionButton, Dialog…) implementation
'com.google.android.material:material:1.11.0'
5. AppCompat + ConstraintLayout implementation
'androidx.appcompat:appcompat:1.6.1' implementation
'androidx.constraintlayout:constraintlayout:2.1.4'
Tổng hợp ví dụ build.gradle (app)
lOMoARcPSD| 60752940
dependencies { // AppCompat + UI implementation
'androidx.appcompat:appcompat:1.6.1' implementation
'com.google.android.material:material:1.11.0' implementation
'androidx.constraintlayout:constraintlayout:2.1.4'
// RecyclerView implementation
"androidx.recyclerview:recyclerview:1.3.2"
// Lifecycle (ViewModel + LiveData) def lifecycle_version = "2.6.2"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// Room def
room_version = "2.6.1"
implementation
"androidx.room:room-
runtime:$room_version"
annotationProcessor
"androidx.room:room-
compiler:$room_version"
implementation
"androidx.room:room-
ktx:$room_version"
}
BÀI 2 – Xây dựng ứng dụng NoteApp gồm:
Màn hình chính hiển thị danh sách ghi chú bằng RecyclerView.
Nút FloatingActionButton (FAB) để thêm ghi chú mới (qua Dialog hoặc Activity).
lOMoARcPSD| 60752940
Dữ liệu được lưu trữ trong Room Database.
Toolbar:
Thay thế ActionBar mặc định bằng Toolbar tùy chỉnh.
Hiển thị tiêu đề: “Note App”.
Menu trên Toolbar:
Tạo file menu_main.xml trong res/menu/.
Bổ sung các chức năng:
o Tìm kiếm (Search): lọc danh sách ghi chú theo từ khóa.
o Thêm mới (Add Note): mở form thêm ghi chú (tương tự FAB).
o Xóa tất cả (Clear All): xóa toàn bộ ghi chú (có AlertDialog xác nhận).
Cập nhật dữ liệu realtime:
Khi thêm/xóa/sửa, RecyclerView tự động cập nhật nhờ LiveData.
Xử lý
Thêm SearchView vào action_search để lọc ghi chú trực tiếp.
Cho phép sửa ghi chú bằng cách click vào item trong RecyclerView.
Hiển thị ngày giờ tạo ghi chú.

Preview text:

lOMoAR cPSD| 60752940
Thực hành ngày 09/092025
Xây dựng ứng dụng Quản lý Danh sách Ghi chú (Notes App) với Android Jetpack Mục tiêu
Làm quen với kiến trúc MVVM trong Android Jetpack. •
Thực hành các thành phần: Room, ViewModel, LiveData, RecyclerView. •
Biết cách tách biệt UI – ViewModel – Repository – Database.
Yêu cầu chức năng 1.
Người dùng nhập ghi chú mới (Note). 2.
Lưu ghi chú vào Room Database. 3.
Hiển thị toàn bộ ghi chú bằng RecyclerView. 4.
Dữ liệu hiển thị tự động cập nhật khi thêm/sửa/xóa nhờ LiveData. 5.
Có nút Xóa toàn bộ ghi chú. Cấu trúc Project app/
├── MainActivity.java // UI chính: nhập ghi chú, hiển thị danh sách ├── adapter/
│ └── NoteAdapter.java // Adapter cho RecyclerView ├── data/
│ ├── Note.java // Entity
│ ├── NoteDao.java // DAO
│ ├── NoteDatabase.java // Room Database
│ └── NoteRepository.java // Repository └── viewmodel/
└── NoteViewModel.java // ViewModel + LiveData
1. Layouts activity_main.xml
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:padding="16dp"> lOMoAR cPSD| 60752940
android:hint="Nhập ghi chú..."
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:text="Thêm ghi chú"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:text="Xóa tất cả"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_height="0dp"
android:layout_weight="1"/>
2. Tạo Entity (Note.java)
@Entity(tableName = "notes") public class Note {
@PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo(name = "content") public String content; lOMoAR cPSD| 60752940
public Note(String content) { this.content = content; } }
3. Tạo DAO (NoteDao.java) @Dao public interface NoteDao { @Insert void insert(Note note);
@Query("SELECT * FROM notes ORDER BY id DESC") LiveData> getAllNotes(); @Query("DELETE FROM notes") void deleteAll(); }
4. Room Database (NoteDatabase.java)
@Database(entities = {Note.class}, version = 1)
public abstract class NoteDatabase extends RoomDatabase { private
static NoteDatabase instance; public abstract NoteDao noteDao();
public static synchronized NoteDatabase getInstance(Context context) {
if (instance == null) { instance =
Room.databaseBuilder(context.getApplicationContext(),
NoteDatabase.class, "note_database")
.fallbackToDestructiveMigration() .build(); lOMoAR cPSD| 60752940 } return instance; } }
5. Repository (NoteRepository.java)
public class NoteRepository { private NoteDao noteDao; private LiveData> allNotes; public NoteRepository(Application application) {
NoteDatabase db = NoteDatabase.getInstance(application);
noteDao = db.noteDao(); allNotes = noteDao.getAllNotes(); }
public void insert(Note note) {
Executors.newSingleThreadExecutor().execute(() -> noteDao.insert(note)); } public void deleteAll() {
Executors.newSingleThreadExecutor().execute(() -> noteDao.deleteAll()); }
public LiveData> getAllNotes() { return allNotes; } }
6. ViewModel (NoteViewModel.java) public class
NoteViewModel extends AndroidViewModel { private lOMoAR cPSD| 60752940
NoteRepository repository; private LiveData> allNotes;
public NoteViewModel(@NonNull Application application) {
super(application); repository = new
NoteRepository(application); allNotes = repository.getAllNotes(); }
public void insert(Note note) { repository.insert(note); } public void deleteAll() { repository.deleteAll(); }
public LiveData> getAllNotes() { return allNotes; } } 7. Adapter (NoteAdapter.java) public class NoteAdapter extends
RecyclerView.Adapter { private List notes = new ArrayList<>();
public void setNotes(List notes) { this.notes = notes; notifyDataSetChanged(); lOMoAR cPSD| 60752940 }
@NonNull @Override public NoteViewHolder onCreateViewHolder(@NonNull
ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,
parent, false); return new NoteViewHolder(v); }
@Override public void onBindViewHolder(@NonNull NoteViewHolder holder,
int position) { holder.textView.setText(notes.get(position).content); } @Override public int getItemCount() { return notes.size(); }
static class NoteViewHolder extends RecyclerView.ViewHolder { TextView textView;
NoteViewHolder(View itemView) { super(itemView); textView =
itemView.findViewById(android.R.id.text1); } } }
8. MainActivity.java public class MainActivity extends
AppCompatActivity { private NoteViewModel lOMoAR cPSD| 60752940
noteViewModel; private EditText edtNote; private NoteAdapter adapter;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edtNote = findViewById(R.id.edtNote);
Button btnAdd = findViewById(R.id.btnAdd);
Button btnClear = findViewById(R.id.btnClear);
RecyclerView recyclerView = findViewById(R.id.recyclerView); adapter = new NoteAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
noteViewModel = new ViewModelProvider(this).get(NoteViewModel.class);
noteViewModel.getAllNotes().observe(this, notes -> adapter.setNotes(notes));
btnAdd.setOnClickListener(v -> {
String content = edtNote.getText().toString().trim(); if (!content.isEmpty()) {
noteViewModel.insert(new Note(content)); edtNote.setText(""); } }); lOMoAR cPSD| 60752940
btnClear.setOnClickListener(v -> noteViewModel.deleteAll()); } }
Chú ý: Các thư viện cần thiết 1.
Room (Database) def room_version = "2.6.1" implementation
"androidx.room:room-runtime:$room_version" annotationProcessor
"androidx.room:room-compiler:$room_version" // Nếu dùng Kotlin: kapt
"androidx.room:room-compiler:$room_version" implementation
"androidx.room:room-ktx:$room_version" 2.
Lifecycle (ViewModel + LiveData) def lifecycle_version = "2.6.2" implementation "androidx.lifecycle:lifecycle- viewmodel:$lifecycle_version" implementation
"androidx.lifecycle:lifecycle-livedata:$lifecycle_version" implementation "androidx.lifecycle:lifecycle- runtime:$lifecycle_version" 3. RecyclerView implementation
"androidx.recyclerview:recyclerview:1.3.2" 4. Material Design (UI, FloatingActionButton, Dialog…) implementation
'com.google.android.material:material:1.11.0'
5. AppCompat + ConstraintLayout implementation
'androidx.appcompat:appcompat:1.6.1' implementation
'androidx.constraintlayout:constraintlayout:2.1.4'
Tổng hợp ví dụ build.gradle (app) … lOMoAR cPSD| 60752940
dependencies { // AppCompat + UI implementation
'androidx.appcompat:appcompat:1.6.1' implementation
'com.google.android.material:material:1.11.0' implementation
'androidx.constraintlayout:constraintlayout:2.1.4'
// RecyclerView implementation
"androidx.recyclerview:recyclerview:1.3.2"
// Lifecycle (ViewModel + LiveData) def lifecycle_version = "2.6.2"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" // Room def room_version = "2.6.1" implementation "androidx.room:room- runtime:$room_version" annotationProcessor "androidx.room:room- compiler:$room_version" implementation "androidx.room:room- ktx:$room_version" }
BÀI 2 – Xây dựng ứng dụng NoteApp gồm:
Màn hình chính hiển thị danh sách ghi chú bằng RecyclerView. •
Nút FloatingActionButton (FAB) để thêm ghi chú mới (qua Dialog hoặc Activity). lOMoAR cPSD| 60752940 •
Dữ liệu được lưu trữ trong Room Database. Toolbar: •
Thay thế ActionBar mặc định bằng Toolbar tùy chỉnh. •
Hiển thị tiêu đề: “Note App”. Menu trên Toolbar: •
Tạo file menu_main.xml trong res/menu/. • Bổ sung các chức năng: o
Tìm kiếm (Search): lọc danh sách ghi chú theo từ khóa. o
Thêm mới (Add Note): mở form thêm ghi chú (tương tự FAB). o
Xóa tất cả (Clear All): xóa toàn bộ ghi chú (có AlertDialog xác nhận).
Cập nhật dữ liệu realtime: •
Khi thêm/xóa/sửa, RecyclerView tự động cập nhật nhờ LiveData. Xử lý
Thêm SearchView vào action_search để lọc ghi chú trực tiếp. •
Cho phép sửa ghi chú bằng cách click vào item trong RecyclerView. •
Hiển thị ngày giờ tạo ghi chú.