martes, 20 de octubre de 2009

Flex/AIR: Error de conversión a ArrayCollection de ObjectProxy en llamadas HTTPService

Me he encontrado con un problema habitual, y ocurre cuando uno hace una llamada a un HTTPService, y en la función que gestiona (handle) la llamada, al asignar el resultado del XML a un ArrayCollection, se encuentra con que no puede asignarse un objeto ObjectProxy al ArrayCollection. Este tipo de operaciones son habituales cuando hay que cargar una lista o un ComboBox. Este problema ocurre cuando solamente se tiene un elemento en la lista, mientras que si hay más de uno, este problema no ocurre. Asimismo, si no hay resultado, también puede dar un error de null. Creo que se trata de un bug de Flex, ya que en estos casos, automáticamente debería dar un ArrayCollection de todas maneras, con cero, uno o más de un item.

Antes de continuar, indicar que este post no contiene un ejemplo completo para poder probar, si no que explica detalladamente como resolver esta situación.

Tras dar vueltas y vueltas al problema, he realizado una pequeña "ñapa" o "parche" para que la carga del ArrayCollection se realice sin problemas.

En primer lugar voy a presentar el XML que devuelve el HTTPService:




<?xml version="1.0" encoding="ISO-8859-1" ?>

<products_providers_in_use>
  <product_provider_in_use>
    <data>6</data>
    <label>Bill Gates</label>
    <id_product>21</id_product>
    <name_product>Escalope de ternera</name_product>
    <total>0</total>
  </product_provider_in_use>
  ...
<products_providers_in_use>




La definición del HTTPService permite definir en qué funciones delegar la ejecución en caso de error (fault) o de éxito (result), así como la URL al HTTPService (url).



<!-- Call to srvGetProductsProvidersInUse service -->
<mx:HTTPService 
  result="handleProductsProvidersInUse(event);"
  fault="handleFault(event);" 
  id="nombre_del_servicio" resultFormat="object"
  url="url_al_httpservice"
  useProxy="false">
</mx:HTTPService>



Es necesario definir el ArrayCollection como una variable de ámbito local, que pueda ser accedida por todas funciones del módulo, componente, etc.



[Bindable]
import mx.collections.ArrayCollection;

private var acInUse:ArrayCollection;



La llamada al HTTPService se realiza mediante el objeto HTTPService (referenciando el nombre indicado en la propiedad id) y al método "send" en el momento que sea necesario (en el creationComplete, al pulsar un botón, etc.):



nombre_del_servicio.send();



Una vez se ejecuta el HTTPService, éste accederá a dicho servicio para obtener un XML. En el caso de que no se haya producido ningún problema en la comunicación, se delegará la ejecución en la función definida en "result". El código para leer correctamente este XML en todos los casos (ya corregido el bug) es el siguiente:



private function handleProductsProvidersInUse(event:ResultEvent):void {
  try {
    acInUse = 
      event.result.products_providers_in_use.product_provider_in_use
        as ArrayCollection;
  }
  catch (err:Error) {
    acInUse=new ArrayCollection();
  }
  if (acInUse==null) {
    var o:Object;
    o=event.result.products_providers_in_use.product_provider_in_use;
    acInUse = new ArrayCollection(
      [{data:o.data,label:o.label,id_product:o.id_product,
      name_product:o.name_product,total:o.total}]);
  }
}



Para controlar las excepciones se abre un bloque try...catch para intentar acceder al XML recogido. Lo normal es asignar al ArrayCollection la colección de datos del XML que se repite. El XML está contenido dentro del parámetro de evento, y en la propiedad "result". El primer elemento del XML ("products_providers_in_use") no se repite, y marca el principio y fin del grupo de datos. Por ello hay que llegar hasta el siguiente nivel del XML: "product_provider_in_use". En teoría, este nivel retornaría un ArrayCollection. En el caso de que tenga más de un elemento no hay problema, pero si hay un sólo elemento, no produce ninguna excepción, pero, inexplicablemente, nos retorna un null. Por ello, tras el catch se hace una comprobación de si es nulo, en cuyo caso se crea un ArrayCollection nuevo, se recoge el único elemento del resultado en un objeto genérico, y se crea un elemento con la información recogida en el XML. En el caso de que el XML estuviera vacío o tuviera algún problema, saltaría una excepción, la cual es recogida por el bloque "catch" y crea un ArrayCollection nuevo, pero vacío, pero no null.

Espero que este ejemplo corte definitivamente el tiempo que os haya hecho perder esta singular situación.

Safe Creative #1001195348495