Insider's Guide to Udacity Android Developer Nanodegree Part 7 - Full Stack Android
Written by Nikos Vaggalis   
Monday, 23 April 2018
Article Index
Insider's Guide to Udacity Android Developer Nanodegree Part 7 - Full Stack Android
Adapting to Android's Needs
HTTP Requests
Combating Memory Leaks
GSON Problems
Filtering and Comparing Devices
Functional Programming in Java
Conclusions

Making the HTTP requests from Android

Making those requests from within the Android app required mapping them to equivalent Retrofit interfaces:

That is mapping

/smartphones/json

to


public interface IGetJSON { @GET("{kind}/json") Call<ResponseBody> getJSON(@Path("kind") String Kind); }

("kind" takes the value of "smartphone","smartwatch" or
"tablet")

/tablets1/smartphone/json to


public interface ISendJSON { @Headers("Accept: application/json") @FormUrlEncoded @POST("tablets1/{kind}/json") Call<Device> sendJSON(@Path("kind") String Kind, @Field("ValuesLookUp[]") List<Integer> valuesLookUp, @Field("ValuesG[]") List<String> valuesG, @Field("ValuesData[]") List<String> valuesData, @Field("offset") int offset, @Field("limit") int limit, @Field("SortById") String sortById, @Field("radioSort") String radioSort, @Field("CpuCores") int cpuCores, @Field("ModelId") int modelId, @Field("BrandName") String brandName, @Field("CPUId") int CPUId, @Field("CPUBrandAndType") String CPUBrandAndType, @Field("OSId") int osId, @Field("ScreenTypeId") int screenTypeId, ... ... ... }

and

/tabletdetails/json?id=858

to:


public interface IGetJSONtabletdetails { @GET("tabletdetails/json") Call<TabletDetail> getJSONtabletdetails(@Query("id")
int ModelId); }

Note the @Headers, @FormUrlEncoded and @POST annotations which get translated to their respective HTTP headers and actions.

The actual Retrofit call implementing the ISendJSON Interface takes place inside method sendValuesSync()

 
public Device sendValuesSync() throws IOException { Device temp = null; ISendJSON iSendJSON = RetrofitBuilder.send(); Call<Device> values = iSendJSON.sendJSON(deviceLabel, menuValuesSingleton.getValuesLookUp(), menuValuesSingleton.getValuesG(), menuValuesSingleton.getValuesData(), menuValuesSingleton.getOffset(), menuValuesSingleton.getLimit(), menuValuesSingleton.getSortById(), menuValuesSingleton.getRadioSort(), menuValuesSingleton.getCpuCores(), menuValuesSingleton.getModelId(), menuValuesSingleton.getBrandName(), menuValuesSingleton.getCPUId(), menuValuesSingleton.getCPUBrandAndType(), menuValuesSingleton.getOsId(), menuValuesSingleton.getScreenTypeId(), ... ... ... try { temp = values.execute().body(); } catch (IOException e) { e.printStackTrace(); throw e; } catch (Exception e) { e.printStackTrace(); throw e; } return temp; } }

Two takeaways.First, "sendValuesSync()".Does it mean that the Retrofit network calls are launched synchronously? Second, the "menuValuesSingleton" object.What's it for and why Singleton? First, let's take a look at "sendValuesSync()".

The way a user interacts with the app in filtering devices by specifying criteria goes like:

1.Inside the Filter screen the user tweaks the UI.For example he sets the Screen Size slider's search range to 3"-5.5".

2.User presses the button marked with a "check" at the bottom of the Filter screen to commence the search.This action will end the Filter screen and bring back the Listing screen which is now filled with the list of devices matching the search.

3.To revise the search the user has to jump to the Filter screen again, which remembers the last performed search so that the new search can start from that point over with any extra criteria added up.This means that tweaking the FCamera slider initiates a new search which uses both the previously set Screen Size's ranges AND the new FCamera ones.

 

menuValuesSingleton passed forth and back

1.Listing screen -->to Filter screen.177 devices

 

 

 

 2.Pressing the "check" button goes back to Listing screen.166 devices.

 

