Filter results by

Developing With SAMI: A Mobile App That Pulls Weight Data

This is Part 2 of a series of articles exploring the possibilities of SAMI from a developer’s perspective. In this article, we will build a simple iOS app that reads weight data from a Withings smart scale via SAMI.

Read Part 1: Making the Connection and Part 3: Let’s Bring Your Data Together

If this is your first time viewing this article, you should read it from top to bottom. Later on, you can directly jump to the sections that interest you, using the following links:

Introduction

In Part 1, I connected my Withings smart scale to SAMI and visualized my weight data on the User Portal. Having thus gained an intuitive understanding of data on SAMI, I would now like to develop an iOS app to interact with SAMI in a more profound way.

I name my app SAMIHMonitor. It allows a user to monitor his health-related data from multiple data sources. The development of this app helps me validate that SAMI, as a data exchange platform, helps a third-party app to not just store the data, but also easily get data from diverse sources.

Development of this app spans multiple iterations. This post discusses the first version, which allows a user to see his historical weight data on his iPhone. The data is from the Withings scale that has been connected to SAMI. In the next post, I will talk about version two, which allows a user to record his own calorie data and view his historical calorie and weight data from SAMI.

From this post, you will learn how to code to achieve the following tasks:

  • Authenticate a user on SAMI.
  • Query a user’s information.
  • Discover a user’s connected devices.
  • Analyze device information, such as its data format and units.
  • Get data from a connected device.

Demo: Showing weight data on a phone

Before digging in, here’s a preview of how the first version of this app will work.

  • Start SAMIHMonitor in iOS Simulator – iPhone 6. SAMIHMonitor V1
  • Click “CONNECT” and be presented this login screen: SAMIHMonitor V1
  • Enter your SAMI account credentials to login. Note that these are the same credentials used to log into the User Portal in Part 1. (They are also your Samsung account credentials, which any user with a Samsung phone will already have.)
  • Click “Allow” when the app asks you to give it permission to read your Withings data on SAMI.
  • After you finish the whole authentication flow, you will see the user information screen: SAMIHMonitor V1 The user info screen lists your user ID, full name, email, Samsung account creation time, etc.
  • If you have connected your Withings scale to SAMI as in Part 1, the “See Weight” button is enabled. Click the button and see the data on the screen: SAMIHMonitor V1
  • You can pull down on the data screen to refresh and show the latest data. For example, make a new measurement on your scale after navigating to the data screen on your phone. The pull gesture reveals this new weight data.
  • Use the back arrow on the top left to navigate back to the user info screen. You can logout from SAMI on the user info screen.

Installation and setup

Register the app

I log into the Developer Portal to register my iOS application on SAMI. To properly test my application, I have two separate Samsung accounts with different email addresses:

  1. My official developer account. This is the account I use to log into the Developer Portal and register my application.
  2. A simulated user account. This is the account I used in my previous post to log into the User Portal and connect my Withings smart scale.

I follow these instructions to create an application. For this iOS app, I select the following:

  • Set “Redirect URL” to ios-app://redirect.
  • Choose “Client Credentials Flow & Implicit Flow”.
  • Under “PERMISSIONS”, click the “Add Device Type” button. Choose “Withings Device” as the device type. Check “Read” permissions for this device type.

Make a note of the client ID. This is my unique application ID, which I will use in my source file later.

Prepare source files and libraries

To make my development easier, I will use the SAMI iOS SDK, which takes care of managing all my REST calls to SAMI. Other prerequisites are Xcode V6.1 or above and CocoaPods. SAMIHMonitor is written for and tested in iOS 8.

  1. Download SAMIHMonitor V1.
  2. Download the SAMI Objective-C/iOS SDK from GitHub.
  3. Install CocoaPods. See this page for instructions. In a terminal window, cd to the root directory of SAMIHMonitor app, and run pod install. This installs all the SAMI SDK prerequisites like AFNetworking. Here is what my terminal window looks like:SAMIHMonitor V1
  4. Double-click SAMIHMonitor.xcworkspace in the Finder window to open it in Xcode. Now import the SAMI SDK. Drag the client folder of the downloaded SAMI iOS SDK from the Finder window into the SAMIHMonitor group in Xcode. SAMIHMonitor V1
  5. Use the client ID (obtained when registering the app in the Developer Portal) to replace YOUR CLIENT APP ID in SamiConstants.m. SAMIHMonitor V1

