Mobile

The DynamicWhere mobile sample demonstrates using Dynamic Where clauses (DADynamicWhereClause) to request a subset of the records available in a schema's DADataTable. The sample demonstrates 3 different ways to compose a Dynamic Where clause to pass to the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter which will retrieve the records. There are also two examples of filtering table data locally in the client app.

Getting Started

The sample is typically located in /Developer/RemObjects Software/Samples/Data Abstract/Mobile/DynamicWhere, though you may have installed the Data Abstract for Cocoa and it's samples in another location.

To build it, you will of course need Xcode, and like all the samples provided with Data Abstract for Cocoa you will need to be running the Relativity Server with the DASamples Domain and the Simple Schema available.

Running the Sample

When the sample loads it starts a Zeroconf discovery service that looks for instances of Relativity Server running on the local network. Any discovered servers will appear in the initial table, if no servers are discovered then you can specify a server address yourself by pressing the "+" button; you only need to supply the address the port number and http:// will be added for you.

Touching a server name will then display a list of the available filters in the app which are split into two groups; those that filter on the server and those that filter locally.

  • Remote Filters
    • XML to DynamicWhere uses an XML based Dynamic Where clause to retrieve all table records where the Type field is equal to "3".
    • NSPredicate to DynamicWhere creates a Dynamic Where clause from a NSPredicate that will filter all records where the Role is equal to "manager"
    • Complex NSPredicate creates a more complex Dynamic Where clause that is comprised of 3 NSPredicates & NSCompoundPredicates to request all records where the Role is "manager" and the Name begins with "J", or where the Id is either "20, 22 or 24".
  • Local Filters
    • Simple NSPredicate filters the local table data for all records where the Name contains ll
    • Complex NSPredicate creates a complex NSPredicate, using 3 NSPredicates & NSCompoundPredicates, which filters the table for all records where the Role is "Guest" and the Name begins with "Je", or those records where the Id is between "19 and 23".

When you press on a remote filter the request is sent to the server and the result is sent back to the mobile app and the results displayed in a table. In the case of local filters, the filter is applied to the copy of the Users table that was retrieved when the connection to the server was first established and then displayed in a table.

Examining the Code

The code for creating the connection to the server and handling logging in is covered by other samples. In this section we shall focus solely on creating the DADynamicWhereClause objects and retrieving the records using the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter.

App Structure

The sample is built around eight classes; AppDelegate, DataAccess, ServiceAccess, DynamicWhereViewController, ResultViewController, ServersViewController, ReportCell and ReportInfoCell.

The DataAccess class handles everything related to interacting with the Data Abstract SDK; including retrieving data from an instance of Relativity Server and applying changes back to the server.

The ServiceAccess class handles the discovery of any instances of Relativity Server that are available on the local network using ROZeroConf which is a feature available with the Remoting SDK that Data Abstract is built upon. ServiceAccess sets up a ROZeroConfBrowser Class object which searches for any servers that broadcast a value matching the value defined for RELATIVITY_SERVICE_NAME. When a service is found, or indeed disappears, the server list is updated and the popup with the list of available servers is also updated. It also handles the registration of custom server addresses. To explore further the ServiceAccess class and Zeroconf discovery see the article: The ServiceAccess Class and Zeroconf discovery.

The ServersViewController class handles displaying the table of available servers (be they either discovered by Zeroconf, or manually added) and presenting a UIAlert to enter a server address when the "+" button is pressed. When an row is pressed, it configures the DataAccess class to set up a connection to the selected server and passes the name of that server to the SimpleDataOperationsViewController by implementing prepareForSegue:sender:.

The DynamicWhereViewController class displays a table that contains all of the available Dynamic Where clauses and handles segueing to the ResultViewController.

The ResultViewController class handles requesting and display the records from the Users table based on the selected table row in DynamicWhereViewController.

The ReportCell and ReportInfoCell classes are the source half off the UI table cells that will be used to display the details of the Dynamic Where request (ReportInfoCell) and each record in the table (ReportCell). There is no customization in those source files.

Lastly the AppDelegate class is a stock version and has no customization.

Using XML for a Dynamic Where clause

In Data Abstract for Cocoa, Dynamic Where clauses can be constructed from either XML statements or from NSPredicate & NSCompoundPredicate objects. Here we demonstrate composing a multi-line XML statement with fixed arguments rather than dynamically supply them, which you could do. See the Dynamic Where page to learn more about the different types of expressions available and the XML you will need to use them.

