Omówienie ViewModel (widoki)

Pojęcia i implementacja w Jetpack Compose

Klasa ViewModel to obiekt przechowujący stan logiki biznesowej lub ekranu. Udostępnia stan interfejsowi i hermetyzuje powiązaną logikę biznesową. Jego główną zaletą jest to, że buforuje stan i utrzymuje go podczas zmian konfiguracji. Oznacza to, że interfejs nie musi ponownie pobierać danych podczas przechodzenia między aktywnościami ani po zmianach konfiguracji, np. po obróceniu ekranu.

Korzyści z ViewModel

Alternatywą dla ViewModelu jest zwykła klasa, która zawiera dane wyświetlane w interfejsie. Może to stanowić problem podczas przechodzenia między aktywnościami lub miejscami docelowymi nawigacji. Jeśli nie przechowujesz tych danych za pomocą mechanizmu zapisanego stanu instancji, zostaną one zniszczone. ViewModel udostępnia wygodny interfejs API do utrwalania danych, który rozwiązuje ten problem.

Klasa ViewModel ma 2 główne zalety:

  • Umożliwia zachowanie stanu interfejsu.
  • Zapewnia dostęp do logiki biznesowej.

Zakres

Podczas tworzenia instancji klasy ViewModel przekazujesz do niej obiekt, który implementuje interfejs ViewModelStoreOwner. Może to być miejsce docelowe nawigacji, wykres nawigacji, aktywność, fragment lub dowolny inny typ, który implementuje interfejs. ViewModel jest następnie ograniczony do Lifecycle elementu ViewModelStoreOwner. Pozostaje w pamięci, dopóki ViewModelStoreOwner nie zniknie na stałe.

Szereg klas jest bezpośrednimi lub pośrednimi podklasami interfejsu ViewModelStoreOwner. Bezpośrednie podklasy to ComponentActivity, FragmentNavBackStackEntry. Pełną listę podklas pośrednich znajdziesz w dokumentacji ViewModelStoreOwner.

Implementowanie klasy ViewModel

Poniżej znajdziesz przykładową implementację ViewModelu dla ekranu, który umożliwia użytkownikowi rzucanie kostkami.

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
            )
        );
    }
}

Następnie możesz uzyskać dostęp do ViewModel z aktywności w ten sposób:

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
        });
    }
}