androidmvvmviewmodelsavestate

How to handle config changes in ViewModel


I have simple registration form. When I enter data and change configuration the data is lost. I use ViewModel in my project and official documentation says ViewModel can handle orientation change automatically but it does not happen. How i suppose to store data with SaveState or I made a mistake in ViewModel?

my registration form

Fragment code

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

ViewModel

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

ViewModelFactory

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

enter image description here

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>

Solution

  • View state is handled by the framework so you don't need to implement it yourself. However the saved state depends on having unique IDs on each view, so try adding an ID to each TextInputEditText.