Robospice storing object that extends ArrayList in

2019-05-07 14:53发布

问题:

Background

I have been trying to modify the Robospice Ormlite example code, which I have been successful to run. The only changes I have made is that I have a JSON response that contains an array of objects.

I created 2 classes:

public class Foos extends ArrayList<Foo>
public class Foo

I had initial problems, as my JSON response is a pure array with no outer container. I read via google groups that the way to solve this is to create a "fake" class (Foos) that extends an ArrayList. This produced the following code:

public class FooRequest extends SpringAndroidSpiceRequest< Foos > {

   private String baseUrl;

    public FooRequest() {
        super( Foos.class );
        this.baseUrl = "http://somewhere.com/jsonurl";
    }

    @Override
    public Foos loadDataFromNetwork() throws RestClientException {
        Ln.d( "Call web service " + baseUrl );
        return getRestTemplate().getForObject( baseUrl, Foos.class );
    }
}

I then created the following service code, adapted from the examples.

public class MySpiceService extends SpringAndroidSpiceService {

    private static final int WEBSERVICES_TIMEOUT = 10000;

    @Override
    public CacheManager createCacheManager( Application application ) {
        CacheManager cacheManager = new CacheManager();
        List< Class< ? >> classCollection = new ArrayList< Class< ? >>();

        // add persisted classes to class collection
        classCollection.add( Foos.class );

        // init
        RoboSpiceDatabaseHelper databaseHelper = new RoboSpiceDatabaseHelper( application, "sample_database.db", 2 );
        InDatabaseObjectPersisterFactory inDatabaseObjectPersisterFactory = new InDatabaseObjectPersisterFactory( application, databaseHelper, classCollection );
        cacheManager.addPersister( inDatabaseObjectPersisterFactory );
        return cacheManager;
    }

    @Override
    public RestTemplate createRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        // set timeout for requests

        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setReadTimeout( WEBSERVICES_TIMEOUT );
        httpRequestFactory.setConnectTimeout( WEBSERVICES_TIMEOUT );
        restTemplate.setRequestFactory( httpRequestFactory );

        // web services support xml responses
        MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter();
        FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        final List< HttpMessageConverter< ? >> listHttpMessageConverters = restTemplate.getMessageConverters();

        listHttpMessageConverters.add( jsonConverter );
        listHttpMessageConverters.add( formHttpMessageConverter );
        listHttpMessageConverters.add( stringHttpMessageConverter );
        restTemplate.setMessageConverters( listHttpMessageConverters );
        return restTemplate;
    }

}

The problem

The code runs without any force closes however I never hit the RequestListener inner class within my activity.Neither the success or failure message methods are fired, and it seems quite difficult to debug, so it feels like A "silent failure".

    public final class MyRequestListener implements RequestListener< Foos > {

        @Override
        public void onRequestFailure( SpiceException spiceException ) {
            Toast.makeText( SampleSpiceActivity.this, "failure", Toast.LENGTH_SHORT ).show();
        }

        @Override
        public void onRequestSuccess( final Articles result ) {
            Toast.makeText( SampleSpiceActivity.this, "success", Toast.LENGTH_SHORT ).show();

        }
    }

What I have tried

I have tried to annotate the class Foo, with Ormlite annotations, to see if the library needed a helping hand, but still no luck. As outlined below:

@DatabaseTable
public class Foo {

    @DatabaseField(generatedId = true)
    private int id;

    @DatabaseField
    private String someText;

}

I wonder if this is a result of the Foos class when I actually want a database table storing Foo. Any advice would be great!

回答1:

This question is not really a bug in RoboSpice, but a limitation of the ORM Lite model.

If you want to serialize a collection of Foo using ORMLite, then you must use serialize a class that is a collection of Foo. You approach is right. Nevertheless, you can't just get rid of it to store the Foo elements individually. You not only have to use the Foos class to parse the response but also to store data in the database.

Foos should :

  • define an id
  • be annotated using DataBaseTable and DataField and Foreign collection

The problem is that you can't annotate a class and saying "I am a Foreig Collection". This annotation can only be used on fields, not on classes. So If your class would "hold" a collection, that would work, but not for a class that "is a" collection.

So, I believe that the short answer is : no, you can't do that with ORM Lite. You must store a top class like Foos, but it should be serializable for ORM Lite and for this it needs to have a collection field, and can't be a collection directly.



回答2:

To add on to this excellent question, I was also experiencing this 'silent failure' and did not know what to do. I eventually selected inside Android Studios to see all the messages in logcat, so not only the android errors, but everything. I did this by selecting "Verbose" and "No Filters".

When I ran my app, I discovered that the reason why I was getting a silent error was because I did not generate unique ID for my SQL and this caused it to bomb out.

 Caused by: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: post.ID (code 1555)

I fixed it by doing this on my outer class ID field which allowed it to generate unique IDs by itself:

@DatabaseField(allowGeneratedIdInsert=true, generatedId=true)
private int ID;


回答3:

As @Snicolas said, you should change to Collection instead of ArrayList. I wanted to do more or less the same but without the result object, my workaround on this question RoboSpice persist JSON array with OrmLite should work for you.

@DatabaseTable
public class Foos {
    @DatabaseField(id = true)
    private int id;
    @ForeignCollectionField(eager = false)
    private Collection<Foo> result;

    // getters and setters
    ...
}

@DatabaseTable
public class Foo {
    @DatabaseField(id = true)
    private int id;
    @DatabaseField(foreign = true)
    private Foos foos;

    // getters and setters
    ...
}