Using Play, create new app in the usual way:
play new donation-service
Generate an eclipse view of the project"
play eclipsify
... and import the project into your Eclipse workspace.
Create a new package called 'models' - and bring in these classes:
package models;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.List;
import play.db.jpa.Model;
@Entity
public class Donation extends Model
{
public int amount;
public String method;
public Donation (int amount, String method)
{
this.amount = amount;
this.method = method;
}
public String toString()
{
return amount + ", " + method;
}
}
package models;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.List;
import play.db.jpa.Model;
@Entity
public class User extends Model
{
public String firstName;
public String lastName;
public String email;
public String password;
public User(String firstName, String lastName, String email, String password)
{
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}
}
Create a new package called 'utils', and bring in these classes:
package utils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import play.data.binding.Global;
import play.data.binding.TypeBinder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
@Global
public class GsonBinder implements TypeBinder<JsonElement>
{
public Object bind(String name, Annotation[] notes, String value, Class toClass, Type toType) throws Exception
{
return new JsonParser().parse(value);
}
}
package utils;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import models.Donation;
import models.User;
public class JsonParsers
{
static Gson gson = new Gson();
public static User json2User(String json)
{
return gson.fromJson(json, User.class);
}
public static List<User> json2Users(String json)
{
Type collectionType = new TypeToken<List<User>>() {}.getType();
return gson.fromJson(json, collectionType);
}
public static String user2Json(Object obj)
{
return gson.toJson(obj);
}
public static Donation json2Donation(String json)
{
return gson.fromJson(json, Donation.class);
}
public static String donation2Json(Object obj)
{
return gson.toJson(obj);
}
public static List<Donation> json2Donations(String json)
{
Type collectionType = new TypeToken<List<Donation>>() {}.getType();
return gson.fromJson(json, collectionType);
}
}
Into the controllers package, create a new controller called 'DonationServiceAPI':
package controllers;
import play.*;
import play.mvc.*;
import utils.JsonParsers;
import java.util.*;
import com.google.gson.JsonElement;
import models.*;
public class DonationServiceAPI extends Controller
{
public static void users()
{
List<User> users = User.findAll();
renderJSON(JsonParsers.user2Json(users));
}
public static void user(Long id)
{
User user = User.findById(id);
if (user == null)
{
notFound();
}
else
{
renderJSON(JsonParsers.user2Json(user));
}
}
public static void createUser(JsonElement body)
{
User user = JsonParsers.json2User(body.toString());
user.save();
renderJSON(JsonParsers.user2Json(user));
}
public static void deleteUser(Long id)
{
User user = User.findById(id);
if (user == null)
{
notFound();
}
else
{
user.delete();
renderText("success");
}
}
public static void deleteAllUsers()
{
User.deleteAll();
renderText("success");
}
public static void donations()
{
List<Donation> donations = Donation.findAll();
renderText(JsonParsers.donation2Json(donations));
}
public static void donation (Long id)
{
Donation donation = Donation.findById(id);
renderJSON (JsonParsers.donation2Json(donation));
}
public static void createDonation(JsonElement body)
{
Donation donation = JsonParsers.json2Donation(body.toString());
Donation newDonation = new Donation (donation.amount, donation.method);
newDonation.save();
renderJSON (JsonParsers.donation2Json(newDonation));
}
public static void deleteDonation(Long id)
{
Donation donation = Donation.findById(id);
if (donation == null)
{
notFound();
}
else
{
donation.delete();
ok();
}
}
}
Enable our in-memory database by uncommenting the usual setting in 'application.conf'
db=mem
Introduce the 'Bootstrap' java class directly into the 'app' folder:
import java.util.List;
import play.jobs.*;
import play.test.*;
import models.*;
@OnApplicationStart
public class Bootstrap extends Job
{
public void doJob()
{
if (User.count() == 0)
{
Fixtures.deleteDatabase();
Fixtures.loadModels("data.yml");
}
}
}
.. and now specify our routes in config/routes:
GET /api/users DonationServiceAPI.users
GET /api/users/{id} DonationServiceAPI.user
POST /api/users DonationServiceAPI.createUser
DELETE /api/users/{id} DonationServiceAPI.deleteUser
GET /api/donations DonationServiceAPI.donations
GET /api/donations/{id} DonationServiceAPI.donation
POST /api/donations DonationServiceAPI.createDonation
DELETE /api/donations/{id} DonationServiceAPI.deleteDonation
Finally, provide some initial database entries in conf/data.yaml:
User(homer):
usaCitizen: true
firstName: Homer
lastName: Simpson
email: homer@simpson.com
password: secret
User(marge):
usaCitizen: true
firstName: Marge
lastName: Simpson
email: marge@simpson.com
password: secret
User(lisa):
usaCitizen: true
firstName: Lisa
lastName: Simpson
email: lisa@simpson.com
password: secret
User(bart):
usaCitizen: true
firstName: Bart
lastName: Simpson
email: bart@simpson.com
password: secret
User(maggie):
usaCitizen: true
firstName: Maggie
lastName: Simpson
email: maggie@simpson.com
password: secret
Donation(a):
amount : 210
method : paypal
Donation(b):
amount : 20
method : cash
Donation(c):
amount : 330
method : cash
Donation(d):
amount : 10
method : paypal
The project should build without error. Launch it and browse to the database in the usual way.
Also, in a browser, explore these urls:
There is a folder in your eclipse project called 'libs'. Download and copy this jar into libs:
add to build path (right click and add to build path in Eclipse)
In Eclipse, create new package app.http
, and bring in these classes:
package app.http;
import java.util.List;
public interface Response<T>
{
public void setReponse(List<T> aList);
public void setReponse(T anObject);
public void errorOccurred (Exception e);
}
package app.http;
import java.util.List;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
@SuppressWarnings("rawtypes")
public abstract class Request extends AsyncTask<Object, Void, Object>
{
public Response responder;
public ProgressDialog dialog;
public Context context;
public String message;
public Exception error;
public Request(Context context, Response responder, String message)
{
this.responder = responder;
this.context = context;
this.message = message;
}
@Override
protected void onPreExecute()
{
super.onPreExecute();
this.dialog = new ProgressDialog(context, 1);
this.dialog.setMessage(message);
this.dialog.show();
}
@Override
protected Object doInBackground(Object... params)
{
error = null;
try
{
return doRequest(params);
}
catch (Exception e)
{
error = e;
}
return null;
}
protected abstract Object doRequest(Object... params) throws Exception;
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result)
{
super.onPostExecute(result);
if (dialog.isShowing())
{
dialog.dismiss();
}
if (error != null)
{
responder.errorOccurred(error);
}
else
{
if (result instanceof List)
{
responder.setReponse((List)result);
}
else
{
responder.setReponse(result);
}
}
}
}
package app.http;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
public class Rest
{
private static DefaultHttpClient httpClient = null;
private static final String URL = "http://10.0.2.2:9000";
private static DefaultHttpClient httpClient()
{
if (httpClient == null)
{
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 10000);
HttpConnectionParams.setSoTimeout(httpParameters, 10000);
httpClient = new DefaultHttpClient(httpParameters);
}
return httpClient;
}
public static String get(String path) throws Exception
{
HttpGet getRequest = new HttpGet(URL + path);
getRequest.setHeader("accept", "application/json");
HttpResponse response = httpClient().execute(getRequest);
return new BasicResponseHandler().handleResponse(response);
}
public static String delete(String path) throws Exception
{
HttpDelete deleteRequest = new HttpDelete(URL + path);
HttpResponse response = httpClient().execute(deleteRequest);
return new BasicResponseHandler().handleResponse(response);
}
public static String put(String path, String json) throws Exception
{
HttpPut putRequest = new HttpPut(URL + path);
putRequest.setHeader("Content-type", "application/json");
putRequest.setHeader("accept", "application/json");
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
putRequest.setEntity(s);
HttpResponse response = httpClient().execute(putRequest);
return new BasicResponseHandler().handleResponse(response);
}
public static String post(String path, String json) throws Exception
{
HttpPost putRequest = new HttpPost(URL + path);
putRequest.setHeader("Content-type", "application/json");
putRequest.setHeader("accept", "application/json");
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
putRequest.setEntity(s);
HttpResponse response = httpClient().execute(putRequest);
return new BasicResponseHandler().handleResponse(response);
}
}
We already have a models package - introduce these new classes into this package:
package app.models;
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class JsonParsers
{
static Gson gson = new Gson();
public static User json2User(String json)
{
return gson.fromJson(json, User.class);
}
public static List<User> json2Users(String json)
{
Type collectionType = new TypeToken<List<User>>() {}.getType();
return gson.fromJson(json, collectionType);
}
public static String user2Json(Object obj)
{
return gson.toJson(obj);
}
public static Donation json2Donation(String json)
{
return gson.fromJson(json, Donation.class);
}
public static String donation2Json(Object obj)
{
return gson.toJson(obj);
}
public static List<Donation> json2Donations(String json)
{
Type collectionType = new TypeToken<List<Donation>>() {}.getType();
return gson.fromJson(json, collectionType);
}
}
package app.models;
import java.util.List;
import android.content.Context;
import app.http.Request;
import app.http.Response;
import app.http.Rest;
public class DonationServiceAPI
{
public static void getUsers(Context context, Response<User> response, String dialogMesssage)
{
new GetUsers(context, response, dialogMesssage).execute();
}
public static void createUser(Context context, Response<User> response, String dialogMesssage, User user)
{
new CreateUser(context, response, dialogMesssage).execute(user);
}
public static void getDonations(Context context, Response<Donation> response, String dialogMesssage)
{
new GetDonations(context, response, dialogMesssage).execute();
}
public static void createDonation(Context context, Response<Donation> response, String dialogMesssage, Donation donation)
{
new CreateDonation(context, response, dialogMesssage).execute(donation);
}
}
class GetUsers extends Request
{
public GetUsers(Context context, Response<User> callback, String message)
{
super(context, callback, message);
}
@Override
protected List<User> doRequest(Object... params) throws Exception
{
String response = Rest.get("/api/users");
List<User> userList = JsonParsers.json2Users(response);
return userList;
}
}
class GetDonations extends Request
{
public GetDonations(Context context, Response<Donation> callback, String message)
{
super(context, callback, message);
}
@Override
protected List<Donation> doRequest(Object... params) throws Exception
{
String response = Rest.get("/api/donations");
List<Donation> donationList = JsonParsers.json2Donations(response);
return donationList;
}
}
class CreateUser extends Request
{
public CreateUser(Context context, Response<User> callback, String message)
{
super(context, callback, message);
}
@Override
protected User doRequest(Object... params) throws Exception
{
String response = Rest.post ("/api/users", JsonParsers.user2Json(params[0]));
return JsonParsers.json2User(response);
}
}
class CreateDonation extends Request
{
public CreateDonation(Context context, Response<Donation> callback, String message)
{
super(context, callback, message);
}
@Override
protected Donation doRequest(Object... params) throws Exception
{
String response = Rest.post ("/api/donations", JsonParsers.donation2Json(params[0]));
return JsonParsers.json2Donation(response);
}
}
This is a restructuring of Login to fetch the users from the donation-service as soon as the activity starts, and then to handle the response appropriately.
package app.activities;
import java.util.List;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import app.donation.R;
import app.http.Response;
import app.main.DonationApp;
import app.models.DonationServiceAPI;
import app.models.User;
public class Login extends Activity implements Response<User>
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
DonationServiceAPI.getUsers(this, this, "Retrieving list of users");
}
public void signinPressed (View view)
{
DonationApp app = (DonationApp) getApplication();
TextView email = (TextView) findViewById(R.id.loginEmail);
TextView password = (TextView) findViewById(R.id.loginPassword);
if (app.validUser(email.getText().toString(), password.getText().toString()))
{
startActivity (new Intent(this, Donate.class));
}
else
{
Toast toast = Toast.makeText(this, "Invalid Credentials", Toast.LENGTH_SHORT);
toast.show();
}
}
@Override
public void setReponse(List<User> aList)
{
DonationApp app = (DonationApp) getApplication();
app.users = aList;
}
@Override
public void errorOccurred(Exception e)
{
Toast toast = Toast.makeText(this, "Donation Service Unavailable. Try again later", Toast.LENGTH_LONG);
toast.show();
startActivity (new Intent(this, Welcome.class));
}
@Override
public void setReponse(User anObject)
{}
}
The last step to getting the project running is to specifically enable Internet access for your app. This is controlled by a single entry in the AndroidManifest.xml file:
<uses-permission android:name="android.permission.INTERNET"/>
Try the app now (make sure donation-service play app is running). It should let you log in with user credentials download from the service (try homer@simpson.com, secret - without registering).
Finally, the report activity:
package app.activities;
import java.util.List;
import app.donation.R;
import app.http.Response;
import app.main.DonationApp;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import app.models.Donation;
import app.models.DonationServiceAPI;
public class Report extends Activity implements Response <Donation>
{
private ListView listView;
private DonationApp app;
private DonationAdapter adapter;
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.report, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.menuDonate : startActivity (new Intent(this, Donate.class));
break;
case R.id.menuLogout : startActivity (new Intent(this, Welcome.class));
break;
}
return true;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_report);
app = (DonationApp) getApplication();
listView = (ListView) findViewById(R.id.reportList);
adapter = new DonationAdapter (this, app.donations);
listView.setAdapter(adapter);
DonationServiceAPI.getDonations(this, this, "Downloading Donations List..");
}
@Override
public void setReponse(List<Donation> aList)
{
app.donations = aList;
adapter.donations = aList;
adapter.notifyDataSetChanged();
}
@Override
public void setReponse(Donation anObject)
{
}
@Override
public void errorOccurred(Exception e)
{
Toast toast = Toast.makeText(this, "Donation Service Unavailable!", Toast.LENGTH_LONG);
toast.show();
startActivity (new Intent(this, Welcome.class));
}
}
class DonationAdapter extends ArrayAdapter<Donation>
{
private Context context;
public List<Donation> donations;
public DonationAdapter(Context context, List<Donation> donations)
{
super(context, R.layout.row_donate, donations);
this.context = context;
this.donations = donations;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.row_donate, parent, false);
Donation donation = donations.get(position);
TextView amountView = (TextView) view.findViewById(R.id.row_amount);
TextView methodView = (TextView) view.findViewById(R.id.row_method);
amountView.setText("" + donation.amount);
methodView.setText(donation.method);
return view;
}
@Override
public int getCount()
{
return donations.size();
}
}
When this starts - it requests the donations from the service. When they arrive, they are displayed in the list view.
Archive of projects so far:
Publish the donation-service app to Cloudbees. Using the url of the cloudbees app, change the Rest URL:
public class Rest
{
private static DefaultHttpClient httpClient = null;
private static final String URL = "http://10.0.2.2:9000";
//...
... to your cloudbees app. See if your android app now reads from the cloudbees app.
The application as it currently stands uses two aspects of the donation play application:
See if you can figure out how to:
The facilities are already in the play app for this, so you will only be making changes to the android application.
Because the donation-service play app has no UI, keep an eye on the database via the database browser in the usual way:
Migrate over the UI from the donation play app you completed during the summer school. This is a copy of the app here: