Annyce Davis

Davis Technology Consulting

  • Home
  • About Me
  • Blog
  • Courses
  • Newsletter

Android: Custom Gson Deserialization

October 28, 2014 by Annyce Davis

So here’s the scenario you have a JSON Object that contains a list of objects. Each one of the objects in the primary JSON Object also contain a list of objects. So for demonstration purposes we will use a car with a list of seats and each seat has a list of buckles (Car -> Seats -> Buckles). Completely random example, yes I know.

So if you have the use case where you don’t want to deserialize any seats that don’t have buckles this is how you can do it using a Custom GSON Deserializer.

First, this is a sample JSON response which contains the information for each car:

[{
    "description": "A nice car",
    "name": "Toyota",
    "seats": [{
        "buckles": [],
        "type": "front"
    }, {
        "buckles": ["latch", "snap"],
        "type": "front"
    }]
}]

What you will notice is that there is one Seat object that has an empty list of Buckle objects. So in that case you don’t want Gson to include that seat in the final list of seat objects.  We will use two advanced features of Gson: Exclusion Strategies and Custom Deserializer. 

Since we want to handle the “seats” array on our own we will create an Annotation that can be used by Gson to ignore the field during normal processing.

Car Class (portion):

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SkipThis {

}

@SkipThis
private List<SeatVO> seats;

Now we can proceed to exclude any field that has our custom SkipThis annotation by means of an ExclusionStrategy.

private static class SkipThisAnnotationExclusionStrategy
implements ExclusionStrategy {
    public boolean shouldSkipClass(Class clazz) {
        return clazz.getAnnotation(CarVO.SkipThis.class) != null;
    }
 
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(CarVO.SkipThis.class) != null;
    }
}

Finally, we can proceed to create the custom deserializer that will check for the existence of items in the “buckles” array.

@Override
public CarVO deserialize(JsonElement json, Type arg1,
                         JsonDeserializationContext arg2) throws JsonParseException {
  JsonObject carsJSON = json.getAsJsonObject();
  JsonArray seatsJSON = carsJSON.getAsJsonArray("seats");
 
  Gson g = new Gson();
  CarVO carVO = g.fromJson(json, CarVO.class);
  List<SeatVO> res = new ArrayList<SeatVO>();
 
  for (JsonElement elem : seatsJSON) {
    JsonArray bucklesJSON = elem.getAsJsonObject().getAsJsonArray("buckles");
    if (!bucklesJSON.isJsonNull() && bucklesJSON.size() > 0) {
      res.add(g.fromJson(elem, SeatVO.class));
    }
  }
 
  carVO.setSeats(res);
 
  return carVO;
}

The complete code:

Android: Location Updates with the FusedLocationApi

September 30, 2014 by Annyce Davis

If you want to retrieve the user’s location in your Android application you can take advantage of Google’s FusedLocationApi. Essentially you establish a connection to the GoogleApiClient and then set up a LocationRequest.  It’s up to you to configure the rate at which you would like to receive updates.  Here is an example of using the API:

import android.app.Activity;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MainActivity extends Activity implements
LocationListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {

private static final long ONE_MIN = 1000 * 60;
private static final long TWO_MIN = ONE_MIN * 2;
private static final long FIVE_MIN = ONE_MIN * 5;
private static final long POLLING_FREQ = 1000 * 30;
private static final long FASTEST_UPDATE_FREQ = 1000 * 5;
private static final float MIN_ACCURACY = 25.0f;
private static final float MIN_LAST_READ_ACCURACY = 500.0f;

private LocationRequest mLocationRequest;
private Location mBestReading;

private GoogleApiClient mGoogleApiClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (!servicesAvailable()) {
finish();
}

setContentView(R.layout.activity_main);

mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(POLLING_FREQ);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_FREQ);

mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}

@Override
protected void onResume() {
super.onResume();

if (mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
}

@Override
protected void onPause() {
super.onPause();

if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}

@Override
public void onLocationChanged(Location location) {
// Determine whether new location is better than current best
// estimate
if (null == mBestReading || location.getAccuracy() < mBestReading.getAccuracy()) {
mBestReading = location;

if (mBestReading.getAccuracy() < MIN_ACCURACY) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}
}

@Override
public void onConnected(Bundle dataBundle) {
// Get first reading. Get additional location updates if necessary
if (servicesAvailable()) {
// Get best last location measurement meeting criteria
mBestReading = bestLastKnownLocation(MIN_LAST_READ_ACCURACY, FIVE_MIN);

if (null == mBestReading
|| mBestReading.getAccuracy() > MIN_LAST_READ_ACCURACY
|| mBestReading.getTime() < System.currentTimeMillis() - TWO_MIN) {

LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);

// Schedule a runnable to unregister location listeners
Executors.newScheduledThreadPool(1).schedule(new Runnable() {

@Override
public void run() {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, MainActivity.this);
}

}, ONE_MIN, TimeUnit.MILLISECONDS);
}
}
}

@Override
public void onConnectionSuspended(int i) {

}

