ViewModel की खास जानकारी   Android Jetpack का हिस्सा.

Kotlin Multiplatform का इस्तेमाल करके देखें
Kotlin Multiplatform की मदद से, कारोबार के लॉजिक को अन्य प्लैटफ़ॉर्म के साथ शेयर किया जा सकता है. KMP में ViewModel को सेट अप करने और उसका इस्तेमाल करने का तरीका जानें

ViewModel क्लास, कारोबार के लॉजिक या स्क्रीन लेवल की स्थिति को बनाए रखने वाला ऑब्जेक्ट होता है. यह यूज़र इंटरफ़ेस (यूआई) को स्थिति दिखाता है और इससे जुड़े कारोबारी नियम को इनकैप्सुलेट करता है. इसका मुख्य फ़ायदा यह है कि यह स्थिति को कैश मेमोरी में सेव करता है और कॉन्फ़िगरेशन में बदलाव होने पर भी उसे बनाए रखता है. इसका मतलब है कि गतिविधियों के बीच नेविगेट करते समय या कॉन्फ़िगरेशन में बदलाव करने के बाद, आपके यूज़र इंटरफ़ेस (यूआई) को डेटा फिर से फ़ेच नहीं करना पड़ता. जैसे, स्क्रीन घुमाते समय.

स्टेट होल्डर के बारे में ज़्यादा जानने के लिए, स्टेट होल्डर से जुड़े दिशा-निर्देश देखें. इसी तरह, यूज़र इंटरफ़ेस (यूआई) लेयर के बारे में ज़्यादा जानने के लिए, यूज़र इंटरफ़ेस (यूआई) लेयर से जुड़े दिशा-निर्देश देखें.

ViewModel के फ़ायदे

ViewModel के बजाय, एक सामान्य क्लास का इस्तेमाल किया जा सकता है. यह क्लास, यूज़र इंटरफ़ेस (यूआई) में दिखाए जाने वाले डेटा को सेव रखती है. इससे, एक गतिविधि से दूसरी गतिविधि या नेविगेशन डेस्टिनेशन के बीच नेविगेट करते समय समस्या हो सकती है. ऐसा करने से वह डेटा मिट जाता है. हालांकि, अगर आपने सेविंग इंस्टेंस स्टेट मैकेनिज़्म का इस्तेमाल करके डेटा सेव किया है, तो वह मिटेगा नहीं. ViewModel, डेटा को सेव करने के लिए एक आसान एपीआई उपलब्ध कराता है. इससे यह समस्या हल हो जाती है.

ViewModel क्लास के दो मुख्य फ़ायदे हैं:

  • इससे यूज़र इंटरफ़ेस (यूआई) की स्थिति को बनाए रखने में मदद मिलती है.
  • इससे कारोबारी नियमों का ऐक्सेस मिलता है.

डेटा पर्सिस्टेंस

ViewModel, दो तरीकों से डेटा को बनाए रखने की सुविधा देता है. पहला, ViewModel में मौजूद स्थिति के ज़रिए और दूसरा, ViewModel से ट्रिगर होने वाले ऑपरेशनों के ज़रिए. इस कैश मेमोरी का मतलब है कि आपको स्क्रीन रोटेशन जैसे सामान्य कॉन्फ़िगरेशन में बदलाव करके, डेटा को फिर से फ़ेच नहीं करना पड़ता.

दायरा

ViewModel को इंस्टैंशिएट करते समय, आपको उसे ऐसा ऑब्जेक्ट पास करना होता है जो ViewModelStoreOwner इंटरफ़ेस लागू करता हो. यह नेविगेशन डेस्टिनेशन, नेविगेशन ग्राफ़, ऐक्टिविटी, फ़्रैगमेंट या कोई अन्य टाइप हो सकता है, जो इंटरफ़ेस लागू करता है. इसके बाद, आपका ViewModel, ViewModelStoreOwner के Lifecycle के दायरे में आ जाता है. यह मेमोरी में तब तक सेव रहता है, जब तक इसका ViewModelStoreOwner हमेशा के लिए मिट नहीं जाता.

कई क्लास, ViewModelStoreOwner इंटरफ़ेस की डायरेक्ट या इनडायरेक्ट सबक्लास होती हैं. इसकी डायरेक्ट सबक्लास ComponentActivity, Fragment, और NavBackStackEntry हैं. अप्रत्यक्ष सबक्लास की पूरी सूची देखने के लिए, ViewModelStoreOwner रेफ़रंस देखें.

