Expand my Community achievements bar.

Enhance your AEM Assets & Boost Your Development: [AEM Gems | June 19, 2024] Improving the Developer Experience with New APIs and Events

Storing complex objects in a SharedProperty

Avatar

Level 2
I'm trying to store a SWFLoader in a SharedObject (sProperty
below) but with no luck. Instead, I'm able to store and publish a
simple string object.



Doesn't Work:

var m:MessageItem = new MessageItem("swf1",swf1); //swf1 is
SWFLoader

sProperty.collectionNode.publish Item(m,true); // used space
in "publish Item" avoid forum "censored" word



Works:

var m:MessageItem = new MessageItem("swf1","test string");

sProperty.collectionNode.publish Item(m,true); // used space
in "publish Item" avoid forum "censored" word

--------------------------------------

Client code:

. . . .

sProperty.addEventListener(SharedPropertyEvent.CHANGE,
sPropertyChange);

. . . .

private function sPropertyChange(e:SharedPropertyEvent):void
{

trace("sPropChange called");

swf1 = e.value; //swf1 is a SWFLoader

}

16 Replies

Avatar

Former Community Member
Hi c_a,



SWFLoader is a descendant of displayObject, which means it
isn't serializable. In general, only non-displayObject types can be
sent over the wire - this is a limitation of the Flash Player, not
AFCS in specific.



A technique that could work for you is to keep unique IDs or
other details about the loader in the sharedProperty, and use those
to assign the URL of the loader, or choose which loader to use
based on ID.



As an aside, complex objects that are not displayObject
based can be serialized by AFCS, but need to be registered as
serializable. See MessageItem.registerBodyClass in the ASDocs for
more information.



hope that helps

nigel

Avatar

Level 3
I'd like to jump onto this thread if I may.

I'm currently using MessageItem.registerBodyClass to add
class information to the MessageItem like so:

MessageItem.registerBodyClass( DocumentVO );



On the receiving end I see a ByteArray coming in as the
evt.item.body. That seems correct. However when I try and cast it
to a DocumentVO I get a null value:



// throws no error

var mydoc:DocumentVO = evt.item.body as DocumentVO;

// throws error:

trace(doc.mycustomprop);



Is there anything I am missing here when doing the cast? I've
not worked with ByteArrays before.



Regards,



Stefan



Avatar

Former Community Member
Hi Stefan,

Does the DocumentVO class have other objects/classes
contained in it ? If so, you would need to register them also.
Please look at the ComplexObjectTransfer example for this.

And If you are entering all the embedded classes also, then
at the receiving end you will get the same DocumentVO as
evt.item.body.

If it still doesn't work, please send us a small code that is
giving the problem and I will run it on our side.



Thanks

Hironmay Basu

Avatar

Level 3
thanks for the reply.

I don't have a code snippet handy since I now changed the
code to just send a string, but my DocVO looks like the attached
code (I can't paste it here since the forum tells me I have
'censored words' in my post...).



The code which sent the doc looked like the second piece of
code that's attached. Darn this forum software is a bit of a pain.



Do you see any obvious mistakes?



Avatar

Former Community Member
Hi,



I tried running a short example(modifying my complexobject
transfer ) and using your DocumentVO class and it sends over the
network perfectly fine. I will attach the code here, may be you can
try to us RegisterBodyClass in the beginning like i do. Here is my
code for your class which works fine.



Hope this helps.

Thanks

Hironmay Basu

Avatar

Level 3
thanks, I'll give it a try.



Just to make sure: did you trace the resulting docVO you
received back? I didn't get an error, but a null value after the
cast. I saw the Bytearray in the debugger however before the cast.



Regards,



Stefan

Avatar

Former Community Member
Hi,



Yes I did trace the resulting object in ItemReceive and I was
getting a DocumentVO object as evt.item.body and not a ByteArray. I
was also getting the values I passed for this object ( in this
example, id, added and entered_name) .

Thanks

Hironmay Basu

Avatar

Former Community Member

