iOS client SDK for Appacitive platform.
- Setup
- Initialize
- Conventions
- Data storage and retrieval
- Connections
- Get Connection by Id
- Get all Connections for an Endpoint Object Id
- Get Connected Objects
- Get Connection by Endpoint Object Ids
- Get all connections between two Object Ids
- Get Interconnections between one and multiple Object Ids
- Updating
- Deleting
- Queries
- Paging
- Sorting
- Fields
- Filter
- Geolocation
- Tag Based Searches
- Composite Filters
- FreeText
- Graph Search
- User Management
- Emails
- Device
- Push Notifications
- Files
- User Groups
- Access Control
- Logging
To send and receive your data from Appacitive, you will need to integrate the Appacitive iOS SDK in your iOS Xcode project. There are two ways you can do that.
1. Adding the framework bundle to your project: Open your Xcode project, drag the Appacitive.framework
file to the Xcode window and drop it into the Frameworks
group in the project navigator. Xcode will show a pop-up, check the box that says copy items into destination group's folder (if needed) and also check the box against the target name in the Add to targets section. You can also simply select the Frameworks
group and click Add Files
from the Files
menu, navigate to the location where you have stored the Appacitive.framework
file and select the file. Make sure the check-box labelled Copy items into destination group's folder (if needed)
is checked and also check the check-boxes against all the targets, from the 'Add to targets' section, in which you wish to use the Appacitive iOS SDK. You can download the SDK framework bundle from here.
2. Using the CocoPods dependency manager: Check this link for a comprehensive guide on integrating the Appacitive iOS SDK using CocoaPods.
Appacitive is session-less. Therefore, every API call made to the Appacitive API needs to be authenticated. To do so, we use the API key which is unique to your Appacitive application. There are two types of API keys viz. the master API key
and the client API key
. In the most typical scenarios, you will be using the client API key. The difference between the master and client API keys is that, the access control is completely ignored if you use the master API key, which means you will have access to all the data despite access control being defined on objects. In order to make sure that the access controls get enforced, you should use the client API key.
To get the API keys for your Appacitive application, Log-in to the Appacitive Portal with your account credentials. You will be presented with a dashboard page where you will see some statistics about your account and your apps below the statistics. Select the application you are working on and you will be presented with the application details screen where you will find the Master and Client API keys.
Make sure you add the import statement for AppacitiveSDK.
[Appacitive registerAPIKey:@"<insert_apiKey_here>" useLiveEnvironment:NO];
The above line of code will initialize the Appacitive SDK with the environment setting set to sandbox. To enable the live environment set the useLiveEnvironment parameter to YES
.
[Appacitive registerAPIKey:@"<insert_apiKey_here>" useLiveEnvironment:YES];
All the network calls made by the Appacitive iOS SDK are asynchronous. Therefore, most of the methods in the SDK make use of blocks. There are two types of blocks, successBlocks and failureBlocks usually called successHandler and failureHandler in method names. When using an SDK method, put the code that you wish to get executed when the operation is successful into the successBlock and put the code that you wish to get executed when the operation fails in the failureBlock. Whatever you pass in the success or failure blocks will be executed on the main thread.
APObject *post = [[APObject alloc] initWithTypeName:@"post"];
[post addAttributeWithKey:@"title" value:@"sample post"];
[post addAttributeWithKey:@"text" value:@"This is a sample post"];
[post saveObjectWithSuccessHandler:^(NSDictionary *result)
{
//This is the successBlock.
//The code that you wish to get executed, when the operation is successful, goes here.
NSLog(@"Object saved successfully!");
}
failureHandler:^(APError *error)
{
//This is the failureBlock.
//The code that you wish to get executed, when the operation fails, goes here.
NSLog(@"Error occurred: %@",[error description]);
}
];
All data is represented as objects. You can perform the basic create, read, update and delete operations on these objects. Additionally, you can also search/find objects.
In the iOS SDK, the Appacitive type
is represented by the APObject
class, Appacitive relation
is represented by APConnection
class, Appacitive user
by APUser
class and the Appacitive device
by APDevice
class.
APObject *player = [[APObject alloc] initWithTypeName:@"player"];
While initializing an APObject instance, the type parameter is mandatory and it should be set to the name of the type
whose object you wish to create.
A type is like a table of a relational database. Just like tables have columns with a data-type, an Appacitive type has properties with data-type. You can set the constraints on the properties depending upon the need of your application just like you define constraints on the columns of a table.
The above defined player object is an instance of the APObject
class which encapsulates the data and methods that provide ways to manipulate the data.
To add a property use the addPropertyWithKey:value:
method.
APObject *player = [[APObject] initWithType:@"player"];
[player addPropertyWithKey:@"name" value:@"John Doe"];
To update existing property, use the updatePropertyWithKey:value:
method.
APObject *player = [[APObject] initWithType:@"player"];
[player addPropertyWithKey:@"name" value:@"John Doe"];
[player updatePropertyWithKey:@"name" value"Johnny Doel"];
To get the property of an attribute use the getPropertyWithKey
method.
// using the getters
NSLog(@"Player Name: %@",[player getPropertyWithKey:@"name"]);
To save an object to Appacitive use the saveObject
method.
[player addPropertyWithKey:@"age" value:25];
[player saveObjectWithSuccessHandler:^(NSDictionary *result){
NSLog(@"Player Object saved successfully!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
When you save an object, a unique identifier __id
is generated and stored along with the player object. This identifier is also returned to the object on the client-side. You can access it using player.objectId
or [player objectId]
.
This is what is available in the player
object after a successful save.
{
"__id": "14696753262625025",
"__type": "player",
"__typeid": "12709596281045355",
"__revision": "1",
"__createdby": "System",
"__lastmodifiedby": "System",
"__tags": [],
"__utcdatecreated": "2013-01-10T05:18:36.0000000",
"__utclastupdateddate": "2013-01-10T05:18:36.0000000",
"name": "John Doe",
"__attributes": {}
}
You'll see a a lot of fields that were created automatically by the server. They are used for housekeeping and storing meta-information about the object. All system generated fields start with __
, avoid changing their values. Your values will be different than the ones shown here.
To retrieve an object, simply call the fetch method on the instance and on success the instance will be populated with the object from Appacitive.
// retrieve the player
[player fetchWithSuccessHandler:^(){
NSLog(@"Player: %@",[player description]);
}failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
You can also retrieve multiple objects at a time, which will return an array of APObject
objects in the successBlock. Here's an example
NSArray *objectIdList = [[NSArray alloc] initWithObjects:@"33017891581461312",@"33017891581461313", nil];
[APObject fetchObjectsWithObjectIds:objectIdList typeName:@"post"
successHandler:^(NSArray *objects){
NSLog("%@ number of objects fetched.", [objects count]);
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
});
NOTE: When performing fetch, search operations, you can choose to retrieve only specific properties of your object stored on Appacitive. This feature applies to all, system defined as well as user defined properties except __id and __type, which will always be fetched. Using this feature essentially results in lesser usage of network resources, faster response times and lesser memory usage for object storage on the device. Look for fetch methods that accept an NSArray type of parameter named propertiesToFetch and pass it an array of properties you wish to fetch. For search methods, set the propertiesToFetch NSArray type property of an instance of APQuery class and pass that instance of APQuery to the query parameter of the search methods. More on the APQuery class in the Queries section.
// retrieve the player
[player fetchWithPropertiesToFetch:@[@"name",@"score"] successHandler:^(){
NSLog(@"Player: %@",[player description]);
}failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
You can update your existing objects and save them to Appacitive.
// Incase the object is not already retrieved from the system,
// simply create a new instance of an object with the id.
// This creates a "handle" to the object on the client
// without actually retrieving the data from the server.
// Simply update the fields that you want to update and call the update method on the object.
// This will simply create a handle or reference to the existing object.
APObject *post = [[APObject alloc] initWithTypeName:@"post" objectId:@"33017891581461312"];
//Update properties
[post updatePropertyWithKey:@"title" value:@"UpdatedTitle"];
[post updatePropertyWithKey:@"text" :@ "This is updated text for the post."];
// Add a new attribute
[post addAttributeWithKey:@"topic" value:@"testing"];
// Add/remove tags
[post addTag:@"tagA"];
[post removeTag:@"tabC"];
[post updateWithSuccessHandler:^(){
NSLog(@"post title:%@, post text:%@",[object getTitle],[object getText]);
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
Lets say we've had enough of John Doe and want to remove him from the server, here's what we'd do.
[player deleteObjectWithSuccessHandler:^(){
NSLog(@"JohnDoe player object has been deleted!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
//You can also delete object with its connections in a simple call.
APObject *player = [[APObject alloc] initWithTypeName:@"player" objectId:@"123456678809"];
[friend deleteObjectWithConnectingConnectionsSuccessHandler:^(){
NSLog(@"friend object deleted with its connections!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
// Multiple objects can also be deleted at a time. Here's an example
[APObjects deleteObjectsWithIds:@["14696753262625025",@"14696753262625026"] typeName:@"player" successHandler:^(){
NSLog(@"player objects deleted!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
All data that resides in the Appacitive platform is relational, like in the real world. This means you can do operations like fetching all games that any particular player has played, adding a new player to a team or disbanding a team whilst still keeping the other teams and their players
data perfectly intact.
Two entities can be connected via a relation, for example two entities of type person
might be connected via a relation friend
or enemy
and so on. An entity of type person
might be connected to an entity of type house
via a relation owns
. Still here? OK, lets carry on.
One more thing to grok is the concept of labels. Consider an entity of type person
. This entity is connected to another person
via relation marriage
. Within the context of the relation marriage
, one person is the husband
and the other is the wife
. Similarly the same entity can be connected to an entity of type house
via the relation owns_house
. In context of this relation, the entity of type person
can be referred to as the owner
.
Wife
, husband
and owner
from the previous example are labels
. Labels are used within the scope of a relation to give contextual meaning to the entities involved in that relation. They have no meaning or impact outside of the relation.
As with entities (objects), relations are also contained in collections.
Let's jump in!
Connections represent relations between objects. Consider the following.
//`reviewer` and `hotel` are the endpoint labels
APObject *reviewer = [[APObject alloc] initWithTypeName:@"reviewer" objectId:@"123445678"];
APObject *hotel = [[APObject alloc] initWithTypeName:@"hotel" objectId:@"987654321"];
//`review` is relation name
APConnection *connection = [[APConnection alloc] initWithRelationType:@"review"];
[connection createConnectionWithObjectA:reviewer objectB:hotel successHandler^() {
NSLog(@"Connection created!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
There is another easier way to connect two new entities. You can pass the new entities themselves to the connection while creating it.
/* Will create a new myScore connection between
- new player object which will be created along with the connection.
- new score object which will be created along with the connection.
*/ The myScore relation defines two endpoints "player" and "score" for this information.
//Create an instance of object of type score
APObject *score = [[APObject alloc] initWithTypeName:@"score"];
[score addPropertyWithKey:@"points" value:@"150"];
//Create an instance of object of type player
APObject *score = [[APObject alloc] initWithTypeName:@"player"];
[score addPropertyWithKey:@"points" value:@"150"];
APConnection *connection = [[APConnection alloc] initWithRelationType:@"myScore"];
[connection createConnectionWithObjectA:player objectB:score labelA:@"player" labelB:@"score" successHandler^() {
NSLog(@"Connection created!");
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
This is the recommended way to do it. In this case, the myScore relation will create the entities player and score first and then connect them using the relation marriage
.
NOTE: It doesn't matter whether player and score have been saved to the server yet. If they've been saved, then they will get connected via the relation 'myScore'. And if both (or one) hasn't been saved yet, the required entities will get connected and stored on the server. So you could create the two entities and connect them via a single call, and if you see the two entities will also get reflected with saved changes, so your objects are synced.
//This works exactly the same as in case of APObjects.
[myScore addPropertyWithKey:@"matchname" value:@"European Premier League"];
NSLog(@"Match name: %@", [myScore getPropertyWithKey:@"matchname"]);
[APConnections fetchConnectionWithRelationType:@"review" objectId:@"33017891581461312" successHandler^(NSArray objects) {
NSLog(@"Connection fetched:%@",[[objects lastObject] description]);
}failureHandler:^(APError *error){
NSLog(@"Error occurred: %@",[error description]);
}];
Retrieving can also be done via the fetch
method. Here's an example
APConnection *review = [[APConnection alloc] initWithTypeName:@"review" objectId:@"35097613532529604"];
[review fetchWithSuccessHandler:^()
{
NSLog(@"Connection fetched: %@",[review description]);
}
failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}
];
The review object is similar to the object, except you get two new fields viz. endpointA and endpointB which contain the id and label of the two entities that this review object connects.
Consider Jane
has a lot of friends whom she wants to invite to her marriage. She can simply get all her friends who're of type person
connected with Jane
through a relation friends
with label for Jane as me
and friends as friend
using this search
[APConnections fetchConnectedObjectsOfType:@"person" withObjectId:@"1234567890" withRelationType:@"friends" successHandler:^(NSArray *objects)
{
NSLog(@"Jane's friends:\n");
for(APObject *obj in objects)
{
NSLog(@"%@ \n"[obj getPropertyWithKey:@"name"]);
}
}
Scenarios where you may need to just get all connections of a particular relation for an objectId, this query comes to rescue.
Consider Jane
is connected to some objects of type person
via invite
relationship, that also contains a bool
property viz. attending
, which is false by default and will be set to true if that person is attending marriage.
Now she wants to know who all are attending her marriage without actually fetching their connected person
object, this can be done as
APObject *Jane = [[APObject alloc] initWithTypeName:@"person" objectId:@"12345678"];
APQuery *queryObj = [[APQuery alloc] init];
queryObj.filterQuery = [[APQuery queryExpressionWithProperty:@"attending"] isEqualTo:@"true"];
[APConnections searchAllConnectionsWithRelationType:@"invite" byObjectId:Jane.objectId withLabel:@"attendee" withQuery:[queryObj stringValue] successHandler:^(NSArray *objects, NSInteger pageNumber, NSInteger pageSize, NSInteger totalRecords) {
NSLog(@"Attendees:");
for(APObject *obj in objects)
NSLog(@"%@ \n",[obj getPropertyWithKey:@"name"]);
}];
In this query, you provide a relation type (name) and a label of opposite side whose connection you want to fetch and what is returned is a list of all the connections for above object.
Appacitive also provides a reverse way to fetch a connection between two objects. If you provide two object ids of same or different type types, all connections between those two objects are returned.
Consider you want to check whether John
and Jane
are married, you can do it as
//'marriage' is the relation between person type
//and 'husband' and 'wife' are the endpoint labels
[APConnections searchAllConnectionsWithRelationType:@"marriage" fromObjectId:@"22322" toObjectId:@"33422" labelB:@"wife" withQuery:nil successHandler:^(NSArray *objects, NSInteger pageNumber, NSInteger pageSize, NSInteger totalRecords) {
if([objects count] <= 0)
NSLog(@"John and Jane are married");
else
NSLog(@"John and Jane are not married");
}];
//For a relation between same type type and different endpoint labels
//'label' parameter becomes mandatory for the get call
Consider Jane
is connected to John
via a marriage
and a friend
relationship. If we want to fetch all connections between them we could do this as
[APConnections searchAllConnectionsFromObjectId:@"12345" toObjectId:@"67890" withQuery:nil successHandler:^(NSArray *objects, NSInteger pageNumber, NSInteger pageSize, NSInteger totalRecords) {
NSLog(@"John and Jane share the following relations:");
for(APConnection *obj in objects)
NSLog(@"\n%@",[obj description]);
}];
On success, we get a list of all connections that connects Jane
and John
.
Consider, Jane
wants to what type of connections exists between her and a group of persons and houses , she could do this as
[APConnections searchAllConnectionsFromObjectId:@"12345" toObjectIds:@[@"24356", @"56732", @"74657"] withQuery:nil successHandler:^(NSArray *objects, NSInteger pageNumber, NSInteger pageSize, NSInteger totalRecords) {
NSLog(@"Jane share the following relations:");
for(APConnection *obj in objects)
NSLog(@"\n%@",[obj description]);
}];
});
Updating is done exactly in the same way as entities, i.e. via the updateConnection
method.
Important: While updating, changing the endpoint objects (the __endpointa
and the __endpointb
property) will not have any effect and the operation will fail. In case you need to change the connected endpoints, you need to delete the connection and create a new one.
APConnection *newConnection = [[APConnection alloc] initWithRelationType:@"myconnection"];
[newConnection fetchConnection];
[newConnection updatePropertyWithKey:@"name" value:@"newName"];
[newConnection updateConnection];
Deleting is provided via the del
method.
APConnection *newConnection = [[APConnection alloc] initWithRelationType:@"myconnection"];
[newConnection fetchConnection];
[newConnection deleteConnection];
});
// Multiple connection can also be deleted at a time. Here's an example
[APConnections deleteConnectionsWithRelationType:@"myConnections" objectIds:@[@"123123",@"234234",@"345345"] failureHandler:^(APError *error) {
NSLog(@"Some error occurred: %@", [error description]);
}];
All searching in SDK is done via APQuery
object. You can retrieve many objects at once, put conditions on the objects you wish to retrieve, and more.
APSimpleQuery *nameQuery = [[APQuery queryExpressionWithProperty:@"name"] isEqualTo:@"John Doe"];
APQuery *queryObj = [[APQuery alloc] init];
queryObj.filterQuery = nameQuery;
[APObject searchAllObjectsWithTypeName:@"user" withQuery:[queryObj stringValue] successHandler:^(NSArray *objects, NSInteger pageNumber, NSInteger pageSize, NSInteger totalRecords) {
NSLog(@"All users with John as their first name:");
for(APObject *obj in objects) {
NSLog(@"\n%@",[obj description]);
}
}];
The above query will return all the players with 'John' as the first name. We first instantiated an APSimpleQuery object by using two class methods queryExpressionWithProperty:
and isEqualTo:
. We then instantiated an APQuery object and assigned the APSimpleQuery object that we just instantiated before to its filterQuery
property. Finally, we used the APObject's class method searchAllObjectsWithTypeName:withQuery:successHandler:
and passed the NSString representation of the query object to its withQuery:
parameter.
Take a look at the documentation of the APQuery
class to get the complete list of all types of queries you can construct.
The APQuery interface provides various modifiers in the form of properties like pageSize
, pageNumber
, orderBy
, isAscending
, filterQuery
, fields
and freeText
. These are the options that you can specify in a query. Lets get to those.
//A filter query that will filter the objects based on the first name.
APSimpleQuery *nameQuery = [[APQuery queryExpressionWithProperty:@"firstname"] isEqualTo:@"John"];
APQuery *queryObj = [[APQuery alloc] init];
//Set the page number to the first page.
queryObj.pageNumber = 1;
//Set the page size to 10. i.e. 10 records per page.
queryObj.pageSize = 10;
//Sort the objects by name.
queryObj.orderBy = @"lastname";
//Set the sorting order to ascending.
queryObj.isAsc = YES;
//Set the filter query i.e. an instance of APSimpleQuery or APCompoundQuery.
queryObj.filterQuery = nameQuery;
//Set the properties to be fetched. All other properties of the object will be
queryObj.propertiesToFetch = @[@"firstname", @"lastname", @"username", @"location"];
//Set the free text search.
queryObj.freeText = @"xyz123";
//The APQuery class has a class method called 'stringValue' that will give the NSString representation of the APQuery object.
NSLog(@"String Representation of the queryObj:"[queryObj stringValue]);
});
All search queries on the Appacitive platform support pagination. To specify pagination on your queries, you need to set the properties as shown in the above code sample.
NOTE: By default, pageNumber is 1 and pageSize is 50
The data fetched using the query object can be sorted on any existing property of the object. In the above example we are sorting the results by the lastname
property of the object. The sorting order is set to Ascending by setting the isAsc
property to YES
. If you do not set the isAsc
property to YES
, then the default sorting order would be descending.
You can also mention exactly which fields/properties you need to be fetched in query results.
The __id
and __type
/__relationtype
fields will always be returned.
In the above example we set the propertiesToFetch property of the queryObj to to an array with objects: firstname
, lastname
, username
and location
. Doing so will fetch only the specified properties along with __type
and __id
for all the objects returned as a result of a fetch or search operation.
NOTE: If you do not set the propertiesToFetch
property, then all the proper tie for the objects will be fetched.
Filters are useful for limiting or funneling your results. They can be added on properties, attributes, aggregates and tags.
You can use the APSimpleQuery
and APCompoundQuery
interfaces to construct custom filters. The documentation will provide more insight into constructing custom filters.
You can filter on property
, attribute
, aggregate
or tags
.
In the above example we have filtered based on the first name property using the APSimpleQuery interface object. You can also find some more examples here for the filter queries.
You can specify a property type as a geography type for a given type or relation. These properties are essential latitude-longitude pairs. Such properties support geo queries based on a user defined radial or polygonal region on the map. These are extremely useful for making map based or location based searches. E.g., searching for a list of all restaurants within 20 miles of a given users locations.
A radial search allows you to search for all records of a specific type which contain a geocode property which lies within a predefined distance from a point on the map. the following example query will filter the objects based on the location
property whose value lies within the 5 miles of current location.
CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:23.2 longitude:72.3];
APSimpleQuery *radialSearch = [APQuery queryWithRadialSearchForProperty:@"location" nearLocation:currentLocation withinRadius:@5 usingDistanceMetric:kMiles];
A polygon search is a more generic form of geographical search. It allows you to specify a polygon region on the map via a set of geocodes indicating the vertices of the polygon. The search will allow you to query for all data of a specific type that lies within the given polygon. This is typically useful when you want finer grained control on the shape of the region to search.
CLLocation *point1 = [[CLLocation alloc] initWithLatitude:1 longitude:1];
CLLocation *point2 = [[CLLocation alloc] initWithLatitude:1 longitude:5];
CLLocation *point3 = [[CLLocation alloc] initWithLatitude:5 longitude:5];
CLLocation *point4 = [[CLLocation alloc] initWithLatitude:5 longitude:1];
CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:5.3 longitude:5.9];
APSimpleQuery *polygonSearch = [APQuery queryWithPolygonSearchForProperty:@"location" withPolygonCoordinates:@[point1, point2, point3, point4]];
The Appacitive platform provides inbuilt support for tagging data (objects, connections, users and devices). You can use this tag information to query for a specific data set. The different options available for searching based on tags is detailed in the sections below.
For data of a given type, you can query for all records that are tagged with one or more tags from a given list. For example - querying for all objects of type message that are tagged with the names Gina
, George
, Walt
.
APSimpleQuery *tagQuery = [APQuery queryWithSearchUsingOneOrMoreTags:@[@"Gina", @"George", @"Walt"]];
An alternative variation of the above tag based search allows you to query for all records that are tagged with all the tags from a given list. For example, querying for all objects that are tagged with football
, soccer
and rugby
.
APSimpleQuery *tagQuery = [APQuery queryWithSearchUsingAllTags:@[@"football", @"soccer", @"rugby"]];
Compound queries allow you to combine multiple queries into one single query. The multiple queries can be combined using logical OR
and logical And
operators. NOTE: All queries of type APSimpleQuery with the exception of free text queries can be combined into a compound query.
APCompoundQuery *complexQuery = [APQuery booleanAnd:@[[[APQuery queryExpressionWithProperty:@"name"] isLike:@"John"], [[APQuery queryExpressionWithAttribute:@"height"] isGreaterThan:@"6.0"]]];
Similarly you can also construct a complex query with the boolean OR operator.
APCompoundQuery *complexQuery = [APQuery booleanOr:@[[[APQuery queryExpressionWithProperty:@"name"] isLike:@"John"], [[APQuery queryExpressionWithAttribute:@"eye color"] isEqualTo:@"brown"]]];
There are situations when you would want the ability to search across all text content inside your data. Free text queries are ideal for implementing this kind of functionality. As an example, consider a free text lookup for users which searches across the username, firstname, lastname, profile description etc.You can pass multiple values inside a free text search. It also supports passing certain modifiers that allow you to control how each search term should be used. This is detailed below.
APQuery *freeTextQuery = [[APQuery alloc] init];
freeTextQuery.freeText = @"Jonathan White";
Graph queries offer immense potential when it comes to traversing and mining for connected data. There are two kinds of graph queries, filter and projection.
You can create filter and projection graph queries from the management portal. When you create such queries from the portal, you are required to assign a unique name with every saved search query. You can then use this name to execute the query from your app by making the appropriate api call to Appacitive.
You can execute a saved graph query (filter or projection) by using it’s name that you assigned to it while creating it from the management portal. You will need to send any placeholders you might have set up while creating the query.
[APGraphNode applyFilterGraphQuery:@"namefilter" usingPlaceHolders:@{@"firstname":@"Jonathan", @"lastname":@"White"} successHandler:^(NSArray *objects) {
NSLog(@"ObjectIds:");
for(NSString *obj in objects)
NSLog(@"\n%@",obj);
}];
Executing saved projection queries works the same way as executing saved filter queries. The only difference is that you also need to pass the initial ids as an array of strings to feed the projection query. The response to a projection query will depend on how you design your projection query. Do test them out using the query builder from the query tab on the management portal and from the test harness.
[APGraphNode applyProjectionGraphQuery:@"project_sales" usingPlaceHolders:nil forObjectsIds:@[@"12345",@"34567"] successHandler:^(NSArray *nodes) {
for(APGraphNode *node in nodes)
NSLog(@"Sales Projection:%@",[node description]);
}];
Users represent your app's users. There is a host of different functions/features available in the SDK to make managing users easier. The APUser
interface deals with user management.
There are multiple ways to create users.
You create users the same way you create any other data.
APUser *spencer = [[APUser alloc] init];
spencer.username = @"spencemag";
spencer.firstName = @"Spencer";
spencer.lastName = @"Maguire";
spencer.email = @"[email protected]";
spencer.phone = @"9421234567";
spencer.password = @"H3LL0_K177Y";
[spencer createUser];
You can give your users the option of signing up or logging in via Facebook. For this you need to
- Setup Facebook app.
- Follow these instructions to include Facebook SDK in your app.
To create a user with Facebook, you need the user's Facebook access token and you need to pass it to the createUserWithFacebook:
method. If you set the signUp
parameter to YES
, the API will also create a new user based on the Facebook account. The sessionExpiresAfter:
parameter takes number of minutes as an argument. If you set it to nil, the user token will be valid forever. The limitAPICallsTo:
parameter will limit the number of API calls you can make to Appacitive with
APUser *spencer = [[APUser alloc] init];
[spencer createUserWithFacebook:@"Hsdfbk234kjnbkb23k4JLKJ234kjnkkJK2341nkjnJSD" signUp:NO sessionExpiresAfter:nil limi
Similarly you can also create a user with Twitter Oauth v1.0 and Oauth v2.0.
### Retrieve
There are three ways you could retrieve the user
#### By id.
Fetching users by id is exactly like fetching objects/data. Let's say you want to fetch user with `__id` 12345.
```objectivec
APUser *johndoe = [[APUser alloc] initWithTypeName:@"user" objectId:@"12345"];
[johndoe fetch];
NOTE: The APUser
class is a subclass of APObject
class. Therefore, all APObject
operations can be performed on an APUser
object, but you will need a user logged in to perform user-specific operations like update, fetch and delete.
APUser *spencer = [[APUser alloc] init];
spencer.username = @"spencemag";
[spencer fetch]; ```
#### By UserToken
```objectivec
APUser *spencer = [[APUser alloc] init];
[spencer fetchUserWithUserToken:@"25jkhv5k7h8vl4jh5b26l3j45"];
Again, there's no difference between updating a user and updating any other data. It is done via the update
method.
APUser *spencer = [[APUser alloc] init];
[spencer fetchUserByUserName:@"spencemag"];
[spencer setUsername:@"spencer.maguire"];
[spencer updateObject];
There are 2 ways of deleting a user.
APUser *spencer = [[APUser alloc] initWithTypeName:@"user" objectId:@"123456"];
[spencer deleteObject];
```
#### Via the username
```objectivec
APUser *spencer = [[APUser alloc] init];
[spencer deleteObjectWithUserName:@"spencer.maguire"];
Authentication is the core of user management. You can authenticate (log in) users in multiple ways. Once the user has authenticated successfully, you will be provided the user's details and an access token. This access token identifies the currently logged in user and will be used to implement access control. Each instance of an app can have one logged in user at any given time. When you call any of the authenticate methods provided by the SDK, on successful authentication, the SDK will set the user's access token to the token provided by Appacitive. The APUser class also provides a static instance called currentUser
. This instance will also be instantiated with the user object returned by appacitive when the call to the authenticate
method returns success. The userToken
and currentUser
are read-only and can only be set by successfully authenticating a user.
You can ask your users to authenticate via their username and password.
[APUser authenticateUserWithUserName:@"spencer.maguire" password:@"H3LL0_K177Y" sessionExpiresAfter:nil limitAPICallsTo:nil];
You can ask your users to log in via Facebook. The process is very similar to signing up with fFacebook.
[APUser authenticateUserWithFacebook:@"23klj4bkjl23bn4knb23k4ln" signUp:NO sessionExpiresAfter:nil limitAPICallsTo:nil];
You can ask your users to log in via Twitter. This'll require you to implement twitter login and provide the SDK with consumerkey, consumersecret, oauthtoken and oauthtokensecret. There are two version of Twitter's Oauth authentication that the SDK provides, viz. the Oauth1.0 and Oauth2.0
//Oauth1.0
[APUser authenticateUserWithTwitter:@"kjbknkn23k4234" oauthSecret:@"5n33h4b5b" signUp:NO sessionExpiresAfter:nil limitAPICallsTo:nil];
//Oauth2.0
[APUser authenticateUserWithTwitter:@"3kjn2k34n" oauthSecret:@"2m34234n" consumerKey:@"2vhgv34v32hg" consumerSecret:@"sdfg087fd9" signUp:NO sessionExpiresAfter:nil limitAPICallsTo:nil];
Once the user has authenticated successfully, you will be provided the user's details and an access token. This access token identifies the currently logged in user and will be used to implement access control. Each instance of an app can have one logged in user at any given time.By default the SDK takes care of setting and unsetting this token. However, you can explicitly tell the SDK to start using another access token.
//Get the currently logged-in user object.
APUser *user = [APUser currentUser];
//Log out the currently logged-in user.
[APUser logOutCurrentUser];
//Validate the currentUser's sesion.
[APUser validateCurrentUserSessionWithSuccessHandler:^(NSDictionary *result) {
NSLog(@"Current user session is valid");
} failureHandler:^(APError *error) {
NSLog(@"Current user session is invalid");
}];
NOTE: Here, we assume that the user has already logged-in with Facebook.
If you want to associate an existing loggedin APUser to a Facebook account, you can do it as shown below.
APUser *user = [APUser currentUser];
[user linkFacebookAccountWithAccessToken:@"jlj4jl2lj34jl324ljhb23lj4b"];
});
NOTE: Here, we assume that the user has already logged-in with Facebook.
If you want to associate a new APUser to a Facebook account, you can simply use the authenticateUserWihFacebook
method.
NOTE: Here, we assume that the user has already logged-in with Twitter.
If you want to associate an existing loggedin APUser to a Twitter account, you can link it like so
//Oauth1.0
[user linkTwitterAccountWithOauthToken:@"234kjb23k4bk23b4" oauthSecret:@"23d4kj32n4kjbk32bn4k"];
//Oauth2.0
[user linkTwitterAccountWithOauthToken:@"kj3h24k234b" oauthSecret:@"23hb4jh2b3j4" consumerKey:@"2jh3lb4jh2b34" consumerSecret:@"2j3b4j234jb"];
NOTE: Here, we assume that the user has already logged-in with Twitter.
If you want to associate a new APUser to a Twitter account, you can simply use the authenticateUserWithTwitter method.
//Retrieveing a specific linked account with service name viz. facebook or twitter.
[user getLinkedAccountWithServiceName:@"twitter" successHandler:^(NSDictionary *result) {
NSLog(@"Linked twitter account details:%@",[result description]);
}];
//Retrieving
[user getAllLinkedAccountsWithSuccessHandler:^(NSDictionary *result) {
NSLog(@"All Linked account details:%@",[result description]);
}];
[user delinkAccountWithServiceName:@"facebook"];
});
Users often forget their passwords for your app. So you are provided with an API to reset their passwords.To start, you ask the user for his username and call
[user sendResetPasswordEmailWithSubject:@"APPACITIVE : Reset your password"];
This will send the user an email, with a reset password link. When user clicks on the link, he'll be redirected to an Appacitive page, which will allow him to enter new password and save it.
Users need to change their passwords whenever they've compromised it. You can update it using this call:
[user changePasswordFromOldPassword:@"0LDP4$$W0RD" toNewPassword:@"N3WP4$$W0RD"];});
Users can check-in at a particular co-ordinate using this call.
[APUser setUserLocationToLatitude:@"23.27" longitude:@"72.30" forUserWithUserId:@"spencer.maguire"];
});
Sending emails from the SDK is quite easy. There are primarily two types of emails that can be sent
- Raw Emails
- Templated Emails
Email is accessed through the APEmail interface. Before you get to sending emails, you need to configure SMTP settings. You can either configure it from the portal or in the APEmail
interface with your mail provider's settings.
A raw email is one where you can specify the entire body of the email. An email has the structure
APEmail *emailObj = [[APEmail alloc] initWithRecipients:@[@"[email protected]", @"[email protected]", @"[email protected]"] subject:@"Welcome to my app" body:@"Hey Guys, Welcome to app. Hope you enjoy the experience. Regards,Allan Matthews"];
//Sending email with default SMTP settings from Appacitive.
[emailObj sendEmail];
//Sending email with your email providers SMTP configuration parameters.
[emailObj sendEmailUsingSMTPConfig:[APEmail makeSMTPConfigurationDictionaryWithUsername:@"allan.matthews" password:@"4V3NG3R$" host:@"smtp.email.com" port:@443 enableSSL:YES]];
});
//Alternative way for setting all the APEmail properties individually.
APEmail *emailObj = [[APEmail alloc] init];
emailObj.toRecipients = @[@"[email protected]", @"[email protected]", @"[email protected]"];
emailObj.ccRecipients = @[@"[email protected]", @"[email protected]", @"[email protected]"];
emailObj.bccRecipients = @[@"[email protected]", @"[email protected]"];
emailObj.subjectText = @"Welcome to my app.";
emailObj.bodyText = @"Hey Guys, Welcome to app. Hope you enjoy the experience. Regards,Allan Matthews";
emailObj.fromSender = @"[email protected]";
emailObj.isHTMLBody = NO;
emailObj.replyToEmail = @"[email protected]";
[emailObj sendEmail];
You can also save email templates in Appacitive and use these templates for sending mails. The template can contain placeholders that can be substituted before sending the mail.
For example, if you want to send an email to every new registration, it is useful to have an email template with placeholders for username and confirmation link.
Consider we have created an email template named welcome_email
where the templatedata is -
"Welcome [#username] ! Thank you for downloading [#appname]."
Here, [#username] and [#appname] denote the placeholders that we would want to substitute while sending an email. An email has the structure
APEmail *emailObj = [[APEmail alloc] init];
emailObj.toRecipients = @[@"[email protected]", @"[email protected]", @"[email protected]"];
emailObj.ccRecipients = @[@"[email protected]", @"[email protected]", @"[email protected]"];
emailObj.bccRecipients = @[@"[email protected]", @"[email protected]"];
emailObj.subjectText = @"Welcome to my app.";
emailObj.bodyText = @"Hey Guys, Welcome to app. Hope you enjoy the experience. Regards,Allan Matthews";
emailObj.fromSender = @"[email protected]";
emailObj.isHTMLBody = NO;
emailObj.replyToEmail = @"[email protected]";
emailObj.templateBody = @{@"username":@"Robin", @"appname":@"DealHunter"};
[emailObj sendTemplatedEmailUsingTemplate:@"welcome_email"];
NOTE: Emails are not transactional. This implies that a successful send operation would mean that your email provider was able to dispatch the email. It DOES NOT mean that the intended recipient(s) actually received that email.
Appacitive provides an out-of-the box data type called device. This type helps you in managing the devices that your apps are installed on. You can use the device class to track your user's current device and send push notifications. The SDK only supports registering a single device which on success populates the static APDevice object of the APDevice class. You can fetch other devices through the fetch
methods. You cannot modify the devicetype
and devicetoken
properties of devices other than the current device.
The registerCurrentDeviceWithPushDeviceToken
method will create a device instance in your app at Appacitive with the push device token and also set it active for receiving push notifications. the device instance that gets instantiated is the static APdevice instance currentDevice
. Make sure that the push device token that you enter as the parameter for the register call is the same token that you receive from iOS for receiving push notifications.
[APDevice registerCurrentDeviceWithPushDeviceToken:nil enablePushNotifications:NO successHandler:^{
NSLog(@"Device created!");
} failureHandler:^(APError *error) {
NSLog(@"Some error occurred: %@",[error description]);
}];
The deregisterCurrentDevice method will set the isActive flag of the current device to false
thereby disabling push notifications on the device.
[APDevice deregisterCurrentDeviceWithSuccessHandler:^{
NSLog(@"Device de-registered!");
} failureHandler:^(APError *error) {
NSLog(@"Some error occurred: %@",[error description]);
}];
Deleting a device works the same way as deleting an APObject.
Updating an APDevice works the same way as updating an APObject.
Retrieving a device works the same way as retrieving an APDObject.
Using Appacitive platform you can send push notification to iOS, Android and Windows Phone devices.
We recommend you to go through this section, which explains how you can configure Appacitive app for Push notification. You will need to provide some basic one time configurations like certificates, using which we will setup push notification channels for different platforms for you. Also we provide a Push Console using which you can send push notification to the users.
Appacitive provides four ways to select the sender list
- Broadcast
- Platform specific Devices
- Specific List of Devices
- To List of Channels
- Query
If you want to send a push notification to all active devices, you can use the following options
APPushNotification *notification = [[APPushNotification alloc] initWithMessage:@"Bonjour!"];
notification.isBroadcast = YES;
[notification sendPushWithSuccessHandler:^{
NSLog(@"Push Sent!");
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
If you want to send push notifications to specific platforms, you can do so in the following way.
APPushNotification *notification = [[APPushNotification alloc] initWithMessage:@"Bonjour!"];
notification.query = [[[APQuery queryExpressionWithProperty:@"devicetype"] isEqualTo:@"ios"] stringValue];
[notification sendPushWithSuccessHandler:^{
NSLog(@"Push Sent!");
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
If you want to send push notifications to specific devices, will need to provide the device ids.
APPushNotification *notification = [[APPushNotification alloc] initWithMessage:@"Bonjour!"];
notification.deviceIds = @[@"23423432545", @"4353452352"];
[notification sendPushWithSuccessHandler:^{
NSLog(@"Push sent to requested devices!"]);
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
You can also send PUSH messages to specific channels.
APPushNotification *notification = [[APPushNotification alloc] initWithMessage:@"Bonjour!"];
notification.channels = @[@"updates", @"upgrades"];
[notification sendPushWithSuccessHandler:^{
NSLog(@"Push sent on selected channels!");
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
You can send push notifications to devices using a Query. All the devices which comes out as result of the query will receive the push notification.
APPushNotification *notification = [[APPushNotification alloc] initWithMessage:@"Bonjour!"];
notification.query = [[[APQuery queryExpressionWithProperty:@"devicetype"] isEqualTo:@"ios"] stringValue];
[notification sendPushWithSuccessHandler:^{
NSLog(@"Push Sent!");
} failureHandler:^(APError *error) {
NSLog(@"Error occurred: %@",[error description]);
}];
}
Appacitive supports file storage and provides api's for you to easily upload and download files. In the background we use amazon's S3 services for persistence. To upload or download files, the SDK provides APFile
class, which you can instantiate to perform operations on file.
You can download file directly.
[APFile uploadFileWithName:@"BannerImage" data:[NSData dataWithContentsOfFile:@"BannerImage.png"] urlExpiresAfter:@10 contentType:@"image/png"];
If you wish to manage the upload process on your own, you can just fetch the upload URL.
[APFile getUploadURLForFileWithName:@"bannerImage.png" urlExpiresAfter:@10 contentType:@"image/png" successHandler:^(NSURL *url) {
NSLog(@"URL:%@",url);
}];
[APFile downloadFileWithName:@"BannerImage" urlExpiresAfter:@10 successHandler:^(NSData *data) {
UIImage *bannerImage = [UIImage imageWithData:data];
}];
If you wish to manage the download process on your own, you can just fetch the download URL.
[APFile getDownloadURLForFileWithName:@"bannerImage.png" urlExpiresAfter:@10 successHandler:^(NSURL *url) {
NSLog(@"URL:%@",url);
}];
You can create usergroups from the appacitive portal and manage them. The SDK provides the functionality of adding and removing users from existing user groups.
[APUserGroup addUsers:@[@"ppatel",@"aTestUser"] toUserGroup:@"vendors"
successHandler:^{
NSLog(@"User added to group");
} failureHandler:^(APError *error) {
NSLog(@"Some error occurred: %@"[error description]);
}];
[APUserGroup removeUsers:@[@"ppatel",@"aTestUSer"] fromUserGroup:@"vendors" successHandler:^{
NSLog(@"User removed from group");
} failureHandler:^(APError *error) {
NSLog(@"Some error occurred: %@"[error description]);
}];
You can also manage access controls on your objects using the acl property of the APObect, APUser or the APDevice classes and their sub classes.
You can either allow, deny or reset permissions(read, update, delete, manage access) for users or usergroups on your objects.
NOTE: Access controls cannot be enforced on connection objects.
To allow users certain permissions on your object use the allowUsers:permissions: method. The users parameter accepts an array of usernames or user object's objectIds and the permissions parameter accepts an array of strings whose acceptable values are read
, create
, update
, delete
and manageaccess
.
APObject *myObject = [[APObject alloc] initWithTypeName:@"mytype"];
[myObject.acl allowUsers:@[@"johndoe",@"janedoe",@"9874135633"] permissions:@[@"update",@"manage permissions"]];
[myObject saveObjectWithSuccessHandler:^(NSDictionary *result){
NSLog(@"Object saved with access conttrols");
}failureHandler:^(APError *error){
NSLog(@"Some error occurred: %@"[error description]);
}];
Similarly, to allow user groups, use allowUserGroups:permissions:
method, to deny users or usergroups, use the denyUsers:permissions:
or denyUserGroups:permissions
and to reset users or usergroups, use the resetUsers:permissions:
or resetUsergroups:permissions
methods.
The appacitive iOS SDK provides a class called APLogger for debugging the network requests and responses. The APLogger class logs the all messages to the console. To log to a file or someplace else, you will have to extend the APLogger class and override the -[APLogger log:withType:]
method. Logging is disabled by default. To enable Logging use the -[APLogger enableLogging:];
method. The SDK uses a static sharedLogger
instance for logging the network requests. If you decide to use the same instance for logging the messages in your app, note that disabling the logging on the sharedLogger
instance will disable the logging of Appacitive network request calls.
[[APLogger sharedLogger] enableLogging:YES];
To disable logging, use the same method with the enableLogging
parameter set to NO
.
[[APlogger sharedLogger] enableLogging:NO];
Enabling logging will only log error messages by default. This is to prevent the sensitive data, which is sent in the network requests, from getting logged. For debugging purposes, you can enable logging of the debug messages, which logs all requests and responses, by using the -[[APLogger sharedLogger] enableVerboseMode:];
method.
[[APLogger sharedLogger] enableVerboseMode:YES];
You can disable verbose mode by setting the parameter to NO
.
To use APLogger to log your own messages, use the -[APLogger log:withType:]
method. The type parameter accepts either APMessageTypeError
, which represents an error message, or APMessageTypeDebug
, which represents a debug message.
You can also instantiate your own logger instance to log your own messages using the -[APLogger initWithLoggingEnabled:verboseMode:];
APLogger myLogger = [[APLogger alloc] initWithLoggingEnabled:YES verboseMode:NO];
To log the messages to the console, use the -[APLogger log:withType:]
method.
[myLogger log:@"Some error message" withType:APMessageTypeError];