นักพัฒนาคงคุ้นเคยกับ ViewModel กันเป็นอย่างดี เพราะเป็น Components สำคัญสำหรับการพัฒนาแอปบนแอนดรอยด์ แต่รู้หรือไม่ว่านอกจาก ViewModel แล้ว ยังมี Component อีกตัวที่ชื่อว่า AndroidViewModel ให้ใช้งานด้วยนะ
อ้าว!? แล้วมันต่างกันยังไงล่ะ?
Constructor ของ ViewModel และ AndroidViewModel
ถ้าลองสร้าง ViewModel กับ AndroidViewModel ขึ้นมาเทียบดูก็จะพบว่า จุดที่แตกต่างกันคือ ViewModel จะเป็น Empty Constructor ในขณะที่ AndroidViewModel มีคลาส Application เป็น Constructor นั่นเอง
// AwesomeViewModel.kt
class AwesomeViewModel : ViewModel() {
// Do something
}
// AwesomeAndroidViewModel.kt
class AwesomeAndroidViewModel(app: Application) : AndroidViewModel(app) {
// Do something
}
แต่ทั้ง 2 แบบสร้างผ่าน ViewModelProvider เหมือนกันเลย
val owner: ViewModelStoreOwner = /* ... */
val viewModel = ViewModelProvider(owner).get(AwesomeViewModel::class.java)
val androidViewModel = ViewModelProvider(owner).get(AwesomeAndroidViewModel::class.java)
โดยคลาส Application จะถูกส่งเข้ามาให้ AndroidViewModel เองโดยไม่ต้องทำอะไรเพิ่มเติม
AndroidViewModel สามารถดึง Context มาใช้งานได้เลย
AndroidViewModel นั้นเป็น Context Aware ทำให้นักพัฒนาสามารถดึง Context จากคลาส Application มาใช้งานได้ทันที และสามารถสร้างเป็นตัวแปร Context เพื่อให้เรียกใช้งานง่ายขึ้นได้ด้วยนะ
// AwesomeAndroidViewModel.kt
class AwesomeAndroidViewModel(
app: Application
) : AndroidViewModel(app) {
private val context: Context
get() = getApplication<Application>().applicationContext
// Do something
}
ในขณะที่ ViewModel ไม่สามารถทำแบบนี้ได้ ถ้าอยากจะใช้ Context ก็อาจจะต้องโยนเข้ามาเป็น Argument แบบนี้แทน
// AwesomeViewModel.kt
class AwesomeViewModel : ViewModel() {
fun doSomething(context: Context) {
// Do something
}
}
ดังนั้นความแตกต่างของ ViewModel กับ AndroidViewModel ก็จะเป็นเรื่องของ Context นั่นเอง
ส่ง Context ผ่าน Dependency Injection ให้ ViewModel ก็ได้นี่นา ?
วิธีหนึ่งที่ทำให้ ViewModel มี Context ไว้เรียกใช้งานได้ ก็คือการทำ Dependency Injection แล้วส่ง Context เข้ามาใน Constructor นั่นเอง
// AwesomeViewModel.kt
class AwesomeViewModel(
private val context: Context
): ViewModel() {
// Do something
}
นักพัฒนาสามารถใช้วิธีนี้ได้เช่นกัน ขอแค่ส่ง Application Context เข้ามาในคลาสนี้ก็พอ
เพราะถ้าโยน Activity Context เข้ามาจะทำให้เกิดปัญหา Context Leak
แต่ถึงกระนั้น Android Studio ก็จะขึ้น Warning อยู่ดี เพราะจริง ๆ แล้วคลาสนี้สามารถรับ Context อะไรก็ได้ ถึงแม้ว่าโค้ดของเราจะส่งเป็น Application Context ก็ตาม
เพราะจริง ๆ แล้วคลาสนี้สามารถรับ Context อะไรก็ได้ จึงเป็นที่มาว่าทำไม AndroidViewModel ถึงรับเป็นคลาส Application แทนที่จะเป็น Context โดยตรง
ดังนั้นถ้าไม่อยากรำคาญกับ Warning ตัวนี้ แนะนำให้ใช้ AndroidViewModel แทนดีกว่า
ใช้เป็น AndroidViewModel ไปให้หมดเลยดีมั้ย ?
เจ้าของบล็อกแนะนำให้ใช้ AndroidViewModel ก็ต่อเมื่อต้องการเรียกใช้งาน Context ข้างใน แต่ถ้าไม่ได้ใช้ Context ก็แนะนำให้ใช้ ViewModel แทนดีกว่า
เพราะ ViewModel ที่ไม่ต้องการ Context จะเขียนเทสเป็น Local Unit Test ได้เลย ทำให้รันเทสได้เร็วกว่าเทสของ AndroidViewModel ที่ต้องการ Context (ต่อให้ใช้ Robolectric เข้ามาช่วยก็ตาม)
สรุป
ViewModel กับ AndroidViewModel แตกต่างกันตรงที่การเรียกใช้งาน Context ภายในคลาส ในกรณีที่ไม่ต้องการ Context ให้ใช้ ViewModel แต่การทำงานใด ๆ ที่ต้องการ Context ก็ให้ใช้ AndroidViewModel แทน