Adding Briefcase Support (Java)

In the previous tutorial you built a ToDo List application that pulls its data from a Relativity Server instance and passes any changes you make straight back to the server.

Nothing is persisted locally, so every time the user launches the app it has to fetch the data from the server again. If the network connection or the server is unreachable the user is left with an empty application, which is usually not acceptable.

In this tutorial we will add local storage using a feature of Data Abstract we call Briefcase support, and then look at how and where the data is saved.

What is Briefcase Support?

A briefcase is a local snapshot of the data that your application can use when it either cannot or should not connect to the Relativity Server. Storing data locally serves a few purposes:

  1. After the first run the app can start faster, because it loads the local data without having to talk to the server first.

  2. The application can ask the server only for the records that have changed and then merge that delta with the tables loaded from the local briefcase.

  3. The application can be used without a connection to the server – for example when the user is out of reach of Wi-Fi or a cellular network. Any changes the user makes are kept locally and, thanks to Data Abstract's conflict resolution, can be sent to the server later.

In Java a briefcase works directly with the DataTable objects your application already uses, so briefcase support drops in cleanly. The briefcase classes live in the com.remobjects.dataabstract package. A complete, runnable example ships with Data Abstract as the Briefcase sample; the snippets on this page are taken from that sample, which stores the Orders and OrderDetails tables. When you add briefcase support to your own application you simply use your own tables (in the ToDo List app those would be the Priorities and Tasks tables).

Saving Data to a Briefcase

Saving happens in a few small steps: create the briefcase, add the tables you want to persist, and write it out.

Briefcase lBriefcase = new FileBriefcase(BRIEFCASE_FILENAME, false);
lBriefcase.getProperties().put("UpdateDate", "");
lBriefcase.getProperties().put("Version", BRIEFCASE_VERSION);

lBriefcase.addTable(lTableOrder);
lBriefcase.addTable(lTableOrderDetails);

lBriefcase.getProperties().put("UpdateDate",
        Calendar.getInstance().getTime().toString());
lBriefcase.writeBriefcase();

Each table is added to the briefcase with addTable, and the whole briefcase is persisted to disk with writeBriefcase.

Notice the Version property. Every briefcase stores a set of string properties (getProperties()), and here we write our own application version. When the app later loads the briefcase it can compare that value against the version the application expects, and only use the local data if the two match.

This gives you a simple upgrade path: whenever you change the database or the shape of the data in a way that would clash with what a user already has stored, just bump BRIEFCASE_VERSION. The next time the application runs it sees the mismatch, ignores the outdated briefcase and downloads fresh data from the server instead.

The tables themselves are filled from the server through the RemoteDataAdapter before they are stored, for example:

lDataAdapter.fill(lTableOrder, lRequestInfo);

Loading Data Back from a Briefcase

To read the data back, create a FileBriefcase with the read flag set to true. You can then inspect its properties and pull the tables out by name:

lBriefcase = new FileBriefcase(BRIEFCASE_FILENAME, true);

System.out.println(String.format("Briefcase version is: %s",
        lBriefcase.getProperties().get("Version")));
System.out.println(String.format("Briefcase update date is: %s",
        lBriefcase.getProperties().get("UpdateDate")));

for (int i = 0; i < lBriefcase.getTableNames().length; i++) {
    DataTable lTable = lBriefcase.tableNamed(lBriefcase.getTableNames()[i]);
    // ... use the table
}

getTableNames() returns the names of the tables stored in the briefcase, and tableNamed() returns the DataTable for a given name. Because the data now lives locally, this step does not contact the server at all.

Once the user is online again, any pending changes can be applied to the server and the briefcase refreshed:

DataTable[] lDataSet = new DataTable[] { lTableOrder, lTableOrderDetails };

lDataAdapter.applyChanges(lDataSet);
lBriefcase.addTable(lTableOrder);
lBriefcase.addTable(lTableOrderDetails);
lBriefcase.writeBriefcase();

Where the Briefcase is Stored

The file name a briefcase was created with is available through getFileName():

System.out.println(String.format("Briefcase: %s", lBriefcase.getFileName()));

Briefcase Storage Format

In Java a briefcase is stored as a single, self-contained file using the FileBriefcase class. The file carries the .daBriefcase extension and holds all of the tables together, which makes it easy to hand around if your app is designed for the user to deal with briefcases as files.

FileBriefcase takes the file name and a boolean that tells it whether to read the existing data on construction – pass false when you are about to write a fresh briefcase, and true when you want to load an existing one:

// create a briefcase to write into
Briefcase lBriefcase = new FileBriefcase(BRIEFCASE_FILENAME, false);

// open an existing briefcase to read from
Briefcase lBriefcase = new FileBriefcase(BRIEFCASE_FILENAME, true);