Persistence Framework Details - Android

The Android implementation of the updated persistence framework is described in this section.

AbstractServiceWeb

AbstractServiceWeb.java

  1. package com.crowleyworks.futilefishing.service;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.DataInputStream;
  5. import java.io.DataOutputStream;
  6. import java.io.InputStream;
  7. import java.io.InputStreamReader;
  8. import java.net.HttpURLConnection;
  9. import java.net.URL;
  10.  
  11. import android.os.Handler;
  12.  
  13. public abstract class AbstractServiceWeb<T> implements Runnable {
  14.  
  15. public static final String trustedHost = "your_server_name_here";
  16. public static final String BASE_URL = "https://" + trustedHost + "/additional/server/path/here/";
  17.  
  18. // The various actions supported by this broker
  19. public static final int NONE = 0;
  20. public static final int LIST = 1;
  21. public static final int READ = 2;
  22. public static final int CREATE_JSON = 3;
  23. public static final int CREATE_MULTIPART = 4;
  24. public static final int DELETE = 5;
  25. public static final int UPDATE = 6;
  26.  
  27. protected int action;
  28. public void setAction(int action) { this.action = action; }
  29.  
  30. // All subclasses must implement these methods
  31. protected abstract String getContentURL();
  32. protected abstract void parseContent(String rawJSON);
  33.  
  34. private Handler handler = new Handler();
  35.  
  36. /*
  37. * All data is loaded asynchronously, so that the UI remains responsive
  38. * @see java.lang.Runnable#run()
  39. */
  40. @Override
  41. public void run() {
  42. // Multipart data is posted to the server; all other action types use loadContentFromServer()
  43. final String s = (action == CREATE_MULTIPART) ? postContent() : loadContentFromServer();
  44.  
  45. handler.post(new Runnable() {
  46.  
  47. @Override
  48. public void run() {
  49. // NOTE: parseContents() is now responsible for notifiying the delegate
  50. parseContent(s);
  51. }
  52.  
  53. });
  54. }
  55.  
  56.  
  57. /*
  58. * Default function for getting the body of a multipart post. (Subclasses should override this.)
  59. */
  60. protected void getBody(DataOutputStream dos, String curBoundary) throws Exception {
  61. }
  62.  
  63. /*
  64. * Generic code for posting multipart content to a server
  65. */
  66. protected String postContent() {
  67. String boundary = "0xToDaYiSaFuNdAyOhSaYcAnUcEe";
  68.  
  69. try {
  70. URL url = new URL(getContentURL());
  71. HttpURLConnection c = (HttpURLConnection) url.openConnection();
  72. c.setDoInput(true);
  73. c.setDoOutput(true);
  74. c.setUseCaches(false);
  75. c.setRequestMethod("POST");
  76. c.setRequestProperty("Connection", "Keep-Alive");
  77. c.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);
  78.  
  79. DataOutputStream dos = new DataOutputStream(c.getOutputStream() );
  80. dos.writeBytes("--" + boundary + "\r\n");
  81. getBody(dos, boundary);
  82. dos.writeBytes("--" + boundary + "--\r\n");
  83. dos.flush();
  84. dos.close();
  85.  
  86. // Read the response
  87. DataInputStream dis = new DataInputStream(c.getInputStream());
  88. String curLine = dis.readUTF();
  89. while (curLine != null) {
  90. sb.append(curLine);
  91. curLine = dis.readUTF();
  92. }
  93. return sb.toString();
  94. } catch (Exception e) {
  95. e.printStackTrace();
  96. }
  97.  
  98. return "complete";
  99. }
  100.  
  101. /*
  102. * Generic code for loading the contents of a URL into a string. (This should be done in a non-UI thread)
  103. */
  104. protected String loadContentFromServer() {
  105. try {
  106. String sUrl = getContentURL();
  107. System.err.println("Content url: " + sUrl);
  108. URL url = new URL(sUrl);
  109. HttpURLConnection c = (HttpURLConnection) url.openConnection();
  110. c.setReadTimeout(10000);
  111. c.setConnectTimeout(10000);
  112. HttpURLConnection.setFollowRedirects(true);
  113. // get the input stream and create a bitmap from it
  114. InputStream is = c.getInputStream();
  115. // ------------------------
  116. BufferedReader buf = new BufferedReader(new InputStreamReader(is,"UTF-8"));
  117. StringBuilder sb = new StringBuilder();
  118. String curString;
  119.  
  120. while(true) {
  121. curString = buf.readLine();
  122. if ((curString == null) || (curString.length() == 0)) {
  123. break;
  124. }
  125. sb.append(curString);
  126. }
  127. buf.close();
  128. is.close();
  129. return sb.toString();
  130. } catch (Exception e) {
  131. e.printStackTrace();
  132. }
  133. return null;
  134. }
  135.  
  136. }

