Filters sample (Android) (Eclipse)

The Filters Sample demonstrates how to filter data obtained from a Data Abstract Server, and how to selectively return only certain fields rather than all of them. The sample is a demonstration of using Dynamic Where and Dynamic Select which are a useful alternative to using DA SQL where you might not want to issue full SQL statements.

Getting Started

To get started with this sample you need to have Eclipse, the Eclipse ADT plugin and the Android SDK installed. Regarding the SDK you need a minimum of one of the Android API builds, the build tools and a system image if you wish to test the sample out in the simulator.

When you load the project in Eclipse you will need to copy the com.remobjects.dataabstract.jar and com.remobjects.sdk.jar from where you installed Data Abstract for Java (typically "c:\Program Files (x86)\RemObjects Software\Data Abstract for Java\bin") into the "libs" folder of the project and if needed go into the project properties and add them on the "Java Build Path" tab.

Finally by default the sample interacts with the RemObjects sample server at "http://remobjects.com:8099/bin".

Running the Sample

When the sample starts it has a ListView which is initially empty apart from an instruction message. Then there is a menu bar with 3 buttons, the first "MODE" allows you to swap between "Local Filtering" and "Remote Filtering" modes. Next is a "Refresh" button that will retrieve the initial table data when pressed. Finally the sliders open the "Settings" screen where you can change the URL that the app connects to and provides some additional information.

The first step is to press the "Refresh" button, which will retrieve the data. As the sample defaults to "Local filtering" the full "Products" table is retrieved and Dynamic Where to retrieve only three rows from the "Groups" table. The ListView is then updated with the data from the "Groups" table.

Pressing on one of the rows in the "Groups" ListView will then display a table of filtered rows where the "ProductsGroup" field matches the selected group. You can change the filter "Mode" and press the "Refresh" button which will filter that table either locally or remotely and briefly display the number of records retrieved from the table in the server. With local filtering roughly 1200 records are retrieved and then filtered down to 4, with remote filtering only 4 records are returned.

Examining the Code

The Java based sample is comprised of 5 classes that handle the UI and those functions needed to interact with Relativity Server.

  • DataModule handles initializing the basic connection to the server and creating the RemoteDataAdapter. It also registers itself to listen for changes to the preferences so that when the settings data is changed the RemoteDataAdapter is updated to reflect the new data.
  • TableListAdapter is a class used by MainActivity to display the "Clients" data in the ListView. The class implements the TableChangedListener.
  • SettingsActivity handles interacting with the "Preferences" subsystem for storing and retrieving data that will be used for logging in like the server url, username and password.
  • GroupsActivity handles retrieving the "Groups" table, and displaying that data in a ListView.
  • ProductsActivity handles retrieving & filtering of the "Products" table and display those results in a ListView.

Filtering data remotely with Dynamic Select & Dynamic Where

There are two examples of filtering the data remotely on the server in this sample. The first we'll describe uses both Dynamic Where and Dynamic Select to retrieve any rows from the "Products" table where the field "ProductsGroup" matches a particular value and only return certain fields from that row. The second is simpler and only uses Dynamic Where to retrieve 3 particular rows from the "Groups" table.

Starting with the first example, the first step to retrieving filtered data is to create a TableRequestInfoV5 object to which we can pass the WHERE and SELECT clauses. The number of records returned could be controlled by passing a value to the setMaxRecords of TableRequestInfoV5, without passing a value the class defaults to returning all matching records.

The next step is to create a Dynamic Where clause that will match any rows in the "Groups" table where the "ProductsGroup" field equals the value in fGroupId. The [WhereExpression] is composed of a BinaryExpression, the first argument is the field to match against which is passed as a FieldExpression object whose constructor takes the field name as a string. The second argument is the value we want to match the field against, this is passed as a ConstantExpression object whose constructor takes the value to use as the first argument, and a data type as the second argument. The last argument passed to the BinaryExpression tells the expression how it is to treat the previous arguments, here BinaryOperator.Equal is passed to indicate that the two previous arguments are to be equal to each other.

If you wrote the BinaryExpression as an SQL statement it would be:

SELECT * FROM Groups WHERE (ProductsGroup=fGroupId)

To pass the WhereExpression to the setWhereClause method of TableRequestInfoV5, it needs to first be converted to XML using the toXmlNode method and wrapped in a XmlType object.

When retrieving rows normally all of the fields of that row are retrieved. If you want to limit the fields to only those you are interested in (perhaps to simply limit the amount of data transfered), then you can use the setDynamicSelectFieldNames method of TableRequestInfoV5. As an argument pass in a StringArray object which takes an array of field names as strings.

To retrieve the data pass the table to fill, the TableRequestInfoV5 object and the callback to execute when the fill method is finished.

 

private void loadProductsWithRemoteFiltering(FillRequestTask.Callback callback) {
    TableRequestInfoV5 request = new TableRequestInfoV5();
    WhereExpression dw;
    dw = new BinaryExpression(new FieldExpression("ProductGroup"),new ConstantExpression(fGroupId, DataType.Integer) , BinaryOperator.Equal);
    request.setWhereClause(new XmlType(dw.toXmlNode()));
    request.setDynamicSelectFieldNames(new StringArray(new String[] {
                                                                     "ProductGroup",
                                                                     "ProductCode",
                                                                     "ProductName" }));

    fDataModule.DataAdapter.fillAsync(fDataModule.productsTable, request, callback).execute();
}

You can see another example of using remote filtering in the GroupsActivity class, where the loadGroups method creates a Dynamic Where expression that will retrieve any row where the "GroupId" field is equal to either "1", "23" or "46". (Specifically if the value of "GroupId" is in the list which contains the values "1", "23" and "46").

Unlike before, here the WhereExpression is passed directly to the fillAsync method of RemoteDataAdapter, along with a table to be filled and an anonymous callback class which will be executed when the fillAsync method completes. As we are only interested in using a WhereExpression we can save creating a TableRequestInfo object, and use one of the 6 available fillAsync methods available for different uses.

The callback simply causes the "Groups" listview to update with the newly retrieved data and display a short message to indicate that the data was retrieved.

 

private void loadGroups() {

    WhereExpression dw;
    dw = new BinaryExpression(new FieldExpression("GroupId"), new ListExpression(new ConstantExpression(1, DataType.Integer), new ConstantExpression(23, DataType.Integer), new ConstantExpression(46, DataType.Integer)), BinaryOperator.In);

    fDataModule.DataAdapter.fillAsync(fDataModule.groupsTable, dw, new FillRequestTask.Callback() {
        @Override
        public void completed(FillRequestTask task, Object state) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    fGroupsAdapter.notifyDataSetChanged();
                    Toast.makeText(GroupsActivity.this, "groups retrieved", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }).execute();
}

Filtering data locally

To filter a DataTable locally you need to apply a filter to a DataTableView object. The DataTableView represents a customised view of the DataTable that is suitable for sorting, filtering, grouping and navigation and is the object passed to the TableListAdapter for the ListView.

To set a filter the data pass a Filter object to the setFilter of DataTableView. The simplest way is to use an anonymous class and implement the evaluate as you need.

The example here will return all rows where the "ProductGroup" field matches fGroupId, exactly like the remote filter does.

 

private void loadProductsWithLocalFiltering(boolean reload, FillRequestTask.Callback callback) {
    if (reload) {

        // simple request: neither DynamicWhere nor DynamicSelect is used
        fDataModule.DataAdapter.fillAsync(fDataModule.productsTable, callback).execute();
    } else
        Toast.makeText(this, "no need in server request, just changing data view filter", Toast.LENGTH_SHORT).show();

    // assigning local filter
    fDataModule.productsView.setFilter(new DataTableView.Filter() {
        DataColumn col;

        @Override
        public boolean evaluate(DataRow row) {
            if (col == null)
                col = fDataModule.productsTable.getColumns().getColumn("ProductGroup");
            return ((Integer) row.getField(col.getOrdinal())).equals(fGroupId);
        }
    });

}