How do I reference the same CB db in an always-running background service & an activity on Android?


#1

Hello - My application has an always-running background service and a user interface (Activity). The background service listens for events that are synced to our CB gateway in the cloud (but for this example, I’ve turned that off). The user can also open an application on the device to view the information that is being captured by the background service. Currently I’ve set up a Manager in both the background service and the foreground Activity.

I’d like to figure out how to write data in the background service and have it accessible from the foreground activity without a roundtrip to the server to minimize unnecessary data usage. Is this possible? Is there a good pattern?

Here’s the identical set up in both the service and the activity:

    manager = null;
    try {
        manager = new Manager(new AndroidContext(this), Manager.DEFAULT_OPTIONS);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Create or open the database
    try {
        database = manager.getDatabase(Constants.CB_DB_NAME);
    } catch (CouchbaseLiteException e) {
        e.printStackTrace();
    }

In the background service I write to the database:

final Document updateDocument = database.getDocument(Constants.MY_DOC);
            try {
                updateDocument.update(new Document.DocumentUpdater() {
                    @Override
                    public boolean update(UnsavedRevision newRevision) {
                        Map<String, Object> props = newRevision.getProperties();
                        // <Set properties>
                        newRevision.setProperties(props);
                        return true;
                    }
                });
            } catch (CouchbaseLiteException e) {
                Log.e("Scan", "CouchbaseLiteException thrown", e);
            }

In the foreground I listen for changes and load the latest version when the activity is resumed.

    View myView = database.getView(Constants.MY_VIEW);
    if(myView.getMap() == null) {
        myView.setMap(new Mapper() {
            @Override
            public void map(Map<String, Object> document, Emitter emitter) {
                if(document.containsKey(TYPE) &&
                        Objects.equals(document.get("type"), Constants.MY_DOC_ID)) {
                    emitter.emit(MY_STUFF, document.get(MY_STUFF));
                }
            }
        }, "1.0");
    }

    LiveQuery myQuery = myView.createQuery().toLiveQuery();
    myQuery.addChangeListener(new LiveQuery.ChangeListener() {
        @Override
        public void changed(LiveQuery.ChangeEvent event) {
            String stuff = (String)event.getRows().getRow(0).getDocument().getProperty(MY_STUFF);
            TextView textView = (TextView) findViewById(R.id.myStuff);
            textView.setText(myStuff);
        }
    });

The activity doesn’t receive any update events when I write in the background.

Any good patterns here or do I need to make that roundtrip to the server?

Thanks


Sharing a CBL Database between apps
#2

Answered my own question:

Because my service is always running when the Activity resumes and because the Activity and the service are running in the same application / thread I can directly share the Service’s Manager and Database with the activity through the Binder interface in my service.

In my service:

public class MyBinder extends Binder {
    public Manager getManager() {
        return manager;
    }
    public Database getDatabase() {
        return database;
    }
}
private MyBinder myBinder = new MyBinder();

And in my Activity:

//onResume()
    Intent bindMyServiceIntent = new Intent(this, MyService.class);
    this.bindService(myServiceIntent, myServiceConnection, Service.BIND_AUTO_CREATE);

private final ServiceConnection myServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        MyService.MyBinder binder = (MyService.MyBinder)service;

        database = binder.getStepsDatabase();
        manager = binder.getManager();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
};

#3

Hi Michael,

If you’re running the Activity and Service in the same process, you could also share them through a static class (possibly as a singleton).

A lot of people create singletons by creating an Application subclass. I personally don’t like this, since it has occasional tricky aspects, and it’s overloading the intention of the Application class.

The alternative I prefer is to create a Content Provider subclass. People use Content Providers this way because they are guaranteed to initialize at app startup before any activities start. I can post a sample if you like.

Hod


#4

Hi Hod -

Thanks, I’d love to see some sample code.

Do you wrap the CBLite Manager and Database in the the ContentProvider and pass your queries and results through the query() / find() / update() etc. interface?

Michael


#5

No, really, I just use the content provider as a bit of a hack, although there’s no reason you couldn’t fully adapt it. I really just use it to retain the Manager and Database objects where they’re easily pulled in (i.e. as a classic singleton, so the fact it’s a content provider only matters in that it’s initialized early).

I’m not finding the example I was working on based on our Todo project. I hope I didn’t discard it by accident. If I don’t find it I’ll see if I can put something together again.