Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member208486
Participant
1,709
Happy #APIFriday Everyone! Hope your week was as exciting (and exhausting) as mine. This week on #APIFriday we are taking a look at Uber's API for user details and ride requests. Uber doesn't provide direct documentation for using their API in a JS app, so I learned A LOT trying to add it into my UI5 app. The pitfalls I enjoyed are included in some of my anecdotes so read on!



To get started, let's take a look at the Uber Developer documentation.  As you will notice almost immediately, there is no (aka little) guidance for Web! I used the Curl API documentation and made so adaptions so it works in AJAX, which I will point out as we go. There is a partner PHP library/example available, but that's not the language I'm using today. We'll do our best to follow along and adapt the Uber Ride Request tutorial in Curl.

First things first, you will need to sign up for a developer account on your Uber account (if you already have the Uber app, you can use that account). In your account, you will be able to get a Server Token and a Personal Token. The Personal Token can be used in place of doing OAuth2 in your application. More on the perils of OAuth later....so sign up and take note of your tokens as we will need them to get started.



In your Web IDE, go ahead and create a new SAPUI5 application. I have a new found love in Fiori Tiles, so let's create a Tile Container in the view. Open your main view file and in between the <contents> tags, add in a tile container we can bind the Uber Ride Types to.
<TileContainer
id="container"
tiles="{rideTypes>/data/products}">
<StandardTile
title="{rideTypes>display_name}"
icon="{rideTypes>image}"
info="{rideTypes>description}"
press="tilePress" />
</TileContainer>

We'll create a rideTypes model in our controller soon which is bound to the aggregate for a tile container. Meaning the number of tiles will be dynamically created based on the number of Uber ride types in your area...cool!

Into the controller! Open your main controller file and an onInit function. The init function will set some models for the view and then start the Uber API call chain. If you scanned through the Uber Ride Request tutorial, you saw that there is a (kind of long) series of calls. First you get the products to get the product id, then you get the fare for the product, then you call for a ride (IN THEIR SANDBOX!!), and finally we get the status of the ride. So let's get cracking!
onInit: function(){
var meModel = new JSONModel({
"data":{}
});
var ridesModel = new JSONModel({
"data": {}
});
this.getView().setModel(meModel, "profile");
this.getView().setModel(ridesModel, "rideTypes");
this.getMe();
this.getRideTypes();
}

Let's start with the getModel function. Define a new function called getMe that takes no parameters. In this function, we'll do an AJAX call to get your user profile data. The /me method in the Uber API is a GET call and we need to provide our personal token. Once we get to the done callback, we'll bind the results to our profile model which we could the use in the view to display some data about the user. To change the Curl call to an Ajax call, -H attributes will become part of the headers array. In your onInit function, comment out the this.getRideTypes() call and you can run the app and check the console. If the call was a success, you'll see your data in the console. If it wasn't...well I might know what your issue is.
getMe: function(){
var sUrl = "https://api.uber.com/v1.2/me";
var oModel = this.getView().getModel("profile");
$.ajax({
url: sUrl,
type: 'GET',
async: true,
headers: {
"Authorization" : "Bearer " + personalToken,
"Content-Type": "application/json"
}
}).done(function(results){
console.log(results );
oModel.setProperty("/data", results);
});
}

If you get an error message saying that you cannot access the /me method or access denied or the like, don't freak out because it happened to me too. There's an easy fix for right now. Back in your Uber Developer dashboard, click on the Authorizations tab. Under General Scopes, you will find a profile scope. Check it. If you try to save, you'll see that you need to provide a redirect url and a privacy policy link. You can use the test version's of your application url as the redirect and SAP's privacy policy link is below.











Redirect URL https://webidetestingXXXXX-INUMBERtrial.dispatcher.hanatrial.ondemand.com/webapp/extended_runnable_f...
Privacy Policy URL  http://www.sap.com/corporate/en/legal/privacy.html

Once you save those changes and re-run your application, the errors will go away.

Back in your controller, let's add the getRideTypes function. This Ajax call will be to the /products method of the Uber API. The /products method is very similar to the /me method, but it does take a longitude and latitude as the available products are different by area. Swap out the -H parameters for the headers array, set the type to GET, and the url to the /products method of the Uber API. We'll bind the results to the rideTypes model we set in the onInit function which will populate the tiles in the view.
getRideTypes: function() {			
var sUrl = "https://api.uber.com/v1.2/products?latitude=37.7752315&longitude=-122.418075";
var oModel = this.getView().getModel("rideTypes");
$.ajax({
url: sUrl,
type: 'GET',
async: true,
headers: {
"Authorization" : "Bearer " + oAuth,
"Content-Type": "application/json"
}
}).done(function(results){
console.log(results);
oModel.setProperty("/data", results);
});
}

When you run you application, you should see a variety of tiles. The provided coordinates are for San Francisco where there are 9 ride types for Uber. My output of tiles looks like this.



In our view, we defined a function name for the tiles when the are pressed. For now, we'll have the press show a Message Toast of the ride type selected and estimated cost. I have some preset values for starting location and destination, but you can add an input field or 2 to your UI to get the user to enter those. Just remember, if they provide a human readable text address, you will need to geocode it to get the latitude and longitude.