Hi,

  I'm also having problems with the complex object transfer – any advice/help would be great!

  I’m following the AFCS ComplexObjectTransfer example, stripping out what I don’t need and making small additions.  I want to save a complex object ('HCOT_DataClass’) that contains a String, a Date, and a BitmapData.  The strings and dates serialize properly; the BitmapData does not.  I've tried the following cases.  Can anyone suggest how this is supposed to work, or what else I might be missing or might need to try?  Thanks!

1) in the HCOT_DataClass object: (SEE NOTE BELOW)

           public var bmd:BitmapData;
           public var date:Date;
           public var str:String;
     when registering the object

           registerBodyClass(HCOT_DataClass);

    in onClick():

           var bmd1:BitmapData = new BitmapData(100, 100, true, 0x00000000);

           data = new HCOT_DataClass(date, creator, bmd1);

     in onItemReceive():

           var data:HCOT_DataClass = p_evt.item.body as HCOT_DataClass;

     --> in this case, when the object is received, data.bmd is null.

2) same as #1, but when registering the object

           registerBodyClass(HCOT_DataClass); 

           registerBodyClass(BitmapData);

     --> in this case, when the object is received, data is null.

3) in the HCOT_DataClass object:

           public var bmd:HCOT_ImageClass;
           public var date:Date;
           public var str:String;
    in the HCOT_ImageClass object:

           public var image:BitmapData;

     when registering the object

           registerBodyClass(HCOT_DataClass); 

           registerBodyClass(HCOT_BmdClass); 

    in onClick():

           var bmd1:BitmapData = new BitmapData(100, 100, true, 0x00000000);

           var image:HCOT_ImageClass = new HCOT_ImageClass(bmd1);

           data = new HCOT_DataClass(date, creator, image);

    in onItemReceive():

           var data:HCOT_DataClass = p_evt.item.body as HCOT_DataClass;

     --> in this case, when the object is received, data is valid, and bmd.image is null.

4) same as #3, but when registering the object

           registerBodyClass(HCOT_DataClass); 

           registerBodyClass(HCOT_BmdClass); 

           registerBodyClass(BitmapData);

     --> in this case, when the object is received, data is null.

NOTE:  My HCOT_DataClass looks like:

            [Bindable] public class HCOT_DataClass extends EventDispatcher

because I need to store this in an arraycollection (for use in a display grid), and because I was getting warnings about not being able to convert objects (and I found something about needing to make the class an extension of EventDispatcher).

Avatar

Former Community Member

We'd hit this issue as well, building the Collab Pic Viewer example. As it turns out, BitmapData is not de/serializable by the Flash Player to/from AMF. See this article for more details, it's not specific to AFCS, and there are workarounds.

  nigel

Avatar

Former Community Member

Hi,

The BitMapData contains within in the Rectangle class and that has Point class. Unfortunately, when you try to get the data onItemReceive , you get a  byteArray which if you assign as BitMapData will return as null as seen by you.

One of the ways is to pass the width and height of the bitmapdata along with the byteArray object by calling getPixels(), and recreate it on the receiving client side by using the width, height and the byteArray.

In one of our examples. the collabpicviewer in examples folder , if you look at the CollabPictureViewer.mxml file , you can get some help.

I can copying one relevant example I ran by modifying the ComplexObjectTransfer example locally for your problem ,   This might help you in showing ways to send the BitMapData.

Hope this helps.

Thanks

