ใช้ ViewModel หรือ AndroidViewModel ดี?
ViewModel ถือว่าเป็น Component ยอดนิยมสำหรับนักพัฒนาในปัจจุบันที่ต้องการสร้างโปรเจคที่ใช้ App Architecture ตามที่ทีมแอนดรอยด์แนะนำโดยใช้รูปแบบของ MVVM ในการทำงาน
แต่รู้หรือไม่ว่าการสร้าง ViewModel ขึ้นมา นักพัฒนาสามารถเลือกได้ว่าจะสร้างด้วยคลาส ViewModel หรือ AndroidViewModel ก็ได้นะ
อ้าว!? แล้วมันต่างกันยังไงล่ะ?
ทั้งคู่มี Constructor Parameter ที่ต่างกัน
ถ้าลองสร้าง ViewModel กับ AndroidViewModel ขึ้นมาเทียบกันก็จะพบว่า ViewModel เป็นคลาสที่ไม่มี Constructor Parameter ในขณะที่ AndroidViewModel ต้องการคลาส Application
// MainViewModel.kt
class MainViewModel : ViewModel() {
/* ... */
}
// MainAndroidViewModel.kt
class MainAndroidViewModel(app: Application) : AndroidViewModel(app) {
/* ... */
}
และถึงแม้จะมี Constructor Parameter ที่ไม่เหมือนกัน แต่ทั้งคู่ก็สร้างด้วยคำสั่ง viewModels()
ที่เหมือนกันได้เลย
// MainActivity.kt
class MainActivity: AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private val androidViewModel: MainAndroidViewModel by viewModels()
/* ... */
}
AndroidViewModel สามารถดึง Application Context มาใช้งานได้
ในการใช้ AndroidViewModel จะมีข้อดีตรงที่นักพัฒนาสามารถเรียกใช้งาน Application Context ได้เลย
// MainAndroidViewModel.kt
class MainAndroidViewModel(
private val app: Application
) : AndroidViewModel(app) {
private val context: Context
get() = app.applicationContext
/* ... */
}
ซึ่งแน่นอนว่า ViewModel จะไม่มี Context ให้เรียกใช้งาน และนั่นคือความแตกต่างระหว่าง ViewModel และ AndroidViewModel นั่นเอง
ดังนั้นการทำงานใน ViewModel ที่ต้องการใช้ค่าจากคลาส Application อย่างเช่น Application Context ก็ควรสร้าง ViewModel ด้วยคลาส AndroidViewModel
Context Leak จากการส่ง Context ให้ ViewModel ผ่าน Constructor Parameter
นักพัฒนาบางคนอาจจะใช้คลาส ViewModel และใช้วิธีส่ง Context ให้กับ ViewModel ผ่าน Constructor Parameter แบบนี้แทน
// MainViewModel.kt
class MainViewModel(private val context: Context): ViewModel() {
/* ... */
}
ซึ่งเป็นวิธีที่ไม่แนะนำให้ทำ เพราะ Context ที่ส่งเข้ามาไม่ได้เจาะจงว่าเป็น Application Context โดยตรง ทำให้สามารถส่ง Activity Context เข้ามาและอาจจะเกิดปัญหา Context Leak ได้
ดังนั้นถ้าจำเป็นต้องใช้ Context ใน ViewModel ก็ควรใช้ AndroidViewModel ตั้งแต่แรกดีกว่า
ควรใช้งาน Application Context ใน ViewModel เท่าที่จำเป็น
ถึงแม้ว่าการใช้ AndroidViewModel จะช่วยให้นักพัฒนาเรียกใช้งาน Application Context ใน ViewModel ได้ แต่ Application Context จะไม่เหมาะกับการเรียกข้อมูลจาก Android Resource ที่สามารถเปลี่ยนแปลงตาม Configuration Changes ได้
ทั้งนี้ก็เพราะว่า Configuration Changes จะส่งผลต่อ Activity Context เท่านั้น ในขณะที่ Application Context จะยังเป็นตัวเดิมนับตั้งแต่สร้างขึ้นมาในตอนแรก ดังนั้นการใช้ Application Context ใน ViewModel เพื่อเรียกข้อมูลจาก Android Resource จึงไม่ใช่เรื่องที่ถูกต้องซักเท่าไร
ยกตัวอย่างเช่นแอปที่สามารถเปลี่ยนภาษาภายในแอปโดยใช้ความสามารถของ Per-app Language Preferences ของแอนดรอยด์ จะเจอปัญหาว่าเมื่อเปลี่ยนภาษาภายในแอปจะมีผลเฉพาะ Activity Context เท่านั้น ดังนั้นถ้าเรียก String Resource จาก Application Context จะยังได้ภาษาเดิมอยู่
การใช้งานใน Kotlin Multiplatform
ถึงแม้ว่าคลาส ViewModel และคลาส AndroidViewModel จะเรียกใช้งานใน Kotlin Multiplatform ได้ทั้งคู่เพราะอยู่ใน Artifact เดียวกัน (androidx.lifecycle:lifecycle-viewmodel
) แต่จะมีแค่เพียงคลาส ViewModel เท่านั้นที่เรียกใช้งานใน Common Module ได้ เพราะไม่มีการทำงานที่เจาะจงกับแอนดรอยด์โดยตรง ในขณะที่ AndroidViewModel จะใช้งานได้เฉพาะใน Android-specific Module เท่านั้น
สรุป
AndroidViewModel จะมีคลาส Application ส่งเข้ามาเป็น Constructor Parameter ในขณะที่ ViewModel ไม่มีให้ ทำให้ใน AndroidViewModel สามารถเรียกใช้งานบางอย่างในคลาส Application ได้ เช่น การเรียก Application Context มาใช้งาน เป็นต้น