For the tilePress function, we'll call the /requests/estimate method of the Uber API. This method is a little harder to translate from Curl to Javascript. Again, the -H attributes will be transferred to the headers array in the Ajax call. Since we will be passing data, the type for this call is POST. For the -d (or data) array, we need to translate that to the data parameter in the Ajax call. However, the /requests/estimate method does not accept JSON in the data array. You need to "stringify", or translate to plain text,  the JSON data containing the coordinates and product id. We can get the product id from the tile press event by finding the related data binding to the clicked tile.
tilePress: function(oEvent){
var oView = this.getView();
var oItem = oEvent.getSource();
var data = JSON.stringify({
"product_id": oItem.getBindingContext("rideTypes").getProperty("product_id"),
"start_latitude": 37.7752278,
"start_longitude": -122.4197513,
"end_latitude": 37.7773228,
"end_longitude": -122.4272052
});
var sUrl = "https://api.uber.com/v1.2/requests/estimate";
$.ajax({
url: sUrl,
type: 'POST',
async: true,
headers: {
"Authorization" : "Bearer " + oAuth,
"Content-Type": "application/json"
},
data: data
}).done(function(results){
//console.log(results);
sap.m.MessageToast.show("You selected " + oItem.getBindingContext("rideTypes").getProperty("display_name") +
". Estimated cost is " + results.fare.display);
});
}

The Message Toast will display the name of the Ride Type selected, by getting the binding context of the selected tiles. Additionally, once we get the results, we can use the estimated fare to display in the message as well.



Now that we have the fare estimate (and therefore the fare id), we can actually request the ride. Yay! Well, bad news folks. The personal token we generated earlier won't work since the requests scope is a privileged scope. The only way to get a token with that scope is to do the OAuth2 handshake. And unfortunately I have not solved that riddle. The end user needs to authorize you too access their Uber data. After they accept that authorization, a code is provided back. That code is needed in the token request. I have not figured out how to extract the code from the redirect (this is where that redirect url comes in handy). I had to do some manually (and pretty hacky) stuff to get a properly scoped token. I copied the authorize URL into my browser and set the redirect url to localhost.
https://login.uber.com/oauth/v2/authorize?response_type=code&client_id=<CLIENT_ID>&scope=request&red...

When the localhost page loads, I found a code parameter in the URL. This is the code needed for the token request.



Then I actually ran the Curl request in my terminal with all my parameters and got a new token returned. As a note, I had some issues with the Curl request and had to change the -F to --form. I grabbed the returned token and created a scopedToken variable in my controller with the value. When storing a token in your application, you should always encrypt it. However, I'm being lazy and hacky, so I didn't. Don't be like me in your productive apps! With this properly scoped token, I was able to make a call against the /requests method of the Uber API. If you are doing this for testing, use their SANDBOX ENDPOINT!!! You don't accidentally want to charge yourself for a ride you're not taking.

To call a ride, I added the /requests method call to my /requests/estimate done callback. The /requests Ajax call looks very similar to the /requests/estimates call as you need to start and ending coordinates, and the ride type, but we can also add in the fare id now that we have the estimate. Again, since we are passing a data array, this is a POST call. We also want to use our new scoped token as the old token won't work.
var rData = JSON.stringify({
"product_id": oItem.getBindingContext("rideTypes").getProperty("product_id"),
"start_latitude": 37.7752278,
"start_longitude": -122.4197513,
"end_latitude": 37.7773228,
"end_longitude": -122.4272052
});
$.ajax({
url: "https://sandbox-api.uber.com/v1.2/requests",
type: 'POST',
async: true,
headers: {
"Authorization" : "Bearer " + oAuthScoped,
"Content-Type": "application/json"
},
data: rData
}).done(function(results2){
console.log(results2);
});

Run you application, When you click a tile, you will see a message showing the ride type selected and the estimated cost. If you look in your Javascript console, you will see that a ride request is being processed. You can use the /requests/current method of the Uber API to get more info on the request.

The Uber API introduces us to the pitfalls of securing your app and data. I am not saying security is a bad thing, but it is not an easy problem to solve. It took me 4+ hours to get this far as 1 developer. I talked with a friend of mine who is building an application using the SoundCloud API, and he still hasn't figured out the handshake for OAuth2 after 2 weeks of working on it. If you have a solution for the OAuth2 handshake issues for either generic AJAX or the SAP UI5 world, I would love to hear it! For now, I will keep hacking away at it and hope to get you all a better answer soon.

In the interest of time and making sure I get this to you all for #APIFriday, this app is a little unfinished, but the final steps would be to show the request on the page (whether it be the same or a new page) and then allowing the user to check the status or cancel the ride. The last 2 methods I didn't cover are very similar to the ones I did. If you want to finish out the app and show it off, that would be awesome! You don't even have to keep my tiles. I might do it too so we'll see how 1 API can become a variety of Apps! When I do make a nice little packaged Fiori App, I will be sure to share it with you all.

Until next week, happy #APIFriday!

 
2 Comments