Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Repository files navigation

ExistAPI

A framework for working with the Exist API on iOS.

ExistAPI wraps the Exist API in promises (via PromiseKit) to make it easier to work with API responses imperatively and chain API calls together. It also deserialises all API responses into strongly typed, Decodable models.

Installation

via Cocoapods:

pod 'ExistAPI', '~> 0.0.13'

Quick examples

Get a token from the Exist API to authorise your user (details in the Exist API docs here). Then create an API instance:

lettoken=getTokenFromAuthProcess()letexistAPI=ExistAPI(token: token)

Get the user's attributes for the past week:

existAPI.attributes().done{ attributes, response in
// handle attribute models here
}.catch{ error in
// deal with errors
}

Get attributes with some params:

existAPI.attributes(names:["steps","mood"], limit:12).done{ attributes, response in
// handle data here
}.catch{ error in
// handle error here
}

Acquire an attribute:

existAPI.acquire(names:["steps"]).done{ attributeResponse, urlResponse in
// deal with data here
}.catch{ error in
// handle error
}

Update data for some attributes:

letsteps=IntAttributeUpdate(name:"steps", date:Date(), value:3158)letdistance=FloatAttributeData(name:"steps_distance", date:Date(), value:1.2)
existAPI.update(attributes:[steps, distance]).done{ attributeResponse, urlResponse in
// if some attributes failed but some succeeded, check failures here
}.catch{ error in
// handle error
}

Usage

ExistAPI uses PromiseKit to handle asynchronous networking tasks. If you haven't used promises before, the PromiseKit docs are very good, and will help you understand the basic usage. Essentially, promises let you act on the result of an asynchronous task as if it were synchronous, making your code easier to write, read, and maintain.

Each of the public functions available in the ExistAPI class returns a Promise. You can chain these together, but the most simple use is to use a .done closure for handling the result and a .catch closure for handling errors.

Please see the examples below for more ideas on how to use ExistAPI's promises.

Requirements

  • Swift 4.0
  • iOS 12
  • An Exist account
  • Cocoapods

Getting started

First, you'll need to create an Exist developer client. This blog post has detailed instructions on how to create a developer client.

To quickly get started, you can simply copy and paste your personal auth token from your developer client details page. This will let you start testing your app with the Exist API immediately, and save building the authorisation flow for your users until later.

Important notes

  • When using Dates with ExistAPI, always use the local device time.

Creating an ExistAPI instance

Create an instance of ExistAPI with your token, and optionally set a specific timeout period for network requests:

letexistAPI=ExistAPI(token: yourToken, timeout:40)

GET requests

func attributes(names:[String]?, limit:Int?, minDate:Date?, maxDate:Date?)->Promise<(attributes:[Attribute], response:URLResponse)>

Returns an array of Attribute models:

structAttribute:AttributeValues{letname:Stringletlabel:Stringletgroup:AttributeGroupletpriority:Intletservice:StringletvalueType:ValueTypeletvalueTypeDescription:Stringprivateletvalues:[AttributeData]}publicprotocolAttributeValues{func getIntValues()throws->[IntValue]func getStringValues()throws->[StringValue]func getFloatValues()throws->[FloatValue]}

Each attribute uses the AttributeValues protocol to return its values as Ints, Strings, or Floats. It's up to you to use the correct corresponding function depending on the value type of the attribute you're working with. All available attributes and their types can be found here in the Exist API docs.

The value type models are as follows:

publicprotocolValueObject{associatedtypeValueTypevarvalue:ValueType?{get}vardate:Date{get}}publicstructIntValue:ValueObject{publictypealiasValueType=Intpublicvarvalue:Int?publicvardate:Date}publicstructStringValue:ValueObject{publictypealiasValueType=Stringpublicvarvalue:String?publicvardate:Date}publicstructFloatValue:ValueObject{publictypealiasValueType=Floatpublicvarvalue:Float?publicvardate:Date}
func insights(limit:Int?, pageIndex:Int?, minDate:Date?, maxDate:Date?)->Promise<(insights:InsightResponse, response:URLResponse)>

Returns an InsightResponse:

structInsightResponse{letcount:Intvarnext:String?varprevious:String?letresults:[Insight]}structInsight:Codable{letcreated:DatelettargetDate:Date?lettype:InsightTypelethtml:Stringlettext:String}
func averages(for attribute:String?, limit:Int?, pageIndex:Int?, minDate:Date?, maxDate:Date?)->Promise<(averages:[Average], response:URLResponse)>

Returns an array of Average models:

classAverage:Codable{letattribute:Stringletdate:Dateletoverall:Floatletmonday:Floatlettuesday:Floatletwednesday:Floatletthursday:Floatletfriday:Floatletsaturday:Floatletsunday:Float}
func correlations(for attribute:String?, limit:Int?, pageIndex:Int?, minDate:Date?, maxDate:Date?, latest:Bool?)->Promise<(correlations:[Correlation], response:URLResponse)>

