Search code examples
androidbackground-processretrofitrealm

Realm and Retrofit, how to save outside the UI thread?


I'm using Retrofit to download a json of Clients, I then have to persist them with Realm (Client extends RealmObject). The problem is I have 15k Clients, so saving takes some seconds. How do I pass the Client list I get from Retrofit to a background thread if I can't pass RealmObject between threads?

This is the code I have right now, it blocks the UI for several seconds when I perform the save.

api.clients(token, date, new Callback<List<Client>>() {
            new Callback<LoginResponse>() {
    @Override
    public void success(List<Client> clients, Response response) {
        Logs.retrofit("Clients status: " + response.getStatus());
        if (response.getStatus() == 200) {
            //It's all good man!
            saveClients(clients);
            if (callbacks != null) callbacks.onClientListDownloadSuccessful();
        } else if (response.getStatus() == 401) {
            //Wrong token, refresh it and retry
            refreshLoginToken();
        } else {
            //Other error status
            if (callbacks != null)
                callbacks.onClientListDownloadError(response.getStatus());
        }
    }

    @Override
    public void failure(RetrofitError error) {
        Logs.retrofit("Client error: " + error.getMessage());
        if (error.getResponse() != null && error.getResponse()
                                                   .getStatus() == 401) {
            refreshLoginToken();
        } else if (callbacks != null) callbacks.onClientListDownloadError(0);
    }

private void saveClients(List<Client> clients) {
    Logs.realm("Saving clients to local db: " + clients.size());
    Realm realm = App.getCommercialiRealm(context);

    List<Long> ids = new ArrayList<>();
    realm.beginTransaction();
    for (Client client : clients) {
        if (client.isDeleted()) {
            //Delete client
            ids.add(client.getID());
        }
    }

    //Add or update clients
    realm.copyToRealmOrUpdate(clients);
    Logs.realm("Client in db " + realm.where(Client.class)
            .count());

    //Delete clients
    deleteClients(realm, ids);
    Logs.realm("Client in db after cleanup " + realm.where(Client.class)
            .count());

    realm.commitTransaction();
    PrefsUtils.saveLastSync(context, System.currentTimeMillis());
}

Solution

  • Personally I would try to keep my service and persistence layers decoupled. Parse the network response to a Client class implemented as a simple POJO, and use RealmObjects only when dealing with the database (you would need some kind of mapping utility between the two 'client' entities).

    If you really want to use Retrofit responses as RealmObjects you are probably going to need to drop the 'Callback' format in your API interface and implement your own threading solution so both the Retrofit and the Realm operations are performed in the same thread.

    Basically, set it up so that you can execute something like this:

    try {
        List<Client> clients = api.clients(token, date);
        saveClients(clients);
    } catch(RetrofitError e){
        Response response = e.getResponse();
        if (response.getStatus() == 401) {
            // Wrong token, refresh it and retry
            refreshLoginToken();
        } else {
            // Propagate error on the UI thread or whatever :P
        }
    }
    

    in a background thread.