Model refactoring

Allow the database to auto-generate the primary key (id). On creating a residence in the remote database, echo back the new record to inform the client of the id value. We demonstrate using a Play service, a JUnit test app and the MyRent Android client.

Setup

Hint: when myrent service and test downloaded ensure you run play eclipsify in a terminal in the project root folders before importing to Eclipse.

Download these three applications.

myrent-16

MyRent-16 is an Android client. It has been refactored to consume an API from a database in which the primary key (id) is a Long type and is auto-generated at the server side.

This differs from previous MyRent version(s) where the id has been generated client-side.

The purpose of the lab is to assist in adapting your Twitter clone for use with MongoDB.

  • MongoDB auto-generates its ids.
  • These are String types.
  • We are using Long types because the server (myrent-16-service) is a Play app.
  • It will be necessary to modify your SQLite component to use String type ids. This is not dealt with in this lab.

myrent-16-service

This is a Play app. It ships with a data.yml file. If you wish to make use of this it will be necessary to remove the comments in Bootstrap.java.

Once downloaded change into its folder and run

play run

If you wish to view the code in Eclipse then first run:


play eclipsify

myrent-16-service-test

Download the app and import into Eclipse, first running play eclipsify.

Import the project into Eclipse.

Clean the project.

If there are any errors it may be necessary to import JUnit library.

  • Select the project and in context menu: Build Path | Add Libraries.
  • Select JUnit and proceed as prompted by wizard

Test as follows:

  • Run the service.
  • Select src/ResidenceTest.java, right click and run as JUnit Test.

The test should pass, indicated by a green progress bar.

In the following steps we shall identify the key changes made to the tester and the Android client to accommodate communicating with a database (where the id is auto-generated on the server).

Tester (myrent-16-service-test)

This is the new model class:

package app.models;

import java.util.Date;
import java.util.Random;

import com.google.common.base.Objects;

public class Residence
{

  public Long id;
  public String geolocation;
  public Long date;
  public boolean rented;
  public String tenant;
  public double zoom;
  public String photo;


  public Residence()
  {
    date = new Date().getTime();
    geolocation = "";
    date = 0L;
    rented = false;
    zoom = 0;
    photo = "";
  }

  @Override
  public boolean equals(final Object obj)  {
    if (obj instanceof Residence)  {
      final Residence other = (Residence) obj;
      return Objects.equal(id, other.id) 
          && Objects.equal(geolocation, other.geolocation)
          && Objects.equal(date, other.date)
          && Objects.equal(rented, other.rented)
          && Objects.equal(tenant, other.tenant)
          && Objects.equal(zoom, other.zoom)
          && Objects.equal(photo, other.photo);
    }
    return false;
  }
}

Note the following:

  • The id is declared but no longer explicitly initialized. Previously we generated a random long value and assigned it to the id.

  • We still use the id field in the equals method when comparing two Residence objects.

  • This implies that it is necessary to obtain the id value from the server before the method is invoked

In ResidenceTest.setup we initialize the residence id values with values obtained from the server.


    /**
     * Set up for test.
     * Write (create) an array of residences on server.
     * Server echoes back each individual residence as it is created.
     * The server model generates the residence id.
     * Obtain this from the reflected back residence and apply 
     * to ResidenceTest.residences fields.
     * 
     * @throws Exception
     */
  @Before
  public void setup() throws Exception {

    for (int i = 0; i < NUMBER_RESIDENCES; i += 1) {
      Residence res = service.createResidence(residences[i]);
      residences[i].id = res.id;
    }
  }

Android Client (myrent-16-service)

In ResidenceListFragment, before refactoring we have:

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.menu_item_new_residence:
        Residence residence = new Residence();
        portfolio.addResidence(residence);
        Intent i = new Intent(getActivity(), ResidencePagerActivity.class);
        i.putExtra(ResidenceFragment.EXTRA_RESIDENCE_ID, residence.id);
        startActivityForResult(i, 0);
        return true;

This method remains unchanged. We provide it here to supply context:

  public void createResidence(Residence res) {
    Call<Residence> call = app.residenceService.createResidence(res);
    call.enqueue(this);
  }

The default Residence constructor generates a residence id.

  • This is then passed to the ResidenceFragment as an extra in an intent.

We now require to obtain this id from the server. To achieve this we do the following:

  • Create a residence on the server.
  • The residence object will have a valid id. We explain below how this is achieved.
  • Echo back that residence.
  • Furthermore, this cannot be done on the UI thread. Hence, we move the code into the Retrofit response as follows:

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.menu_item_new_residence:
        Residence residence = new Residence();
        createResidence(residence);
        return true;
  @Override
  public void onResponse(Response<Residence> response, Retrofit retrofit) {
    Residence res = response.body();
    if (res != null) {
      Toast.makeText(getActivity(), "Residence created successfully", Toast.LENGTH_SHORT).show();

      portfolio.addResidence(res);
      Intent i = new Intent(getActivity(), ResidencePagerActivity.class);
      i.putExtra(ResidenceFragment.EXTRA_RESIDENCE_ID, res.id);
      startActivityForResult(i, 0);

    }
    else {
      Toast.makeText(getActivity(), "Residence null returned due to incorrectly configured client", Toast.LENGTH_SHORT).show();

    }
  }

This is what existed prior to refactoring:

  @Override
  public void onResponse(Response<Residence> response, Retrofit retrofit) {
    Residence res = response.body();
    if (res != null) {
      Toast.makeText(getActivity(), "Residence created successfully", Toast.LENGTH_SHORT).show();
    }
    else {
      Toast.makeText(getActivity(), "Residence null returned due to incorrectly configured client", Toast.LENGTH_SHORT).show();

    }
  }

Observe what we have done. How the block of code shown in Figure 1 has been moved from the UI to a worker thread:

Figure 1: Code block move from UI to worker thread

Test as follows:

  • Run the service.
  • Build and install MyRent-16 on an emulator (not a physical device).
  • Create a new residence.
  • Note the Toast message(s) to determine of successful.
  • Open a brower and navigate to : localhost:9000/api/residences and view the json-formatted residences list.
  • Use PostMan to look into the database.

Server-side id generation

This is a snippet from the myrent-service-2016 ResidenceAPI. It demonstrates how an id is generated and used to initialize the Residence object being written to the database. The residence object containing this newly generated id is then echoed back to the caller.

  /**
   * 
   * @param id
   * @param body
   */
  public static void createResidence(JsonElement body) {
    Residence residence = gson.fromJson(body.toString(), Residence.class);
    Residence res = new Residence();
    residence.id = res.id;
    residence.save();
    renderJSON(gson.toJson(residence));
  }