Hironmay Basu

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    xmlns:rtc="AfcsNameSpace">
   
    <mx:Script>
        <![CDATA[
            import com.adobe.rtc.messaging.MessageItem;
            import com.adobe.rtc.events.CollectionNodeEvent;
            import com.adobe.rtc.sharedModel.CollectionNode;
            import flash.display.BitmapData;
           
            private var collectionNode:CollectionNode ;
            private var bitdata:BitmapData ;
            public var mydata:UserProfile ;
       
            private function onCreationComplete():void
            {
                //Creating and subscribing the collection node to pass the message
                collectionNode = new CollectionNode();
                collectionNode.sharedID = "complexObject6" ;
                collectionNode.connectSession = cSession ;
                //collectionNode.subscribe();
                collectionNode.addEventListener(CollectionNodeEvent.SYNCHRONIZATION_CHANGE,onSyncChange);
                collectionNode.addEventListener(CollectionNodeEvent.ITEM_RECEIVE,onItemReceive);
                collectionNode.subscribe() ;
               
                /********Important*****************/
                //Registering all the complex object classes using MessageItem.registerBodyClass(p_class:Class) method
                MessageItem.registerBodyClass(UserProfile);
                MessageItem.registerBodyClass(BitmapData);
                MessageItem.registerBodyClass(Rectangle);
                MessageItem.registerBodyClass(Point);
   
               
                    bitdata = new BitmapData(100, 100, true, 0x00000000);
                   
                    mydata = new UserProfile();
                    mydata.width = 10 ;
                    mydata.height = 10 ;
                    mydata.bitdata = bitdata.getPixels(bitdata.rect); ;

            }
           
            /**
             * Handler for synchronization change.
             */
            private function onSyncChange(p_evt:CollectionNodeEvent):void
            {
                if ( collectionNode.isSynchronized ) {
                    if ( !collectionNode.isNodeDefined("complexObjectNode")) {
                        collectionNode.createNode("complexObjectNode");
                    }
                }
            }
           
            /**
             * Publishes the UserProfile object within the message by registering the objects as described above.
             * We have to no longer use createValueObject and readValueObject for passing complex objects.
             */
            private function publish():void
            {
                collectionNode.publish Item(new MessageItem("complexObjectNode",mydata));
            }
           
            /**
             * When we receive the item, update the user interface.
             */
            private function onItemReceive(p_evt:CollectionNodeEvent):void
            {
                var userProf:UserProfile = p_evt.item.body as UserProfile ;
                var bitMapData:BitmapData=new BitmapData(userProf.width, userProf.height);
                var byteArray:ByteArray = userProf.bitdata as ByteArray;
                var bitMap:Bitmap=new Bitmap(toBitmapData(bitMapData.width, bitMapData.height, byteArray));
               
            }
           
            protected function toBitmapData(p_width:Number, p_height:Number, p_byteArray:ByteArray):BitmapData
            {
                var bitMapData:BitmapData=new BitmapData(p_width, p_height);
                p_byteArray.position=0;
                for(var i:uint=0; i < bitMapData.width; i++) {
                    for(var j:uint=0; j < bitMapData.height; j++) {
                        bitMapData.setPixel(i, j, p_byteArray.readUnsignedInt());
                    }
                }
                return bitMapData;
            }
           
           
            /**
             * Clicking on the Send User Data button creates the object and calls publish() to publish the data.
             */
            private function onClick(p_evt:MouseEvent):void
            {
                publish();
            }
           
           
        ]]>
    </mx:Script>
   
    <!--
        You would likely use external authentication here for a deployed application;
        you would certainly not hard code Adobe IDs here.
    -->
    <rtc:AdobeHSAuthenticator
        id="auth"
        userName="hironmay_b@yahoo.com"
        password="hiruana80"  />
   
    <rtc:ConnectSessionContainer id="cSession" authenticator="{auth}" width="100%"
         height="100%" roomURL="http://connect.acrobat.com/cocomobasu/myfourthroom" creationComplete="onCreationComplete()" >
        <mx:VBox>
             <mx:HBox width="100%" paddingTop="20">
                  <mx:Spacer width="100" />
                  <mx:Button label="Enter User Data" click="onClick(event)"  />
             </mx:HBox>
            
        </mx:VBox>
    </rtc:ConnectSessionContainer>
</mx:Application>

-------------------------------------------------

And The Userrofile class is

// ActionScript file
package
{
    import flash.utils.ByteArray;
   
    /**
     * UserProfile describes the user's profile details.
     *
     */
    public class UserProfile
    {       
        public var bitdata:ByteArray ;
        public var width:Number ;
        public var height:Number ;
    }
}

Avatar

Former Community Member

Hi Nigel, Hironmay

   Thanks for your quick, excellent responses, this is very helpful.  I can now see how to do this.

   I have some follow-up questions.  What I'd *really* prefer to do is used the SharedCollection as the basis for what I'm doing. That is, I'd like to use AFCS to store a collection of objects, which include Date, String, and (now) a serialized version of the BitmapData.  Users would login, and see a list of images (with descriptions) that others had previously created.  Right now, I'm basing off of ComplexObjectType and trying to keep a local ArrayCollection, which is less desirable and less efficient.

   In SharedCollection.as, it notes that class types aren't preserved (which led me to ComplexObjectType in the first place).

   However, there is a cryptic follow-on (well, at least to me) there:

    "However, by specifying the <code class="property">itemClass</code> property of the SharedCollection, the collection will automatically create instances of the appropriate class and transfer any properties from the received item to the typed objects."

   My questions:

     1) What does this mean, as far as implementation?

     2) Should I go that route, or should I just try to serialize to ByteArray, and then to String, and then base things off of the SharedCollection example.  It looks (from using the AFCS viewer) like the AFCS 'prefers' String, Number, Boolean, and combinations of those.

     3) Or, is there a better way?

Regards,

- Chris

Avatar

Former Community Member

Hi,

