Insider's Guide To Udacity Android Developer Nanodegree Part 3 - Making the Baking App
Written by Nikos Vaggalis   
Monday, 03 July 2017
Article Index
Insider's Guide To Udacity Android Developer Nanodegree Part 3 - Making the Baking App
Step 1 Fragments
Step 2 - Libraries & Networking
Step 3 - Adding Exoplayer
Step 4 - Widgets
Step 5 - The Widget Provider
Step 6 - UI Testing
Step 7 - Testing Intents

Step 2 - Libraries and networking

Of course before we even get to the fragments we have to somehow acquire the recipe data on which the fragments are going to work. For this, I'm going to handle networking with the excellent Retrofit library, something that would also fulfill the 'App should display recipes from provided network resource' and 'Application sensibly utilizes a third-party library to enhance the app's features.' requirements.

Retrofit needs an interface defined, let's call it IRecipe, that represents the underlying HTTP transaction.The interface's definition should include a @GET annotation, the required endpoint URL baking.json, the name of a getter method, getRecipe(), and the acquired data returned inside a Call object, as in Call<ArrayList<Recipe>>

To make the actual network request we have to use Retrofit.Builder, passing it the base URL: 

https://d17h27t6h515a5.cloudfront.net/topher/
                                              2017/May/59121517_baking/


Builder implements the IRecipe interface and converts the received data from JSON to a Java object (POJO) with

addConverterFactory(
           GsonConverterFactory.create(gson))

Note that the complete URL that Builder is working on is the concatenation of the base URL with its endpoint 'baking.json', that is:

https://d17h27t6h515a5.cloudfront.net/topher/
                        2017/May/59121517_baking/baking.json


The snippet that makes the actual networking call lies inside RecipeFragment's code, and Retrofit's enqueue method


 /****
RecipeFragment
 ****/

   IRecipe iRecipe = RetrofitBuilder.Retrieve();
   Call<ArrayList<Recipe>> recipe = iRecipe.getRecipe();

   recipe.enqueue(new Callback<ArrayList<Recipe>>() {
            @Override
            public void onResponse(Call<ArrayList<Recipe>> call,
                    Response<ArrayList<Recipe>> response) {

                Integer statusCode = response.code();
                Log.v("status code: ", statusCode.toString());

                ArrayList<Recipe> recipes = response.body();

                Bundle recipesBundle = new Bundle();
                recipesBundle.putParcelableArrayList(
                                                        ALL_RECIPES, recipes);
                recipesAdapter.setRecipeData(recipes,getContext());
            }

            @Override
            public void onFailure(Call<ArrayList<Recipe>> call,
                                                                          Throwable t) {
                Log.v("http fail: ", t.getMessage());
            }
        });
 


This retrieves and stores the list of recipes in an ArrayList<Recipe> after de-serializing from JSON.

  
Retrofit's enqueue is run asynchronously and, when complete, its callback method onResponse is marshaled onto the main UI thread so that you get the chance to update the UI with the received data.The thing to note here, is that you must set the RecyclerView Adapter, recipesAdapter, to the list of the received Recipe objects within the scope of onResponse because otherwise, since the UI has already completed running its part of the code while enqueue is working, Recipe data won't yet be ready and you end up with an empty adapter without any items!


 public void onResponse(Call<ArrayList<Recipe>> call,
                       Response<ArrayList<Recipe>> response) {
 ....
    recipesAdapter.setRecipeData(recipes,getContext());
 }
 


The other point to note is that the Gson converter should be provided with a model of our Recipe object in order to map the deserialzed JSON object to the corresponding Java object. Furthermore we need this POJO to be also convertible to a Parcelable object so that we can pass it back and forth between our activities and fragments.

  
 Bundle recipesBundle = new Bundle();
 recipesBundle.putParcelableArrayList(ALL_RECIPES, recipes);

   
So that makes two steps of converting, from JSON to a POJO and from a POJO to Parcelable. The next question was 'do I do the mapping manually?', time consuming and potentially error prone, or 'do I use a well tested and automated way' in order to be effective and productive? The answer was pretty easy to figure, the latter of course!

image14

Thanks to www.jsonschema2pojo.org, I could just paste a sample of the JSON structure into the webform, tweak one or two options remembering to deselect 'Allow additional properties' since that option generates a hash map which cannot be converted to Parcelable:

 
private Map<String, Object> additionalProperties =
                                               new HashMap<String, Object>();


and let jsonschema2pojo perform its magic.

image15
jsonschema2pojo correctly identifies that the JSON schema submitted in fact contains three different object joined together by Composition.

Recipe with Ingredient and Recipe with Step


/****
Recipe POJO
****/
package com.example.android.android_me.retrofit;

import java.util.List;

public class Recipe {

    private Integer id;
    private String name;
    private List<Ingredient> ingredients = null;
    private List<Step> steps = null;
    private Integer servings;
    private String image;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Ingredient> getIngredients() {
        return ingredients;
    }

    public void setIngredients(List<Ingredient> ingredients) {
        this.ingredients = ingredients;
    }

    public List<Step> getSteps() {
        return steps;
    }

    public void setSteps(List<Step> steps) {
        this.steps = steps;
    }

    public Integer getServings() {
        return servings;
    }

    public void setServings(Integer servings) {
        this.servings = servings;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

}


You can see that Recipe keeps references to a List of Ingredients and a List of Steps

public List<Ingredient> getIngredients() {
        return ingredients;
    }
   
   public List<Step> getSteps() {
        return steps;
    }


So three Java classes with the appropriate names end up being produced.

 



Last Updated ( Monday, 20 November 2017 )