Lesson Weekend

Now that we have everything in place, let's begin creating authenticated API requests to the Yelp API, and return the resulting data into our application.

Building a Request

First, we’ll add the base url and query parameters to our Constants.java file:

Constants.java
public class Constants {
    public static final String YELP_TOKEN = BuildConfig.YELP_TOKEN;
    public static final String YELP_BASE_URL = "https://api.yelp.com/v3/businesses/search?term=restaurants";
    public static final String YELP_LOCATION_QUERY_PARAMETER = "location";
}

There's many different types of information the Yelp API can return. But because our app will only be requesting restaurant information, we can simply re-use this URL "template", and insert a different zip code each time. This also keeps our code DRY, because we only ever have to declare the base URL and miscellaneous parameter names in one place.

YelpService

Next, let's create a new class called YelpService.java inside our main package. This class will contain all logic necessary for interacting with the Yelp API, including using OkHttp and to create and send requests.

First, we'll define a findRestaurants() method that takes two parameters: The location a user provides, and a callback that will execute when our API request receives a readable response from Yelp:

YelpService.java
package com.epicodus.myrestaurants;

import okhttp3.Callback;

 public class YelpService {

    public static void findRestaurants(String location, Callback callback) {

  }

}

(Note: When importing necessary classes, several classes listed here (such as Callback) have multiple options. Make sure to double-check that you're importing the correct classes, as depicted in this lesson's example code.)

OkHttp Client

Next, we'll create a new OkHttpClient client to create and send our request. We can do this using the OkHttpClient.Builder() including in the OkHttpClient package, like this:

YelpService.java
package com.epicodus.myrestaurants;

import okhttp3.Callback;
import okhttp3.OkHttpClient;

 public class YelpService {

    public static void findRestaurants(String location, Callback callback) {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();
    }

}

Building the Request URL

Next, we'll use another OkHttp class to construct the URL we'll send our request to:

YelpService.java
package com.epicodus.myrestaurants;

import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;

public class YelpService {

    public static void findRestaurants(String location, Callback callback) {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        HttpUrl.Builder urlBuilder = HttpUrl.parse(Constants.YELP_BASE_URL).newBuilder();
        urlBuilder.addQueryParameter(Constants.YELP_LOCATION_QUERY_PARAMETER, location);
        String url = urlBuilder.build().toString();

    }
}
  • Here, the line HttpUrl.Builder urlBuilder = HttpUrl.parse(Constants.YELP_BASE_URL).newBuilder(); creates a new URL using the YELP_BASE_URL value stored in our Constants class.

  • Then, the line urlBuilder.addQueryParameter(Constants.YELP_LOCATION_QUERY_PARAMETER, location); adds the YELP_LOCATION_QUERY_PARAMETER, and the specific location the user has opted to search.

  • Finally, String url = urlBuilder.build().toString(); turns the finished URL into a string. Essentially this process is constructing the API endpoint URL (dynamic parameters and all) we'll be making a request to.

Creating a New Request with OkHttp

Now we'll create our request using the newly-constructed URL:

YelpService.java
package com.epicodus.myrestaurants;

import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;

 public class YelpService {

    public static void findRestaurants(String location, Callback callback) {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        HttpUrl.Builder urlBuilder = HttpUrl.parse(Constants.YELP_BASE_URL).newBuilder();
        urlBuilder.addQueryParameter(Constants.YELP_LOCATION_QUERY_PARAMETER, location);
        String url = urlBuilder.build().toString();

        Request request= new Request.Builder()
                .url(url)
                .header("Authorization", Constants.YELP_TOKEN)
                .build();
    }

}
  • Here, we create a new Request object aptly named request, and provide it the string url we've just constructed using the built-in url() method.

  • Then, we call header() to add a header to our request. The Yelp API requires our requests include a header with our unique access token. As explicitly stated in the Yelp Fusion Documentation on Authentication:

To authenticate API calls with the access token, set the Authorization HTTP header value as Bearer ACCESS_TOKEN.

  • Headers, like parameters, are key-value pairs. So, in the code above we're adding a header with the key "Authorization", and a value of our YELP_TOKEN constant, which includes both the term Bearer and our unique access token, per the requirements of the Yelp API.

  • After adding the header, we call build() to build our new Request object.

Calling a Request Asynchronously

And finally, we'll execute this request:

YelpService.java
package com.epicodus.myrestaurants;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;

 public class YelpService {

    public static void findRestaurants(String location, Callback callback) {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        HttpUrl.Builder urlBuilder = HttpUrl.parse(Constants.YELP_BASE_URL).newBuilder();
        urlBuilder.addQueryParameter(Constants.YELP_LOCATION_QUERY_PARAMETER, location);
        String url = urlBuilder.build().toString();

        Request request= new Request.Builder()
                .url(url)
                .header("Authorization", Constants.YELP_TOKEN)
                .build();

        Call call = client.newCall(request);
        call.enqueue(callback);
    }
}
  • Here, we create a Call object and place our request in it.

  • Then, we use call.enqueue(callback); to execute it. We could use the call.execute() method to synchronously dispatch the request, but this would clog the main thread of our application. Instead, we use enqueue() to perform an asynchronous request.

  • The OkHttp enqueue() method will add our request to a queue. Since this is the only call in the queue of our app, it will run right away. OkHttp will create a new thread to dispatch our request. Once it has a readable response it will trigger our callback method, where it will send our response data.

(Don't worry too much about threading now. We'll look at thread and threading in detail in an upcoming lesson!)

Receiving a Response

Great! Now that our application includes code to construct and execute an API request, we need to ensure it can also handle the resulting response. Our YelpService class is a great place to separate our network call from our user interface code, but because we will be updating the UI with our response data, let's create our callback method inside of RestaurantsActivity. We'll call it getRestaurants():

RestaurantsActivity.java
    ...

        private void getRestaurants(String location) {
            final YelpService yelpService = new YelpService();
            yelpService.findRestaurants(location, new Callback() {

        });
    }

The first thing this method will do is create a new instance of our YelpService, and call the findRestaurants() method. As we know, findRestaurants() takes two arguments: The location we're searching for restaurants in, and a Callback. We create a new empty Callback to provide as the second argument.

Callback Methods

Our callback will have two methods to override: onFailure() and onResponse(). onFailure() is triggered when our request fails (if we create a bad URL, for example). onResponse() is triggered when the request is successful. Let's add onFailure() first:

RestaurantsActivity.java
...
import okhttp3.Call;
import okhttp3.Callback;
import java.io.IOException;

    ...

       private void getRestaurants(String location) {
            final YelpService yelpService = new YelpService();
            yelpService.findRestaurants(location, new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

        });
    }

If the API request executed by findRestaurants() fails for any reason, the onFailure() callback method will execute. It simply calls .printStackTrace(); to print details regarding the error to our output console.

Next, inside of onResponse() we will tell our app what to do with the data returned from Yelp. We'll eventually save this data into a model, but for now we'll log it to the logcat:

RestaurantsActivity.java
import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

public class RestaurantsActivity extends AppCompatActivity {
    public static final String TAG = RestaurantsActivity.class.getSimpleName();
    ...

 private void getRestaurants(String location) {
            final YelpService yelpService = new YelpService();
            yelpService.findRestaurants(location, new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    String jsonData = response.body().string();
                    Log.v(TAG, jsonData);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

Here, we create a new string, jsonData and set it to the string of the response body. Then, we print the data to the logcat. If we catch any exceptions, we display their error messages.

Next, let's call our new getRestaurants() method in the onCreate() method of RestaurantsActivity. Because RestaurantsActivity is only ever accessed after a user has submitted the form from MainActivity, we can safely assume that getRestaurants() should be called each time RestaurantsActivity is accessed:

RestaurantsActivity.java
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        getRestaurants(location);
    }
...

Now, we should be able to launch the application in the emulator, submit a zip code into the EditText on our MainActivity, and see data returned from the Yelp API in the logcat!


Example GitHub Repo for MyRestaurants