private Location bestLastKnownLocation(float minAccuracy, long minTime) {
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;

// Get the best most recent location currently available
Location mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);

if (mCurrentLocation != null) {
float accuracy = mCurrentLocation.getAccuracy();
long time = mCurrentLocation.getTime();

if (accuracy < bestAccuracy) {
bestResult = mCurrentLocation;
bestAccuracy = accuracy;
bestTime = time;
}
}

// Return best reading or null
if (bestAccuracy > minAccuracy || bestTime < minTime) {
return null;
}
else {
return bestResult;
}
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {

}

private boolean servicesAvailable() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

if (ConnectionResult.SUCCESS == resultCode) {
return true;
}
else {
GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0).show();
return false;
}
}
}

Android: Determining if a Wearable Device is Connected

July 29, 2014 by Annyce Davis

I’m playing around with developing an application that integrates with the new Android Wear API. In my case I don’t want to perform certain actions locally if a wearable device is connected. So this is how you can detect if a device is connected. You would have this code included in the Main Activity of your primary (phone) application:

Grails: Functional Testing with Geb and Spock

April 1, 2014 by Annyce Davis

Functional testing allows you to execute HTTP requests against your running application and verify if it’s performing the desired behavior.

Since Grails doesn’t ship with any direct support for functional testing (using Grails 2.2.4) I decided to use a common configuration of Geb with Spock for my testing needs. My configuration files are included below.  I had to modify BuildConfig.groovy and then include a separate configuration file for Geb.


BuildConfig.groovy

    repositories {
       mavenRepo "http://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-support"
    }

    def gebVersion = "0.9.2"
    def seleniumVersion = "2.35.0"

    dependencies {
        test "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
        test "org.spockframework:spock-grails-support:0.7-groovy-2.0"
        test "org.gebish:geb-spock:$gebVersion"
        test "org.seleniumhq.selenium:selenium-support:2.40.0"
    }

    plugins {
        test ":geb:$gebVersion"
        test (":spock:0.7") {
            exclude "spock-grails-support"
        }
    }

The tests and config files are stored in the test/functional folder of the main application. In the primary functional folder you can place your GebConfig.groovy file. There are various configuration options available to you. I prefer to use a very simple version, consisting of the following:

GebConfig.groovy

import org.openqa.selenium.chrome.ChromeDriver

reportsDir = "target/geb-reports"
driver = { new ChromeDriver() }

You will also need to install the ChromeDriver.  Info available here.

Once that’s done, you should define some Geb Pages that represent the corresponding gsp pages in your application. I typically define three key components: the url, the at closure, and the content closure. These elements will be used in your Spock Specification to navigate to the desired page in your application, verify that you are at the correct page and then interact with elements on the page that you define in the content closure.

Sample Page

import geb.Page

class LoginPage extends Page {

    static url = "login/auth"

    static at = { title == "Login" }

    static content = {
        loginForm { $("#loginForm") }
        loginButton { $("#loginButton") }
        registerLink { $("a[href*='register/index']") }
    }
}

Sample Spec

import geb.spock.GebReportingSpec
import spock.lang.Stepwise

@Stepwise
class LoginSpec extends GebReportingSpec {

    def "invalid login"() {
        given: "I am at the login page"
        to LoginPage

        when: "I am entering invalid password"
        loginForm.j_username = "me@gmail.com"
        loginForm.j_password = "ioguffwf"
        loginButton.click()

        then: "I am being redirected to the login page"
        at LoginPage
        !loginForm.j_username
        !loginForm.j_password
    }

    def "admin login"() {
        given : "I am at the login page"
        to LoginPage

        when: "I am entering valid username and password"
        loginForm.j_username = "me@gmail.com"
        loginForm.j_password = "me"
        loginButton.click()

        then: "I am being redirected to the homepage"
        at HomePage
    }
}

Spock specifications are very simple to read and should be written in such a way that it’s very clear what you are attempting to test and verify.  In this case, the LoginSpec, the specification has only two methods which are executed in order.  The first makes sure that when a user enters invalid login credentials they are redirected back to the log in page.  The second verifies that they are taken to the HomePage when the login succeeds.

In order to run your functional tests you can use the following commands:

grails test-app functional: -https
grails test-app -clean functional: LoginSpec -https

I add the -https flag because my application has support for https and I want to be able to test that functionality as well.

Happy testing!

« Previous Page
Next Page »

Follow Me

  • Bluesky

Categories

  • Android (61)
  • Career (5)
  • Communication (4)
  • Flutter (1)
  • Git (4)
  • Gradle (4)
  • Grails (23)
  • iOS (1)
  • Java (8)
  • JavaScript (6)
  • Kotlin (17)
  • Life (5)
  • Public Speaking (26)
  • Revenue (2)
  • RxJava (1)
  • Software Development (14)
  • Twitter (3)
  • Uncategorized (11)
  • Video Course (5)

Follow Me

  • Bluesky

Copyright © 2025 · All Rights Reserved · Log in