The dynamicWhereClauseWithXmlString: method will create and return a DADynamicWhereClause object for you to use. It is important to note that the class returned is merely a wrapped for the XML and that no validation of the XML is performed. If there are any typos they will be caught by the server at runtime which will generate an ROException object.

To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select1 feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.

This request will return all rows from the table Users where their Type is equal to 3, and as the argument passed to select: is nil we indicate that we want all of the fields to be returned for that row.

NOTE If the table name, or the field names do not exist then an ROException will be raised and should be handled appropriately.

- (void)beginRemoteFilteringUsingPlainXML {

    NSString *dynamicWhereXML =
    @"                                                                           \n"
    @"  <query                                                                   \n"
    @"    version='5.0'                                                          \n"
    @"    xmlns='http://www.remobjects.com/schemas/dataabstract/queries/5.0'>    \n"
    @"    <where xmlns=''>                                                       \n"
    @"      <binaryoperation operator='Equal'>                                   \n"
    @"        <field>Type</field>                                            \n"
    @"        <constant type='Integer' null='0'>3</constant>                     \n"
    @"      </binaryoperation>                                                   \n"
    @"    </where>                                                               \n"
    @"  </query>                                                                 \n";
    NSLog(@"Generating DADynamicWhereClause from XML %@", dynamicWhereXML);
    DADynamicWhereClause * filterClause = [DADynamicWhereClause dynamicWhereClauseWithXmlString:dynamicWhereXML];

    [self.dataAdapter beginGetDataTable:@"Users"
                    select:nil /* select all fields */
                     where:filterClause
                 withBlock:^(DADataTable *table) {
                     NSLog(@"Recieved data from the DataAbstract server.");
                     self.reportTable = table;

                     NSDictionary *userInfo =
                        @{REPORT_ROWS_INFO: self.reportTable.rows,
                          REPORT_HEADER_INFO: @"Select GUEST Users",
                          REPORT_DETAIL_INFO: @"XML translated into following condition \nType == 3"};

                     [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
                 }];
}

Using an NSPredicate for a Dynamic Where clause

As an alternative to composing a Dynamic Where clause with XML, you can instead use the native Cocoa NSPredicate class. This is a simpler approach that requires fewer lines of code and feels more natural. There is nothing special you need to do in terms of formatting the predicate, you build it as you normally would.

The dynamicWhereClauseWithPredicate creates and returns an instance of DADynamicWhereClause using the supplied NSPredicate object. It is important to note that the class returned is merely wraps the NSPredicate object and that no validation of the specified field names is performed. If there are any typos they will be caught by the server at runtime, which will generate an ROException object.

To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.

This request will return all rows from the table Users where their Role contains the string "manager", and as the argument passed to select: is nil we indicate that we want all of the fields to be returned for that row.

- (void)beginRemoteFilteringUsingPredicate {

    NSString *userRolePattern = @"manager";
    NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"Role CONTAINS[cd] %@", userRolePattern];
    NSLog(@"Generating DADynamicWhereClause from NSPredicate %@", filterPredicate);
    DADynamicWhereClause *filterClause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:filterPredicate];

    [self.dataAdapter beginGetDataTable:@"Users"
                                 select:nil /* select all fields */
                                  where:filterClause
                              withBlock:^(DADataTable *table) {
                                  NSLog(@"Recieved data from the DataAbstract server.");
                                  self.reportTable = table;

                                  NSDictionary *userInfo =
                                  @{REPORT_ROWS_INFO: self.reportTable.rows,
                                    REPORT_HEADER_INFO: @"Select Managers",
                                    REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", filterPredicate.description]
                                    };

                                  [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
                              }];
}

Using an Compound Predicate for a Dynamic Where clause

Building on the previous step, Data Abstract for Cocoa will accept much more complicated NSPredicates which are built using multiple NSPredicates and NSCompoundPredicates.

The dynamicWhereClauseWithPredicate creates and returns an instance of DADynamicWhereClause using the supplied NSPredicate object. It is important to note that the class returned is merely wraps the NSPredicate object and that no validation of the specified field names is performed. If there are any typos they will be caught by the server at runtime, which will generate an ROException object.

To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.

In the example we are building a where clause, where we want people who are "managers" and whose name starts with "J", otherwise users whose Id is 20, 22 or 24.

