Tengo un fragmento que contiene un googleMap donde estoy creando un montón de marcadores (en los que también se puede hacer clic). Están condimentadas con información diferente (colores, formas, etc.) de una consulta de datos en vivo de una habitación. Además, tengo algunos botones MaterialButton (que tienen el estilo de botones pulsadores) donde activo el estado visible del marcador. Por el momento, la "configuración" de estos marcadores lleva algún tiempo (200 ms-2 segundos, depende de la cantidad de marcadores). Para salir de esa espera, estaba planeando usar un modelo de visor. Dado que hay algunos oyentes de clics para estos botones definidos allí (deberían realizar alguna acción con los marcadores), ¿seguirán vivos cuando finalice la sección de corrutina del modelo de vista? , y ¿tengo que hacer algunas tareas de limpieza en los oyentes cuando finaliza el fragmento y / o el modelo de vista?

ES DECIR:

class MapsFragment:Fragment(){

   private lateinit var mapsViewModel : MapsViewModel
   private lateinit var googleMap : GoogleMap

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

        mapsViewModel = ViewModelProvider(requireActivity()).get(MapsViewModel::class.java)

        _binding = FragmentMapsBinding.inflate(inflater, container, false)
        val root:View = binding.root
//...
      return root
   }//onCreateView   

//...

   override fun onViewCreated(view: View, savedInstanceState:Bundle?){
      super.onViewCreated(view, savedInstanceState)
//...

      mapFragment?.getMapAsync(_googleMap->
         _googleMap?.let{safeGoogleMap->
            googleMap = safeGoogleMap
         }?:let{
            Log.e(TAG,"googleMap is null!!")
            return@getMapAsync
         }   
//...
      
         mapsViewModel.apply{

            liveDataMapsListFromFiltered?.observe(
               viewLifecycleOwner
            ){mapDetailList->
                
               viewModelScope.launch{ 

                
                  binding.apply{

                     //...
                     siteMarkers.map{
                       siteMarker.remove() //removes existing markes from map on update
                     }
                     siteMarkers.clear() //empty the siteMarker array on update 
                     //...
                   
                     mapDetailList?.map{
                        it.apply{
                           //...
                           coordinateSiteLongitude?.let { lng->
                              coordinateSiteLatitude?.let { lat->
                                 siteMarkerLatLng = LatLng(lat,lng)
                                 siteLatLngBoundsBuilder?.include(siteMarkerLatLng)
                              }
                           }
                           //...
                           siteMarkerLatLng?.let { safeSiteMarkerLatLng ->
                              val siteMarkerOptions =
                               MarkerOptions()
                                    .position(safeSiteMarkerLatLng)
                                    .anchor(0.5f, 0.5f)
                                    .visible(siteMarkerState)
                                    .flat(true)
                                  .title(setTicketNumber(ticketNumber?.toDouble()))
                                    .snippet(appointmentName)//TODO: Consider build siteId instead
                                    .icon(siteIcon[iconType])
                              siteMarkers.add(
                                  googleMap.addMarker(siteMarkerOptions) //Here are the markers added
                              )
                           }//siteMarkerLatLng?.let
                         

                        }//it.apply
                   
                     }//mapDetailList?.map

                     onSiteCheckedChangeListener?.let{
                        fragmentMapsMapTagSelector
                           ?.apTagSelectorMaterialButtonSite
                           ?.removeOnCheckedChangeListener(it) //clearing listener on button before update
                     }
                   
                     onSiteCheckedChangeListener = MaterialButton.OnCheckedChangeListener { siteButton, isChecked ->
                            
                        siteMarkers.map {
                            it.isVisible = isChecked
                        }
                     }.also {
                        fragmentMapsMapTagSelector
                          ?.mapTagSelectorMaterialButtonSite
                          ?.addOnCheckedChangeListener(it)
                     }

                     //Will this onCheckedChangeListener still survive when this viewmodelscope runs to the end ?
                   

                  }//binding.apply

               }//viewModelScope.launch

            }//liveDataMapsListFromFiltered.observe
         
         }//mapsviewModel.apply   


      }//getMapAsync
   
   }//onViewCreated

}//MapsFragment

0
Roar Grønmo 13 mar. 2021 a las 20:01

1 respuesta

La mejor respuesta

Creo que no entiende qué es un CoroutineScope. Determina el ciclo de vida de las corrutinas que ejecuta, pero no de los objetos creados en el proceso de ejecución de esas corrutinas.

viewModelScope es un CoroutineScope que cancela automáticamente cualquier corrutina que se esté ejecutando cuando se derriba el ViewModel asociado. La corrutina no sabe qué estás haciendo con ella. La cancelación de una corrutina simplemente evita que se ejecute hasta su finalización, como regresar de una función antes de tiempo. En su código, establece sus oyentes y no ha almacenado referencias a ellos además de las vistas en las que están configurados, por lo que sus vidas están vinculadas a las vidas de sus respectivas vistas.

Si fuera a usar una corrutina en su fragmento para configurar algo para su IU, usaría el lifecycleScope del Fragmento, no el viewModelScope del ViewModel. Como si estuviera buscando algo para mostrar en su interfaz de usuario, querría que esa corrutina se cancele cuando se destruya el Fragmento, no el ViewModel que podría estar sobreviviendo al Fragmento.

El uso de una corrutina en su código de ejemplo parece inútil, porque no veo que se llame a ninguna función de suspensión asincrónica o de bloqueo. Mencionaste que la configuración de marcadores de sitio está tomando como 200ms. No estoy familiarizado con Google Maps ya que no lo he usado en los últimos años, por lo que no estoy seguro de qué parte requiere mucho tiempo. Por lo general, los elementos de la interfaz de usuario no le permiten interactuar con ellos en subprocesos en segundo plano, por lo que es posible que no tenga suerte. Pero tal vez la parte que consume mucho tiempo se puede realizar en subprocesos en segundo plano. Tendrás que leer la documentación. El uso de una corrutina para esto no hará que tome menos tiempo, pero puede evitar que la IU tartamudee o se congele.

Si fuera a hacer un cálculo largo con una corrutina, necesitaría cambiar de despachadores para hacer el trabajo de bloqueo e interactuar con los elementos de la interfaz de usuario en el despachador principal. Simplemente poner algo en una corrutina no hace que tome menos tiempo, pero proporciona una forma conveniente de hacer algo en otro hilo y luego continuar en el hilo principal una vez que el resultado está listo. Por ejemplo:

lifecycleScope.launchWhenStarted { // lifecycle coroutines launch on main thread by default
    val result = withContext(Dispatchers.Default) { // switch to dispatcher for background work
        doTimeConsumingCalculation()
    }
    // back on main thread:
    applyResultsToMyViews(result) 
}

Al usar launchWhenStarted en lugar de launch, el lifecycleScope de un Fragmento pausará la corrutina cuando el Fragmento no esté adjunto, lo que evitará posibles bloqueos al intentar actualizar la IU usando requireContext() o requireActivity() cuando no hay actividad.

1
Tenfour04 14 mar. 2021 a las 16:22