Data Binding with view model Part 2

Suriya Wongkasum
2 min readAug 10, 2020

--

ต่อจากตอนที่แล้วหลังจากเพิ่ม view model เพื่อ subscribe ค่าตัวแปร isNavigateToAddNote แล้วเปิดไปหน้าเพิ่มโน็ต

ไปที่หน้า AddNoteFragment ทำการเพิ่ม AddNoteViewModel สำหรับใส่ business logic ที่เกี่ยวกับการเพิ่มข้อมูลโน็ต โดยเมื่อกรอกข้อมูลโน็ตครบทุก field แล้วกดปุ่ม save จะทำการบันทึกข้อมูลลงฐานข้อมูล

class AddNoteViewModel(
val dataSource: NoteDao,
private val context: Context
) : ViewModel() {

var title = MutableLiveData<String>("")
var noteDetail = MutableLiveData<String>("")

private var _isSaveNote = MutableLiveData<Boolean>()
val isSaveNote: LiveData<Boolean>
get() = _isSaveNote

private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

fun onSave() {
uiScope.launch {
insertData()
}
}

private suspend fun insertData() {
val detail = this.noteDetail.value ?: ""
val title = this.title.value ?: ""

val isValidateNote = detail.isNotEmpty() && title.isNotEmpty()

if (isValidateNote) {
withContext(Dispatchers.IO) {
val data = NoteItem(note = detail, title = title)
dataSource.insertNote(data)
}
_isSaveNote.postValue(true)
Toast.makeText(context, "save complete", Toast.LENGTH_SHORT).show()
} else {
showInvalidData()
}
}

private fun showInvalidData() {
Toast.makeText(context, "Please complete all information.", Toast.LENGTH_SHORT)
.show()
}

override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

สำหรับการใช้ room database แล้วมีการ insert หรือ update ข้อมูลต่างๆ จะต้องอยู่ใน IO thread แล้วครอบด้วย main thread เพื่อให้รองรับการเรียกใช้ function insert เมื่อกดปุ่ม save

ในกรณีนี้ AddNoteViewModel มี constructor อยู่จึงจำเป็นต้องทำ view model factory class เหมือนในหน้า home

class AddNoteFactory(
private val dataSource: NoteDao,
private val context: Context
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AddNoteViewModel::class.java)) {
return AddNoteViewModel(
dataSource = dataSource,
context = context
) as T
}
throw IllegalAccessException("Unknown class")
}
}

ไปที่ไฟล์ add_note_fragment.xml ทำการ convert layout ให้เป็น data binding layout แล้วเพิ่ม data เพื่อผูกเข้ากับ layout วิธีเดียวกับตอนที่ทำในหน้า HomeFragment

<data>
<variable
name="addNoteViewModel" type="my.learing.com.recyclerviewbinding.add.AddNoteViewModel" />
</data>

data binding

ปกติการทำ data binding ที่ UI ยกตัวอย่างเช่นการแสดงข้อความอย่างเดียวใน text view จะต้องเช็ตเป็น

<TextView
...
android:text="@{addNoteViewModel.title}"
/>

ส่วนในกรณีหน้า addNoteFragment จะเป็นการกรอกข้อมูลซึ่งไม่ได้ใช้แค่แสดงผล อย่างเดียว เมื่อเรากรอกข้อมูลทุกครั้งข้อมูลตัวแปรที่ AddNoteViewModel จะต้องเปลี่ยนแปลงตามค่าที่กรอกเข้าไปด้วย การ set data binding แบบวิธีแรกจึงไม่เหมาะที่จะใช้กับ edit text และเราจะไม่เรียกใช้ addTextChangedListener

เราจะเช็ตแบบ two way data binding ซึ่งต่างจากวิธีแรกนิดเดียวคือเพื่มเครื่องหมาย = เข้าไป

<EditText                                              
android:id="@+id/edt_title"
...
android:text="@={addNoteViewModel.title}"
/>

โดยตัวแปรที่ใช้เก็บค่า title จะต้องเป็น MutableLiveData

class AddNoteViewModel(...    var title = MutableLiveData<String>("")

ทำการเพิ่ม function ตอนคลิกปุ่ม save

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_save"
...
android:onClick="@{()->addNoteViewModel.onSave()}"
/>

หลังจากเพิ่ม data binding เข้ากับ view ของหน้าเพิ่มโน็ตครบแล้ว ทำการเปลี่ยนวิธี inflate view และเพิ่ม view model class ที่ AddNoteFragment class

override fun onCreateView(...
val binding: AddNoteFragmentBinding =
DataBindingUtil.inflate(inflater, R.layout.add_note_fragment, container, false)

val dataSource = NoteDatabase.getInstance(requireContext()).noteDao

val factory =
AddNoteFactory(dataSource = dataSource, context = requireContext())
val addNoteViewModel =
ViewModelProvider(this, factory).get(AddNoteViewModel::class.java)

binding.addNoteViewModel = addNoteViewModel
binding.lifecycleOwner = this

return binding.root
}

ทำการเพิ่มการ subscribe ตัวแปร isSaveNote เพื่อสั่งให้ปิดหน้านี้ไปเมื่อบันทึกข้อมูลเสร็จแล้ว

addNoteViewModel.isSaveNote.observe(viewLifecycleOwner, Observer {
it
?.let {
if (it) {
val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(binding.root.windowToken, 0)

requireActivity().onBackPressed()
}
}
}
)

build ทดสอบการทำงานดู เมื่อกดปุ่ม save แสดงข้อความ save complete แล้วกลับไปหน้า home

โปรดติดตามรับชมได้ในตอนต่อไป part 3

--

--