SDK Reference
This documentation will guide you through the usage of our SDKs. Please read the Quickstart Guides to see how ApiOmat works. For a quick overview our Cheat Sheet might also be helpful.
Quickstart Guides:
Cheat Sheets:
Some of the SDKs that we offer are very similar to each other, like the JavaScript and the Titanium SDK, or the C# and Xamarin SDK. In those cases the following documentation will only mention the main used language ("JavaScript" or "C#" and the code snippets will also only contain the code once.
The SDKs are generated for each app individually and the whole content of the SDK is located in its own package / namespace ("com.apiomat.frontend" / "Apiomat", depending on the chosen SDK language). The operations you do within these objects and classes / prototypes (create, read, update, delete, referencing, etc.), however, are always the same.
For this guide we’ll create an app with the following classes and attributes:
-
Class: Conference
-
name (Text)
-
ranking (Floating point)
-
image (Image)
-
confSessions (Collection of ConfSession)
-
tags (Collection of Strings)
-
-
Class: Room
-
number (String)
-
floor (String)
-
capacity (Long)
-
-
Class: Person:
-
firstname (String)
-
lastname (String)
-
-
Class: ConfSession
-
name (String)
-
fromTime (Date)
-
toTime (Date)
-
i18n_name (Map)
-
conference(Conference)
-
handout(File)
-
room (Room as Embedded Object)
-
attendees (Persion as Embedded Object Collection)
-
Content
Working with Users
A User is a client that sends requests to the backend that you created within ApiOmat. So an object of the class User is a unique user of your website, app, etc. A User usually logs in to the backend.
We provide several methods by which you can manage users. First of all, you have to configure the Datastore (or in Backbone.js: "authentication service") to be able to send requests with the user's credentials.
User user = new User();user.setUserName("user12");user.setPassword("123asdf");Datastore.configureWithCredentials(user);var apiomat = new Apiomat();var userName = "myuserName";var password = "myPassword"; var user = apiomat.User({userName, password});user.setUserName("user");user.setPassword("pasword"); apiomat.authenticationService().configureWithCredentials(userName, password);User user = new User() { UserName = "user12", Password = "123asdf" };Datastore.ConfigureWithCredentials(user);AOMUser* user = [[AOMUser alloc] init];[user setUserName:@"user12"];[user setPassword:@"1a2s3d4f"];[AOMDatastore configureWithUser:user];let user = User()user.userName = "yourName"user.password = "yourPassword"DataStore.configureWithCredentials(user: user)var user = new Apiomat.User();user.setUserName("user12");user.setPassword("123asdf");Apiomat.Datastore.configureWithCredentials(user);When using the Objective-C SDK, please don’t forget the import statements for the Datastore (AOMDatastore.h) and User (AOMUser.h) class.
In addition to the user's credentials, you can configure the Datastore with custom parameters like baseurl, apikey and the used system (LIVE, STAGING, TEST).
AOMUser* user = [[AOMUser alloc] init];[user setUserName:@"user12"];[user setPassword:@"1a2s3d4f"];NSString *apiKey = @"1234567890";NSString *baseURL = @"http://localhost:8080/yambas/rest/apps/Conference";NSString *system = @"LIVE";NSDictionary *params = @{"baseURL":baseURL,"apiKey":apiKey,"usedSystem":system};[AMODatastore configureWithCredentials: user andParams: params];let user = User();user.userName = "yourName"user.password = "yourPassword" let configuration = DataStoreConfiguration()configuration.apiKey = "1234567890"configuration.baseUrl = "http://localhost:8080/yambas/rest/apps/Conference"configuration.usedSystem = "LIVE"DataStore.configureWithCredentials(user: user, configuration: configuration)
Now your connection to our servers is initialized and you can start by logging in or signing up.
To sign up with a new user, call the save function on the previously created user object. Some SDKs offer a synchronous and an asynchronous way to do this.The UI thread can be blocked by a synchronous function call in many frontends. this poses the problem of a UI that appears to be frozen, leading to a bad user experience. Therefore, it's not recommended to do this. See below for the asynchronous function calls.
try { user.save();} catch (ApiomatRequestException e) { // Handle exception}user.SaveAsync().Wait();@try { [user save];}@catch (NSException *exception) { /* An error occured */}
Here are the asynchronous method calls for the same task:
user.saveAsync( new AOMEmptyCallback(){ @Override public void isDone(ApiomatRequestException exception){}});user.save({ success: function(model, response, options) { user.fetch({ success : function(model, response, options) { //successfully signed up, now we can continue }, error : function(model, response, options) { //sth. unexpected happened here } }); }, error: function(model, response, options) { //error while saving the user }});
await user.SaveAsync();
[user saveAsyncWithBlock:^(NSError *error) { if(error) { /* request was not successful */ } else { /* sign up was successful */ }}];user.save(loadAfterwards: true, completion: { error in if error != nil { //handle error }})user.save({ onOk: function() { //Successfully sign up }, onError: function(error) { //do error handling }});
If the user already exists you can log the user in instead:
try { user.loadMe();} catch (ApiomatRequestException e) { // Handle exception}apiomat.Users().fetch({ search: "userName==\"" + userName + "\"", // there has to be a variable called userName, containing your user's name, as shown in the code snippet above success: function(model, response, options) { // user found, we can go on }, error: function(model, response, options) { // do error handling }});try{ await user.LoadMeAsync();}catch (ApiomatRequestException ex){ // Handle exception}user.loadMe { error in if error != nil { //handle error }}user.loadMe({ onOk: function() { //Successfully logged in. }, onError: function(error) { //do error handling }});
In many cases you might want to combine the two methods above - you want to try logging the user in, and if this leads to an error because the user doesn't exist yet, sign them up (create the account). There is a common pattern for doing that:
final User user = new User();user.setUserName("_username_");user.setPassword("_password_");Datastore.configureWithCredentials(user);user.loadMeAsync(new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { if (exception != null) { // if the member is not found on the server just create it user.saveAsync(new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { } }); } }});var apiomat = new Apiomat();var userName = "myuserNam4235e";var password = "myPassword"; var user = apiomat.User({userName, password});user.set("userName", userName);user.set("password", password); apiomat.authenticationService().configureWithCredentials(userName, password) apiomat.Users().fetch({ search: "userName==\"" + userName + "\"", // there has to be a variable called userName, containing your user's name, as shown in the code snippet above success: function(model, response, options) { if(model.length == 0) { user.save({ success: function(model, response, options) { //user was successfully saved }, error: function(model, response, options) { //error while saving user } }); } }, error: function(model, response, options) { user.save({ success: function(model, response, options) { //user was successfully saved }, error: function(model, response, options) { //error while saving user } }); }});private async Task<User> LogInOrSignUpUserAsync(string userName, string password){ User user = new User() { UserName = userName, Password = password }; Datastore.ConfigureWithCredentials(user); bool userExists = false; try { await user.LoadMeAsync(); userExists = true; } catch (ApiomatRequestException e) { if (e.Status == Status.UNAUTHORIZED) { userExists = false; } else { throw e; } } if (!userExists) { // Regarding this saving of the user outside of the previous catch block, please read the section "Error handling" in the documentation below. await user.SaveAsync(); } // only needed if you want to use the user to configure the Datastore again later// user.Password = password; return user;}AOMUser* user = [[AOMUser alloc] init];[user setUserName:@"yourName"];[user setPassword:@"yourPassword"];[AOMDatastore configureWithUser:user];[user loadMeAsyncWithFinishingBlock:^(NSError *error) { if([error code] == AOMUNAUTHORIZED) { [user saveAsyncWithBlock:^(NSError *error) { }]; } }];let user = User()user.userName = "yourName"user.password = "yourPassword"DataStore.configureWithCredentials(user: user) user.loadMe { error in if error != nil { user.save(loadAfterwards: true, completion: { error in if error != nil { //handle error } }) //handle error }}var user = new Apiomat.User();user.setUserName("user12");user.setPassword("123asdf");Apiomat.Datastore.configureWithCredentials(user); user.loadMe({ onOk: function() { //Successfully logged in. }, onError: function(error) { user.save({ onOk: function() { //Successfully sign up }, onError: function(error) { //do error handling } }); }});Be aware that the password field of the user object is empty after loading and saving, because the password doesn’t get transferred from the server. This is not an issue because at the beginning of the method the Datastore already got configured and all following requests have the correct credentials. But in case you want to configure the Datastore to something else and then back to the user, you might want to store the password somewhere safe, or set it as value of the password attribute afterwards.
Using OAuth2
For authenticating users, you can use our OAuth2 implementation. Read the dedicated documentation page to learn more about it: OAuth2.
Handling Objects
Let’s take a look at how we work with plain objects and do CRUD operations on them. Every object in our system has an ID, which is also part of the object's HREF. This ID and URL identify the object.
Depending on the SDK, there are synchronous and asynchronous functions to send requests to the server. Remember, synchronous function calls usually block the UI thread and are not recommended. The asynchronous function need a way to return a value - this is solved either with callbacks / blocks / completion handlers or with the async/await pattern.
For some SDKs the callback handling requires more explanation, so here's some code that shows you what a callback generally looks like:
{ success: function(model, response, options) { /** /* Request successful /* If server returns anything than you will find this in the model variable /* further informations you can find in the response */ }, error: function(model, response, options) { /** /* Request failed /* see also section about error handling below */ }}{ onOk: function([result]) { //Request successful //If server returns anything than you will find this in the result variable }, onError: function(error) { /* Request failed the parameter error contains more information see also section about Error handling below */ }}
The following section will show you how to create, read, update and delete objects synchronously and asynchronously in the different SDKs.
Save an object
Saving an object in the backend always follows the same pattern:
Conference conference = new Conference();conference.setName("Our first conference");// We give the save command an additional parameter which is an new object of class AOMEmptyCallback. The isDone method will be executed after the request is finished. You can check the error parameter to see if something went wrong.conference.saveAsync(new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception){}});var conference = apiomat.Conference();conference.save({ success : function(model, response, options) { //object successfully saved Apiomat.log.info("Saved succesfully Conference"); }, error: function(model, response, options) { //handle error }});Conference conference = new Conference();conference.Name = "Xamarin Evolve 2014";// In C# there are no checked exceptions like there are in Java, so you don't have to try/catch here, but it's advised to do so. This will be covered in more detail in the documentation below under the topic "Error handling".await conference.SaveAsync();Conference *conf = [[Conference alloc] init];[conf setName:@"Our first conference"];/* set some other attributes here *//* We give the save command a parameter of type "AOMEmptyBlock" (AOMEmptyBlock is only a type definition). This block will be executed after the request is finished. You can check the error parameter to see if something went wrong */[conf saveAsyncWithBlock:^(NSError *error) { if(!error) { /* everything was ok */ } else { /* sth went wrong */ }}];let conf = Conference()conf.name = "our first conference"/* set some other attributes here */conf.save { error in if error == nil { //everything okay } else { //something went wrong, handle error }}var conference = new Apiomat.Conference();//you may set some attributes hereconference.save({ onOk: function() { //Successfully saved }, onError: function(error) { //do error handling }});Getting an object
To get a single object you have to know its unique identifier (the HREF). By using following command you are able to get a specific object.
Conference conference = new Conference();conference.loadAsync(href, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { //Now you're holding your object. }});var id = "0123456"var conference = new Apiomat.Conference();conference.loadByHref(id, { success: function(model, response, options) { //Successfully saved }, error: function(model, response, options) { //do error handling }});Conference conference = new Conference();await conference.LoadAsync(href);NSString *href = @"https://apiomat.org/yambas/rest/apps/Conference/models/ConferenceMain/Conference/0123456";Conference *conf = [[Conference alloc] init];[conf loadAsyncWithHref:href andBlock:^(NSError *error) { /* Now you're holding your object. */}];let conf = Conference() conf.load(withHref: href) { error in if error == nil { //successfully reloaded your conference } else { //something went wrong, handle error }}var href = "https://apiomat.org/yambas/rest/apps/Conference/models/ConferenceMain/Conference/0123456"var conference = new Apiomat.Conference();conference.loadWithHref(href, { onOk: function() { //Successfully saved }, onError: function(error) { //do error handling }});
type in the following if you need to reload the data from server:
loadedConf.loadASync(new AOMEmptyCallback(){ @Override public void isDone(ApiomatRequestException exception) { //you reloaded your object }});loadedConference.fetch({ success: function(model, response, options) { //successfully saved }, error: function(model, response, options) { //do error handling }});await loadedConf.LoadAsync();[loadedConf loadAsyncWithBlock:^(NSError *error) { /* you reloaded your object */}];conf.load { error in if error == nil { //successfully reloaded your conference } else { //something went wrong, handle error }}loadedObj.load({ onOk: function() { //Successfully saved }, onError: function(error) { //do error handling }});Getting a list of objects
You can also retrieve a list of objects from the server. To do so you have to work with the class / prototype the objects are instances of.
conference.getConferencesAsync(null, new AOMCallback<List<Conference>>() { @Override public void isDone(List<Conference> resultObject, ApiomatRequestException exception) { }});//Load list of conference objectsapiomat.Conferences().fetch({ success: function(model, response, options) { //Getting list was successful //You can now work with parameter objects which contains loaded elements }, error: function(model, response, options) { //do error handling }});IList<Conference> loadedConferences = await Conference.GetConferencesAsync();/* Load list of conference objects */Conference.loadList { conferences, error in if error == nil { let loadedConferences = conferences } else { //something went wrong, handle errror }}Apiomat.Conference.getConferences(undefined,{ onOk : function(conferences) { // loading Conferences was successful }, onError : function(error) { // do error handling here }});
Instead of no parameter or ‘null’ (depending on the SDK) you can also add a query to filter the objects that get returned by the server.
//Load list of conference objectsapiomat.Conferences().fetch({ search: 'name=="Node Summit"' success: function(model, response, options) { //Getting list was successful //You can now work with parameter objects which contains loaded elements }, error: function(model, response, options) { //do error handling }});IList<Conference> loadedConferencesFiltered = await Conference.GetConferencesAsync("name==\"Microsoft Build 2014\"");/* Load list of conference objects filtered by name */[Conference getAsyncWithQuery:@"name like \"Backend\"" withBlock:^(NSMutableArray *loadedConfs, NSError *error) { /* Getting list was successful if exception == null */ /* You can now work with parameter loadedConfs which contains filtered elements */}];/* Load list of conference objects filtered by name */Conference.loadList("name like \"backend\"") { conferences, error in if error == nil { let loadedConferences = conferences } else { //something went wrong, handle errror }}Apiomat.Conference.getConferences("name==\"Microsoft Build 2014\"",{ onOk : function(conferences) { // loading Conferences was successful }, onError : function(error) { // do error handling here }});You can find some more information about queries in the respective section on this page, or check out the dedicated documentation article about our query language.
Get the count of a list of objects
You don't have to fetch the full list to then call some count() method in your code in case you just want to know the count of elements in a list (either filtered by a query or not). Instead, you can call the count() method in our SDK so that the server responds with the count of elements directly:
//Load count of conference objects filteredconference.getConferencesCountAsync("name like \"JavaOne\"", new AOMCallback<Long>() { @Override public void isDone(Long resultObject, ApiomatRequestException exception) { }});//Load count of conference objectsapiomat.Conference().count({ success: function(count, response, options) { }, error: function(model, response, options) { //do error handling }});//Load count of conference objects filteredapiomat.Conference().count({ search: 'name=="Node Summit"' success: function(model, response, options) { }, error: function(model, response, options) { //do error handling }});long conferenceCount = await GetConferencesCountAsync(); // Counts all conferenceslong conferenceCountFiltered = await GetConferencesCountAsync("name like \"JavaOne\""); // Counts filtered conferenceslong cnt = [Conference getCountWithQuery:@"name like \"JavaOne\""];[Conference getCountAsyncWithQuery:@"name like \"JavaOne\"" withBlock:^(long count, NSError *error) { cnt = count; err = error;}];// Count all conferencesConference.loadCount { conferencesCount, error in if error == nil { if let conferencesCount = conferencesCount { // work with conferencesCount } else { // handle error }}// Count filtered conferencesConference.loadCount(query: "name like \"JavaOne\"") { conferencesCount, error in if error == nil { if let conferencesCount = conferencesCount { // work with conferencesCount } else { // handle error }}//Load count of conference objectsApiomat.Conference.getConferencesCount(undefined, { onOk : function(cnt) { // count is in variable cnt }, onError : function(error) { // handle error here }});//Load count of conference objects filteredApiomat.Conference.getConferencesCount("name like \"JavaOne\"", { onOk : function(cnt) { // count is in variable cnt }, onError : function(error) { // handle error here }});Update an object
The process is identical to saving. The client code checks if the object already contains an ID / HREF and afterwards performs an update. For example:
// Get or save updatedObj// Change sth on updatedObj conference.saveAsync( new AOMEmptyCallback(){ @Override public void isDone(ApiomatRequestException exception) { //Successfully updated }});var conference = apiomat.Conference();conference.save({ success : function(model, response, options) { //object successfully saved Apiomat.log.info("Saved succesfully Conference"); }, error: function(model, response, options) { //handle error }});// Get or save a conference// ...// Change something on the conference// ...// Update the conferenceawait conference.SaveAsync();/* Get or save updatedObj *//* Change sth on updatedObj *//* udpate it on server */[updatedObj saveAsyncWithBlock:^(NSError *error) {}];/* Get or save updatedObj *//* Change something on updatedObj *//* udpate it on server */updatedConf.save { error in if error == nil { //everything okay } else { //something went wrong, handle error }}// Get or save updatedObj// Change sth on updatedObj updatedObj.save({ onOk: function() { //Successfully updated }, onError: function(error) { //do error handling }});Delete an object
According to our pattern, you can delete an object simply by entering the following:
// Load object you want to deleteconference.deleteAsync(new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});conference.destroy({ success : function(model, response, options) { //object successfully deleted }, error: function(model, response, options) { //handle error }});await conference.DeleteAsync();//Load object you want to delete[conf deleteAsyncWithBlock:^(NSError *error) { /* check error if sth went wrong */}];//First load object you want to delete (e.g. a conference), then callconf.delete { error in if error == nil { //successfully deleted } else { //something went wrong, handle error }}obj.deleteModel({ onOk: function() { //Successfully deleted }, onError: function(error) { //do error handling }});Offline Handling
We extended the offline handling capabilities and have a dedicated documentation page now. See offline handling.
DeltaSync
To reduce the amount of data sent between the device and backend, you can enable DeltaSync. Read the dedicated documentation page to learn more about it: DeltaSync.
Working with attributes of objects
You can access attributes that you’ve created in your dashboard the standard Java / C# / JavaScript / Objective-C / Swift / etc way. Please be careful to use the appropriate data type. It’s not recommended to add new attributes to generated classes, as it will break serialisation when you call the “save()” method to send an instance of that class to the server.
Setting an attribute
Assuming our conference class has an attribute called ‘name’ , you can set this property easily with:
Conference conference = new Conference();conference.setName("Android Dev Con");//save the objectvar conference = apiomat.Conference();conference.set("name", "JavaScript Dev Con");// Either with an object initializerConference conf1 = new Conference() { Name = "Microsoft Build 2014" };// Or with the property of the objectConference conf2 = new Conference();conf2.Name = "Apps World Europe 2014";// don't forget to save the objectsConference *conf = [[Conference alloc] init];[conf setName:@"The iOS dev conference"];Conference conf = Conference()conf.name = "The iOS dev conference"var conference = new Apiomat.Conference();conference.setName("JavaScript Dev Con");or set an attribute “someMap” of ApiOmat type map, which looks like this:
Map<String, String> map = new HashMap<String,String>();map.put("de", "Wie benutze ich properties?");map.put("en", "How to use properties");ConfSession confSession = new ConfSession();confSession.setI18n_name(map);var i18ns = { "de" : "Wie benutze ich Properties", "en" : "How to use properties"}; confSession.set("i18n_name", i18ns);//save object on serverconference.save({ success : function(model, response, options) { //object successfully saved Apiomat.log.info("Conference successfully saved"); }, error: function(model, response, options) { //handle error }});// Either with a dictionary initializerIDictionary dict = new Dictionary<string,string< () { {"de", "Wie benutzt man Properties?"}, {"en", "How to use properties?"} };// or after the creationdict.Add("fr", "Comment utiliser les propriétés");// then set the conference's property accordinglyConfSession confSession = new ConfSession();confSession.I18n_name = dict;NSMutableDictionary *i18n = [[NSMutableDictionary alloc] init]; [i18n setObject:@"wie benutze ich properties" forKey:@"de"]; [i18n setObject:@"how to use properties" forKey:@"en"]; Session *session = [[Session alloc] init]; [session setI18n_name:i18n];var i18n = [String: String]()i18n["de"] = "wie benutze ich Properties"i18n["en"] = "how to use properties" let session = Session()session.i18n_name = i18n as [String : AnyObject]var i18ns = { "de" : "Wie benutze ich Properties", "en" : "How to use properties"}confSession.setI18n_name(i18ns);//save object on serverconfSession.save();Getting an attribute
Like accessing “normal” attributes, you can also access properties of ApiOmat classes.
//Make sure you loaded object conference beforeString confName = conference.getName();//Make sure you loaded object conference before (see above)var confName = conference.get("name");// Make sure you loaded the conference object beforestring confName = conference.Name;/* Make sure you loaded object conference before (see above) */NSString *confName = [conference name];/* Make sure you have loaded object conference before (see above) */let confName = conf.name//Make sure you loaded object conference before (see above)var confName = conference.getName();Working with geo points
You can also set ‘Location’ as type of an attribute. To save an attribute of the type “location” with name ‘place’, do:
conference.setPlaceLatitude(10.2);conference.setPlaceLongitude(20.2); //To retrieve coordinates, you can call the methods 'getLatitude' and 'getLongitude'.double latitude = conference.getPlaceLatitude();double longitude = conference.getPlaceLongitude();conference.setPlaceLatitude(10.2);conference.setPlaceLongitude(20.2);// To retrieve coordinates, you can call the methods get<PropertyName>Latitude() and get<PropertyName>Longitude()var latitude = conference.getPlaceLatitude();var longitude = conference.getPlaceLongitude();conference.PlaceLatitude = 10.2;conference.PlaceLongitude = 20.3;// Access the properties in the same waydouble latitude = conference.PlaceLatitude;double longitude = conference.PlaceLongitude;[conf setPlaceLatitude:10.2];[conf setPlaceLongitude:20.2]; //To retrieve coordinates,//you can call the methods 'latitude' and 'longitude'. double latitude = [conf placeLatitude];double longitude = [conference placeLongitude];conf.place?.longitude = 10.2conf.place?.latitude = 20.2 //To retrieve coordinates,//you can access the corresponding attributes of your your location-typed property let confLng = conf.place?.longitudelet confLat = conf.place?.latitudeconference.setPlaceLatitude(10.2);conference.setPlaceLongitude(20.2);// To retrieve coordinates, you can call the methods 'getLatitude' and 'getLongitude'.var latitude = conference.getPlaceLatitude();var longitude = conference.getPlaceLongitude();Working with files
Objective-C SDK
Instead of a byte array you have to use the class NSData.
Backbone.js SDK
Starting with version 2.5.6 the Backbone.js SDK provides a load and post method named load<PropertyName>AsByteArray and post<PropertyName>AsByteArray. To use these methods, the JQuery plugin BinaryTransport is necessary. For further information have a look at the Backbone.js Quickstart Guide.
If you have an attribute of the type “File” you can upload a file either as a byte array or as stream (or in Android directly as "File"). The method which uploads your file is called post<Attributename>. For our “handout” in the ConfSession class it should look like this:
// As byte array:byte[] filearr = {0,3,6,2};ConfSession confsession = new ConfSession();confsession.postHandoutAsync(filearr, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});// As stream (for streams where mark and reset is supported)InputStream inputStream = new ByteArrayInputStream(filearr);ConfSession confsession = new ConfSession();confsession.postHandoutAsync(inputStream , new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }}); // As fileFile file = new File(path);ConfSession confsession = new ConfSession();confsession.postHandoutAsync(file , new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }}); // As byte arrayvar filearr = [0,3,6,2];var confSession = apiomat.ConfSession();confSession.postHandoutAsByteArray(filearr, { success : function(model, response, options) { // file content successfully posted Apiomat.log.info("file posted successfully"); }, error: function(model, response, options) { // handle error }}); // As stream (this could be any kind of stream, especially useful when working with files from the device like photos or videos)// Therefore you have to pass a file object to the function, resulting from a form's file input var file = new File(filearr, "filename");confSession.postHandoutAsStream(file, { success: function(result, status, jqXHR) { //file content successfully posted Apiomat.log.info("file posted successfully"); }, error: function(error, status, jqXHR) { //handle error }}// As byte array:byte[] fileArr = new byte[] {0x00, 0x30, 0x60, 0x20};ConfSession confSession = new ConfSession();await confSession.PostHandoutAsync(fileArr); // As stream (this could be any kind of stream, especially useful when working with files from the device like photos or videos):MemoryStream stream = new MemoryStream(fileArr);await confSession.PostHandoutAsync(stream);// As NSMutableDataNSMutableData *fileData = [[NSMutableData alloc] init];/* set value for property fileData */ConfSession *confSession = [[ConfSession alloc] init];[confSession postHandoutAsync:fileData andWithBlock:^(NSError *error) {}];// As stream (this could be any kind of stream, especially useful when working with files from the device like photos or videos):NSInputStream* stream = [NSInputStream inputStreamWithData: fileData];[confSession postHandoutAsStreamAsync: stream andWithBlock:^(NSError *error) { if(error == nil) { // data successfully posted } else { // something went wrong, handle error }}]// As byte arraylet data = NSMutableData()let confSession = ConfSession()confSession.postHandout(data) { error in if error == nil { //data successfully posted } else { //something went wrong, handle error }}// As stream (this could be any kind of stream, especially useful when working with files from the device like photos or videos):let stream = file as Data?confSession.postHandout(stream!) { error in if error == nil { // data successfully posted } else { // something went wrong, handle error }}// _data is an array containing the example data_data = [0,3,6,2];// session is an object of a class that has a file attribute "slides"session.postSlides(_data, { onOk : function() { // data successfully posted }, onError : function(error) { // handle error here }});Please note two things:
1) There's an upload size limit on the server. It can be configured in the apiomat.yaml and defaults to 200 MB.
2) There's a limit for offline storage in the device. The limit is ignored when uploading while the device is online (nothing gets stored this way anyway), but an exception is thrown if the device is offline. When downloading a file, it only gets cached (either in memory or persistently, depending on the setting) if the limit isn't exceeded, otherwise an error will be logged (but no exception thrown). This limit can be configured via the Datastore and defaults to 15 MB.
After the file object was uploaded, the property <property>URL contains an URL to this file.
string slidesUrlPlain = conference.getSlidesResourceURL(); // Without the addition of API key and systemstring slidesUrl = conference.getSlidesUrl(); // With the addition of API key and system//Get plain URL without concerting parametersvar slidesUrl = conference.get("slidesURL");string slidesUrlPlain = conference.SlidesURLResource; // Without the addition of API key and systemstring slidesUrl = conference.SlidesUrl; // With the addition of API key and systemNSString *handoutURL = [confSession handoutURL];let handoutURL = confSession.handoutURLvar slidesURL = session.getSlidesURL();
To load the file, you can use a method on the object that contains the file directly. If you only have a URL of the file and not the object that contains the file, then you can use a method in the Datastore, called loadResourceAsyncWithHref().
// Via the object that contains the file:byte[] result = session.LoadSlidesAsync(new AOMEmptyCallback() { // usePersistentStorage is optional @Override public void isDone(ApiomatRequestException exception) { }}); /* Indicating whether the loaded image should be cached using the persistent storage or not,for more information have a closer look at the caching topic */boolean usePersistentStorage = false;// Via Datastore when you only have a file's URLbyte[] result2 = await Datastore.getInstance().LoadResourceAsync(fileUrl, false, usePersistentStorage, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});apiomat.loadResource(slidesURL, { asByteArray: true, success: function(data, xhr, options) { // imageData was loaded successfully }, error: function(xhr, options) { // handle error }})/* Alternatively you can use the following method as well */confSession.loadHandoutAsByteArray({ success : function(model, response, options) { // file content successfully posted Apiomat.log.info("file posted successfully"); }, error: function(model, response, options) { // handle error }});// Via the object that contains the file:byte[] result = await session.LoadSlidesAsync(); // usePersistentStorage is optional// Via Datastore when you only have a file's URLbyte[] result2 = await Datastore.Instance.LoadResourceAsync(fileUrl, false, false, usePersistentStorage);NSString *handoutURL = [confSession handoutURL];[[AOMDatastore sharedInstance] loadResourceAsyncWithHref:handoutURL andWithFinishingBlock:^(NSData *data, NSError *error) { /* data object contains your file data */}];/* Alternatively you can use the following method as well */[confSession loadHandoutAsync:^(NSData *data, NSError *error) { /* data object contains your file data */}];let handoutURL = confSession.handoutURLDataStore.sharedInstance.loadResource(withHref: handoutURL, usePersistentStorage: false) { data, error in if error != nil { /* data object contains your file data */ }}/* Alternatively you can use the following method as well */confSession.loadHandout { (data, error) in if error != nil { /* data object contains your file data */ }}var loadCB = { onOk : function(data) { // data successfully loaded, do something with it }, onError : function(error) { // handle error here }};/* you can either load it via Datastore */var slidesURL = session.getSlidesURL();Apiomat.Datastore.getInstance().loadResource( slidesURL, loadCB ); /* Or alternatively you can use the following method as well */session.loadSlides( loadCB );Working with images
It’s possible to add a property of type “image” to your classes. If you do this, you can find some special methods in the generated code for these classes. Upload an image to an instance of your class as a byte array / array buffer. The method which adds an image is named after the pattern “Post<Propertyname>”. For our “Conference” class and its property “image” it should look like in following snippet:
For Backbone.js: Take a look at this page if you don’t know how you transform an image to the required type
In Objective-C, you have to upload the file as an instance of the class NSData.
byte[] imgArr = new byte[] {0,2,6,8};conference.postImageAsync(imgArr, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});var filearr = [0,3,6,2];var confSession = apiomat.ConfSession();confSession.postHandoutAsByteArray(filearr, { success: function(model, response, options) { // image data posted successfully }, error: function(model, response, options) { // do error handling }});byte[] fakeImage = new byte[] {0x00, 0x30, 0x60, 0x20};ConfSession confSession = new ConfSession();await confSession.PostImageAsync(fakeImage); NSMutableData *imageData = [[NSMutableData alloc] init];/* set value for property imageData *//* save data in image property of conf object */[conf postImageAsync:imageData andWithBlock:^(NSError *error) { /* check error parameter */}];let data = NSMutableData()let conf = Conference()conf.postImage(data) { error in if error == nil { //data successfully posted } else { //something went wrong, handle error }}var imgArr = [0,2,6,2];conference.postImage(imageArr,{ onOk: function() { //Successfully uploaded image }, onError: function(error) { //do error handling }});
If the image was uploaded, the property ‘<PropertyName> URL’ contains an URL to the image. To get this image URL back from your object, you can do the following:
//Get plain URL without concerting parametersString imgUrl = test.getImageURL();//Get URL with resized image to 200x100, black background, no transparency and in format jpgString resizedImgUrl = test.getImageURL(200, 100, "ffffff", null, "jpg");//Get plain URL without concerting parametersvar imgUrl = conference.get("imageURL");// 1) Get plain URL without concerting parameters from the propertystring imgUrl = conference.ImageUrl;// 2) Get URL with resized image to 200x100, black background, no transparency and as jpgstring resizedImgUrl = conference.GetImageUrl(200, 100, "ffffff", null, "jpg");/* Get plain URL without concerting parameters */NSString *imageURL = [conf imageURL];/* Get URL with resized image to 200x100, black background, no transparency and in format jpg */NSString *resizedImageURL = [conf scaledImageURLWithWidth:200 andHeight:100 andBGColor:@"ffffff" andAlpha:0 andFormat:@"jpg"];conf.loadScaledImage(width: 200, height: 100, backgroundColorAsHex: "ffffff", alpha: 0, format: "jpg") { data, error in if error == nil { let imageData = data } else { //something went wrong, handle error }}//Get plain URL without concerting parametersvar imgUrl = conference.getImageURL();//Get URL with resized image to 200x100var resizedImgUrl = conference.getImageURL(200,100);
If you only set either the width or height parameter, the image will be resized while the scale will be kept (the other parameter will be calculated accordingly).
When using the Objective-C SDK, there are 2 possibilities to download an image:
-
Use your own NSURLConnection or anything else
-
Use loadResourceAsyncWithHref of DataStore class (see section above for example)
When using a file URL in a web page you can add the URL parameter asstream=true. This changes the file handling and enables the following behaviour:
-
When clicking on the link and the file is for example a PDF, the browser detects the file type correctly and can show the PDF directly
-
When downloading the file
-
Your OS detects the file type and can propose to open the file with a program that supports the file type
-
When saving, the proposed name includes the file type, e.g. "report.pdf" instead of just "report"
-
Handling References
By adding attributes containing references, you are able to build data structures in an object oriented manner. You can reference a single or a list of classes.
You can add a list of type ConfSession to your Conference class or set a Conference as a reference of the ConfSession class, for example. This would resemble a one-to-many relationship: [Conference]-1—n-[ConfSession].
The generated SDKs will contain several methods starting with ‘post..’, ‘load…’ or ‘remove…’ afterwards.
Add a reference
Our example Conference class contains a property ‘confSessions’ which is of type ‘Collection of ConfSession’. To add a ConfSession to an object of type Conference you can do the following:
//Create and save an object of type ConfSession (see above)//Create and save an object of type Conference (see above) conference.postConfSessionAsync(confSession, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});//Create and save an object of type ConfSession (see above)//Create and save an object of type Conference (see above) conference.postSessions(confSession, { success: function(model, response, options) { //Successfully saved the reference on object conference }, error: function(model, response, options) { //do error handling }});// Create and save an object of type ConfSession (see above on how to do that)// Create and save an object of type Conference (see above on how to do that)await conference.PostConfSessionAsync(confSession);/* Create and save an object (called confSession) of type ConfSession (see above) *//* Create and save an object (conference) of type Conference (see above) */ [conference postConfSessionsAsync:confSession andWithBlock:^(NSError *error) {}];/* Create and save an object (called confSession) of type ConfSession (see above) *//* Create and save an object (conference) of type Conference (see above) */ conference.postConfSessions(confSession) { href, error in if error == nil { //confSession successfully posted, href contains the href to the posted reference } else { //something went wrong, handle error }};//Create and save an object of type ConfSession (see above)//Create and save an object of type Conference (see above) conference.postConfSessions(confSession,{ onOk: function() { //Successfully saved the reference on object conference }, onError: function(error) { //do error handling }})Get references
To retrieve the referenced object(s) you have to call the “load” function for this property. This function downloads the object(s) from the backend. Depending on the SDK, you can either get the loaded objects from the return value or from a property that stores the values after they have been loaded:
//Assume that our object conference has already referenced confSessions conference.loadConfSessionAsync(null, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { //if there is no exception you can access the local list on conference object List<confsession> sessions = conference.getConfSessions(); }});//Assume that our object conference has already referenced confSessionsconference.loadSessions({ success: function(model, response, options) { //Successfully returned references //Access local property var confSessions = model; }, error: function(model, response, options) { //do error handling }});// Assume that our object conference already contains referenced confSessions// either use the return valueIList<ConfSession> sessions = await conference.LoadConfSessionAsync();// or the propertysessions = conference.ConfSessions;/* Assume that our object conference has already referenced confSessions */[conference loadConfSessionsAsync:@"" andWithBlock:^(NSError *error) { /* if there is no exception you can access the local list on conference object*/ NSMutableArray *loadedSessions = [conference sessions];}];/* Assume that our object conference has already referenced confSessions */conference.loadConfSessions { sessions, error in if error == nil { let loadedSessions = sessions } else { //something went wrong, handle error }}//Assume that our object conference has already referenced confSessionsconference.loadConfSessions(undefined, { onOk: function() { //Successfully returned references //Access local property var confSessions = conference.getConfSessions(); }, onError: function(error) { //do error handling }});
Instead of ‘null’ / 'undefined' / empty parameter list you can set a query parameter to filter the list. For this please see the Query section below or the documentation article about Queries.
Update a reference
If you change properties of an object which are already referenced to another object and send an update to the server, than the reference will also be changed. If you call “load” again you will retrieve the updated reference.
Hint: If you add an existing reference to the same object again, it will not be added as new reference again.
Delete reference
Delete a reference between objects is straightforward.
//Assume that conference contains a reference to confSession//Assume that both objects (conference, confSession) are loaded from serverconference.removeConfSessionAsync(confSession, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { }});//Assume that conference contains a reference to confSession//Assume that both objects (conference, confSession) are loaded from serverconference.removeConfSessions(confSession, { success: function(model, response) { //successfully removed reference }, error: function(model, response) { //do error handling }});// Assume that conference contains a reference to confSession// Assume that both objects (conference, confSession) are loaded from serverawait conference.RemoveConfSessionAsync(confSession);/* Assume that conference contains a reference to confSession *//* Assume that both objects (conference, confSession) are loaded from server */[conference removeConfSessionsAsync:confSession andWithBlock:^(NSError *error) {}];/* Assume that conference contains a reference to confSession *//* Assume that both objects (conference, confSession) are loaded from server */conference.removeConfSession(confSession) { error in if error == nil { //confSession successfully removed } else { //something went wrong, handle error }}// Assume that conference contains a reference to confSession// Assume that both objects (conference, confSession) are loaded from serverconference.removeConfSessions(confSession);Embedded Objects
Instead of using the relational approach, it is also possible to embed objects within one another. Take a look at the related documentation page for a general introduction.
Therefore the SDKs provide methods for reading embedded objects and also embedded collections.
Example: There's a class "Session" and a class "Room". Session has an attribute of the type Room and the attribute is configured as embedded reference. Now, if you have an object of the class Session, you can call the method loadRoomAsync(), which will return the referenced object of type Room. Just so you know what happens in the background here: Instead of loading the single Room object, a load() call on the whole "parent" (embedding) object gets executed. So the Session gets loaded in order for it to contain the latest Room JSON and then the Room gets "extracted" from it.
Embedded Objects are on client side currently only supported (partially) in the C# SQLite and Xamarin SQLite SDK.
As it is currently only possible to read embedded objects in the SDK, no post or delete methods are generated. It's also not possible to filter the elements of an embedded collection by using a query.
Please also note that offline handling using the persistent storage is not working for embedded objects and collections of embedded objects, but using the in-memory cache is already supported.
Create, update and delete actions and full offline handling support will be possible in a future version. Please talk to our sales representatives or project managers if you would like these features to be prioritized.
The static count methods will not include embedded objects or objects included within a collection of embedded objects, as they are not accessible directly anyway.
Example: A Class "Conference" has an embedded reference collection to the class "Session". So one conference has multiple sessions. Let's say there are 2 conferences saved on their own, and 20 sessions saved on their own. The conferences also have 5 sessions embedded. When calling Session.count() or sending a GET request to /count for the Session class, the result will be 20, not 30 (20+2*5). Only these 20 sessions are directly accessible (by their own ID) instead of via their parent (embedding) objects.
// After loading an existing session you can immediately access any embedded object or collection of embedded objectsRoom room = existingSession.Room;IList<Person> attendees = existingSession.Attendees;// Calling the load method on an embedded attribute will cause the embedding session object the be reloadedRoom room = await existingSession.loadRoomAsync().ConfigureAwait(false);IList<Person> attendees = await existingSession.LoadAttendeesAsync ().ConfigureAwait(false);
Queries
Our backend supports a lot of query operations. For the full documentation please see here.
But let’s have a look at some examples.
Retrieving a filtered list
To get a list of objects you can set the query parameter.
//Load list of conference objects filtered by nameConference.getConferencesAsync("name like \"Backend\"", new AOMCallback<List<Conference>>() { @Override public void isDone(List<Conference> resultObject, ApiomatRequestException exception) { //Getting list was successful if exception == null //You can now work with parameter objects which contains loaded elements }});//Load list of conference objects filtered by nameapiomat.Conferences().fetch({ search: "name like \"Backend\"", success: function(model, response, options) { //Getting list was successful //You can now work with parameter objects which contains loaded elements }, error: function(model, response, options) { //do error handling }});// Load list of conference objects filtered by nameIList<Conference> loadedConferences = await Conference.GetConferencesAsync("name like \"Backend\"");/* Load list of conference objects filtered by name */[Conference getAsyncWithQuery:@"name like \"Backend\"" withBlock:^(NSMutableArray *loadedConfs, NSError *error) { /* Getting list was successful if exception == null */ /* You can now work with parameter loadedConfs which contains filtered elements */}];/* Load list of conference objects filtered by name */Conference.loadList("name like \"backend\"") { conferences, error in if error == nil { let loadedConferences = conferences } else { //something went wrong, handle errror }}//Load list of conference objects filtered by nameApiomat.Conference.getConferences("name like \"Backend\"",{ onOk: function(objects) { //Getting list was successful //You can now work with parameter objects which contains loaded elements }, onError: function(error) { //do error handling }});
This will load a list of allConference objects, where the name includes the string ’backend’.
Retrieving filtered list of references
If you only want a subset of referenced objects, you can call the “load” method like this:
//Assume that our object conference has already referenced confSessionsconference.loadConfSessionsAsync(confSession, new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { conference.getConfSessions(); }});//Assume that our object conference has already referenced confSessionsconference.loadConfSessions({ search: "name ==\"backend\"", success: function(model, response, options) { //Successfully returned referenced confSessions with name 'backend' //Access local property var confSessions = model }, error: function(model, response, options) { //do error handling }});// Assume that our object conference already contains referenced confSessionsIList<ConfSession> sessions = await conference.LoadConfSessionAsync("topic=\"C#\"");/* Assume that our object conference has already referenced confSessions */[conference loadConfSessionsAsync:@"name like \"Backend\"" andWithBlock:^(NSError *error) { NSMutableArray *filteredSessions = [conference sessions];}];/* Assume that our object conference has already referenced confSessions */conference.loadConfSessions("name like \"backend\"") { confSessions, error in if error == nil { let loadedConfSessions = confSessions } else { //something went wrong, handle errror }//Assume that our object conference has already referenced confSessionsconference.loadConfSessions("name ==\"backend\"", { onOk: function() { //Successfully returned referenced confSessions with name 'backend' //Access local property var confSessions = conference.getConfSessions(); }, onError: function(error) { //do error handling }});Error Handling
The error handling is quite different in the various SDKs, so instead of one common description, the description is in the different SDK tabs:
If you use the synchronous methods you have always a try-catch block around the methods which do network requests. If an error occurs an exception of class ApiomatRequestException (which is a subclass of Exception) is thrown and catched. There are several methods to get information about details of this error. The exception object contains specific information like error code and a textual description of the reason.- exception.getReason() — Gives you a textual description of the error- exception.getExpectedReturnCode() — The expected HTTP return code- exception.getReturnCode() — The returned statusCode- exception.getStatus() — If is set then this method returns an instance of enum StatusOn asynchronous requests isDone method that is executed after request finished contains also an exception object as parameter. If this object does not equal null, an error occurred and it would be nice to handle this. The given object is an instance of the previous explained class ApiomatRequestException. The following snippets gives you an idea how you can handle exception in async methods.Conference conference = new Conference (); conference.saveAsync(new AOMEmptyCallback() { @Override public void isDone(ApiomatRequestException exception) { //if exception != null an error occurred if(exception != null) { Log.e("ConferenceSave", "An error occurred: " + e.getReason()); } }});The error handling works similar to Backbone.js default behaviour, as shown in the examples above. You can define an error callback function and access the parameters model, response and options.Every request you send will enter the callback object you have given the function to act as a parameter. If any error occurs while sending the request, the error function will be called and the options will provide additional information you can access by evaluating the error property. This error is an object of type Apiomat.ApiomatRequestError. You can access the following functions and properties on this object:- message: Gives you a textual description of the error- statusCode: The returned status code- getStatusObj: Give you an object of type Apiomat.Status if the statusCode can be mapped to one- expectedCodes: Returns the HTTP status codes that this request expectedAn example will demonstrate how to work with the error object:conference.loadConfSessions({ success: function(model, response, options) { //success }, error: function(model, response, options) { //error handling var error = options.error; var errorCode = error.statusCode; var description = error.message; }});The class Apiomat.Status contains references to every statusCode that can occur. Also, you can find a textual description of these codes there. You can compare the returned statusCode with a property of the type Apiomat.Status.var options = { success: function(model, response, options) { //successful }, error: function(model, response, options) { //check if error was an auth error if(options.error.statusCode == Apiomat.Status.UNAUTHORIZED) { //handle this error } }}All the methods that do network I/O might throw an ApiomatRequestException. In C# there are no checked exceptions like in Java, so you don't have to use try/catch everywhere, but it's advised to do that so you can handle errors when they appear. There are several Properties to get information about details of an error. The exception object contains specific information like error code and a textual description of the reason.- exception.Reason - Gives you a textual description of the error- exception.ExpectedReturnCode - The expected HTTP return code- exception.ReturnCode - The returned status code- exception.Status - Returns an instance of the enum "Status"The returned status codes are not only HTTP status codes, but also some custom ones. For example the above mentioned error "ID already exists" when trying to sign up a user that has already been signed up before, is the error code 830 and Status.ID_EXISTS in the Status enum.Here is an example for how to handle exceptions:Conference conference = new Conference();try{ await conference.SaveAsync();}catch (ApiomatRequestException e){ Console.WriteLine("An error occured during saving a conference: " + e.Reason); // In Windows Store apps:// System.Diagnostics.Debug.WriteLine("An error occured during saving a conference: " + e.Reason);}In case you want to send another request after the error occurs, be advised that you can't use await in a catch block of an exception in C# 5 (this will probably change in C# 6 with the Roslyn .NET compiler). If you just want to send the same or other requests asynchronously, initialize a flag with "false" before the try block, set it to "true" in the catch block and send another request if the flag evaluates to true:Conference conference = new Conference();bool tryAgain = false;try{ await conference.SaveAsync();}catch (ApiomatRequestException e){ tryAgain = true; // do what is necessary to fix the problem // ...}if (tryAgain){ await CallMoreAsyncCode();}However, you might also want to throw the exception so the caller knows about it. If you throw it in the last if-block the exception's stack trace doesn't contain the correct location where the exception happened.To fix that, you can use the ExceptionDispatchInfo class like in the following example and also omit the flag:Conference conference = new Conference();ExceptionDispatchInfo capturedException = null;try{ await conference.SaveAsync();}catch (ApiomatRequestException e){ capturedException = ExceptionDispatchInfo.Capture(e);}if (capturedException != null){ await CallMoreAsyncCode(); // Throw the exception with the captured info from above capturedException.Throw();}If you use the synchronous methods you have always a try-catch block around the methods which do network requests. If an error occurs an exception of class NSException with name ApiomatRequestExceptionis thrown and catched. There are several methods to get information about details of this error. The exception object contains specific information like error code and a textual description of the reason.- [exception name] or exception.name - Gives you the name of the error (should be ApiomatRequestException)- [exception reason] or exception.reason- The returned statusCode- [[exception userInfo] valueForKey:@"code"] or error?.userInfo["code"] - The returned statusCode (same as above)- [[exception userInfo] valueForKey:@"message"] or error?.userInfo["message"] - Gives you a textual description of the errorOn asynchronous requests every block that is executed after request finished contains also an NSError object as parameter. If this object not equals nil, an error occurred and it would be nice to handle this. The given error object contains specific information like error code and a textual description of the reason. The following snippets gives you an idea how you can handle exception in async methods.Conference *conference = [[Conference alloc] init];[conference saveAsyncWithBlock:^(NSError *error) { /* If error occured the error parameter != NIL */ if(error) { /* errorDomain == com.apiomat.frontend */ NSString *errorDomain = [error domain]; NSInteger errorCode = [error code]; NSString *reason = [[error userInfo] objectForKey:@"message"]; }}];The class AOMStatus contains references to every error code (statusCode) that can occur. Also, you can find a textual description of these codes there. You can compare the returned statusCode with a enum of the type AOMStatusCode. The following examples will demonstrate you the usage./* get textual description of error code */NSInteger errorCode = 840;NSString *errorMsg = [AOMStatus getReasonPhraseForCode:errorCode];The Swift methods perform their work asynchronously and call the passed in completion handler when they are done. If an error occurs an object of Type NSError is passed to the completion handler, otherwise the argument is nil.There are several methods to get information about details of this error. The error object contains specific information like error code and a textual description of the reason.- error?.domain - The error domain (com.apiomat.frontend)- error?.userInfo["code"] - The returned statusCode- error?.userInfo["message"] - Gives you a textual description of the errorThe following snippets gives you an idea how you can handle errors in async methods:let conference = Conference()conference.save { error in if error == nil { //successfully saved conference } else { /* errorDomain == com.apiomat.frontend */ let errorDomain = error?.domain let errorCode = error?.userInfo["code"] let errorMessage = error?.userInfo["message"] }}The class AOMStatus contains references to every error code (statusCode) that can occur. Also, you can find a textual description of these codes there. You can compare the returned statusCode with a enum of the type AOMStatusCode. The following examples will demonstrate you the usage. /* get textual description of error code */let errorCode = 840let errorReason = Status.reasonPhrase(forStatusCode: errorCode)Every request you send will enter the callback object you have given the function to act as a parameter. If an error occurs while sending the request the 'onError' function of your callback object will be called with a parameter 'error'. This 'error' is an object of type Apiomat.ApiomatRequestError. You can call the following function on this object:- message: Gives you a textual description of the error- statusCode: The returned status code- getStatusObj: Give you an object of type Apiomat.Status if the statusCode can be mapped to one- expectedCodes: Returns the HTTP status codes that this request expectedAn example will demonstrate the access to an 'error' object:conference.loadConfSessions(undefined, { onOk: function() { //Successful }, onError: function(error) { //Error handling var errorCode = error.statusCode; var description = error.message; }});The class 'Apiomat.Status' contains references to every statusCode that can occur. Also, you can find a textual description of these codes there. You can compare the returned statusCode with a property of the type 'Apiomat.Status'.var callback = { onOk: function() { //Successful }, onError: function(error) { //Check if error was an auth error if(error.statusCode == Apiomat.Status.UNAUTHORIZED) { //Handling this error } }}Custom Error Codes
ApiOmat enables you to define and throw your own error codes within e.g. a Native Module. Those custom error codes will be delegated through each SDK to allow you to catch and handle them within your projects.
Error Code Limitation
Unfortunatly SDKs like Android, iOS or PHP do not support error codes greater than 999.
Their internal basic libraries often block HTTP responses with unsupported error codes or throw ProtocolExceptions. To avoid such complications it is suggested to follow the definition of response status codes within the HTTP/1.1 RFC.
Cache and Offline Handling
When using the Objective-C SDK, please note:
The class AOMModelStore inherits from the class NSURLCache. So if you have any problems with clear cache functions in the simulator especially for ios 9, you should use the storagePolicy: NSURLCacheStorageAllowedInMemoryOnly.
For info about how to use caching and offline data, please look here: Offline Handling in SDKs
Meta data of data model
To access information about the structure of your app, you can use the appConfig, which provides the names of all available modules including it's models and attributes in JSON format.
For example:
{ "app": { "name": "Conference", "modules": [ { "module": { "name": "Basics", "type": "STATIC", "usedInSystem": "TEST", "classes": [ { "class": { "name": "User", "attributes": [ { "attribute": { "name": "userName", "type": "String" } }, ...snip other attributes } }, ... snip other classes } }, ... snip other modules }}
var appConfig = apiomat.appConfig();//now you can access the name of the app for examplevar appName = appConfig.app.name;var appConfig = JSON.parse(Apiomat.User.APPCONFIG); //now you can access the name of the app for examplevar appName = appConfig.app.name;string slidesUrlPlain = conference.SlidesURLResource; // Without the addition of API key and systemstring slidesUrl = conference.SlidesUrl; // With the addition of API key and systemAdding and modifying headers
It is possible to add new or modify existing headers sent with each SDK request to ApiOmat using a special method in the Datastore class. Like the Datastore.configure..() method, setting additional request headers this way will persist until another call to the method is executed:
Map<String, String> myHeaders = new HashMap<>();myHeaders.put("cookie", "123456");/* adding multiple header */Datastore.setCustomHeaders(myHeaders);/* removing all headers */Datastore.setCustomHeaders(null);/* Add custom headers */Datastore.Instance.CustomHeaders = new Dictionary<string, string> () { {"foo", "bar"} }; /* Remove custom headers */Datastore.Instance.CustomHeaders = null;/* setting a custom header */[[AOMDatastore sharedInstance].customHeaders setObject:@"Header_Value" forKey:@"Header_Name"];/* removing a custom header */[[AOMDatastore sharedInstance].customHeaders removeObjectForKey:@"Header_Name"];/* adding multiple headers */NSDictionary* headers = @{@"Header_Name1" : @"Header_Value1", @"Header_Name2" : @"Header_Value2" };[[AOMDatastore sharedInstance].customHeaders addEntriesFromDictionary:headers];/* removing all headers */[[AOMDatastore sharedInstance].customHeaders removeAllObjects];/* setting a custom header */DataStore.sharedInstance.setCustomHeaders(["cookie", "123456"])/* setting multiple headers */DataStore.sharedInstance.setCustomHeaders(["cookie", "123456", "key", "value"])/* removing all headers */DataStore.sharedInstance.setCustomHeaders(nil);