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

Filtering the Devices based on a range of specifications

The action of customizing the search takes place inside the Filter screen.The options that you can filter by are :

  • Maker Brand , such as Xiaomi,Samsung,ZTE (Dropdown/Spinner)
  • Operating System, such as Android 4.4.0,Android 6 (Dropdown/Spinner)
  • CPU Brand such as Qualcomm,Mediatek,Intel (Dropdown/Spinner)
  • Screen Type, such as LCD,IPS (Dropdown/Spinner)
  • Screen Resolution such as 1280x720,1920x1080 (Dropdown/Spinner)
  • Screen Size ",(Slider)
  • FCamera mpx, (Slider)
  • RCamera mpx, (Slider)
  • RAM Gb, (Slider)
  • ROM Gb, (Slider)
  • Features such as Gyroscope,Proximity,Accelometer (Checkboxes)

To keep it light on the mobile side, most of the criteria available through the WebSite were left out, with only a few crucial ones ending up in the mobile application.However there were still limitations due to the HTML/webforms' handling of the Primary Keys of the underlying database tables.


Database diagram

In data modelling one is not forced to keep a unique surrogate (artificial) PK the likes of a single column integer.One can very well have a single column natural key (a key that has business meaning) or even a composite (multicolumn) natural key. For example, an entry in the CPU table can be uniquely identified by the union of CPUType and CPUModel, a composite PK comprising of these two.







HELIO X20 10 2GHz
HELIO X10 10 1GHz
MT 6223
MT 6582
MT 6735
Snapdragon 200
Snapdragon 400
Snapdragon 415

CPU table


But how can you model this composite key relation in a HTML form dropdown menu when there's a slot for just a single value? That is, the dropdown menu expects :

<option value="Key"> Value </option>

or interpolated:

<option value="1"> Model : HELIOS X20 </option>

thus the composite key doesn't fit the picture:

<option value="HELIO,X20"> Model : HELIOS X20 </option>

So to accommodate to the single value PK requirement, I've built an autoincremented Integer surrogate key, CPUId as in

1 HELIO X20 10 2GHz
2 HELIO X10 10 1GHz
3 MT 6223
4 MT 6582
5 MT 6735
6 Snapdragon 200



Amended CPU table





The template renders that data according to :


<span style="display:block"> CPU Brands/Models </span> <select name="CPU" id="PerformanceCpuBrandChild"
class="childs" > <optgroup label="" class="optgroup"> <option value="-1">All</option> </optgroup> [% FOREACH cpukey IN cpuhash.keys.sort %] <optgroup label="[% cpukey %]" class="optgroup"> <option selected="selected" value="0">
All [% cpukey %] models</option> [% FOREACH cpuinnerhash IN cpuhash.$cpukey %] <option value="[% cpuinnerhash.item('CPUId') %]">
Model: [% cpuinnerhash.item('CPUType') %]
[% cpuinnerhash.item('CPUModel') %] </option> [% END %] </optgroup> [% END %] </select>

therefore the menu's entries would look like :

<option value="1"> Model : HELIOS X20 </option>
<option value="2"> Model : HELIOS X10 </option>
<option value="3"> Model : MT 6223 </option>

However, a single column PK can't shield from duplicate values creeping in as the following case demonstrates:


CPUId CPUType CpuModel


So aside from already possessing a PK in CPUId, I've also built a Unique Index comprising of CPUType and CPUModel to disallow these kind of attempts.(Violation of Unique Index when trying to insert HELIO,X20 a second time)


CPUType, CPUModel Unique Index

The final result :


CPU table's data


Mapping the PK's to Android Spinners

Since the website was built with this model in mind, the android app was once more hostage to the WebSite's intricacies.To map this PK relation to the equivalent Android Spinners, their underlying morphology had to change.So despite that the Spinners present text to the user, the behind the scenes actions are all tied to the underlying hidden integer PKs.So for example while the "Operating System" spinner displays "Android 4.4" , or "Android 6.0" these values actually get mapped to PKs 17 and 149 respectively.


Operating System spinner


Therefore the Spinner's Adapter had to be turned into a collection of HashMaps, with each HashMap entry (spinnerMap) filled as :