- (void)beginRemoteFilteringUsingCompoundPredicate {

    // users with role - manager (both sales manager and income managers)
    NSString *userRolePattern = @"manager";
    NSPredicate *roleCondition = [NSPredicate predicateWithFormat:@"Role CONTAINS[cd] %@", userRolePattern];

    // users that name begins with "J"
    NSString *userNamePattern = @"J";
    NSPredicate *nameCondition = [NSPredicate predicateWithFormat:@"Name BEGINSWITH %@", userNamePattern];

    // set of userID to select
    NSArray *userIds = @[@20, @22, @24];
    NSPredicate *idCondition = [NSPredicate predicateWithFormat:@"Id IN %@", userIds];

    // Building final condition ((roleCondition AND nameCondition) OR idCondition)
    NSPredicate *compoundAndCondition = [NSCompoundPredicate andPredicateWithSubpredicates:@[roleCondition, nameCondition]];
    NSPredicate *finalCondition = [NSCompoundPredicate orPredicateWithSubpredicates:@[compoundAndCondition, idCondition]];

    NSLog(@"Generating DADynamicWhereClause from NSPredicate %@", finalCondition);
    DADynamicWhereClause *filterClause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:finalCondition];

    [self.dataAdapter beginGetDataTable:@"Users"
                                 select:nil /* select all fields */
                                  where:filterClause
                              withBlock:^(DADataTable *table) {
                                  NSLog(@"Recieved data from the DataAbstract server.");
                                  self.reportTable = table;

                                  NSDictionary *userInfo =
                                  @{REPORT_ROWS_INFO: self.reportTable.rows,
                                    REPORT_HEADER_INFO: @"Complex Compound Predicate",
                                    REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", finalCondition.description]
                                  };
                                  [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
                              }];
}

Using an predicate to filter locally

This method creates an NSPredicate that will filter records whose Name contains "ll" . The predicate is passed to the filteredArrayUsingPredicate: of NSArray which evaluates each object against the given predicate.

A notification is then dispatched to indicate that the data is ready which includes the filtered rows and a description of what the filter was in an NSDictionary. The notification will be handled by the ResultViewController object and will cause the table to reload with the filter table.

- (void)applyLocalFilteringUsingPredicate {

    NSString *userNamePattern = @"*ll*";
    NSPredicate *condition = [NSPredicate predicateWithFormat:@"Name LIKE[cd] %@", userNamePattern];

    NSArray *reportRows = [[self.usersTable rows] filteredArrayUsingPredicate:condition];

    NSDictionary *userInfo =
    @{REPORT_ROWS_INFO: reportRows,
      REPORT_HEADER_INFO: @"Simple Local Predicates",
      REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", condition.description]
    };
    [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}

Using a complex predicate to filter locally

This method creates a complex NSPredicate which uses three separate NSPredicates and two NSCompoundPredicate objects to combine them. Here the predicate will filter those records where the Role is "Guest" and the Name begins with "Je", or records where the Id is between 19 and 23. The predicate is passed to the filteredArrayUsingPredicate: of NSArray which evaluates each object against the given predicate.

A notification is then dispatched to indicate that the data is ready which includes the filtered rows and a description of what the filter was in an NSDictionary. The notification will be handled by the ResultViewController object and will cause the table to reload with the filter table.

- (void)applyLocalFilteringUsingCompoundPredicate {

    NSString *rolePattern = @"Guest";
    NSPredicate *roleCondition = [NSPredicate predicateWithFormat:@"Role == %@", rolePattern];

    NSString *namePattern = @"Je";
    NSPredicate *nameCondition = [NSPredicate predicateWithFormat:@"Name BEGINSWITH[cd] %@", namePattern];

    // userID range
    NSArray *userIds = @[@19, @23];
    NSPredicate *idCondition = [NSPredicate predicateWithFormat:@"Id BETWEEN %@", userIds];

    // Building final condition ((roleCondition AND nameCondition) OR idCondition)
    NSPredicate *compoundAndCondition = [NSCompoundPredicate andPredicateWithSubpredicates:@[roleCondition, nameCondition]];
    NSPredicate *finalCondition = [NSCompoundPredicate orPredicateWithSubpredicates:@[compoundAndCondition, idCondition]];

    NSLog(@"Applying complex compound predicate %@", finalCondition);

    NSArray *reportRows = [[self.usersTable rows] filteredArrayUsingPredicate:finalCondition];

    NSDictionary *userInfo =
    @{REPORT_ROWS_INFO: reportRows,
      REPORT_HEADER_INFO: @"Complex Local Predicate",
      REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", finalCondition.description]
      };
    [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}

  1. Dynamic Select provides a means to request only the columns you need from a table row.