Procesar muerte y cambio de orientación en RecyclerView
Procesar muerte y cambio de orientación en RecyclerView
Esta será la parte 2 de Todo lo que necesita saber sobre RecyclerView.En este artículo, se muestra cómo usar SavedStateHandle para la muerte del proceso y el cambio de orientación en RecyclerView.
proceso de muerte
Cuando el sistema se queda sin memoria, elimina los procesos en el caché, comenzando con el proceso utilizado menos recientemente. Cuando el usuario navega de regreso a la aplicación, el sistema reinicia la aplicación en un nuevo proceso. Dado que esto solo sucede si el usuario no ha interactuado con la aplicación durante un tiempo, puede ser aceptable que regrese a la aplicación y la encuentre en su estado inicial.
Te recomiendo leer este artículo y video para entenderlo mejor.
cambiar de orientación
Algunas configuraciones de dispositivos pueden cambiar durante el tiempo de ejecución, p. B. la orientación de la pantalla. Cuando ocurre tal cambio, Android reinicia la actividad en curso. El comportamiento de reinicio está diseñado para ayudar a que su aplicación se adapte a las nuevas configuraciones recargando automáticamente su aplicación con recursos alternativos que coincidan con la nueva configuración del dispositivo.
Para manejar adecuadamente el cambio de orientación, necesitamos restaurar el estado anterior. Para esto usamos onSaveInstanceState() y ViewModel. En primer lugar, no podemos almacenar grandes cantidades de datos en nuestro sistema, como dice la documentación.
… no está diseñado para transportar objetos grandes (como mapas de bits) y los datos que contiene deben serializarse en el subproceso principal y luego deserializarse, lo que puede consumir mucha memoria y ralentizar el cambio de configuración.
Así que usamos ViewModel. Los objetos de ViewModel persisten a través de los cambios de configuración, lo que los convierte en el lugar perfecto para conservar los datos de la interfaz de usuario sin tener que volver a consultarlos. Estamos trabajando en nuestra base de código anterior de Todo lo que necesitas saber sobre RecyclerView. Antes de hacer una implementación, necesitamos hacer algunos cambios.Clase sellada NetworkResponse {clase de datos Cargando (val isPaginating: Boolean = false,): NetworkResponse()clase de datos Éxito(val data: T,val isPaginationData: Boolean = false,val isPaginationExhausted: Boolean = false,): NetworkResponse() falla de clase de datos (mensaje de error de valor: cadena,): NetworkResponse()}En NetworkResponse, movemos el manejo de la tarea PaginationExhaust a Correcto porque anteriormente, en caso de un cambio de orientación en PaginationExhaust, ViewModel mantuvo el valor de estado como Error, lo que resultó en una lista vacía después del cambio de orientación. También necesitamos hacer pequeños ajustes en otras clases. class MainRepository {//…fun fetchData(página: Int): Flujo>> = flujo {emitir (NetworkResponse.Loading (página! = 1)) kotlinx.coroutines.delay (2000L) probar {si (página == 1) emitir (NetworkResponse.Success (tempList.toList() como ArrayList))else {val tempPaginationList = arrayListOf().apply {for (i in 0..PAGE_SIZE) {add(RecyclerViewModel(UUID.randomUUID().toString(), «Content ${i * 2}»)),)}}if (Página < 4) { emit(NetworkResponse.Success(tempPaginationList,isPaginationData = true,))} else {// CHANGEemit(NetworkResponse.Success(arrayListOf(),isPaginationData = true,isPaginationExhausted = true))}}} catch (e : Exception) {/ / CHANGEif (página != 1) {emit(NetworkResponse.Success(arrayListOf(),isPaginationData = true,isPaginationExhausted = true))} else {emit(NetworkResponse.Failure(e.message ?: e.toString (),)) } }}.flowOn(Dispatchers.IO)} Cambiamos NetworkResponse.Failure a NetworkResponse.Success con una lista vacía e isPaginationExhausted = true. Estamos enviando una lista vacía porque no tenemos más datos para paginar. Se puede cambiar dependiendo del comportamiento de su punto final. clase abstracta BaseAdapter(abrir Val interacción: interacción): RecyclerView.Adaptador() {//…fun setErrorView(errorMessage: String) {setState(RecyclerViewEnum.Error)this.errorMessage = errorMessagenotifyDataSetChanged()}fun setData(newList: ArrayList,isPaginationData: Boolean = false,isPaginationExhausted: Boolean = false) {setState(if (isPaginationExhausted) RecyclerViewEnum.PaginationExhaust else RecyclerViewEnum.View)if (!isPaginationData) {if (arrayList.isNotEmpty())arrayList.clear()arrayList.addAll (newList)notifyDataSetChanged()} else {notifyItemRemoved(itemCount)newList.addAll(0, arrayList)handleDiffUtil(newList)} }}Finalmente, en BaseAdapter cambiamos las funciones setErrorView y setData. setErrorView no será responsable por PaginationExhaust. Solo se actualiza cuando se produce un error. En setData agregamos el parámetro isPaginationExhausted y lo usamos para la función setState. Y eso fue todo. Ahora estamos listos para la implementación. proceso de muerte y cambiar de orientación.// valores claveconst val PAGE_KEY = «rv.page»const val SCROLL_POSITION_KEY = «rv.scroll_position»class MainViewModel(private val SavedStateHandle: SavedStateHandle) : ViewModel() {// Otras variables// …/** Muerte del proceso Restore * isRestoringData es necesario para gestionar recyclerview.scrollToPosition*. scrollPosition es la posición del elemento central en recyclerview*/var isRestoringData = falseprivate var page: Int = SavedStateHandle[PAGE_KEY] ?: 1var posición de desplazamiento: int = saveStateHandle[SCROLL_POSITION_KEY] ?: 0private set// Variable para detectar cambios de orientaciónvar didOrientationChange = falseinit {if (page != 1) {restoreData()} else {fetchData()}}private fun restoreData() {isRestoringData = truevar isPaginationExhausted = falseval tempList = arrayListOf< RecyclerViewModel>()viewModelScope.launch(Dispatchers.IO) {for (p in 1..page) {val job = launch(Dispatchers.IO) {repository.fetchData(p).collect { estado ->si (el estado es NetworkResponse .Success ) {tempList.addAll(state.data)isPaginationExhausted = state.isPaginationExhausted}}}job.join()}withContext(Dispatchers.Main) {_rvList.value = NetworkResponse.Success(tempList, isPaginationData = true, isPaginationExhausted = isPaginationExhausted)} }} diversión privada setPagePosition(newPage: Int) {page = newPagesavedStateHandle[PAGE_KEY] = newPage}fun setScrollPosition(newPosition: Int) {if (!isRestoringData && !didOrientationChange) {scrollPosition = newPositionsavedStateHandle[SCROLL_POSITION_KEY] = nuevaPosición } } // Otras Funciones // … }
SavedStateHandle es un mapeo de clave-valor que le permite escribir y recuperar objetos hacia y desde el estado guardado. Estos valores persisten después de que el sistema termina el proceso y permanecen disponibles a través del mismo objeto.
En ViewModel, pasamos saveStateHandle en constructor.isRestoringData y didOrientationChange es necesario para volver a las posiciones de desplazamiento anteriores. Si hemos guardado los valores en SavedStateHandle en page y scrollPosition los obtenemos, en caso contrario devolvemos valores por defecto. Endpoint» desde el primer número de página hasta el último número de página en el que estaba el usuario antes de la muerte del proceso. Agregamos todos los datos en tempList y esperamos a que se completen todas las solicitudes y finalmente los enviamos de vuelta a la interfaz de usuario. Esto no es realmente ideal para la mayoría de los escenarios de producción, ya que hace que el usuario espere mucho tiempo y realice muchas solicitudes consecutivas. En cambio, puede extraer datos de los cachés y presentárselos al usuario, lo que es mucho más eficaz y fácil de usar. setPagePosition simplemente establece el valor de newPage en page y también lo almacena en SavedStateHandle. setScrollPosition es similar a setPagePosition pero con un estado. isRestoringData y didOrientationChange deben ser falsos. Porque sin la verificación de condición, scrollPosition se establece en cero en el caso de muerte del proceso y cambio de orientación. Eso es todo para MainViewModel.class MainFragment: BaseFragment() {// …anula la diversión onSaveInstanceState(outState: Bundle) {super .onSaveInstanceState(outState)viewModel.didOrientationChange = true}private fun setObservers() {viewModel.rvList.observe(viewLifecycleOwner) {response ->// . ..when(response) {is NetworkResponse.Failure -> {recyclerViewAdapter?.setErrorView (response.errorMessage)}is NetworkResponse.Loading -> {recyclerViewAdapter?.setLoadingView(response.isPaginating)}is NetworkResponse.Success -> {recyclerViewAdapter? .setData(response.data, response.isPaginationData, response.isPaginationExhausted)if (viewModel .isRestoringData || viewModel.didOrientationChange) {binding.mainRV.scrollToPosition(viewModel.scrollPosition – 1)if (viewModel.isRestoringData) {viewModel.isRestoringData = false} else {viewModel.didOrientationChange = false}}}}}/ / …}private fun setRecyclerView() {binding.mainRV.apply {// Implementación de Recyclerview// …var isScrolling = falseaddOnScrollListener(objeto: RecyclerView. OnScrollListener() {anula diversión onS crollStateChanged(recyclerView: RecyclerView, newState: Int ) {super.onScrollStateChanged(recyclerView, newState)isScrolling = newState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE}override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {super.onScrolled (recyclerView, dx, dy)val itemCount = linearLayoutManager.itemCountval lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition()// Set new positionval centerScrollPosition = (linearLayoutManager.findLastCompletelyVisibleItemPosition() + linearLayoutManager.findFirstCompletelyVisibleItemPosition()) / 2viewModel.setScrollPosition(center.VisibleItemPosition)if (lastPosition.VisibleItemPosition) > PAGE_SIZE.plus(PAGE_SIZE.plus(PAGE_SIZE.div(PAGE_SIZE.div(PAGE_SIZE) 2)) && dy <= -75) {binding.fab.show()} else if (lastVisibleItemPosition <= PAGE_SIZE.plus(PAGE_SIZE.div(2 )) || dy >= 60) {binding.fab.hide()}recyclerViewAdapter?.let {if (isScrolling &&lastVisibleItemPosition >= itemCount.minus(5) &&it.canPaginate &&!it.isPaginating) {viewModel.fetchData()}}}} )} }}He eliminado o comentado códigos innecesarios, ignórelos, no se relacionan con este artículo. Puede consultar el código completo al final de este artículo. En onSaveInstanceState establecemos didOrientationChange = true para controlar los cambios de orientación. Dentro de setObservers() > es NetworkResponse.Success, verificamos si isRestoringData o didOrientationChange es verdadero y nos desplazamos a la posición que mantenemos en viewModel. Después de desplazarnos, establecemos la variable necesaria en falso. Finalmente, en setRecyclerView() > addOnScrollListener establecemos el valor centerScrollPosition y lo enviamos a viewModel. Puede probar y cambiar la posición de desplazamiento como desee. Me pareció mejor establecer la posición de desplazamiento en el elemento medio, completamente visible. ¡Eso es! Espero que haya sido útil. 👋👋Nota: si desea probar la muerte del proceso, le recomiendo que siga este artículo MrNtlu/RecyclerView-Guide (github.com)