जिस फ़्रैगमेंट या ऐक्टिविटी के स्कोप में ViewModel होता है उसके बंद होने पर, ViewModel में एसिंक्रोनस तरीके से काम जारी रहता है. यह लगातार बने रहने की कुंजी है.

ज़्यादा जानकारी के लिए, नीचे दिया गया ViewModel का लाइफ़साइकल सेक्शन देखें.

SavedStateHandle

SavedStateHandle की मदद से, डेटा को सिर्फ़ कॉन्फ़िगरेशन में बदलाव होने पर ही नहीं, बल्कि प्रोसेस को फिर से शुरू करने पर भी सेव किया जा सकता है. इसका मतलब है कि यह आपको यूज़र इंटरफ़ेस (यूआई) की स्थिति को बरकरार रखने की सुविधा देता है. ऐसा तब भी होता है, जब उपयोगकर्ता ऐप्लिकेशन को बंद कर देता है और बाद में उसे खोलता है.

कारोबारी नियमों का ऐक्सेस

ज़्यादातर कारोबार से जुड़े नियम डेटा लेयर में मौजूद होते हैं. हालांकि, यूज़र इंटरफ़ेस (यूआई) लेयर में भी कारोबार से जुड़े नियम हो सकते हैं. ऐसा तब हो सकता है, जब स्क्रीन के यूज़र इंटरफ़ेस (यूआई) की स्थिति बनाने के लिए, कई रिपॉज़िटरी से डेटा को एक साथ जोड़ा जा रहा हो या जब किसी खास तरह के डेटा के लिए डेटा लेयर की ज़रूरत न हो.

यूज़र इंटरफ़ेस (यूआई) लेयर में, कारोबार के लॉजिक को हैंडल करने के लिए ViewModel सही जगह है. ViewModel, इवेंट को मैनेज करने और उन्हें हाइरार्की की अन्य लेयर को सौंपने का काम भी करता है. ऐसा तब होता है, जब ऐप्लिकेशन डेटा में बदलाव करने के लिए कारोबारी लॉजिक लागू करना होता है.

Jetpack Compose

Jetpack Compose का इस्तेमाल करते समय, ViewModel, स्क्रीन के यूज़र इंटरफ़ेस (यूआई) की स्थिति को कंपोज़ेबल के सामने लाने का मुख्य तरीका है. हाइब्रिड ऐप्लिकेशन में, गतिविधियां और फ़्रैगमेंट सिर्फ़ आपके कंपोज़ेबल फ़ंक्शन को होस्ट करते हैं. यह पिछली अप्रोच से अलग है. पिछली अप्रोच में, ऐक्टिविटी और फ़्रैगमेंट की मदद से, यूज़र इंटरफ़ेस (यूआई) के ऐसे कॉम्पोनेंट बनाना आसान नहीं था जिन्हें बार-बार इस्तेमाल किया जा सके. इस वजह से, वे यूज़र इंटरफ़ेस (यूआई) कंट्रोलर के तौर पर ज़्यादा ऐक्टिव थे.

Compose के साथ ViewModel का इस्तेमाल करते समय, यह ध्यान रखना ज़रूरी है कि ViewModel को किसी कंपोज़ेबल के स्कोप में नहीं रखा जा सकता. ऐसा इसलिए है, क्योंकि कंपोज़ेबल, ViewModelStoreOwner नहीं होता. कंपोज़िशन में एक ही कंपोज़ेबल के दो इंस्टेंस या एक ही ViewModelStoreOwner के तहत एक ही ViewModel टाइप को ऐक्सेस करने वाले दो अलग-अलग कंपोज़ेबल को ViewModel का एक ही इंस्टेंस मिलेगा. ऐसा अक्सर नहीं होना चाहिए.

Compose में ViewModel के फ़ायदे पाने के लिए, हर स्क्रीन को किसी फ़्रैगमेंट या ऐक्टिविटी में होस्ट करें. इसके अलावा, Compose Navigation का इस्तेमाल करें और कंपोज़ेबल फ़ंक्शन में ViewModels का इस्तेमाल करें. ये फ़ंक्शन, नेविगेशन डेस्टिनेशन के जितना हो सके उतना करीब होने चाहिए. ऐसा इसलिए है, क्योंकि ViewModel को नेविगेशन डेस्टिनेशन, नेविगेशन ग्राफ़, ऐक्टिविटी, और फ़्रैगमेंट के स्कोप में रखा जा सकता है.

