สร้าง ViewModels ที่มีทรัพยากร Dependency เป็นส่วนหนึ่งของ Android Jetpack

ViewModel สามารถรับพารามิเตอร์ที่เป็น Dependency ไว้ในตัวสร้างตามแนวทางปฏิบัติแนะนำของการฉีด Dependency ซึ่งส่วนใหญ่แล้วจะเป็นประเภทจากโดเมนหรือเลเยอร์ข้อมูล เนื่องจากเฟรมเวิร์กมี ViewModel คุณจึงต้องใช้กลไกพิเศษเพื่อสร้างอินสแตนซ์ของ ViewModel กลไกดังกล่าวคืออินเทอร์เฟซ ViewModelProvider.Factory เฉพาะการใช้งานของอินเทอร์เฟซนี้เท่านั้นที่สร้างอินสแตนซ์ ViewModel ได้ในขอบเขตที่เหมาะสม

ViewModel ที่มี CreationExtras

หากคลาส ViewModel ได้รับ Dependency ใน Constructor ให้ระบุ Factory ที่ใช้อินเทอร์เฟซ ViewModelProvider.Factory ลบล้างฟังก์ชัน create(Class<T>, CreationExtras) เพื่อระบุอินสแตนซ์ใหม่ของ ViewModel

CreationExtras ให้คุณเข้าถึงข้อมูลที่เกี่ยวข้องซึ่งช่วยยืนยัน ViewModel ได้ รายการคีย์ที่เข้าถึงได้จากส่วนเพิ่มเติมมีดังนี้

คีย์ ฟังก์ชันการทำงาน
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY ให้สิทธิ์เข้าถึงคีย์ที่กำหนดเองที่คุณส่งให้กับ ViewModelProvider.get()
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY ให้สิทธิ์เข้าถึงอินสแตนซ์ของชั้นเรียน Application
SavedStateHandleSupport.DEFAULT_ARGS_KEY ให้สิทธิ์เข้าถึงกลุ่มอาร์กิวเมนต์ที่คุณควรใช้ในการสร้าง SavedStateHandle
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY ให้สิทธิ์เข้าถึง SavedStateRegistryOwner ที่ใช้ในการสร้าง ViewModel
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY ให้สิทธิ์เข้าถึง ViewModelStoreOwner ที่ใช้ในการสร้าง ViewModel

หากต้องการสร้างอินสแตนซ์ใหม่ของ SavedStateHandle ให้ใช้ฟังก์ชัน CreationExtras.createSavedStateHandle() และส่งไปยัง ViewModel

CreationExtras ด้วย APPLICATION_KEY

ต่อไปนี้เป็นตัวอย่างวิธีระบุอินสแตนซ์ของ ViewModel ที่ใช้ repository ที่มีขอบเขตระดับคลาส Application และ SavedStateHandle เป็น Dependency

Kotlin

    import androidx.lifecycle.SavedStateHandle
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.ViewModelProvider
    import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
    import androidx.lifecycle.createSavedStateHandle
    import androidx.lifecycle.viewmodel.CreationExtras

    class MyViewModel(
        private val myRepository: MyRepository,
        private val savedStateHandle: SavedStateHandle
    ) : ViewModel() {

        // ViewModel logic
        // ...

        // Define ViewModel factory in a companion object
        companion object {

            val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    modelClass: Class<T>,
                    extras: CreationExtras
                ): T {
                    // Get the Application object from extras
                    val application = checkNotNull(extras[APPLICATION_KEY])
                    // Create a SavedStateHandle for this ViewModel from extras
                    val savedStateHandle = extras.createSavedStateHandle()

                    return MyViewModel(
                        (application as MyApplication).myRepository,
                        savedStateHandle
                    ) as T
                }
            }
        }
    }

Java

import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;

import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.viewmodel.ViewModelInitializer;

public class MyViewModel extends ViewModel {

    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }

    static final ViewModelInitializer<MyViewModel> initializer = new ViewModelInitializer<>(
        MyViewModel.class,
        creationExtras -> {
            MyApplication app = (MyApplication) creationExtras.get(APPLICATION_KEY);
            assert app != null;
            SavedStateHandle savedStateHandle = createSavedStateHandle(creationExtras);

            return new MyViewModel(app.getMyRepository(), savedStateHandle);
        }
    );
}

จากนั้นคุณจะใช้ Factory นี้เมื่อเรียกข้อมูลอินสแตนซ์ของ ViewModel ได้ดังนี้

Kotlin

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    // Rest of Activity code
}

Java

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

MyViewModel myViewModel = new ViewModelProvider(
    this,
    ViewModelProvider.Factory.from(MyViewModel.initializer)
).get(MyViewModel.class);

// Rest of Activity code
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(factory = MyViewModel.Factory)
) {
    // ...
}

หรือจะใช้ ViewModel factory DSL เพื่อสร้าง Factory โดยใช้ Kotlin API ที่เขียนตามรูปแบบมากขึ้นก็ได้

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

ส่งพารามิเตอร์ที่กำหนดเองเป็น CreationExtras

คุณสามารถส่งพารามิเตอร์ให้กับ ViewModel ผ่าน CreationExtras ได้โดยการสร้างคีย์ที่กำหนดเอง ซึ่งจะเป็นประโยชน์ในกรณีที่ ViewModel ของคุณใช้ออบเจ็กต์ที่เข้าถึงไม่ได้ผ่านคลาส Application และ APPLICATION_KEY ตัวอย่างคือเมื่อสร้าง ViewModel ภายในโมดูลหลายแพลตฟอร์มของ Kotlin จึงไม่มีสิทธิ์เข้าถึงทรัพยากร Dependency ของ Android

ในตัวอย่างนี้ ViewModel จะกำหนดคีย์ที่กำหนดเองและใช้คีย์นั้นใน ViewModelProvider.Factory

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {

        // Define a custom key for your dependency
        val MY_REPOSITORY_KEY = object : CreationExtras.Key<MyRepository> {}

        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                // Get the dependency in your factory
                val myRepository = this[MY_REPOSITORY_KEY] as MyRepository
                MyViewModel(
                    myRepository = myRepository,
                )
            }
        }
    }
}

คุณสามารถสร้างอินสแตนซ์ ViewModel ด้วย CreationExtras.Key จาก ViewModelStoreOwner เช่น ComponentActivity, Fragment หรือ NavBackStackEntry หรือใช้ Jetpack Compose

Kotlin

import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
// ...
    // Use from ComponentActivity, Fragment, NavBackStackEntry,
    // or another ViewModelStoreOwner.
    val viewModelStoreOwner: ViewModelStoreOwner = this
    val myViewModel: MyViewModel = ViewModelProvider.create(
        viewModelStoreOwner,
        factory = MyViewModel.Factory,
        extras = MutableCreationExtras().apply {
            set(MyViewModel.MY_REPOSITORY_KEY, myRepository)
        },
    )[MyViewModel::class]

Jetpack Compose

import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.viewModel
// ...
@Composable
fun MyApp(myRepository: MyRepository) {
    val extras = MutableCreationExtras().apply {
        set(MyViewModel.MY_REPOSITORY_KEY, myRepository)
    }
    val viewModel: MyViewModel = viewModel(
        factory = MyViewModel.Factory,
        extras = extras,
    )
}

โรงงานสำหรับ ViewModel เวอร์ชันเก่ากว่า 2.5.0

หากคุณใช้ ViewModel เวอร์ชันเก่ากว่า 2.5.0 คุณจะต้องระบุ Factory จากชุดย่อยของคลาสที่ขยาย ViewModelProvider.Factory และใช้งานฟังก์ชัน create(Class<T>) คุณต้องขยายคลาสอื่นจากคลาสต่อไปนี้ โดยขึ้นอยู่กับ ViewModel ที่ต้องการใช้ Dependency

หากไม่จำเป็นต้องใช้ Application หรือ SavedStateHandle ก็ให้ขยายเวลาจาก ViewModelProvider.Factory

ตัวอย่างต่อไปนี้ใช้ AbstractSavedStateViewModelFactory สำหรับ ViewModel ที่ใช้ที่เก็บและประเภท SavedStateHandle เป็นข้อกำหนด

Kotlin

class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {

// ViewModel logic ...

// Define ViewModel factory in a companion object
companion object {
    fun provideFactory(
        myRepository: MyRepository,
        owner: SavedStateRegistryOwner,
        defaultArgs: Bundle? = null,
    ): AbstractSavedStateViewModelFactory =
        object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                key: String,
                modelClass: Class<T>,
                handle: SavedStateHandle
            ): T {
                return MyViewModel(myRepository, handle) as T
            }
        }
    }
}

Java

import androidx.annotation.NonNull;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }
}

public class MyViewModelFactory extends AbstractSavedStateViewModelFactory {

    private final MyRepository myRepository;

    public MyViewModelFactory(
        MyRepository myRepository
    ) {
        this.myRepository = myRepository;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    protected <T extends ViewModel> T create(
        @NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle
    ) {
        return (T) new MyViewModel(myRepository, handle);
    }
}

จากนั้นใช้ Factory เพื่อเรียกข้อมูล ViewModel ดังนี้

Kotlin

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels {
        MyViewModel.provideFactory((application as MyApplication).myRepository, this)
    }

    // Rest of Activity code
}

Java

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        ViewModelProvider.Factory.from(MyViewModel.initializer)
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(
        factory = MyViewModel.provideFactory(
            (LocalContext.current.applicationContext as MyApplication).myRepository,
            owner = LocalSavedStateRegistryOwner.current
        )
    )
) {
    // ...
}