Relationship List Sometimes Loads as HashMap

Ok, Mark. I’ve cracked it. The problem stems from the implementation of the update(BackendlessUser, AsyncCallback) function. The problem is, I’m sending my user object off to be saved asynchronously, and while its being saved, I still want to use data from my user (i.e., their contacts). However, the update function directly mutates the object that I pass into it instead of making a copy. So, I’m assuming as part of the serialization process, relations on the user are cast to Object in order to make them generic and then by the time the update process is over they are cast back to their appropriate types. However, this causes problems when you continue to try to use your data during the asynchronous update process. This problem isn’t easy to code around either, since BackendlessUser implements neither a copy constructor or the Cloneable interface.

Here is code that will replicate the bug with the application you used in your prior example:

public class LaunchActivity extends ActionBarActivity {

    BackendlessUser user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_launch);

        Backendless.initApp(this, "757E349F-C515-5AF3-FF5D-EFF0379BFB00", "115455F0-5317-46D0-FFB2-82712757C900", "v1" );
        Backendless.Data.mapTableToClass( "Contact", Contact.class );
        Backendless.Data.of( BackendlessUser.class ).findById( "A8A2A0B6-472B-5C15-FF97-30751A2C6F00", 2, new AsyncCallback<BackendlessUser>()
        {
            @Override
            public void handleResponse( BackendlessUser user )
            {
                LaunchActivity.this.user = user;
                Object[] contacts = (Object[]) LaunchActivity.this.user.getProperty( "contacts" );
                ((Contact) contacts[ 0 ]).phoneNumber = "1111111";

                Backendless.UserService.update(LaunchActivity.this.user, new AsyncCallback<BackendlessUser>() {
                    @Override
                    public void handleResponse(BackendlessUser response) {
                        System.out.println("Data has been saved");
                    }

                    @Override
                    public void handleFault(BackendlessFault fault) {
                    }
                });

                Object[] c = (Object[])LaunchActivity.this.user.getProperty("contacts");
                Contact[] cTyped = new Contact[c.length];

                System.arraycopy(c, 0, cTyped, 0, c.length);
            }
            @Override
            public void handleFault( BackendlessFault fault )
            {
            }
        } );
    }
}

Thanks, Tim. I will give it a try. Of course, a workaround for the problem is to use the “Object[] contacts” array which you got before the update call ))

Regards,
Mark

In this sample app case, yes, that would be a workaround. However, in actual practice things are not so linear.

Hi Tim,

If you need to get a hold of the properties of the user object while it is being updated, then you should use the following call to update it on the backend:

Backendless.Data.of( BackendlessUser.class ).save( user, asyncCallback );

Simply replace “Backendless.UserService.update” with the call shown above and things will work again.

Regards,
Mark

Mark,

Now that I am using the Data service to save my user, I am getting the following stack trace when trying to add a contact and save.


BackendlessException{ code: 'IllegalArgumentException', message: 'Contacts' }
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:299)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.Persistence$4.handleResponse(Persistence.java:187)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage$ResponseHandler.handle(AsyncMessage.java:64)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage.handleCallback(AsyncMessage.java:41)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.core.AndroidCarrier$1.handleMessage(AndroidCarrier.java:37)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:98)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5586)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:  Caused by: java.lang.NoSuchFieldException: Contacts
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at java.lang.Class.getDeclaredField(Class.java:596)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getField(ReflectionUtil.java:65)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getField(ReflectionUtil.java:71)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getFieldValue(ReflectionUtil.java:39)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:266)
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.Persistence$4.handleResponse(Persistence.java:187) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage$ResponseHandler.handle(AsyncMessage.java:64) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage.handleCallback(AsyncMessage.java:41) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.backendless.core.AndroidCarrier$1.handleMessage(AndroidCarrier.java:37) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:98) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5586) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084) 
10-23 14:17:01.746 29039-29039/com.domain.appname E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method) 

Could you please share the code where you perform the API call resulting in the exception?


addContact(someContact);
Backendless.Data.mapTableToClass("Contact", Contact.class);
Backendless.Data.of(BackendlessUser.class).save(mServiceUser, new AsyncCallback<BackendlessUser>() {
...
});


public void addContact(Contact contact) {
    List<Contact> contacts = getContactsList();


    if(contacts == null) contacts = new ArrayList<>();


    if(!contacts.contains(contact)) {
        contacts.add(contact);
        mServiceUser.setProperty("contacts", contacts);
    }
}


public List<Contact> getContactsList() {
    if(mServiceUser.getProperty("contacts") == JSONObject.NULL ||
            mServiceUser.getProperty("contacts") == null) {
        return new ArrayList<>();
    }


    Object[] c = (Object[])mServiceUser.getProperty("contacts");
    Contact[] cTyped = new Contact[c.length];


    System.arraycopy(c, 0, cTyped, 0, c.length);


    return new ArrayList<>(Arrays.asList(cTyped));
}

Could you try changing the following lines to create “strongly-typed” generics and see if it makes a difference? Java does not have runtime enforcement of the generics and thus the information about the underlying type is lost by the time it gets to our serialization logic.

change this:

return new ArrayList<>();

to:

return new ArrayList<Contact>();

And change this:

return new ArrayList<>(Arrays.asList(cTyped));

to:

return new ArrayList<Contact>(Arrays.asList(cTyped));

Same error / same stack trace.

And the IDE (Android Studio) marks the types as redundant since the return type for the function is List<Contact>.

Do you use Backendless.UserService.update anywhere in your code?

Nope. Just searched the entire project to be sure.

The new contact is saved though, right? I just tried and got the same error, but I see that the object is saved. It means the error occurs with the response. P

Yes, the contact is saved but the error is still crashing my app. It appears to be happening internal to the Backendless SDK so I’m not sure what to do about it.

For now just wait, we’re looking into it.

Tim, could you check if you have the latest version of Backendless SDK for Android? I tried the code again with the latest build (mine was a bit outdated) and the exception does not occur any more.

I upgraded from 2.0.1 (which was the latest on Maven Central) to 2.0.4 (the latest from the backendless website) and I am still getting the same crash.

Thy with this file, it is the latest build, which is not on the website yet:

https://github.com/Backendless/Android-SDK/blob/master/out/backendless.jar

Ok. I’m using the Backendless SDK from github now. I’m getting a similar but different error. Now, instead of a gripe about the ‘Contacts’ field not existing, it is griping about ‘User’ not existing.


10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime: BackendlessException{ code: 'IllegalArgumentException', message: 'User' }
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:354)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:342)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.Persistence$4.handleResponse(Persistence.java:187)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage$ResponseHandler.handle(AsyncMessage.java:64)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage.handleCallback(AsyncMessage.java:41)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.core.AndroidCarrier$1.handleMessage(AndroidCarrier.java:37)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:98)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5586)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:  Caused by: java.lang.NoSuchFieldException: User
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at java.lang.Class.getDeclaredField(Class.java:596)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getField(ReflectionUtil.java:65)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getField(ReflectionUtil.java:71)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.utils.ReflectionUtil.getFieldValue(ReflectionUtil.java:39)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:260)
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.FootprintsManager$Inner.updateFootprintForObject(FootprintsManager.java:342) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.Persistence$4.handleResponse(Persistence.java:187) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage$ResponseHandler.handle(AsyncMessage.java:64) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.async.message.AsyncMessage.handleCallback(AsyncMessage.java:41) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.backendless.core.AndroidCarrier$1.handleMessage(AndroidCarrier.java:37) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:98) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5586) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084) 
10-23 15:41:25.546 24141-24141/com.domain.appname E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method) 

Could you attach a screenshot of the User Properties screen from your app? I’d like to see how the “user” property is declared.