Comments:

  • Line 13: Note that this class is declared as abstract. Also, it uses generics to support different types of concrete implementations.
  • Lines 15 - 25: Some constants and basic information used to identify the server.
  • Lines 31 - 32: Two abstract methods. Concrete classes must provide the specifics as to the URL endpoint and the means by which business objects are parsed out of the response.
  • Lines 36 - 54: All data retrieval from the web server is done asynchronously. This method retrieves data in a separate thread, and then notifies the delegate in the UI thread.
  • Lines 57 - 61: Since not all classes will post multipart data, a default implementation is provided.
  • Lines 63 - 100: Post multipart data to the server. This code is very similar to the original ContentBroker code.
  • Lines 102 - 135: Standard boilerplate code for loading JSON data from the server.

LocationService

LocationService.java

  1. package com.crowleyworks.futilefishing.service;
  2.  
  3. import com.crowleyworks.futilefishing.model.FishingSpot;
  4.  
  5. public interface LocationService {
  6.  
  7. public void create(FishingSpot l) throws ServiceException;
  8. public void update(FishingSpot l) throws ServiceException;
  9. public void delete(FishingSpot l) throws ServiceException;
  10. public void get(long id) throws ServiceException;
  11. public void getMany(String criteria) throws ServiceException;
  12.  
  13. public void setDelegate(ContentBrokerDelegate<FishingSpot> delegate);
  14.  
  15. }

Comments:
This interface is very similar to the other service interfaces, such as CatchService, and FishService. Each interface focuses on a specific business object, and exposes basic CRUD operations to the application. The only other item of note is the setDelegate() method: This provides a callback object that will be notified when the various operations complete.

LocationServiceWeb

