es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Después de dos o más rotaciones de pantalla, lifecycleScope.launchWhenCreated deja de funcionar como se espera.

Tengo un código como este:

    private val appViewModel: AppViewModel by activityViewModels()
    private lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Esto establece la variable user en el valor recopilado de un StateFlow desde appViewModel 
    lifecycleScope.launchWhenCreated {
        appViewModel.user.collect { flowUser -> user = flowUser }
    }
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    // Este método utiliza la variable lateinit user
    lifecycleScope.launchWhenStarted {
        doThingWithUser()
    }

    return binding?.root
}

Desde el valor de *StateFlow* persiste incluso después de ser recopilado, después de que se rota la pantalla, se llama alifecycleScope.launchWhenCreated, se recopila nuevamente el **flowUser** del *flow* , se le asigna a la variable lateinit user y luego se llama adoThingWithUser` y todo funciona correctamente.

Pero después de dos o más rotaciones, esto deja de ser cierto, por alguna razón user no se inicializa, se llama a doThingWithUser y la aplicación se bloquea con kotlin.UninitializedPropertyAccessException.

¿Qué estoy haciendo mal? ¿El valor de StateFlow desaparece después de dos recopilaciones o rotaciones de pantalla? ¿Algo sucede con el flow real dentro del ViewModel? ¿Algo sucede con los métodos onCreate y onCreateView? ¿O launchWhenStarted y launchWhenCreated se comportan de manera diferente después de dos rotaciones?

Gracias.

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    El problema con el código está relacionado con el comportamiento del lifecycleScope en conjunto con el ViewModel que se está utilizando, específicamente con el StateFlow que se está utilizando.

    Cuando ocurre un cambio de configuración (como una rotación de pantalla), la Activity se recrea y se crea una nueva instancia del ViewModel, lo que hace que el StateFlow emita su valor actual nuevamente. Esto significa que el código dentro del bloque lifecycleScope.launchWhenCreated se ejecutará nuevamente y se recogerá el valor, pero como la variable user ya ha sido inicializada, no se volverá a asignar, lo que provoca la excepción kotlin.UninitializedPropertyAccessException cuando se llama a la función doThingWithUser más tarde.

    Para solucionar este problema, puedes utilizar el SavedStateHandle proporcionado por el ViewModel para almacenar y recuperar el valor de la variable user. Esto te permitirá persistir el valor a través de cambios de configuración.

    Aquí tienes una versión actualizada del código que utiliza el SavedStateHandle:

    class MyFragment : Fragment() {
    
        private val appViewModel: AppViewModel by activityViewModels()
        private lateinit var user: User
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // Obtén el usuario del estado guardado o del StateFlow si es nulo
            user = savedInstanceState?.getParcelable("USER") ?: appViewModel.user.value
    
            // Observa los cambios en el StateFlow y actualiza el estado guardado cuando emita un nuevo valor
            lifecycleScope.launchWhenCreated {
                appViewModel.user.collect { flowUser -> 
                    user = flowUser
                    appViewModel.savedStateHandle.set("USER", user)
                }
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
    
            // Este método utiliza la variable lateinit user
            lifecycleScope.launchWhenStarted {
                doThingWithUser()
            }
    
            return binding?.root
        }
    
        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
    
            // Guarda el usuario en el bundle
            outState.putParcelable("USER", user)
        }
    }
    

    En este código actualizado, recuperamos el valor de la variable user del SavedStateHandle si existe, o del StateFlow si el estado guardado es nulo. También actualizamos el estado guardado cada vez que el StateFlow emite un nuevo valor.

    Además, guardamos el valor de la variable user en el Bundle en el método onSaveInstanceState, para que se pueda recuperar nuevamente cuando se recrea el Fragment.

Comments are closed.