Now build in Xcode and run SAMIHMonitor in iOS Simulator to play with the app like in the above demo.

Implementation

Hopefully, by now you have succeeded in playing with the first version of the SAMIHMonitor app. Now I’ll discuss the implementation.

At the high level, I need to implement five functionalities:

  • Authenticate a user using the OAuth 2.0 workflow.
  • Collect user information such as user ID, name, email, etc.
  • Query SAMI to learn if the user has a connected a Withings device, and if so, collect the information about that device.
  • Get the weight data of the identified Withings device from SAMI.
  • Request from SAMI the Manifest of the Withings device, and then obtain the unit of the weight data.

Authenticate the user

SAMI supports multiple OAuth 2.0 workflows. I consulted the workflow examples in the SAMI documentation. Since this version of SAMIHMonitor is a standalone mobile application, I chose to implement the Implicit method. This method has a straightforward implementation.

The authentication functionality is implemented in LoginViewController. The controller contains a UIWebView, which submits a GET request to the Authorization endpoint: https://accounts.samsungsami.io/authorize during viewDidLoad. Note that the request contains the client ID and redirect URL, which are configured or obtained in the preceding app registration phase.

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4     self.webView.delegate = self;
 5     
 6     //6. Create the authenticate string that we will use in our request.
 7     // we have to provide our client id and the same redirect uri that we used in setting up our app
 8     // The redirect uri can be any scheme we want it to be... it's not actually going anywhere as we plan to
 9     // intercept it and get the access token off of it
10     NSString *authenticateURLString = [NSString stringWithFormat:@"%@%@?client=mobile&client_id=%@&response_type=token&redirect_uri=%@", kSAMIAuthBaseUrl, kSAMIAuthPath, kSAMIClientID, kSAMIRedirectUrl];
11     //7. Make the request and load it into the webview
12     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:authenticateURLString]];
13     
14     self.addressField.text = authenticateURLString;
15     [self.webView loadRequest:request];    
16 }

In order to get an access token, LoginViewController needs to capture the callback after the authentication succeeds. To do so, it overrides UIWebViewDelegate’s method. In that method, the controller examines whether the redirect URL is the right one and extracts the access token if so.

 1 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
 2     
 3     if([request.URL.scheme isEqualToString:@"ios-app"]){
 4         // 8. get the url and check for the access token in the callback url
 5         NSString *URLString = [[request URL] absoluteString];
 6         if ([URLString rangeOfString:@"access_token="].location != NSNotFound) {
 7             // 9. Store the access token in the user defaults
 8             NSString *accessToken = [[URLString componentsSeparatedByString:@"="] lastObject];
 9             [UserSession sharedInstance].accessToken = accessToken;
10             // 10. dismiss the view controller
11             [self dismissViewControllerAnimated:YES completion:nil];
12         }
13     }
14     return YES;
15 }

UserSession stores the access token in persistent storage. Later on, this object is consulted to get the token for each REST API call to SAMI.

Get the user’s information

UserInfoViewController is in charge of presenting user information after the user logs in. The controller uses the SamiUserApi class in the SDK library to send the request to SAMI and presents the received user information as the following code:

 1 - (void) validateAccessToken {
 2     [self setWithingsDevice:nil];
 3 
 4     NSString* authorizationHeader = [UserSession sharedInstance].bearerToken;
 5 
 6     SamiUsersApi * usersApi = [[SamiUsersApi alloc] init];
 7     [usersApi addHeader:authorizationHeader forKey:kOAUTHAuthorizationHeader];
 8     
 9     [usersApi selfWithCompletionBlock:^(SamiUserEnvelope *output, NSError *error) {
10         if (error) {
11             self.fullnameLabel.text = error.localizedFailureReason;
12         } else {
13             UserSession *session = [UserSession sharedInstance];
14             session.user = output.data;
15             
16             self.idLabel.text = output.data._id;
17             self.nameLabel.text = output.data.name;
18             self.fullnameLabel.text = output.data.fullName;
19             self.emailLabel.text = output.data.email;
20             
21             NSDateFormatter * dateFormat = [[NSDateFormatter alloc] init];
22             [dateFormat setDateFormat:@"MMM dd, yyyy HH:mm"];
23             
24             NSDate *created = [NSDate dateWithTimeIntervalSince1970:([output.data.createdOn doubleValue])];
25             self.createdLabel.text = [dateFormat stringFromDate:created];
26             
27             NSDate *modified = [NSDate dateWithTimeIntervalSince1970:([output.data.modifiedOn doubleValue])];
28             self.modifiedLabel.text = [dateFormat stringFromDate:modified];
29             
30             [self processWithingsDevice];
31         }        
32     }];
33 }

Get the Withings device info

In UserInfoViewController, I need to query SAMI twice to get the Withings device info. The first query is to get the device type ID of Withings devices. This device type ID is a unique identifier for all Withings devices on SAMI.

The second query is to get a list of the devices that the user owns. I will then look for Withings devices in this list, using the device type ID. Based on that piece of information, “See Weight” button is either enabled or disabled on the screen.

When querying for the device type ID, I need to pass the device type name of the Withings device. This name is the one displayed in the User Portal. I define it in SamiConstants.m as the following code:

1 NSString *const kDeviceTypeNameWithings = @"Withings Device";

Then I call the method getDeviceTypesWithCompletionBlock of SamiDeviceTypesApi class in the SDK get the device type ID.

 1 - (void)getWithingsDeviceTypeId
 2 {
 3     SamiDeviceTypesApi *api = [[SamiDeviceTypesApi alloc] init];
 4     NSString* authorizationHeader = [UserSession sharedInstance].bearerToken;
 5     [api addHeader:authorizationHeader forKey:kOAUTHAuthorizationHeader];
 6     [api getDeviceTypesWithCompletionBlock:kDeviceTypeNameWithings offset:@(0) count:@(1) completionHandler:^(SamiDeviceTypesEnvelope *output, NSError *error) {
 7         [UserSession sharedInstance].withingsDeviceTypeId = ((SamiDeviceType *)[output.data.deviceTypes objectAtIndex:0])._id;
 8         NSLog(@"Store Withings Device Type %@", [UserSession sharedInstance].withingsDeviceTypeId);
 9         [self searchWithingsInDeviceList];
10     }];
11 }

After obtaining the device type ID, I call the method getUserDevicesWithCompletionBlock of SamiUsersApi class in the SDK to get the list of the user’s devices and then check for Withings devices using the device type ID.

 1 - (void)processWithingsDevice {
 2     [self setWithingsDevice:nil];
 3 
 4     NSString* authorizationHeader = [UserSession sharedInstance].bearerToken;
 5     
 6     SamiUsersApi * api = [[SamiUsersApi alloc] init];
 7     [api addHeader:authorizationHeader forKey:kOAUTHAuthorizationHeader];
 8     
 9     [api getUserDevicesWithCompletionBlock:@(0) count:@(100) includeProperties:@(YES) userId:[UserSession sharedInstance].user._id completionHandler:^(SamiDevicesEnvelope *output, NSError *error) {
10         NSArray *devices = output.data.devices;
11 
12         NSPredicate *predicateMatch = [NSPredicate predicateWithFormat:@"dtid == %@", kDeviceTypeID_Withings];
13         NSArray *withingsDevice = [devices filteredArrayUsingPredicate:predicateMatch];
14         if ([withingsDevice count] >0) {
15             NSLog(@"Found %lu Withings devices", (unsigned long)[withingsDevice count]);
16             [self setWithingsDevice:((SamiDevice *)withingsDevice[0])];
17          } else {
18             NSLog(@"Found 0 Withings devices");
19         }
20     }];
21 }

In my implementation, the first Withings device in the device list is used. Later on, the app will retrieve weight data from this device.

Get the weight data

DataTableViewController is in charge of querying SAMI to obtain messages from the identified Withings device, parsing the response, and finally presenting weight data.