ज़्यादा जानकारी के लिए, Jetpack Compose के लिए स्टेट होइस्टिंग से जुड़ी गाइड देखें.

ViewModel लागू करना

यहां एक ऐसी स्क्रीन के लिए ViewModel को लागू करने का उदाहरण दिया गया है जिसमें उपयोगकर्ता को डाइस रोल करने की सुविधा मिलती है.

Kotlin

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

इसके बाद, किसी ऐक्टिविटी से ViewModel को इस तरह ऐक्सेस किया जा सकता है:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

ViewModel के साथ कोरूटीन का इस्तेमाल करना

ViewModel में Kotlin कोरूटीन के लिए सहायता शामिल है. यह एसिंक्रोनस टास्क को उसी तरह से सेव कर सकता है जिस तरह से यूज़र इंटरफ़ेस (यूआई) की स्थिति को सेव करता है.

ज़्यादा जानकारी के लिए, Android Architecture Components के साथ Kotlin कोरूटीन इस्तेमाल करना लेख पढ़ें.

ViewModel की लाइफ़साइकल

ViewModel का लाइफ़साइकल, सीधे तौर पर उसके स्कोप से जुड़ा होता है. ViewModel, मेमोरी में तब तक सेव रहता है, जब तक कि वह ViewModelStoreOwner मिट नहीं जाता जिसके स्कोप में वह आता है. ऐसा इन मामलों में हो सकता है:

  • किसी गतिविधि के खत्म होने पर.
  • किसी फ़्रैगमेंट के मामले में, जब उसे अलग किया जाता है.
  • नेविगेशन एंट्री के मामले में, जब इसे बैक स्टैक से हटा दिया जाता है.

इसलिए, ViewModels, कॉन्फ़िगरेशन में बदलाव होने पर भी डेटा को सुरक्षित रखने का एक बेहतरीन तरीका है.

पहली इमेज में, किसी ऐक्टिविटी की अलग-अलग लाइफ़साइकल स्थितियां दिखाई गई हैं. इसमें दिखाया गया है कि रोटेशन के दौरान और रोटेशन के बाद ऐक्टिविटी की स्थिति क्या होती है. इस इलस्ट्रेशन में, ViewModel का लाइफ़टाइम भी दिखाया गया है. यह लाइफ़टाइम, उससे जुड़ी ऐक्टिविटी के लाइफ़साइकल के बगल में दिखाया गया है. इस डायग्राम में, किसी गतिविधि की अलग-अलग स्थितियां दिखाई गई हैं. फ़्रैगमेंट के लाइफ़साइकल पर भी यही बुनियादी स्थितियां लागू होती हैं.

इस इमेज में, ऐक्टिविटी की स्थिति बदलने पर ViewModel की लाइफ़साइकल को दिखाया गया है.

आम तौर पर, सिस्टम किसी गतिविधि ऑब्जेक्ट के onCreate() तरीके को पहली बार कॉल करने पर, ViewModel का अनुरोध किया जाता है. सिस्टम, किसी गतिविधि के दौरान onCreate() को कई बार कॉल कर सकता है. जैसे, जब किसी डिवाइस की स्क्रीन घुमाई जाती है. ViewModel तब से मौजूद होता है, जब आपने पहली बार ViewModel का अनुरोध किया था. यह तब तक मौजूद रहता है, जब तक गतिविधि पूरी नहीं हो जाती और उसे मिटा नहीं दिया जाता.

ViewModel की डिपेंडेंसी हटाना

ViewModel, onCleared तरीके को तब कॉल करता है, जब ViewModelStoreOwner इसे अपनी लाइफ़साइकल के दौरान बंद कर देता है. इससे आपको ViewModel के लाइफ़साइकल के बाद किए गए किसी भी काम या डिपेंडेंसी को हटाने में मदद मिलती है.

यहां दिए गए उदाहरण में, viewModelScope के विकल्प के तौर पर इस्तेमाल किए जा सकने वाले फ़ंक्शन को दिखाया गया है. viewModelScope एक बिल्ट-इन CoroutineScope है, जो ViewModel की लाइफ़साइकल को अपने-आप फ़ॉलो करता है. ViewModel इसका इस्तेमाल, कारोबार से जुड़े ऑपरेशनों को ट्रिगर करने के लिए करता है. अगर आपको आसानी से टेस्टिंग करने के लिए, viewModelScope के बजाय कस्टम स्कोप का इस्तेमाल करना है, तो ViewModel अपने कंस्ट्रक्टर में CoroutineScope को डिपेंडेंसी के तौर पर पा सकता है. जब ViewModelStoreOwner अपनी लाइफ़साइकल के आखिर में ViewModel को हटाता है, तो ViewModel भी CoroutineScope को रद्द कर देता है.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