HashMap<Integer, String> spinnerMap = 
new HashMap<Integer,String>(); spinnerMap.put(object2.getInt("OSId"),

that is

HashMap<17, "Android 4.4">
HashMap<149, "Android 6.0">

so when the user selects "Android 6.0" to retrieve the list of devices that support this kind of OS version, what is actually POSTed is number 149 as in "OSId=149".


Comparing the devices

The user can also add devices to a to-be-compared list by going to the device's detailed specifications screen, the Specs (PageFragment) and press the "Scale" icon from the expanding FAB


something that invokes a call to the underlying Content Provider in order to add a row to the COMPARABLES table


// final String CREATE_TABLE2 = "CREATE TABLE " + Contract.Comparables.TABLE_NAME + " (" + Contract.Comparables._ID + " INTEGER PRIMARY KEY, " + Contract.Comparables.COLUMN_MODELID +
" INTEGER UNIQUE NOT NULL, " + Contract.Comparables.COLUMN_NAME + " TEXT NOT NULL, " + Contract.Comparables.COLUMN_PICTURE +
" BLOB NULL " + ");"; db.execSQL(CREATE_TABLE2);

  • Contract.Comparables._ID stores an auto-incremented PK, which in this case is really unnecessary as COLUMN_MODELID could easily play this role too, but generating a content provider generates column _ID by default too, so I just played along.
  • Contract.Comparables.COLUMN_MODELID stores the model's id, ie 149
  • Contract.Comparables.COLUMN_NAMEstores the model's name.
  • Contract.Comparables.COLUMN_PICTURE stores one of the model's promo pictures.

You can even compare phones to tablets or watches by adding them to this list which list can hold up to 4 devices, a condition checked against upon every time an insertion is attempted:


to-be-compared list


// case COMPARABLES_ID: long count = DatabaseUtils.queryNumEntries(db, Contract.Comparables.TABLE_NAME); if (count > 3) { throw new SQLException(( getContext().getResources().getString(
R.string.error_no_more_than_four)),new NoMoreComparablesException()); }

Note the custom exception NoMoreComparablesException which extends SQLException.It was introduced so that a different course of action can be taken when this condition is met.In this case NoMoreComparablesException signifies that the insertion attempt should be disallowed, also letting the user know by displaying a Toast carrying the message "Can't have more than 4 devices in the Comparables list!".

Transferring the inner exception convention from C#, NoMoreComparablesException got wrapped inside a SQLException so that when catching the generic SQLException and noticing that it's inner exception part is filled, we can be rest assured that we're in fact dealing with a NoMoreComparablesException rather then a general abnormal operation denoted by SQLException.

If all goes well and there's no more than 4 devices on the list, clicking on the green "Compare" button stacks the devices' specs side by side by utilizing the layout "fragment_page_comparables_detail" as their base View


"fragment_page_comparables_detail" layout


This View is repeated for each device on the list, so say for 3 devices on the list, 3 such views are placed adjacent to each other, each one filled with its assigned device's specs.

"fragment_page_comparables_detail" layout repeated for each device


"fragment_page_comparables_detail" layout repeated for each device, scrolling down to check out the Hardware specification category


The processes of filling the layouts as well as placing them one after the other is being dynamically performed:

// Lock lock1 = new ReentrantLock(); devicesArray.forEach(x - > { call = iGetJSONtabletdetails.getJSONtabletdetails(x); call.enqueue(callbackTabletDetail); }); Callback < TabletDetail > callbackTabletDetail = new Callback < TabletDetail > () { @Override public void onResponse(Call < TabletDetail > call, Response < TabletDetail > response) { lock1.lock() TabletDetail tabletDetailObj = response.body(); CoordinatorLayout child = (CoordinatorLayout) inflater.inflate(
R.layout.fragment_page_comparables_detail, null); child.setId(i); coordinators.add(child); if (rootView.getTag() != null && rootView.getTag().equals("phone-land")) { child.setLayoutParams(layoutParamsPhoneLand); } else { child.setLayoutParams(layoutParamsPhonePort); } EventBus.getDefault().post(
new ComparablesFragmentDetailEvent(child, Parcels.wrap(tabletDetailObj))); i = i + 1; lock1.unlock(); }

devicesArray contains the model ids (i.e 19,134,829) of the devices on the to-be-compared list, which sequentially forwards one by one to callbackTabletDetail in order to make the network call to retrieve the specs of the device.

The specs are then stored in tabletDetailObj.This action takes place in the onResponse callback.In detail, what's really happening is that for each device, an instance of the "fragment_page_comparables_detail" layout is created and pinned onto the main layout according to layoutParamsPhonePort or layoutParamsPhoneLand.

After that an EventBus event is fired to notify another part of the code to proceed with filling the newly instantiated layout with the specs of the device stored inside tabletDetailObj.

This sequence, although done in asynchronous fashion, of adding and filling layouts one after the other as each device comes into play, should have been the bread and butter of 'promises' but Retrofit makes them available in its RxJava version.But even if they were available, most probably they wouldn't be a good fit for this case's intricacies.So in order to keep sequence in this string of asynchronous operations, a ReentrantLock was appointed as the traffic controller.

Last Updated ( Monday, 23 April 2018 )