Returns an array of Correlation models:

classCorrelation:Codable{letdate:Dateletperiod:Intletattribute:Stringletattribute2:Stringletvalue:Floatletp:Floatletpercentage:Floatletstars:IntletsecondPerson:StringletsecondPersonElements:[String]letstrengthDescription:StringletstarsDescription:Stringletdescription:String?letoccurrence:String?letrating:CorrelationRating?}
func user()->Promise<(user:User, response:URLResponse)>

Returns a User model:

classUser:Codable{letid:Intletusername:StringletfirstName:StringletlastName:Stringletbio:Stringleturl:Stringletavatar:Stringlettimezone:StringletimperialUnits:BoolletimperialDistance:BoolletimperialWeight:BoolletimperialEnergy:BoolletimperialLiquid:BoolletimperialTemperature:Boollettrial:Boolletdelinquent:Bool}

POST requests

Attributes must be owned by a service before they can be updated. To own an attribute, use the acquire function. Updating an attribute not owned by your client will fail. Read more on attribute ownership in the Exist API docs.

publicfunc acquire(names:[String])->Promise<(attributeResponse:AttributeResponse, response:URLResponse)>

Returns an AttributeResponse:

publicstructAttributeResponse:Codable{varsuccess:[SuccessfulAttribute]?varfailed:[FailedAttribute]?}publicstructSuccessfulAttribute:Codable{varname:Stringvaractive:Bool?}publicstructFailedAttribute:Codable{varname:StringvarerrorCode:Stringvarerror:String}

To stop owning an attribute, use the release function:

publicfunc release(names:[String])->Promise<(attributeResponse:AttributeResponse, response:URLResponse)>

Before using the update function, make sure you've read the Exist API docs on acquiring and updating attributes.

publicfunc update<T:AttributeUpdate>(attributes:[T])->Promise<(attributeResponse:AttributeUpdateResponse, response:URLResponse)>

Takes an array of objects conforming to the AttributeUpdate protocol:

publicprotocolAttributeUpdate:Codable{associatedtypeValue:Codablevarname:String{get}vardate:Date{get}varvalue:Value{get}func dictionaryRepresentation()throws->[String:Any]?}

There are three concrete implementations of AttributeUpdate:

publicstructStringAttributeUpdate:AttributeUpdate{publictypealiasValue=String}publicstructFloatAttributeUpdate:AttributeUpdate{publictypealiasValue=Float}publicstructIntAttributeUpdate:AttributeUpdate{publictypealiasValue=Int}

The update function returns an AttributeUpdateResponse:

publicstructAttributeUpdateResponse:Codable{varsuccess:[SuccessfullyUpdatedAttribute]?varfailed:[FailedToUpdateAttribute]?}publicstructSuccessfullyUpdatedAttribute:Codable{varname:Stringvardate:Datevarvalue:String?}publicstructFailedToUpdateAttribute:Codable{varname:String?vardate:Date?varvalue:String?varerrorCode:Stringvarerror:String}

Chaining

Because ExistAPI uses PromiseKit, you can chain multiple calls together. Here are some examples:

Since we need to first acquire attributes in a user's Exist account before we're able to update those attributes, we can chain these two steps together the first time we want to update an attribute with data:

existAPI.acquire(names:["weight"].then { attributeResponse, urlResponse in
guardlet success = attributeResponse.success,
success.contains("weight")else{return}letupdate=FloatAttributeUpdate(name:"weight", date:Date(), value:65.8)
existAPI.update(attributes: updates).done{ attributeUpdateResponse, urlResponse in
// handle success
}.catch{ error in
// handle error
}

PromiseKit lets us use when to only act on a bunch of promises when they're all completed, and to handle errors in this chain of promises just once. Using when lets us compile a bunch of calls to the Exist API and act on all the returned data at once:

letinsightsPromise= existAPI.insights(days:30)letaveragesPromise= existAPI.averages(limit:30)letcorrelationsPromise= existAPI.correlations
when(fulfilled:[insightsPromise, averagesPromise, correlationsPromise]).done{ insights, averages, correlations in
// handle data
}.catch{ error in
// handle errors just once for all these promises
}

Building the app

Clone the source and build using Xcode 10.

Running the tests

Create a new file inside ExistAPITests called TestConstants.swift and add a string constant called TEST_TOKEN with your own API token, like this:

letTEST_TOKEN="your_token_string_here"

Without this, the tests will fail. You can get your access token by creating a developer client in your Exist account.

TODO

  • GET requests
  • POST requests
  • Create a convenience func for accessing only today's attributes
  • Support appending custom tags
  • Add tests for including queries in requests

About

iOS framework for working with the Exist API (https://developer.exist.io/)

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages