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 theRole
is equal to "manager" - Complex NSPredicate creates a more complex Dynamic Where clause that is comprised of 3
NSPredicate
s &NSCompoundPredicate
s to request all records where theRole
is "manager" and theName
begins with "J", or where theId
is either "20, 22 or 24".
- XML to DynamicWhere uses an XML based Dynamic Where clause to retrieve all table records where the
-
Local Filters
- Simple NSPredicate filters the local table data for all records where the
Name
contains ll - Complex NSPredicate creates a complex
NSPredicate
, using 3NSPredicate
s &NSCompoundPredicate
s, which filters the table for all records where theRole
is "Guest" and theName
begins with "Je", or those records where theId
is between "19 and 23".
- Simple NSPredicate filters the local table data for all records where the
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 NSPredicate
s which are built using multiple NSPredicate
s and NSCompoundPredicate
s.
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 NSPredicate
s 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];
}
-
Dynamic Select provides a means to request only the columns you need from a table row. ↩