นักพัฒนาคงคุ้นเคยกับ 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 แทน