Quiero ejecutar el código solo una vez cuando se carga el componible. Así que estoy usando LaunchedEffect con la clave como verdadera para lograr esto.
LaunchedEffect(true) { // do API call }Este código funciona bien, pero cada vez que hay algún cambio de configuración, como la rotación de la pantalla, este código se ejecuta nuevamente. ¿Cómo puedo evitar que vuelva a ejecutarse en caso de cambio de configuración?
La solución más simple es almacenar información sobre si realizó una llamada a la API con rememberSaveable : estará disponible cuando cambie la configuración.
var initialApiCalled by rememberSaveable { mutableStateOf(false) } if (!initialApiCalled) { LaunchedEffect(Unit) { // do API call initialApiCalled = false } } La desventaja de esta solución es que si la configuración cambia antes de que se complete la llamada a la API, la rutina LaunchedEffect se cancelará, al igual que su llamada a la API.
La solución más limpia es usar un modelo de vista y ejecutar la llamada API dentro de init :
class ScreenViewModel: ViewModel() { init { viewModelScope.launch { // do API call } } } @Composable fun Screen(viewModel: ScreenViewModel = viewModel()) { } La documentación oficial recomienda pasar el modelo de vista como este, como parámetro. En el código de producción, no necesita pasar ningún parámetro a esta vista, simplemente llámelo como Screen() : el modelo de vista se creará con el parámetro viewModel() predeterminado. Se mueve al parámetro para la capacidad de prueba/vista previa como se muestra en esta respuesta .
Supongo que la mejor manera es usar .also en la creación diferida de livedata/stateflow para garantizar que, mientras el modelo de vista esté activo, se llame a loadState solo una vez, y también garantice que no se llame al servicio en sí a menos que alguien lo está escuchando. Luego, escucha el estado desde el modelo de vista, y no es necesario llamar a nada api desde el efecto iniciado, también su código reaccionará al estado específico.
Aquí hay un ejemplo de código
class MyViewModel : ViewModel() { private val uiScreenState: : MutableStateFlow<WhatEverState> = MutableStateFlow(WhatEverIntialState).also { loadState() } fun loadState(): StateFlow<WhatEverState>> { return users } private fun loadUsers() { // Do an asynchronous operation to fetch users. } }Al usar este código, no tiene que llamar a loadstate en absoluto en la actividad, solo escucha al observador.
Puede consultar el siguiente código para escuchar
class MyFragment : Fragment { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { StartingComposeTheme { Box(modifier = Modifier.fillMaxSize()) { val state by viewModel.uiScreenState.collectAsState() when (state) { //do something } } } } } }}}
La respuesta de @ Islam Mansour funciona bien para el modelo de vista dedicado a la interfaz de usuario, pero mi caso es compartido por muchos fragmentos de interfaz de usuario.
En mi caso, las respuestas anteriores no resuelven mi problema para llamar a la API solo por primera vez cuando el usuario navega a la sección de IU en cuestión.
Porque tengo múltiples interfaces de usuario componibles en NavHost como fragmento
Y mi ViewModel a través de todos los fragmentos.
por lo tanto, la API solo debe llamar cuando el usuario navegue hasta el fragmento deseado
entonces, el siguiente inicializador de propiedades perezosas resuelve mi problema;
val myDataList by lazy { Log.d("test","call only once when called from UI used inside)") loadDatatoThisList() mutableStateListOf<MyModel>() } mutableStateListOf< LIST_TYPE > recompone automáticamente la interfaz de usuario cuando se agregan datos a este
variable copiada por by lazy inicializada solo una vez cuando se llama explícitamente