लाइफ़साइकल वर्शन 2.5 और इसके बाद के वर्शन में, ViewModel के कंस्ट्रक्टर को एक या उससे ज़्यादा Closeable ऑब्जेक्ट पास किए जा सकते हैं. ViewModel इंस्टेंस के बंद होने पर, ये ऑब्जेक्ट अपने-आप बंद हो जाते हैं.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

सबसे सही तरीके

ViewModel को लागू करते समय, आपको यहां दिए गए सबसे सही तरीकों का पालन करना चाहिए:

  • स्कोपिंग की वजह से, ViewModels का इस्तेमाल स्क्रीन लेवल के स्टेट होल्डर की जानकारी को लागू करने के लिए करें. इनका इस्तेमाल, बार-बार इस्तेमाल किए जा सकने वाले यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट के स्टेट होल्डर के तौर पर न करें. जैसे, चिप ग्रुप या फ़ॉर्म. इसके अलावा, आपको एक ही ViewModelStoreOwner के तहत, एक ही यूआई कॉम्पोनेंट के अलग-अलग इस्तेमाल में, ViewModel का एक ही इंस्टेंस मिलेगा. ऐसा तब तक होगा, जब तक कि हर चिप के लिए व्यू मॉडल की का इस्तेमाल नहीं किया जाता.
  • ViewModel को यूज़र इंटरफ़ेस (यूआई) लागू करने से जुड़ी जानकारी नहीं होनी चाहिए. ViewModel API के ज़रिए दिखाए गए तरीकों और यूज़र इंटरफ़ेस (यूआई) की स्थिति वाले फ़ील्ड के नाम, ज़्यादा से ज़्यादा सामान्य रखें. इस तरह, आपका ViewModel किसी भी तरह के यूज़र इंटरफ़ेस (यूआई) के साथ काम कर सकता है: मोबाइल फ़ोन, फ़ोल्ड किए जा सकने वाले डिवाइस, टैबलेट या Chromebook!
  • ViewModel, ViewModelStoreOwner से ज़्यादा समय तक मेमोरी में सेव रह सकते हैं. इसलिए, मेमोरी लीक को रोकने के लिए, ViewModels को लाइफ़साइकल से जुड़े एपीआई के किसी भी रेफ़रंस को सेव नहीं करना चाहिए. जैसे, Context या Resources.
  • ViewModel को अन्य क्लास, फ़ंक्शन या अन्य यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट में पास न करें. इन कुकी को प्लैटफ़ॉर्म मैनेज करता है. इसलिए, आपको इन्हें प्लैटफ़ॉर्म के हिसाब से सेट करना चाहिए. आपकी गतिविधि, फ़्रैगमेंट या स्क्रीन लेवल के कंपोज़ेबल फ़ंक्शन के आस-पास. इससे निचले लेवल के कॉम्पोनेंट को ज़रूरत से ज़्यादा डेटा और लॉजिक ऐक्सेस करने से रोका जाता है.

ज़्यादा जानकारी

डेटा ज़्यादा जटिल होने पर, सिर्फ़ डेटा लोड करने के लिए अलग क्लास बनाई जा सकती है. ViewModel का मकसद, यूज़र इंटरफ़ेस (यूआई) कंट्रोलर के लिए डेटा को इनकैप्सुलेट करना है, ताकि कॉन्फ़िगरेशन में बदलाव होने पर भी डेटा सुरक्षित रहे. कॉन्फ़िगरेशन में बदलाव होने पर, डेटा को लोड करने, सेव रखने, और मैनेज करने के तरीके के बारे में जानने के लिए, सेव की गई यूज़र इंटरफ़ेस (यूआई) स्थितियां लेख पढ़ें.

Android ऐप्लिकेशन के आर्किटेक्चर की गाइड में, इन फ़ंक्शन को मैनेज करने के लिए रिपॉज़िटरी क्लास बनाने का सुझाव दिया गया है.

अन्य संसाधन

ViewModel क्लास के बारे में ज़्यादा जानने के लिए, यहां दिए गए संसाधन देखें.

दस्तावेज़

सैंपल