Skip to content

Integrating a New Endpoint

Tyler Wong edited this page May 14, 2017 · 2 revisions

Here you'll find the steps necessary to add a new endpoint to the application and start using data from it in the application.

Adding the Method Declaration to the API Interface

This application uses a library called Retrofit. Retrofit allows us to declare REST API endpoints as Java method declarations, making it very easy to interact with the API from Java code. Retrofit uses another library called OkHttp to make the actual network requests between the client and the server.

In order to add a new endpoint to our application using Retrofit, follow these steps:

  1. Navigate to /app/java/com/snaptiongame/app/data/api/SnaptionApi.java.
  2. SnaptionApi.java is an interface that describes the endpoints that are exposed by our server. Here is the format for a basic method declaration using Retrofit:
@GET("/User/Stats/{userId}/")
Single<UserStats> getUserStats(@Path("userId") int userId);

A few things to note here:

  • Looking at the first line, in order to specify what kind of request you want to make, you use a GET, POST, PUT, etc. annotation. Inside the annotation, you usually specify the path of the endpoint you want to hit (In this case we're hitting the /User/Stats/ endpoint with a GET request and a specified userId).
  • Another thing to note is the Single<UserStats> return type. The Single return type is an object from the RxJava2 library. It will allow us to observe the network request to know when it has completed and given us back a result (more info on this later).
  • Finally, the @Path("userId") annotation will specify that the parameter we pass in, needs to be put in the @GET("/User/Stats/{userId}/") path.

The great thing about Retrofit is that it does all of the network I/O for us! So we don't have to worry about it. We can just write the method declaration and call it later.

  1. Another great thing about using Retrofit is that it uses a JSON parser called Gson behind the scenes to deliver data to use through model objects we write. Following along in the example, we're going to want to create a new model class called UserStats.java that will hold the necessary fields that we expect from the endpoint we are hitting (See /app/java/com/snaptiongame/app/data/models for examples). Make sure to provide constants for the field names that match what the API will send us.

  2. The next step is providing a way to parse the JSON that comes down from the endpoint. There actually is a way for Retrofit to auto-map the data from the endpoint to fields in a model object, but I haven't had that great of a success rate with that (especially with complex responses). Luckily, there is a way for us to manually declare a way to parse our JSON into model objects!

Here are the steps you'll want to take to do so (See /app/java/com/snaptiongame/app/data/converters for examples):

  • Create a new file in /app/java/com/snaptiongame/app/data/converters called UserStatsConverter.java.
  • Make the class implement JsonDeserializer<UserStats> and override the necessary methods (shortcut is ⌘ + N + O + enter). The only method you'll have to override is called deserialize.
  • The method declaration you should have should look like this:
public UserStats deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException

The variable called json is the data we receive from the server, and that's all you really need to care about! All you need to do now is extract the data from the json variable (make sure to call getAsJsonObject on it to convert it to a JsonObject), and initialize a new instance of a UserStats object, and return it!

  • Next we need to specify a way for Retrofit to know that we're using a custom converter to deserialize our objects. Head over to /app/java/com/snaptiongame/app/data/providers/api/ApiProvider.java and look at the setupGson() method. You'll notice that we had to register converters for all of our other model classes and converters. Go ahead and do the same for your new converter and model class.
  1. Once you've declared your new method, model object, and converter, you're going to want to create another method in a corresponding "provider" class (See existing files under /app/java/com/snaptiongame/app/data/providers for examples). For this example, we'll take a look at UserProvider.java. All you need to do is a create a new public static method with the same name as the method you declared in SnaptionApi.java that takes in the same parameters and has the same return type. Simply return a call to the method you declared on the apiService object. This is really just best practice to separate the API from the business logic.

That's it! You're pretty much done adding in all the necessary things to hit a new endpoint on the server. In the next section we'll go over best practices to call and get results displayed to the view.

Model-View-Presenter (MVP)

For the most part, our application follows a model-view-presenter design pattern. This makes things easier to test (when we do get around to testing...), and provides a good separation of view and business logic. Luckily, we've already written all of the "model" logic, so this section will primarily focus on the "view" and "presenter" modules.

Before starting, I recommend taking a look at the wall package (Under /app/java/com/snaptiongame/app/presentation/wall). The three relevant files are called WallFragment.java, WallPresenter.java, and WallContract.java. Here's a breakdown as to what each one is for:

  • WallFragment.java - This is where all of the view code will go. Anything that has to do with changing how the view looks will go in this class.
  • WallPresenter.java - This class holds all of the business logic and acts as the middleman between the view and the model. This is where any network requests will be made from.
  • WallContract.java - This class contains interfaces for the view and presenter. It outlines which methods can be called from what class. The view will only be able to call methods that are outlined here in the Presenter interface, and the presenter will only be able to call methods that are outlined here in the View interface.

Here is a breakdown of how games are currently loaded on the wall:

  1. The user opens the application and the WallFragment is loaded. When the WallFragment is loaded, an instance of a WallPresenter is created, and mPresenter.subscribe() is called. This call will then call loadGames() in the presenter, which will fire off a network request to get a list of games. Here is what the network request looks like:
Disposable disposable = gameRequest
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        games -> {
            mWallView.showGames(games);
            Timber.i("Getting Snaptions completed successfully");
            mWallView.setRefreshing(false);
        },
        e -> {
            Timber.e(e);
            mWallView.showDisconnectedView();
            mWallView.setRefreshing(false);
        }
    );
mDisposables.add(disposable);

A few things to note about the network request:

  • This is using a library called RxJava (linked above). gameRequest is an Observable that allows us to observe the results of the network request. (This can also be a Single, Completable, or Flowable; see the RxJava documentation for more info)
  • observeOn(AndroidSchedulers.mainThread()) means that the results of the network request will be delivered to the Android main thread (the UI thread)
  • .subscribe(...) is where we handle the results and give them to the view.
  • The network request by default will be done on a separate thread so that our app won't lag

Because we have a reference to the View interface in our presenter class, we can call methods outlined by the contract class's View interface that can handle the results from the network request and display it to the user.

That concludes the basic rundown of how to add a new endpoint to our application! Feel free to ask any questions in #android-dev or to any of us individually. Definitely don't hesitate to ask because some things in here might have been confusing/worded poorly. Thanks for reading!