To get the historical messages, I call the method getLastNormalizedMessagesWithCompletionBlock of SamiMessagesApi class in the SDK per following code:

 1 - (void)refreshMessages {
 2     NSString *authorizationHeader = [UserSession sharedInstance].bearerToken;
 3     int messageCount = 20;
 4 
 5     SamiMessagesApi * api2 = [SamiMessagesApi apiWithHeader:authorizationHeader key:kOAUTHAuthorizationHeader];
 6     [api2 getLastNormalizedMessagesWithCompletionBlock:@(messageCount) sdids:device_._id fieldPresence:nil completionHandler:^(SamiNormalizedMessagesEnvelope *output, NSError *error) {
 7         self.messages = output.data;
 8         [self.tableView reloadData];
 9         [self.refreshControl endRefreshing];
10     }];
11 }

I call refreshMessages method in viewDidLoad and also set it as the action of UIRefreshControl. To properly present the weight data and corresponding dates on the screen, I also need to parse the messages in the HTTP response as follows:

 1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 2 {
 3     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MessageCell" forIndexPath:indexPath];
 4     if (cell == nil) {
 5         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MessageCell"];
 6     }
 7     SamiNormalizedMessage *message = (SamiNormalizedMessage *)[self.messages objectAtIndex:indexPath.row];
 8     
 9     NSDictionary *dict = message.data;
10     id value = [dict objectForKey:@"weight"];
11     cell.textLabel.text = [value description];
12     
13     NSDate *date = [NSDate dateWithTimeIntervalSince1970:([message.ts doubleValue]/1000)];
14     cell.detailTextLabel.text = [self.dateFormat stringFromDate:date];
15     
16     return cell;
17 }

Get the weight units

By now, I can see weight numbers showing in the table. However, what are the units of this weight data? According to the SAMI developer documentation, I can get this information from the Manifest of the Withings device type. The following code uses the method getLatestManifestPropertiesWithCompletionBlock of SamiDeviceTypesApi class in the SDK to get the Manifest and then parse it to get the units:

 1 - (void)parseManifestSetUnit {
 2     NSString* authorizationHeader = [UserSession sharedInstance].bearerToken;
 3     
 4     SamiDeviceTypesApi * api = [[SamiDeviceTypesApi alloc] init];
 5     [api addHeader:authorizationHeader forKey:kOAUTHAuthorizationHeader];
 6     
 7     [api getLatestManifestPropertiesWithCompletionBlock:device_.dtid completionHandler:^(SamiManifestPropertiesEnvelope *output, NSError *error) {
 8         NSLog(@"%@ %@", output, error);
 9         
10         NSDictionary *dict = [output.data.properties objectForKey:@"fields"];
11         NSDictionary *fieldInfo = [dict objectForKey:@"weight"];
12         unit_ = [fieldInfo objectForKey:@"unit"];
13         NSString *fieldName = @"Weight in ";
14         NSString *titleWithUnit = [fieldName stringByAppendingString:unit_];
15         self.navigationItem.title = titleWithUnit;
16     }];
17     
18 }

What comes next?

Congratulations! You just learned the basics of developing with SAMI. SAMIHMonitor V1 gives an example of how to authenticate a user, collect user information, discover his devices, get the device Manifest to analyze data formats and units, and retrieve device data through SAMI.

In the next post I will develop SAMIHMonitor V2, where a user can record his calorie data and view historical weight and calorie data. This will illustrate how SAMI, as a data exchange platform, allows a third-party app to easily get data from diverse sources.

Read Part 1: Making the Connection and Part 3: Let’s Bring Your Data Together

Top image: Charis Tsevis

Get the ARTIK Newsletter

You like your news fresh! Sign up now and you will be the first to know about our latest software releases, coding tips, upcoming events, blog posts, datasheet updates, and more.

* By checking either box, you may receive notifications by phone, email, text, and/or other electronic means from Samsung Semiconductor, Inc. and its affiliates. If you choose to receive partner notifications, we may forward your contact information to our partners. You may unsubscribe from these services at any time by clicking on the unsubscribe link in our communications or by submitting a request here. Please see our Privacy Policy and Terms of Use for more information about how your data is stored and used.