Relationship List Sometimes Loads as HashMap

When trying to fetch a list of Contact table relationships from a 1->Many user property called ‘contacts’, sometimes the resulting Java array is of the correct type and other times the same block of code yields a list of type HashMap.
Take the following block for example:

Object[] c = (Object[])user.getProperty("contacts");
Contact[] cTyped = new Contact[c.length];
System.arraycopy(c, 0, cTyped, 0, c.length);

This block will sometimes correctly yield an array of Contacts, but other times the following exception is thrown:

java.lang.ArrayStoreException: source[0] of type java.util.HashMap cannot be stored in destination array of type Contact[]

Upon app launch, I am calling the function Backendless.Data.mapTableToClass(“Contact”, Contact.class); to ensure proper mapping. But this error still happens, seemingly at random. Although, right now it is happening consistently, which is why I’m finally writing a support topic about it.

Server sends the same response every time, so it must be something on the client side. Could you try moving the call to map table to class (Backendless.Data.mapTableToClass) closer to the code where the user object is loaded?

I moved the mapTableToClass call to immediately before my block and the same issue persists.

The mapTableToClass was previously located in my Application class’s onCreate method immediately after initializing Backendless.

Could you please post the source code for the Contact class here?

Do you call Backendless.Data.of( BackendlessUser.class).save( user ) anywhere in your code?


public class Contact {


    private String objectId;
    private BackendlessUser user;
    private double userScore;


    public String getObjectId() {
        return objectId;
    }


    public void setObjectId(String objectId) {
        this.objectId = objectId;
    }


    public BackendlessUser getUser() {
        return this.user;
    }


    public void setUser(BackendlessUser serviceUser) {
        this.user = serviceUser;
    }


    public double getUserScore() {
        return compatibilityScore;
    }


    public void setUserScore(double score) {
        this.userScore = score;
    }


    @Override
    public boolean equals(Object other) {
        if(other instanceof Contact) {
            Contact o = (Contact) other;
            return o.hashCode() == this.hashCode();
        }


        return false;
    }


    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + user.getUserId().hashCode();


        return result;
    }
}

Looks good (the backreference to BackendlessUser is questionable, but should not be a problem). Could you please check if it works in a completely isolated app where all you do is login and then get the contacts property?

The BackendlessUser in this case is not a back reference, it is a different user that the owning user has added to their contacts list. The purpose of the Contact table is to act as a list of users + additional information (in this case userScore) that relates to the owner’s relationship with the Contact user.

Okay, that makes sense. Could you please run the test I described and let us know? If it works in that case, there must be something else in the code that messes up the mapping.

Fetching contacts from a completely empty Android app yields the same problem. Code included below was in my onCreate method:


Backendless.initApp(this, APP_ID, APP_TOKEN, "v1");


Backendless.Data.of(BackendlessUser.class).findById("C901FA47-5F41-FE5F-FF62-6F4FED089700", 2, new AsyncCallback<BackendlessUser>() {
    @Override
    public void handleResponse(BackendlessUser user) {
        try {
        Backendless.Data.mapTableToClass("Contact", Contact.class);


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


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


        Log.d("TEST", cTyped.length + " contacts available.");


        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void handleFault(BackendlessFault backendlessFault) {
        Log.e("TEST", backendlessFault.getMessage());
    }
});

Thanks, Tim. Could you please move this line of code:

Backendless.Data.mapTableToClass("Contact", Contact.class);

to be called right before the findById call? The way you have it now would be too late as the BackendlessUser object is already materialized by then.

Regards,
Mark

Moving it out works. Its yielding contacts correctly. What would cause Backendless SDK to lose its mappings at some point during code execution? Should mapTableToClass just be called before any data retrieval operation? I know calling it in Application.onCreate works because this isn’t a consistent bug. Just curious if there is anything known to cause the mappings to be lost.

The mapping is stored in a static variable. It is known for Android to wipe out static values, perhaps this is the case here. Multiple calls to establish the mapping are harmless and, yes, you can call it before every data retrieval operation.

it would be interesting to run the following test - you can check if the mapping is still in place by making the following call:

weborb.types.Types.getMappedServerClass( "Contact" )

If the method returns null, it would indicate the mapping is lost.

I’m continuing to get this error in my main app, even if I call mapTableToClass before fetching the user. For example, here is how I’m getting my user when they were previously logged in:

public static void VerifyLogin(@NonNull final AuthResponse callback) {
    String loggedInUser = Backendless.UserService.loggedInUser();

    if(loggedInUser == null || loggedInUser.isEmpty()) {
        callback.handleResponse(null, false, "No previous session found. Please login.");
    } else {
        Backendless.Data.mapTableToClass("Contact", Contact.class);
        Backendless.Data.of(BackendlessUser.class).findById(loggedInUser, 2,
                new AsyncCallback<BackendlessUser>() {
            @Override
            public void handleResponse(BackendlessUser response) {
                Backendless.UserService.setCurrentUser(response);
...

How is this code different from the one you posted here?

It is in my actual app, not an empty android app with a single blank activity.

I need to have a way to reproduce the issue. It works in an empty app for us as well. If you’d like you can package your app and share with us and we will debug it there.

I have found a way to reliably reproduce.

  1. Fetch user object from data service as mentioned in previous replies.
  2. Use the Backendless.UserService.update(BackendlessUser user) method to update that user’s record.
  3. Observe that the type associated with the “contacts” property has changed.

See attached screenshots for example.

Screen Shot 2015-10-16 at 1.35.38 PM.png

Screen Shot 2015-10-16 at 1.37.14 PM.png

Excellent! This is very helpful. We’ll give it a try now. Stay tuned.

Tim,

I put together a test which does exactly what you described, yet I cannot reproduce the problem. Here’s what I see in the debugger after the user object is updated:
http://support.backendless.com/public/attachments/f411567ffea7a446a23df6c76da78942.jpg</img>

Here’s my code. Please take a look and let me know if I am doing something different:

    Backendless.initApp( "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 )
      {
        Object[] contacts = (Object[]) user.getProperty( "contacts" );
        ((Contact) contacts[ 0 ]).phoneNumber = "1111111";


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


          @Override
          public void handleFault( BackendlessFault fault )
          {


          }
        } );
      }


      @Override
      public void handleFault( BackendlessFault fault )
      {


      }
    } );

I left my app id and secret key in there as well, so you can try it too. Here’s my Contact class:

package com.mbaas;


public class Contact
{
  public String name;
  public String phoneNumber;
}