 3.Then to Filter screen again amending the search

 

 

4.Pressing the "check" button goes back to Listing screen.27 devices.

 

So in order to maintain the state between the Listing and the Filter screens I needed an object which could be passed back and forth but also be able to keep track of its state.Therefore the singleton class RetrofitSendJSON was born together with its inner associated class MenuValues, that would span the life of the application.


public static class RetrofitSendJSON { static RetrofitSendJSON _instance; public MenuValues menuValuesSingleton; private RetrofitSendJSON() { menuValuesSingleton = new MenuValues(); } static public RetrofitSendJSON getInstance() { if (_instance == null) _instance = new RetrofitSendJSON(); return _instance; } public MenuValues clearMenuValues() { return menuValuesSingleton = new MenuValues(); } public MenuValues getMenuValues() { return menuValuesSingleton; } public void setMenuValues(MenuValues in) { menuValuesSingleton = in; } @Parcel public static class MenuValues { public List<Integer> valuesLookUp; public List<String> valuesG; public List<String> valuesData; public int offset = 0; public int limit = 9; public String sortById = "ModelName"; public String radioSort = "asc"; public int cpuCores = -1; public int modelId = -1; public String brandName = ""; public int CPUId = -1; public String CPUBrandAndType = ""; public int osId = -1; public int screenTypeId = -1; ..... } }

In order for the MenuValues' instance menuValuesSingleton to be able to be passed around, it first had to be Serialized, or better said, Parcelized. Instead of going through that manually, I used the marvellous Parceler library which just requires annotating the class with @Parcel.Before passing on the object you wrap it with Parcels.wrap, while when you receive it you unwrap it with Parcels.unwrap. One caveat though .Initially I had my MenuValues's fields defined as private:

 

     
@Parcel public static class MenuValues { private List<Integer> valuesLookUp; private List<String> valuesG; private List<String> valuesData; private int offset = 0; private int limit = 9; ..... }

but upon building the project I was confronted with:

F:\Udacity\smadeseek\app\src\main\java\
nvglabs\android\com\smartdeviceseeker\pojo\
Favourite.java:12:
warning: Parceler: Reflection is required to
access private field: String modelName,
consider using non-private. private String modelName; ^ F:\Udacity\smadeseek\app\src\main\java\
nvglabs\android\com\smartdeviceseeker\pojo\
Favourite.java:11:
warning: Parceler: Reflection is required to
access private field: int modelId,
consider using non-private. private int modelId;

So in order to avoid taking that performance hit, I was forced to turn them into public. As far as calling Retrofit synchronously, I wrapped that call inside a Loader something that bore distinct advantages.As well known, a Loader is ideal for performing asynchronous operations independently of the lifecycle of the Activity.In case the Activity/Fragment gets destroyed due to a configuration change taking place like the screen rotating, the Loader would continue retrieving the data from the web, reattaching itself to the Activity upon it being recreated in order to make the data available to it.No memory leaks, no blocking the UI thread.

The same however does not stand true when initiating a network call through a simple AsyncTask or even an asynchronous Retrofit call.Thereby by wrapping the Retrofit call inside the Loader I've used the Loader's benefits to my advantage.

 @Override
            public Pair<Device,String> loadInBackground() {
                try {
                    if (RetrofitSendJSONobj.getMenuValues().
                        getJsonWithMenuData()==null) {
                            RetrofitSendJSONobj.
                               getMenuValuesJSONSync();
                    }
                    Device device = RetrofitSendJSONobj.
                                           sendValuesSync();

Of course I could had fired the Retrofit request within the Loader itself in an asynchronous fashion but that would be an asynchronous task, the Loader, firing another asynchronous task, the Retrofit, putting them out of sync.What I wanted was for the Loader to wait for the Retrofilt call to complete in order to fetch the data.Alternatively, I could had fired a callback upon Retrofit's completion, but doing so inside the Loader really didn't make much sense.

tip: Always use the Support Library versions (android.support.v4.app.*) for both Loaders and Fragments as the Framework's ones (android.app.*) have been officially deprecated in Android P.

 

 



Last Updated ( Monday, 23 April 2018 )