Mô hình MVVM

 

Mô hình MVVM



Model

Model : Mỗi model là một entity(table của Database) có các trưởng của một bảng,

@Entity(tableName = "note_table")
class Note(
@ColumnInfo(name = "title_col") var title: String = "",
@ColumnInfo(name = "description_col") var description: String = "") {

@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id_col")
var id: Int = 0
}

Dao : Là một interface or abstract class chứa các thao tác dữ liệu sửa, xoá, update,... của entity. Mỗi emtity sẽ có interface Dao

@Dao
interface NoteDao {
@Insert
suspend fun insertNote(note: Note)

@Update
suspend fun updateNote(note: Note)

@Delete
suspend fun deleteNote(note: Note)

@Query("select * from note_table")
fun getAllNote(): LiveData<List<Note>>

@Query("select * from note_table where title_col=:title")
fun getNoteByTitle(title: String) : LiveData<List<Note>>
}


Database: Nơi tạo Database chứa các entity, và xây dựng phương thức abstract fun trả về các emtity. có duy nhất nhất file database chứa các phương thức abstract trả về những Dao

@Database(entities = [Note::class], version = 1)
abstract class NoteDatabase : RoomDatabase() {
abstract fun getNoteDao() : NoteDao

companion object{
@Volatile
private var instance: NoteDatabase? = null

fun getInstance(context: Context) : NoteDatabase {
if (instance == null){
instance = Room.databaseBuilder(context, NoteDatabase::class.java, "NoteDatabase").build()
}
return instance!!
}
}
}

* Một cách khác hiểu quả và an toàn hơn
@Database(
entities = [Article::class],
version = 1
)
@TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {

abstract fun getArticleDao(): ArticleDao

companion object{
@Volatile
private var instance: ArticleDatabase? = null
private val LOCK = Any()

operator fun invoke(context: Context) = instance ?: synchronized(LOCK){
instance ?: createDatabase(context).also { instance = it }
}

private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ArticleDatabase::class.java,
"article_db.db"
).build()
}
}
Cậu lệnh Sunchoronized(LOCK) chỉ cho phép duy nhất 1 thread thực thi khối lệnh cùng một thời điểm điều này tránh khả năng do rì data

Repository: là phần trung gian giữa Model và ViewModel nhằm kết nối dữ liệu giữa ViewModel và tầng lưu trữ (Database or Api). Có nhiều file Repo mỗi file Dao sẽ là 1 file Repo,....

class NoteRepository(app: Application) {

private val noteDao:NoteDao
init {
val noteDatabase: NoteDatabase = NoteDatabase.getInstance(app)
noteDao = noteDatabase.getNoteDao()
}

suspend fun insertNote(note:Note) = noteDao.insertNote(note)
suspend fun updateNote(note:Note) = noteDao.updateNote(note)
suspend fun deleteNote(note:Note) = noteDao.deleteNote(note)

fun getAllNote() : LiveData<List<Note>> = noteDao.getAllNote()

}


ViewModel: cung cấp dữ liệu cho View, Xử lý sự kiện từ View, Tương tác với Model, quản lý trạng thái, kiểm tra tính đứng đắn và kiểm thử. Nếu project lớn thì sẽ cần mỗi file ViewModel() tương ứng với một Activity or Fragment nhằm phần chia xử lý logic hay tránh quản lý một file ViewModel() quá lớn, với mô hình nhỏ đơn giản thì có thể có duy nhất một file ViewModel()

- Hàm trả về một ViewModel(), ViewModel() này rồng và chưa được liên kết đến class nào

class NoteViewModel(application: Application) : ViewModel() {

private val noteRepository : NoteRepository = NoteRepository(application)

class NoteViewModelFactory(private val application: Application) : ViewModelProvider.Factory{
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if(modelClass.isAssignableFrom(NoteViewModel::class.java)){
return NoteViewModel(application) as T
}
throw IllegalArgumentException("Unable Construct ViewModel")
}
}
}


-Khi View cần hiển thị dữ liệu từ Database : Ta sẽ lấy dữ liệu đó từ dưới Model lên 
fun getAllNote(): LiveData<List<Note>> = noteRepository.getAllNote()
thông qua Repository

-Khi Model muốn lấy dữ liệu từ View:
private val noteRepository : NoteRepository = NoteRepository(application)

fun insertNote(note:Note) = viewModelScope.launch {
noteRepository.insertNote(note)
}

fun updateNote(note:Note) = viewModelScope.launch {
noteRepository.updateNote(note)
}

fun deleteNote(note:Note) = viewModelScope.launch {
noteRepository.deleteNote(note)
}


View: Lấy dữ liệu từ dưới Model hay đưa dữ liệu xuống đó thông qua ViewModel()

-Cách tạo 1 đối tượng ViewModel() trên activity or fragment
private val noteViewModel: NoteViewModel by lazy {
ViewModelProvider(this, NoteViewModel.NoteViewModelFactory(this.application))[NoteViewModel::class.java]
}
trong đó: 
+by lazy: Khi khởi tạo activity or fragment thì đối tượng sẽ không được tạo ngay mà khi nào câu lệnh sử dụng nó thì nó mới bắt đầu được tạo 1 lần duy nhất
+ Tạo đối dượng thông qua ViewModelProvider() với 2 tham số là this, và ViewModelProvider(được khai báo trong class NoteViewModel())
+ [NoteViewModel::class.jave] : là câu lệnh get() thay vì sử dụng get(NoteViewModel::class.java) thì ta sử dụng ngoặc vuông 

- Khi đã đó đối tượng của ViewModel() ta sử dụng nó như một đối tượng bình thường trong kotlin


Nhận xét