LocationServiceWeb.java

  1. package com.crowleyworks.futilefishing.service;
  2.  
  3. import java.io.UnsupportedEncodingException;
  4. import java.net.URLEncoder;
  5. import java.util.ArrayList;
  6.  
  7. import org.json.JSONArray;
  8. import org.json.JSONException;
  9. import org.json.JSONObject;
  10.  
  11. import android.util.Log;
  12.  
  13. import com.crowleyworks.futilefishing.Main;
  14. import com.crowleyworks.futilefishing.model.FishingSpot;
  15. import com.crowleyworks.futilefishing.model.User;
  16. import com.crowleyworks.futilefishing.util.AsyncManager;
  17.  
  18. public class LocationServiceWeb extends AbstractServiceWeb<FishingSpot> implements LocationService, Runnable {
  19.  
  20. private String contentURL = "";
  21. private FishingSpot item;
  22.  
  23. @Override
  24. public void create(FishingSpot l) throws ServiceException {
  25. this.action = CREATE_JSON;
  26. item = l;
  27. String uName = "";
  28. String uComments = "";
  29. try {
  30. uName = URLEncoder.encode(item.getName(), "UTF-8");
  31. uComments = URLEncoder.encode(item.getComments(), "UTF-8");
  32. throw new ServiceException("Encoding not supported");
  33. }
  34. contentURL = BASE_URL + "locations.php?action=create&token=" + Main.getToken() + "&userid=" + Main.getUserId()
  35. + "&id=0&name=" + uName + "&author=" + Main.getUserId() + "&lat=" + item.getLatitude()
  36. + "&lng=" + item.getLongitude() + "&isPublic=" + (item.isPublicSpot() ? 1 : 0)
  37. + "&comments=" + uComments;
  38. AsyncManager.getAsyncManager().submitJob(this);
  39. }
  40.  
  41. @Override
  42. public void update(FishingSpot l) throws ServiceException {
  43. this.action = UPDATE;
  44. item = l;
  45. String uName = "";
  46. String uComments = "";
  47. try {
  48. uName = URLEncoder.encode(item.getName(), "UTF-8");
  49. uComments = URLEncoder.encode(item.getComments(), "UTF-8");
  50. e.printStackTrace();
  51. }
  52. contentURL = BASE_URL + "locations.php?action=update&token=" + Main.getToken() + "&userid=" + Main.getUserId()
  53. + "&id=" + item.getId() + "&name=" + uName + "&author=" + Main.getUserId()
  54. + "&lat=" + item.getLatitude() + "&lng=" + item.getLongitude()
  55. + "&is_public_spot=" + (item.isPublicSpot() ? 1 : 0) + "&comments=" + uComments;
  56. AsyncManager.getAsyncManager().submitJob(this);
  57. }
  58.  
  59. @Override
  60. public void delete(FishingSpot l) throws ServiceException {
  61. this.action = DELETE;
  62. item = l;
  63. contentURL = BASE_URL + "locations.php?action=delete&token="
  64. + Main.getToken() + "&userid=" + Main.getUserId() + "&id=" + item.getId();
  65. AsyncManager.getAsyncManager().submitJob(this);
  66. }
  67.  
  68. @Override
  69. public void get(long id) throws ServiceException {
  70. this.action = READ;
  71. item = new FishingSpot();
  72. item.setId(id);
  73. AsyncManager.getAsyncManager().submitJob(this);
  74. }
  75.  
  76. @Override
  77. public void getMany(String criteria) throws ServiceException {
  78. this.action = LIST;
  79. item = null;
  80. contentURL = BASE_URL + "locations.php?action=list&token=" + Main.getToken() + "&userid=" + Main.getUserId() ;
  81. AsyncManager.getAsyncManager().submitJob(this);
  82. }
  83.  
  84. @Override
  85. protected String getContentURL() {
  86. return contentURL;
  87. }
  88.  
  89. @Override
  90. protected void parseContent(String rawJSON) {
  91. ContentResponse<FishingSpot> response = new ContentResponse<FishingSpot>(-1, "Exception thrown", action);
  92. Log.i(Main.TAG, rawJSON);
  93. ArrayList<FishingSpot> locs = new ArrayList<FishingSpot>();
  94. JSONObject topLevel = null;
  95. try {
  96. JSONObject topJson = new JSONObject(rawJSON);
  97. topLevel = topJson.getJSONObject("fishingResponse");
  98. response.setRc(topLevel.getInt("rc"));
  99. response.setDescr(topLevel.getString("descr"));
  100. if (response.getRc() != 0) {
  101. delegate.contentLoaded(response);
  102. return;
  103. }
  104.  
  105. // Delete is a special case. If successful, set the return code to the id that was deleted
  106. if (action == DELETE) {
  107. if (item != null) {
  108. response.setRc(item.getId());
  109. }
  110. delegate.contentLoaded(response);
  111. return;
  112. }
  113.  
  114. // Construct Location objects from the array of items
  115. JSONArray allItems = topLevel.getJSONArray("items");
  116. if (allItems != null) {
  117. for(int x=0; x< allItems.length(); x++) {
  118. JSONObject curObj = allItems.getJSONObject(x);
  119. long id = curObj.getLong("id");
  120. String name = curObj.getString("name");
  121. double lat = curObj.getDouble("lat");
  122. double lng = curObj.getDouble("lng");
  123. String comments = curObj.getString("comments");
  124. FishingSpot spot = new FishingSpot(id, name, lat, lng);
  125. spot.setPublicSpot(curObj.getInt("isPublic") == 1);
  126. spot.setComments(comments);
  127.  
  128. JSONObject owner = curObj.getJSONObject("owner");
  129. User u = new User();
  130. u.setUserId(owner.getString("userId"));
  131. u.setUserName(owner.getString("userName"));
  132. u.setPictureUrl(owner.getString("pictureUrl"));
  133. spot.setOwner(u);
  134.  
  135. locs.add(spot);
  136. }
  137. }
  138. response.setItems(locs);
  139. } catch (JSONException e) {
  140. e.printStackTrace();
  141. }
  142. delegate.contentLoaded(response);
  143. }
  144.  
  145. private ContentBrokerDelegate<FishingSpot> delegate;
  146. @Override
  147. public void setDelegate(ContentBrokerDelegate<FishingSpot> delegate) {
  148. this.delegate = delegate;
  149.  
  150. }
  151.  
  152. }

Comments:

  • Lines 23 - 40: The web-based implementation for creating a new FishingSpot. This is very similar to the LocationBroker code that was previously used.
  • Lines 42 - 59: Update a FishingSpot on the server.
  • Lines 61 - 84: The implementation for deleting and retrieving FishingSpot objects. The key point is to set the proper action and URL, and then invoke submitJob() This code then invokes the run() method that exists in the parent class - AbstractServiceWeb.
  • Lines 91 - 145: Once the response from the server has been retrieved, this method is responsible for decoding the response, and then notifying the delegate. The method retrieves basic return code information, and then parses any FishingSpot objects that are available.

Now that the two classes and the interface have been described, it's time to create the ServiceFactory class, and then update the app to use this service instead of the LocationBroker class.