如何处理 ViewModel 中的配置更改
How to handle config changes in ViewModel
我有简单的注册表。当我输入数据并更改配置时,数据丢失了。我在我的项目中使用 ViewModel,官方文档说 ViewModel 可以自动处理方向变化,但它并没有发生。我应该如何使用 SaveState 存储数据,或者我在 ViewModel 中犯了错误?
片段代码
class StartFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: StartFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.start_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = UsersDatabase.getInstance(application).usersDatabaseDao
val vm: SavedStateHandle by viewModels()
val viewModelFactory = StartFragmentViewModelFactory(dataSource, application)
val startFragmentViewModel =
ViewModelProvider(
this, viewModelFactory).get(StartFragmentViewModel::class.java)
binding.startFragmentViewModel = startFragmentViewModel
binding.lifecycleOwner = this
binding.start.setOnClickListener {
findNavController().navigate(
StartFragmentDirections
.actionStartFragmentToWebViewFragment())
startFragmentViewModel.doneNavigation()
}
return binding.root
}
}
视图模型
class StartFragmentViewModel(
val database: UsersDatabaseDao,
application: Application
) : AndroidViewModel(application) {
private var viewModelJob = Job()
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var user1 = MutableLiveData<User?>()
private val _navigateToWebView = MutableLiveData<User>()
val navigateToWebView: LiveData<User>
get() = _navigateToWebView
fun doneNavigation() {
_navigateToWebView.value = null
uiScope.launch {
val user = User()
insert(user)
}
}
private suspend fun insert(user: User) {
withContext(Dispatchers.IO) {
database.insert(user)
}
}
}
视图模型工厂
class StartFragmentViewModelFactory (
private val dataSource: UsersDatabaseDao,
private val application: Application
) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(StartFragmentViewModel::class.java)) {
return StartFragmentViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
start_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="startFragmentViewModel"
type="com.example.leadsdoittest.StartFragmentViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_top"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.3" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toTopOf="@id/margin_top">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_phone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@id/input_name">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:hint="@string/phone_number" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@id/input_phone">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="@string/start"
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@+id/input_email" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
旋转后使用实例变量恢复数据。看:
class StartFragment : Fragment() {
//private lateinit var homeViewModel: HomeViewModel
var name = ""
var email = ""
var phone = ""
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeBinding.inflate(inflater)
binding.etName.doOnTextChanged { text, start, before, count ->
name = text.toString()
}
binding.etPhone.doOnTextChanged { text, start, before, count ->
phone = text.toString()
}
binding.etEmail.doOnTextChanged { text, start, before, count ->
email = text.toString()
}
binding.etName.setText(name)
binding.etEmail.setText(email)
binding.etPhone.setText(phone)
return binding.root
}
}
视图状态由框架处理,因此您无需自己实现。但是,保存的状态取决于每个视图的唯一 ID,因此请尝试为每个 TextInputEditText
.
添加一个 ID
我有简单的注册表。当我输入数据并更改配置时,数据丢失了。我在我的项目中使用 ViewModel,官方文档说 ViewModel 可以自动处理方向变化,但它并没有发生。我应该如何使用 SaveState 存储数据,或者我在 ViewModel 中犯了错误?
片段代码
class StartFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: StartFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.start_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = UsersDatabase.getInstance(application).usersDatabaseDao
val vm: SavedStateHandle by viewModels()
val viewModelFactory = StartFragmentViewModelFactory(dataSource, application)
val startFragmentViewModel =
ViewModelProvider(
this, viewModelFactory).get(StartFragmentViewModel::class.java)
binding.startFragmentViewModel = startFragmentViewModel
binding.lifecycleOwner = this
binding.start.setOnClickListener {
findNavController().navigate(
StartFragmentDirections
.actionStartFragmentToWebViewFragment())
startFragmentViewModel.doneNavigation()
}
return binding.root
}
}
视图模型
class StartFragmentViewModel(
val database: UsersDatabaseDao,
application: Application
) : AndroidViewModel(application) {
private var viewModelJob = Job()
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var user1 = MutableLiveData<User?>()
private val _navigateToWebView = MutableLiveData<User>()
val navigateToWebView: LiveData<User>
get() = _navigateToWebView
fun doneNavigation() {
_navigateToWebView.value = null
uiScope.launch {
val user = User()
insert(user)
}
}
private suspend fun insert(user: User) {
withContext(Dispatchers.IO) {
database.insert(user)
}
}
}
视图模型工厂
class StartFragmentViewModelFactory (
private val dataSource: UsersDatabaseDao,
private val application: Application
) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(StartFragmentViewModel::class.java)) {
return StartFragmentViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
start_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="startFragmentViewModel"
type="com.example.leadsdoittest.StartFragmentViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/margin_top"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.3" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toTopOf="@id/margin_top">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_phone"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@id/input_name">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:hint="@string/phone_number" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@id/input_phone">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="@string/start"
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="@id/margin_end"
app:layout_constraintStart_toStartOf="@id/margin_start"
app:layout_constraintTop_toBottomOf="@+id/input_email" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
旋转后使用实例变量恢复数据。看:
class StartFragment : Fragment() {
//private lateinit var homeViewModel: HomeViewModel
var name = ""
var email = ""
var phone = ""
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeBinding.inflate(inflater)
binding.etName.doOnTextChanged { text, start, before, count ->
name = text.toString()
}
binding.etPhone.doOnTextChanged { text, start, before, count ->
phone = text.toString()
}
binding.etEmail.doOnTextChanged { text, start, before, count ->
email = text.toString()
}
binding.etName.setText(name)
binding.etEmail.setText(email)
binding.etPhone.setText(phone)
return binding.root
}
}
视图状态由框架处理,因此您无需自己实现。但是,保存的状态取决于每个视图的唯一 ID,因此请尝试为每个 TextInputEditText
.