Yes ,  you can use shared collection's arrayCollection to store your objects containing byteArray, Numbers and Strings. In fact, the collabpicviewer example exactly does that. The CollabPicViewer.mxml file has the revelant code where it uses the sharedcollection to store and send data.SharedCollection basically extends a ListCollectionView and whatever you assign to its dataProvider will be kept  and you can use it for storing your objects instead of maintaining one yourself.

For String, Numbers, Boolean if you have them inside a class in general, you just need to do MessageItem.registerBodyClass(ClassName) and you don't need to do anything seperattely. I will say take a look at the example and run it and see how sharedCollection is used to store and retrieve data. It will answer lot of your queries. In fact , lot of the code I pasted in my last reply was taken from it.

Hope this helps.

Thanks

Hironmay Basu

Avatar

Former Community Member

Hi Hironmay,

  Got it, sorry for not looking more deeply into this at first.  I hadn't paged through 0.92 yet when I replied.  I have now, and I've integrated CollabPicViewer, and most things are working very nicely now, thanks.

  The final issue I'm having is one with uploading (as a shared file) a Bitmap that I've created (e.g. from a UIComponent).  Using fileReference, I don't see any good way to create a "temp" file, unless I present the user a savedialog, which I really really don't want to do.  Essentially, I want to directly share a Bitmap that I have created within the application - not one from the upload() / onFileselect() / onFileComplete() / byteImageLoaded() sequence.

  I tried short-circuiting all this, here's a simple test example:

/*** Test function, just for uploading bitmapdata. ***/

public function uploadImage():void

{

     var bmpd:BitmapData = new BitmapData(100, 100, true, 0x00000000);

     var rect:Rectangle = new Rectangle(20, 20, 40, 40);

     bmpd.fillRect(rect, 0xFF0000FF);

     // Fixme: something along these lines, with a save dialog for the PNG file, could work for AIR…

     //var pngenc:PNGEncoder = new PNGEncoder(); 

     //var imgByteArray:ByteArray = pngenc.encode(bmpd); 

     //var fl:FileReference = new FileReference();

     _fileReference = new FileReference();  // sure would like a way to ask for a 'sandbox temp file'

     _fileReference.name = "test.bmp";

     _filePublisher.uploadFileReference(_fileReference, _fileReference.name);

     _sharedCollectionObject = new Object();

     _sharedCollectionObject["fileName"] = _fileReference.name;

     var actualImage:ByteArray = new ByteArray();

             

     //Explore alternative ways to create a Thumbnail

     var sourceBMP:Bitmap = new Bitmap(bmpd);

     var thumbNailBitMapData:BitmapData = new BitmapData(128,80);

     var matrix:Matrix = new Matrix();

     var date:Date = new Date();  // get current date

             

     //make the thumbnail 128px X 80px

     matrix.scale(128/sourceBMP.width,80/sourceBMP.height);

     thumbNailBitMapData.draw(sourceBMP.bitmapData,matrix);

     _sharedCollectionObject["width"] = 128;

     _sharedCollectionObject["height"] = 80;

     _sharedCollectionObject["bitMapData"] = toByteArray(thumbNailBitMapData);

     _sharedCollectionObject["date"] = date.toLocaleString();

           

// Dump the Thumbnail info into the shared Collection

     _sharedCollection.addItem(_sharedCollectionObject);

     _sharedCollectionObject = null;

  }

 

  This 'works'; I get a bitmap displayed within the app (e.g. coverflow container).  Unfortunately, double-clicking on this bitmap gives an error in updateSharedValue, at

    var fileDescriptor:FileDescriptor=getFileDescriptor(fileName);

  since fileDescriptor is null.

  I could move to AIR, and create a local file from my clip, and then upload that.  But under AIR, the collabPicViewer app doesn't seem to work.

  I could go back to the approach you originally suggested, but then it seems like I will lose all of the file-based functionality, which is very nice.    This collabPicViewer functionality is exactly what I'm looking for.

  Any suggestions?  Is there a way to patch up getFileDescriptor and the _fileDescriptors?

Thanks,

- Chris

Avatar

Employee

You may want to use the BinaryPublisher instead of FilePublisher.

It works the same way but instead of passing a file reference object you pass a file name (whatever name you want your bitmap to be called) and a bytearray (the bits in your bitmap).

On the receiveing side you can use a BinarySubscriber, that will return you the bytearray.

Avatar

Former Community Member

Unfortunately, you are correct that you need user action to begin a file upload - I haven't found any way around it. Show a dialog w/ a button or something, have the user click it to begin upload.

nigel