Saturday, March 17, 2012

GWT: Creating a simple File hosting site

GWT : Creating a simple file hosting site


The free hosting site that I used to store my source code was gone without warning.
Thus, I no longer trust these free sites anymore.

To store my android tutorial source code, I decided to create a simple file hosting so I can upload my source codes.
Further, since the app-engine service by google is free I decided to use it.

Tools/Libraries/Framework used:

1. Google App Engine
2. GWT
3. Apache File Upload

Links to get started with Google App Engine (GAE) and Google web Toolkit (GWT)

1. GAE http://code.google.com/webtoolkit/doc/latest/tutorial/gettingstarted.html
2. GWT http://code.google.com/webtoolkit/doc/latest/tutorial/RPC.html
3. AppEngine http://code.google.com/appengine/docs/java/gettingstarted/
4. AppEngine-JPA http://code.google.com/appengine/docs/java/datastore/jpa/overview.html
5. TableLess Layout for GWT HTML - http://www.w3schools.com/html/html_layout.asp

I. Defining the BLOB object for the file hosting service.
@Entity
public class FileBlob implements Serializable {
private static final long serialVersionUID = -5835124824181798205L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Blob content;
private String filename;
private String contentType;

II. Creating The Front End
  Create simple class that extends a vertical panel then add a button to display the pop-up upload dialog.

public void onClick(ClickEvent event) {
if (event.getSource() == btnUpload) {
DialogBox innerDialogBox = new BlobFileUpload(this);
// innerDialogBox.center();
innerDialogBox.show();
}
}


Create the CellTable to display the file details. http://code.google.com/webtoolkit/doc/latest/DevGuideUiCellTable.html

To enable pagination for the celltable, see GWT SimplePager.

III. Creating the BlobFileUpload dialogbox

public class BlobFileUpload extends DialogBox {
public BlobFileUpload(DownloadPanel downloadPanel) {
super();
this.downloadPanel = downloadPanel;
clear();
setText("Upload File");
//Set the servlet that will handle multi-part requests.
formPanel.setAction("/downloads/fileupload");
formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
//Set method post
formPanel.setMethod(FormPanel.METHOD_POST);
//Initialize the file upload object
fileUpload = new FileUpload();
fileUpload.setName("upload");
//Add click handler to the button, perform simple validation then call formPanel.submit
btnSubmit.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
lblLoading.setText("Uploading Please Wait");
if(!fileUpload.getFilename().trim().equals("")) {
formPanel.submit();
} else {
Window.alert("No file selected.");
}
}
});
btnClose.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
BlobFileUpload.this.hide();
}
});
verticalPanel.add(fileUpload);
verticalPanel.add(lblLoading);
controlPanel.add(btnSubmit);
controlPanel.add(btnClose);
verticalPanel.add(controlPanel);
//I don't know what this does but I saw this on some tutorial before.
formPanel.addSubmitHandler(new FormPanel.SubmitHandler() {
public void onSubmit(SubmitEvent event) {
}
});
//Callback handler when uploading is completed.
formPanel.addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() {
public void onSubmitComplete(SubmitCompleteEvent event) {
String msg = event.getResults().replace("
", "").replace(
"
", "");
Window.alert("Message : " + msg);
lblLoading.setText("");
BlobFileUpload.this.downloadPanel.initData();
BlobFileUpload.this.hide();
}
});
formPanel.setWidget(verticalPanel);
add(formPanel);
}

}



IV. Creating the FileUploadServlet

Use the sample code for Apache FileUpload.
I just retrieved the filename, contact type and the content.
Then used these values to set the FileBlob object.

 public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
printf("doPost");
if (!isMultipart) {
printf("!isMultipart");
} else {
ServletFileUpload upload = new ServletFileUpload();
upload.setSizeMax(10*1024*1024);
try {
FileItemIterator iterator = upload.getItemIterator(request);
while (iterator.hasNext()) {
printf("(iterator.hasNext())");
FileItemStream item = iterator.next();
InputStream in = item.openStream();
if (item.isFormField()) {
System.out.println("if (item.isFormField())");
System.out.println("FieldName=" + item.getFieldName());
System.out.println("Name=" + item.getName());

} else {
System.out.println("} else {");
System.out.println("FieldName=" + item.getFieldName());
System.out.println("Name=" + item.getName());
System.out.println("contentType=" + item.getContentType());
try {
byte[] byteData = IOUtils.toByteArray(in);
FileBlob fileBlob = new FileBlob(item.getName(), item.getContentType(), byteData);
fileBlobDao.create(fileBlob);
printf(item.getName() + " uploaded successful");
out.println(item.getName() + " uploaded successful");
} catch (Exception ex) {
ex.printStackTrace();
out.println("ERROR " + ex.getMessage());
} finally {
IOUtils.closeQuietly(in);
}
}
}
} catch (Exception e) {
//out.println("ERROR " + e.getMessage());
//e.printStackTrace();
out.println("Upload Failed.. The size of the file is greater than 10MB");
printf("Upload Failed.. The size of the file is greater than 10MB");
}
}
}



V. creating the FileBlob Data Access Object

Creating a BLOB as with all object is straightforward in AppEngine-JPA.

public String create(FileBlob fileBlob) {
String message = null;
EntityManager entityManager = null;
try {
System.out.println("Creating : " + fileBlob.getFilename());
entityManager = EMF.get().createEntityManager();
entityManager.persist(fileBlob);
message = fileBlob.getId() + " succesfully created.";
} catch (Exception ex) {
ex.printStackTrace();
message = "Error : " + ex.getMessage();
} finally {
if (entityManager != null) {
entityManager.close();
}
}
return message;
}



VI. Retrieving Files using FileDetail.

Since we do not need all fields to be displayed on the screen, a FileDetail object which holds only what is neccessary.
@Override
public ArrayList retreiveAll() {
ArrayList fileBlobs = fileBlobDao.retrieveAll();
ArrayList listFileDetail = new ArrayList();
for(FileBlob fb : fileBlobs) {
FileDetail fd = new FileDetail();
fd.setId(fb.getId());
fd.setFilename(fb.getFilename());
listFileDetail.add(fd);
}
return listFileDetail;
}


VII. Creating the DownloadServlet

Convert the BLOB to bytes

public class FileDownloadServlet extends HttpServlet {

private static final long serialVersionUID = -2004901543318306888L;

private FileBlobDao fileBlobDao = new FileBlobDao();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
Long lId = new Long(id);
FileBlob fileBlob = fileBlobDao.findById(lId);
if(fileBlob != null) {
String contentType = fileBlob.getContentType();
if(contentType != null && !contentType.trim().equals("")) {
response.setContentType(contentType);
} else {
response.setContentType("application/octet-stream");
}
response.setHeader( "Content-Disposition", "attachment; filename=\"" + fileBlob.getFilename() + "\"" );
System.out.println("FileBlob:"+fileBlob.getContent().getBytes().length);
response.setContentLength((int)fileBlob.getContent().getBytes().length);
response.getOutputStream().write(fileBlob.getContent().getBytes());
response.getOutputStream().flush();
}
response.getOutputStream().flush();
}
}


VIII. Configurations

Don't forget to configure web.xml, persistence.xml.

IX. See the application in action downloads.androidph.com

Find file-hosting-v1.zip or click here to download this tutorial's source-code.


Monday, March 12, 2012

Announcement : AndroidPH.com Code Not Updated

Hi Friends,

Please bear with me while I try to find time to update the code which was created during Android 1.5.

I am prioritizing tutorial for Android Camera. I have one idea for a practical use for Android Camera, I'll try to add it in the next couple of weeks.

Thanks.

GWT SimplePager on CellTable is not Updating

The code below works on initialization, however if you add new data, it does not display the updated row.

Previous Code :
CellTable table = new CellTable();
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
pager.setDisplay(table);
pager.setPageSize(10);
table.setPageSize(10);
ListDataProvider dataProvider = new ListDataProvider();
dataProvider.addDataDisplay(table);
List list = dataProvider.getList();
for (Carrier contact : data) {
list.add(contact);
}


Fix :
pager.startLoading();

Current Code :
CellTable table = new CellTable();
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
pager.setDisplay(table);
pager.setPageSize(10);
table.setPageSize(10);
pager.startLoading();
ListDataProvider dataProvider = new ListDataProvider();
dataProvider.addDataDisplay(table);
List list = dataProvider.getList();
for (Carrier contact : data) {
list.add(contact);
}

Thursday, January 5, 2012

Android Custom Database ListAdapters

On the ListActivity class :

ListPatientCursorAdapter listAdapter = new ListPatientCursorAdapter(this, R.layout.patient_list, cursor);
setListAdapter(listAdapter);
startManagingCursor(cursor);


Custom List Adapter :

class ListPatientCursorAdapter extends ResourceCursorAdapter {

public ListPatientCursorAdapter(Context context, int layout, Cursor c) {
super(context, layout, c);
}

@Override
public View newView(Context context, Cursor cur, ViewGroup parent) {
LayoutInflater li = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return li.inflate(R.layout.patient_list_row, parent, false);
}


@Override
public void bindView(View v, Context context, final Cursor _c) {
TextView tvId = (TextView)v.findViewById(R.id.tvId);
final Integer id = _c.getInt(_c.getColumnIndex(Patient.PatientField.ID));
TextView tvFirstName = (TextView) v.findViewById(R.id.tvFirstName);
tvFirstName.setText(_c.getString(_c
.getColumnIndex(Patient.PatientField.FIRSTNAME)));

TextView tvMiddleName = (TextView) v
.findViewById(R.id.tvMiddleName);
tvMiddleName.setText(_c.getString(_c
.getColumnIndex(Patient.PatientField.MIDDLENAME)));

TextView tvLastName = (TextView) v.findViewById(R.id.tvLastName);
tvLastName.setText(_c.getString(_c
.getColumnIndex(Patient.PatientField.LASTNAME)));

TextView tvLastUpdate = (TextView) v
.findViewById(R.id.tvLastUpdate);

CheckBox cbFollowUp = (CheckBox) v.findViewById(R.id.cbFollowUp);
cbFollowUp
.setChecked(_c.getInt(_c
.getColumnIndex(Patient.PatientField.FOR_FOLLOWUP)) == 0 ? false
: true);

Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(_c.getLong(_c
.getColumnIndex(Patient.PatientField.UPDATE_DATE)));

tvLastUpdate.setText(sdf.format(cal.getTime()));

v.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
processSelectedId(id);
}
});
}
}


List View :

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="#FFFFFF"
android:dividerHeight="0sp"
android:scrollbars="none" />
android:id="@id/android:empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5px"
android:gravity="center"
android:text="No Patient Record." >




List Row :



android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
android:id="@+id/tvId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:visibility="gone"/>
android:id="@+id/llPatient"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
android:id="@+id/tvLastName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Puti"
android:textAppearance="?android:attr/textAppearanceLarge" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" "
android:textAppearance="?android:attr/textAppearanceMedium" />
android:id="@+id/tvFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kyle"
android:textAppearance="?android:attr/textAppearanceMedium" />

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" "
android:textAppearance="?android:attr/textAppearanceMedium" />

android:id="@+id/tvMiddleName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amousy"
android:textAppearance="?android:attr/textAppearanceMedium" />

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Last Update"
android:textAppearance="?android:attr/textAppearanceMedium" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" "
android:textAppearance="?android:attr/textAppearanceMedium" />
android:id="@+id/tvLastUpdate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Last Update" />

android:id="@+id/cbFollowUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:enabled="false"
android:text="For Follow Up" />

Android SQLite Primary Key Auto Increment

WRONG :
CREATE TABLE PERSON (ID INTEGER PRIMARY KEY AUTOINCREMENT,
FIRSTNAME TEXT,
LASTNAME TEXT)

WRONG :
CREATE TABLE PERSON (ID INT PRIMARY KEY AUTOINCREMENT,
FIRSTNAME TEXT,
LASTNAME TEXT)

CORRECT :
CREATE TABLE PERSON (ID INTEGER PRIMARY KEY,
FIRSTNAME TEXT,
LASTNAME TEXT)

Java Code To Insert (Do Not Set ID field):
ContentValues values = new ContentValues();
values.put("FIRSTNAME", "Java");
values.put("LASTNAME", "Padawan");


Then on your SQLiteDatabase instance do :
sqlDatabase.insert("PERSON" null, values);

Monday, August 10, 2009

Android Parsing JSON

1. Parse the live JSON response from http://beer.androidph.com/beerws. See App Engine Generating JSON.

2. Use the code from Android Networking Tutorial

3. JSON Parser Code



JSONArray parseArray = new JSONArray(message);
for (int i = 0; i < jo =" parseArray.getJSONObject(i);


4. Modify nextscreen.xml of the source code from #2.


















5. Modify NextScreen.java from source code in #2 or Android Networking Tutorial


public class NextScreen extends Activity implements OnClickListener {
private Button btnBack;

private ViewGroup.LayoutParams layoutName;
private ViewGroup.LayoutParams layoutAddress;
private ViewGroup.LayoutParams layoutPrice;
private ViewGroup.LayoutParams layoutLastCallTime;
private ViewGroup.LayoutParams layoutLocation;

private String message;
private LinearLayout linearLayout;
private LinearLayout linearInnerLayout;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nextscreen);
initComponents();
}

public void initComponents() {

linearLayout = (LinearLayout) findViewById(R.id.linearLayout);
linearInnerLayout = (LinearLayout) linearLayout.getChildAt(0);

layoutName = ((TextView) linearInnerLayout.getChildAt(0))
.getLayoutParams();
layoutAddress = ((TextView) linearInnerLayout.getChildAt(1))
.getLayoutParams();
layoutPrice = ((TextView) linearInnerLayout.getChildAt(2))
.getLayoutParams();
layoutLastCallTime = ((TextView) linearInnerLayout.getChildAt(3))
.getLayoutParams();
layoutLocation = ((TextView) linearInnerLayout.getChildAt(4))
.getLayoutParams();

message = getIntent().getStringExtra(NetworkConnection.NC_RESPONSE);

LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);

try {
JSONArray parseArray = new JSONArray(message);
for (int i = 0; i < parseArray.length(); i++) {
JSONObject jo = parseArray.getJSONObject(i);

LinearLayout newRow = new LinearLayout(this);
newRow.setLayoutParams(linearInnerLayout.getLayoutParams());

String name = jo.getString("name") + " | ";
TextView curTvName = new TextView(this);
curTvName.setText(name);
curTvName.setLayoutParams(layoutName);

String address = jo.getString("address") + " | ";
TextView curTvAddress = new TextView(this);
curTvAddress.setText(address);
curTvAddress.setLayoutParams(layoutAddress);

String price = jo.getString("price") + " | ";
TextView curTvPrice = new TextView(this);
curTvPrice.setText(price);
curTvPrice.setLayoutParams(layoutPrice);

String lastCallTime = jo.getString("lastCallTimeString") + " | ";
TextView curTvLastCallTime = new TextView(this);
curTvLastCallTime.setText(lastCallTime);
curTvLastCallTime.setLayoutParams(layoutLastCallTime);

String location = jo.getString("latitude") + "," + jo.getString("longitude");
TextView curTvLocation = new TextView(this);
curTvLocation.setText(location);
curTvLocation.setLayoutParams(layoutLocation);

newRow.addView(curTvName);
newRow.addView(curTvAddress);
newRow.addView(curTvPrice);
newRow.addView(curTvLastCallTime);
newRow.addView(curTvLocation);

linearLayout.addView(newRow, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void onClick(View v) {
if (v == btnBack) {
finish();
}
}
}



6. Screenshot of the android JSON parser.

Friday, August 7, 2009

Generating JSON with Java App Engine

1. Requirements App Engine 1.2.2. (The older version of App Engine has a problem compiling the org.json package)

2. Download Java JSON.zip from http://www.json.org

3. Read my first app engine tutorial.

4. Generate a JSON string from any java.util.List of Objects. For this instance I used the objects from #3.


public class BeerWS extends HttpServlet {

public void process(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
resp.setContentType("text/plain");
PersistenceManager persistenceManager = PMF.get()
.getPersistenceManager();
String query = "select from " + Beer.class.getName()
+ " order by name asc";
List beerLocations = (List)persistenceManager.newQuery(query).execute();
List list = new ArrayList();
for(Beer beer : beerLocations) {
//Add the pojo as a JSONObject
list.add(new JSONObject(beer));
}
//Create a JSONArray based from the list of JSONObejcts
JSONArray jsonArray = new JSONArray(list);
//Then output the JSON string to the servlet response
resp.getWriter().println(jsonArray.toString());
}

public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}

public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}
}


5. Here's the link of the live demo. http://beer.androidph.com/beerws. Below is the object used if you have not already check my previous app engine tutorial.


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Beer {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String name;
@Persistent
private Double price;
@Persistent
private Date lastCallTime;
@Persistent
private String promotions;
@Persistent
private String address;
@Persistent
private Double longitude;
@Persistent
private Double latitude;

public Beer() {}

public Beer(String name, Double price,
Date lastCallTime, String promotions,
String address, Double longitude, Double latitude) {
this.name = name;
this.price = price;
this.lastCallTime = lastCallTime;
this.promotions = promotions;
this.address = address;
this.longitude = longitude;
this.latitude = latitude;
}

public Beer(Long id, String name, Double price,
Date lastCallTime, String promotions, String address,
Double longitude, Double latitude) {
this.id = id;
this.name = name;
this.price = price;
this.lastCallTime = lastCallTime;
this.promotions = promotions;
this.address = address;
this.longitude = longitude;
this.latitude = latitude;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getLastCallTime() {
return lastCallTime;
}
public void setLastCallTime(Date lastCallTime) {
this.lastCallTime = lastCallTime;
}
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
public String getLastCallTimeString() {
if(lastCallTime != null) {
return sdf.format(lastCallTime);
} else {
return "-";
}
}
public String getPromotions() {
return promotions;
}
public void setPromotions(String promotions) {
this.promotions = promotions;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
}

Tuesday, July 21, 2009

Google App Engine - Spring Integration Issues

I'm currently building the server component of Beer Radar using Google App Engine and Spring Framework 3.0.

I've encountered these problems below and posting their corresponding solutions.

Problem:
Google App Engine cannot read JSTL and Spring Expression on JSPs

Solution:
Google App Engine has isELIgnored="true" by default.
Just add the isELIgnored="false" on the <%@page section in the JSP.


Problem:
org.springframework.web.servlet.tags.RequestContextAwareTag doStartTag: access denied (java.lang.RuntimePermission getClassLoader)

Solution:
Add the following on your Controller

For Annotated controllers use
@InitBinder
public void initLogin(WebDataBinder binder) {
System.out.println("initLogin");
binder.registerCustomEditor(String.class, new StringTrimmerEditor
(false));
}

For sub-classed controllers
Override the InitBinder method and register string trimmed editor in the binder
binder.registerCustomEditor(String.class, new StringTrimmerEditor
(false));


Problem:
org.springframework.web.HttpSessionRequiredException: Session attribute '' required - not found in session

and/or

Uncaught exception from servlet
java.lang.RuntimeException: java.io.NotSerializableException:

Solution :
implements Serializable {
private static final long serialVersionUID = <generated value>L

The unserialized Session object will work on development but, it will throw a java.io.NotSerializableException on the appspot server.


Problem :
Attempt was made to manually set the id component of a Key primary key. If you want to control the value of the primary key, set the name component instead.

Solution :
This happens when I update a child object. The solution for this, is to re-attach the parent object first, by
Parent attached = pm.getObjectById(Parent.class, detachedParent.getId());
then
Modify the child
attached.getChild().setName("new value");
then
pm.makePersistent(attachedDrunkard);

This solved the problem for me. Here's a link of a couple of alternative solutions.

Tuesday, April 14, 2009

App Engine Tutorial : Creating the Beer Radar Server component

App Engine App #1.0 - Server Component for Android Application Beer Radar.

I created the Beer Radar Android Application last February 2009, and I was looking for a suitable Java server application. I don't know when it was announced, but I just heard about the Google App Engine support for Java last week (April 10, 2009).

Here's the basic App Engine Application which I'll connect the Beer Radar app soon. :) http://beer.androidph.com/

I quickly signed up to see what is it all about. So, here's what I learned so far.

Here are the steps to create a web app using Google App Engine with Java Lanuage Support.

1. Sign-up for an App Engine Account http://appengine.google.com/ you should click link for Java then click the sign-up button (I waited for 2 days for the confirmation). I don't have a screenshot for it, but its easy to spot.

2. Download the Google App Engine SDK http://code.google.com/appengine/downloads.html

3. Install the App Engine Eclipse Plugin
From the Eclipse Menu, go to HELP > Software Update...
Click the Available Software Tab
Click Add Site, type http://dl.google.com/eclipse/plugin/3.4
Then Install
See http://code.google.com/appengine/docs/java/tools/eclipse.html for more details.
Restart Eclipse


4. Click the New Web Application Icon as shown below


5. Type in the Project name and package. I unchecked the GWT option since I'm not going to use it.


6. Then click Finish. Everything is created for you, even the libraries that you need for your application.


7. Run the server by right-clicking on the project, select Debug As > Web Application


8. If you have tomcat running by default, you'd probably get this error message
WARNING: failed SelectChannelConnector@127.0.0.1:8080
java.net.BindException: Address already in use: bind


9. Fix this by going to the Debug Configurations, then on the Main Tab, set a new port or click "Automatically select an unused port".





10. Click Debug.

11. Now open your browser to http://localhost:8989.

12. Now you can test your application.


13. Once you're done with you application and you already got the Google confirmation message, you can now create an application.


14. Type in a unique Application Identifier and the Application Title. This will be used in deploying your application.


15. Once everything is done, you can now deploy by clicking the Deploy App Engine Project icon.


16. You will need to click the App Engine project settings to enter your Application Identifier.


17. Enter you Application ID then type in a version then click OK.


18. Enter your Google App Engine Account Email and Password and that is done.


20. Here's the app http://beerradar.appspot.com/ I deployed, to be used as the server component of the Beer Radar project.

21. AND YES, I had to put in the ads. ;)

What I really like about the app engine is the JDO support.

Here's the code for my Persistent Object
Beer.java

package com.androidph.beerserver;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Beer {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;

@Persistent
private String name;

@Persistent
private Double price;

@Persistent
private Date lastCallTime;

@Persistent
private String promotions;

@Persistent
private String address;

@Persistent
private Double longitude;

@Persistent
private Double latitude;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

public Date getLastCallTime() {
return lastCallTime;
}

public void setLastCallTime(Date lastCallTime) {
this.lastCallTime = lastCallTime;
}

private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");

public String getLastCallTimeString() {
if(lastCallTime != null) {
return sdf.format(lastCallTime);
} else {
return "-";
}
}

public String getPromotions() {
return promotions;
}

public void setPromotions(String promotions) {
this.promotions = promotions;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public Double getLongitude() {
return longitude;
}

public void setLongitude(Double longitude) {
this.longitude = longitude;
}

public Double getLatitude() {
return latitude;
}

public void setLatitude(Double latitude) {
this.latitude = latitude;
}

public String toString() {
return id + " | " + name + " | " + price + " | " + lastCallTime + " | " + promotions + " | " + address + " | " + longitude + " | " + latitude;
}

public byte[] convertToStream() {
return null;
}

public void fromStream(byte[] data) {

}
}

Here's the code to get a Persistence Manager. I got this from the GuestBook demo included in the AppEngine SDK

PMF.java

package com.androidph.beerserver;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");

private PMF() {}

public static PersistenceManagerFactory get() {
return pmfInstance;
}
}


Here are code snippets on how to use the Persistence Manager.

Persisting an Object

PersistenceManager persistenceManager = PMF.get()
.getPersistenceManager();
Beer beer = beerValidator.getBeer(name, address, price,
lastCallTime, promotions, longitude, latitude);
persistenceManager.makePersistent(beer);

Retrieving Data

String query = "select from " + Beer.class.getName()
+ " order by name asc";
List beerLocations = (List) persistenceManager
.newQuery(query).execute();
for (Beer beer : beerLocations) {
beerServletData.addLog("beer.toString() : " + beer.toString());
}

Sunday, February 8, 2009

App #1.0 - Beer Radar

I want to create an application, where it downloads bar location and some necessary details such as price of a beer, location and last call time. Also, I want it to be plotted with the user's current GPS location. Also, I want to have a server where user's can upload their recommended bars so that other user's may see it on their screens.


Download Source Code Below.

Objectives:

1. Download Bar Details from Server
* 2. Plot Bar Details with respect to the user's current gps location
3. Upload Bar Details to Server
4. Be able adjust the coverage of the radar (1 km - 10 km)

But for this tutorial, I just want to implement #2, since I still can't find a nice free server to host my server app.

The MODEL

STEP 1. Create BeerLocation.java
- this is just a POJO for the Bar Details. It also stores the longitude and latitude values of the Bar.


package com.androidph.beer.model;

import java.text.DecimalFormat;

public class BeerLocation {

private int id;
private String name;
private String price;
private String description;
private String lastCallTime;
private double latitude;
private double longitude;
private DecimalFormat df = new DecimalFormat("####.##");

public BeerLocation(String name, String price, String description,
String lastCallTime, double latitude, double longitude) {
this.name = name;
this.price = price;
this.description = description;
this.lastCallTime = lastCallTime;
this.latitude = latitude;
this.longitude = longitude;
}

public String toString() {
return name + ", " + price + " ( " + df.format(longitude) + ", " + df.format(latitude) + " ) ";
}

public BeerLocation() {
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPrice() {
return price;
}

public void setPrice(String price) {
this.price = price;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getLastCallTime() {
return lastCallTime;
}

public void setLastCallTime(String lastCallTime) {
this.lastCallTime = lastCallTime;
}

public double getLatitude() {
return latitude;
}

public void setLatitude(double latitude) {
this.latitude = latitude;
}

public double getLongitude() {
return longitude;
}

public void setLongitude(double longitude) {
this.longitude = longitude;
}

}


STEP 2. Create interface BarLocationService.java
- contains the method to retrieve a List of Bar Details. Currently, the implementation is just hard-coded and will be replaced on the next iterationsof this application.


package com.androidph.beer.model;

import java.util.List;

public interface BeerLocationService {
public List retrieveAllBeerLocations();
}


STEP 3. Create BeerLocationServiceMemory.java
- Just create a java.util.List and add BeerLocation to the list.


package com.androidph.beer.model.impl;

import java.util.ArrayList;
import java.util.List;

import com.androidph.beer.model.BeerLocation;
import com.androidph.beer.model.BeerLocationService;

public class BeerLocationServiceMemory implements BeerLocationService {

private List beerLocations = new ArrayList();
@Override
public List retrieveAllBeerLocations() {
beerLocations.add( new BeerLocation("Chili Pepper's", "Php45.0", "", "9:00AM", 50, 50));
beerLocations.add( new BeerLocation("OTB", "Php45.0", "", "3:00AM", 0, 0));
beerLocations.add( new BeerLocation("Gerry's Grill", "Php38.0", "", "00:00AM", 235, 100));
beerLocations.add( new BeerLocation("Giligan's", "Php40.0", "", "03:00AM", 180, 150));
return beerLocations;
}
}



The VIEW

STEP 1. Create BeerRadarView
- Extend the android.view.View class. The View class contains the onDraw(Canvas) method which is where the painting is done. The onDraw(Canvas) method is similar to the paint(Graphics g) of AWT and/or Swing components. Even the draw methods are almost the same. (example. g.drawArc(...), canvas.drawArc(...), g.drawLine(...), canvas.drawLine(...) and so forth. The only difference is that the canvas stores most of its paint/draw properties in a Paint object.
- The Paint class stores common drawing properties such as color, font size, stoke etc.
- The coordinates are also the same for both canvas and graphics. 0,0 is the upperleft-most and MAX_WIDTH, MAX_HEIGHT is on the lower right-most.


@Override
protected void onDraw(Canvas canvas) {
if(!hasInitialized) {
initializeConstants();
}
drawRadarGrid(canvas);
drawRadar(canvas);
drawBeer(canvas, beerLocations);
String currentLocation = "( " + df.format(currentLongitude) + ", " + df.format(currentLatitude) + " )";
canvas.drawText(currentLocation, midpointX-30, midpointY-5, paintScreenText);
}


STEP 2. Drawing the Radar Grid
- Just plot two lines that looks like a corsair.

STEP 3. Drawing the Radar Circles
- This uses the canvas.drawCircle(...) which actually is not on the graphics class. The canvas.drawArc(...), is different from the drawCircle, since it uses a RectF to get the size of the arc/ellipse to be drawn.


private void drawRadarGrid(Canvas canvas) {
canvas.drawLine(0, midpointY, screenWidth, midpointY, paintRadarGrid);
canvas.drawLine(midpointX, 0, midpointX, screenHeight, paintRadarGrid);


STEP 4. Animated the Radar
- Use the circle path formula
x = A cos(radian);
y = A Sine(radian);
Where A is the amplitude.
- Since I am using 0-360 degrees for the angles, it is necessary to convert degrees to radians.
- Radian = Angle * (PI / 180)


private void drawRadar(Canvas canvas) {
if (startAngle > 360) {
startAngle = 0;
}
float x = (float) (minimumScreenSize / 2 * Math.cos(startAngle
* (Math.PI / 180)));
float y = (float) (minimumScreenSize / 2 * Math.sin(startAngle
* (Math.PI / 180)));
canvas.drawLine(getWidth() / 2, getHeight() / 2, x + getWidth() / 2, y
+ getHeight() / 2, paintRadar);
startAngle += 3;
}


STEP 5. Plotting the BeerLocation data.
- Plot the longitude and latitude of the BeerLocation with respect to the user's current GPS location
- x = (beerLoc.getLongitude() + midpointX - currentLongitude - BEER_ICON_SIZE);
Since the screens midpoint is not 0, the longitude must the adjusted. The + (positive) value of X should be in the WEST part of the screen. So the midpoint of the X-axis must be added to the location's longitude. Also, to adjust it to the user's current location, the currentLongitude is subtracted. The BEER_ICON_SIZE is subtracted just so it becomes centered.
- y = (midpointY - beerLoc.getLatitude() + currentLatitude - BEER_ICON_SIZE);
There's a slight difference between the computation of Y, since the + (positive) value of y should be on the NORTH part of the screen, thus, the point is adjusted. So, midpoint of Y is subtracted with the beer location's latitude. Also, the currentLatitude is added to the equation.
- The maximum coverage is not yet adjustable yet. In the future, I'll modify so that the user can select a minimum radius of 1KM and a maximum radius of 10KM. I currently do not know how each degrees of the longitude and latitude translates into KM. I'll research on it first.


private void drawBeer(Canvas canvas, List beerLocations) {
if(beerIcon == null) {
beerIcon = BitmapFactory.decodeResource(this.getResources(), R.drawable.beer);
}
for(BeerLocation beerLoc : beerLocations) {
float x = (float)(beerLoc.getLongitude() + midpointX - currentLongitude - BEER_ICON_SIZE);
float y = (float)(midpointY - beerLoc.getLatitude() + currentLatitude - BEER_ICON_SIZE);
canvas.drawBitmap(beerIcon, x, y, paintScreenText);
canvas.drawText(beerLoc.toString(), x, y, paintScreenText);
}
}


The ACTIVITY
STEP 1. Set the BeerRadarView as the Activity's contentView


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
beerRadarView = new BeerRadarView(this);
setContentView(beerRadarView);
setCurrentGpsLocation(null);
thread = new Thread(new MyThreadRunner());
thread.start();
}


STEP 2. Create a android.os.Handler that invalidate's the view when called. Invalidate is similar to calling awt/swing component's repaint() method.

"When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing
the top-level application objects (activities, intent receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will than be scheduled in the Handler's message queue and processed when appropriate."


Handler updateHandler = new Handler() {
/** Gets called on every message that is received */
// @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_LOCATION: {
beerRadarView.setCurrentLatitude(latitude);
beerRadarView.setCurrentLongitude(longitude);
break;
}
}
beerRadarView.invalidate();
super.handleMessage(msg);
}
};


STEP 3. implement android.location.LocationListener on the Activity


public class BeerRadar extends Activity implements LocationListener {


- Implement the necessary methods.

@Override
public void onLocationChanged(Location location) {
setCurrentGpsLocation(location);
}

@Override
public void onProviderDisabled(String provider) {
setCurrentGpsLocation(null);

}

@Override
public void onProviderEnabled(String provider) {
setCurrentGpsLocation(null);
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
setCurrentGpsLocation(null);
}

- Use onLocationChanged(Location location) to update the user's current location


private void setCurrentGpsLocation(Location location) {
if (location == null) {
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, this);
location = locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
longitude = location.getLongitude();
latitude = location.getLatitude();
Message msg = new Message();
msg.what = UPDATE_LOCATION;
BeerRadar.this.updateHandler.sendMessage(msg);
}

Testing

Since it just an emulator, you can use the DDMS perspective to update the longitude and latitude.Longitude =50, Latitude = 50

Longitude =120, Latitude = 100

Source Code

Android Camera Capture Tutorial

Reference: http://www.anddev.org/the_pizza_timer_-_threading-drawing_on_canvas-t126.html

Monday, November 24, 2008

Camera Capture

Source code can be downloaded below.

Before creating the Image Capture application, the SDCard must be configured first in order to be able to save in the Picture Gallery of the emulator.

Creating the SDCARD

1. See the emulator -help-sdcard to see where the default sdcard.img is located. Create if necessary.



2. Type mksdcard in the command line to know the required switches in the command-line.


3. Make SD Card.


4. For my SD Card, I just went to the default folder on step#1 and created the default SD-Card.
-cd to the default sd directory
-mksdcard 16M sdcard.img

*Note: the minumum SD required by the emulator is 8Megabytes, I just used 16M to make sure. Also, the higher the size of the SD, the longer it takes to create. So, when you create a 4GB SD, the command line might seem unresponsive.


I've been trying to figure out how to capture images in android and storing it in the picture gallery for 2 weeks now.

I just discovered, that there is nothing wrong with the code, but there's something wrong with the emulator.

For one thing, I keep on getting this image, even when using the built in Camera Application on the main menu. But, I think it is normal. Is it?


The problem I am having is that when I capture an image in the application, the File Explorer (sdcard/dcim/Camera) does save the image file. But when I check the gallery, it does not seem to exist. I then try to push an image to the emulator's file system (sdcard/dcim/Camera), but still the Picture Gallery does not reflect. Below, is a screenshot of what (sdcard/dcim/Camera) contains and what shows in the emulator's picture gallery.



When I restarted the emulator, the Picture Gallery reflected the content's of the file system. Is this a bug or did I just missed a step?


Anyways, here's the code I compiled from various sources using Google Android SDK 1R1.

http://www.anddev.org/viewtopic.php?p=704#704
http://www.anddev.org/viewtopic.php?p=645#645

I also used another site but I forgot the link. So if you see your code here, just drop me a message and I'll link you up as reference. I think the file I downloaded was CameraTestApi.zip but was based on an older SDK. I apologize for the author of the code that I based my revised Image Capture application.


Here the code for the Image Capture Application.

1. Create two classes.
ImageCapture.java


public class ImageCapture extends Activity implements SurfaceHolder.Callback
{
private Camera camera;
private boolean isPreviewRunning = false;
private SimpleDateFormat timeStampFormat = new SimpleDateFormat("yyyyMMddHHmmssSS");

private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
private Uri target = Media.EXTERNAL_CONTENT_URI;

public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
Log.e(getClass().getSimpleName(), "onCreate");
getWindow().setFormat(PixelFormat.TRANSLUCENT);
setContentView(R.layout.main);
surfaceView = (SurfaceView)findViewById(R.id.surface);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public boolean onCreateOptionsMenu(android.view.Menu menu) {
MenuItem item = menu.add(0, 0, 0, "goto gallery");
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(Intent.ACTION_VIEW, target);
startActivity(intent);
return true;
}
});
return true;
}

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

Camera.PictureCallback mPictureCallbackRaw = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera c) {
Log.e(getClass().getSimpleName(), "PICTURE CALLBACK RAW: " + data);
camera.startPreview();
}
};

Camera.PictureCallback mPictureCallbackJpeg= new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera c) {
Log.e(getClass().getSimpleName(), "PICTURE CALLBACK JPEG: data.length = " + data);
}
};

Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
Log.e(getClass().getSimpleName(), "SHUTTER CALLBACK");
}
};


public boolean onKeyDown(int keyCode, KeyEvent event)
{
ImageCaptureCallback iccb = null;
if(keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
try {
String filename = timeStampFormat.format(new Date());
ContentValues values = new ContentValues();
values.put(Media.TITLE, filename);
values.put(Media.DESCRIPTION, "Image capture by camera");
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
//String filename = timeStampFormat.format(new Date());
iccb = new ImageCaptureCallback( getContentResolver().openOutputStream(uri));
} catch(Exception ex ){
ex.printStackTrace();
Log.e(getClass().getSimpleName(), ex.getMessage(), ex);
}
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
return super.onKeyDown(keyCode, event);
}

if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
camera.takePicture(mShutterCallback, mPictureCallbackRaw, iccb);
return true;
}

return false;
}

protected void onResume()
{
Log.e(getClass().getSimpleName(), "onResume");
super.onResume();
}

protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}

protected void onStop()
{
Log.e(getClass().getSimpleName(), "onStop");
super.onStop();
}

public void surfaceCreated(SurfaceHolder holder)
{
Log.e(getClass().getSimpleName(), "surfaceCreated");
camera = Camera.open();
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
Log.e(getClass().getSimpleName(), "surfaceChanged");
if (isPreviewRunning) {
camera.stopPreview();
}
Camera.Parameters p = camera.getParameters();
p.setPreviewSize(w, h);
camera.setParameters(p);
camera.setPreviewDisplay(holder);
camera.startPreview();
isPreviewRunning = true;
}

public void surfaceDestroyed(SurfaceHolder holder)
{
Log.e(getClass().getSimpleName(), "surfaceDestroyed");
camera.stopPreview();
isPreviewRunning = false;
camera.release();
}
}


ImageCaptureCallback.java


public class ImageCaptureCallback implements PictureCallback {

private OutputStream filoutputStream;
public ImageCaptureCallback(OutputStream filoutputStream) {
this.filoutputStream = filoutputStream;
}
@Override
public void onPictureTaken(byte[] data, Camera camera) {
try {
Log.v(getClass().getSimpleName(), "onPictureTaken=" + data + " length = " + data.length);
filoutputStream.write(data);
filoutputStream.flush();
filoutputStream.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}


2. Create the main.xml as shown below









3. Add a camera permission to the ApplicationManifest




Source Code

Monday, October 27, 2008

Android Map Viewer

1. Create MapViewer Project
- Create MapViewer class that extends MapActivity


package javapadawan.android;

import android.os.Bundle;
import android.view.KeyEvent;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class MapViewer extends MapActivity {
private MapView map;
private MapController mc;
private static int zoomValue = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
map = (MapView) findViewById(R.id.map);
mc = map.getController();
mc.setZoom(zoomValue);
}

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
zoomValue = zoomValue + 1;
mc.setZoom(zoomValue);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
zoomValue = zoomValue - 1;
mc.setZoom(zoomValue);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
map.setSatellite(false);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
map.setSatellite(true);
}
return super.dispatchKeyEvent(event);
}

@Override
protected boolean isRouteDisplayed() {
return false;
}
}


2. Modify main.xml







3. Set the required permissions in the AndroidManifest.xml


















4. Clean, Build and Run the Project. You will see an Empty Map Grid instead of a google map.



4. The reason why the code does not work is that the apiKey needs to be a valid key from google. Below are the steps to obtain a valid key.

- Obtaining a Maps API Key

- Edit the main.xml and replace apiKey with the value from google. See below on how to generate an MD5 Certificate fingerprint.



5. Now clean, build and compile again. Click the UP/Down to ZOOM, and LEFT/RIGHT to switch Map View and Satellite View.






6. Now that I got Map View to work, maybe I'll create something useful next time.

Source Code

Sunday, October 19, 2008

Android Simple SQLiteDatabase

Account List
New Account

Modify Account


Source Code can be downloaded below. Also, the syntaxhighlighter for the XML code, has some problems. For some reason, the JS and CSS I used modifies the XML I place. Just check with the code downloadable below for the correct xml files.



New Classes Used:
ListActivity
SQLiteOpenHelper

1. Create new Android Project.
-Activity Name: AccountList

2. Create View Classes and XML

2.1 Create account_list.xml








account_row.xml




2.2. Create ListActivity class AccountList

public class AccountList extends ListActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_list);
initComponents();
}
private void initComponents() {
}
}

2.3 Create account_detail.xml
Below is an example of combining two or more layouts.



















2.4. Create Activity class AccountDetail

public class AccountDetail extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_detail);
initComponents();
}
private void initComponents() {
}
}
3. Create Model Classes
3.1 Create Account class

public class Account {
public static final String COL_ROW_ID = "_id";
public static final String COL_SERVER_NAME = "server_name";
public static final String COL_USERNAME = "username";
public static final String COL_PASSWORD = "password";
public static final String SQL_TABLE_NAME = "account_db";
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ SQL_TABLE_NAME + " "
+ " (_id integer primary key autoincrement, "
+ "server_name text not null, username text not null, "
+ "password text not null); ";

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

private String username;

private String password;

private String serverName;

private int id;
}

3.2 Create ActiveRecord interface
This is the interface that will be extend by POJO to make it a sort-of Active Domain. I'm not really experienced with the Active Domain pattern but this is my understanding of it. So just correct me if I am wrong and I'll change the code.

public interface ActiveRecord {
public long save();
public boolean delete();
public void load(Activity activity);
public Cursor retrieveAll();
public void setSQLiteDatabase(SQLiteDatabase sqliteDb);
}

3.3 Extend SQLiteOpenHelper
I derived MyDatabaseAdapter class from
Tutorial: A Notepad Application, However I made the MyDatabaseAdapter as a utility class instead of placing CRUD functions inside it. I made the Controller a sort-of Active Domain pattern so that the MyDatabaseAdapter can be re-used for future applications.

public class MyDatabaseAdapter extends SQLiteOpenHelper {
private static SQLiteDatabase sqliteDb;
private static MyDatabaseAdapter instance;

private static final String DATABASE_NAME = "simple_sqlite_db";
private static final int DATABASE_VERSION = 1;


private MyDatabaseAdapter(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Account.SQL_CREATE_TABLE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(getClass().getSimpleName(), "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + Account.SQL_TABLE_NAME );
onCreate(db);
}

private static void initialize(Context context) {
if(instance == null) {
instance = new MyDatabaseAdapter(context, DATABASE_NAME, null, DATABASE_VERSION);
sqliteDb = instance.getWritableDatabase();
}
}

public static final MyDatabaseAdapter getInstance(Context context) {
initialize(context);
return instance;
}

public SQLiteDatabase getDatabase() {
return sqliteDb;
}

public void close() {
if(instance != null ) {
instance.close();
instance = null;
}
}
}

3.4 Create implementation of ActiveRecord on Account class
Here's the code that implements the CRUD methods of the ActiveRecord interface. I also learned that from the Notepad Tutorial, since the Android Docs, does not really explain how to use SQLiteDB, or maybe I just do not know where to look.

public class Account implements ActiveRecord {

public static final String COL_ROW_ID = "_id";
public static final String COL_SERVER_NAME = "server_name";
public static final String COL_USERNAME = "username";
public static final String COL_PASSWORD = "password";

public static final String SQL_TABLE_NAME = "account_db";
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ SQL_TABLE_NAME + " "
+ " (_id integer primary key autoincrement, "
+ "server_name text not null, username text not null, "
+ "password text not null); ";

private SQLiteDatabase sqliteDatabase;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

private String username;

private String password;

private String serverName;

private int id;

@Override
public void load(Activity activity) {
Cursor cursor = sqliteDatabase.query(true, SQL_TABLE_NAME,
new String[] { COL_ROW_ID, COL_SERVER_NAME, COL_USERNAME,
COL_PASSWORD }, COL_ROW_ID + "=" + id, null, null,
null, null, null);
if (cursor != null) {
cursor.moveToFirst();
activity.startManagingCursor(cursor);
setId(cursor.getInt(cursor
.getColumnIndex(COL_ROW_ID)));
setPassword(cursor.getString(cursor
.getColumnIndex(COL_PASSWORD)));
setUsername(cursor.getString(cursor
.getColumnIndex(COL_USERNAME)));
setServerName(cursor.getString(cursor
.getColumnIndex(COL_SERVER_NAME)));
}
}

@Override
public Cursor retrieveAll() {
return sqliteDatabase.query(SQL_TABLE_NAME, new String[] { COL_ROW_ID,
COL_SERVER_NAME, COL_USERNAME, COL_PASSWORD }, null, null,
null, null, null);
}

@Override
public long save() {
ContentValues values = new ContentValues();
if (id <= 0) { values.put(COL_SERVER_NAME, serverName); values.put(COL_USERNAME, username); values.put(COL_PASSWORD, password); return sqliteDatabase.insert(SQL_TABLE_NAME, null, values); } else { values.put(COL_SERVER_NAME, serverName); values.put(COL_USERNAME, username); values.put(COL_PASSWORD, password); return sqliteDatabase.update(SQL_TABLE_NAME, values, COL_ROW_ID + "=" + id, null); } } public boolean delete() { return sqliteDatabase.delete(SQL_TABLE_NAME, COL_ROW_ID + "=" + id, null) > 0;
}

@Override
public void setSQLiteDatabase(SQLiteDatabase sqliteDatabase) {
this.sqliteDatabase = sqliteDatabase;
}
}

4. Create Controllers
4.1 AccountList Controllers
For the AccountList I used the OptionsMenu.

public class AccountList extends ListActivity {
/** Called when the activity is first created. */
private MyDatabaseAdapter myDatabaseAdapter;

private static final int INTENT_NEXT_SCREEN = 0;
public static final String INTENT_EXTRA_SELECTED_ROW = "SELECTED_ROW";

private static final int INSERT_ID = Menu.FIRST;
private static final int DELETE_ID = Menu.FIRST + 1;
private static final int EXIT_ID = DELETE_ID + 1;
private Account account = new Account();
private Intent intent;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_list);
myDatabaseAdapter = MyDatabaseAdapter.getInstance(this);
intent = new Intent(this, AccountDetail.class);
initComponents();
}

private void initComponents() {
account.setSQLiteDatabase(myDatabaseAdapter.getDatabase());
Cursor recordsCursor = account.retrieveAll();
startManagingCursor(recordsCursor);
String[] from = new String[] { Account.COL_SERVER_NAME };
int[] to = new int[] { R.id.tfServerName };
SimpleCursorAdapter records = new SimpleCursorAdapter(this,
R.layout.account_row, recordsCursor, from, to);
setListAdapter(records);
}

@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case INSERT_ID:
createRecord();
return true;
case DELETE_ID:
account.setId((int) getListView().getSelectedItemId());
account.delete();
initComponents();
return true;
case EXIT_ID:
finish();
}
return super.onMenuItemSelected(featureId, item);
}

private void createRecord() {
intent.putExtra(INTENT_EXTRA_SELECTED_ROW, 0);
startActivityForResult(intent, INTENT_NEXT_SCREEN);
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Log.v(getClass().getSimpleName(), "id=" + id);
intent.putExtra(INTENT_EXTRA_SELECTED_ROW, id);
startActivityForResult(intent, INTENT_NEXT_SCREEN);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.btnAdd);
menu.add(0, DELETE_ID, 0, R.string.btnDelete);
menu.add(0, EXIT_ID, 0, R.string.btnExit);
return true;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
initComponents();
}
}
4.2 AccountDetail Controllers

public class AccountDetail extends Activity implements OnClickListener {

private MyDatabaseAdapter myDatabaseAdapter;
private long selectedRow;
private TextView tvId;
private EditText etServerName, etUserName, etPassword;
private Button btnSave, btnCancel;
private Account account;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_detail);
myDatabaseAdapter = MyDatabaseAdapter.getInstance(this);
initComponents();
}

private void initComponents() {
selectedRow = getIntent().getLongExtra(
AccountList.INTENT_EXTRA_SELECTED_ROW, 0);
tvId = (TextView) findViewById(R.id.tvId);
etServerName = (EditText) findViewById(R.id.etServerName);
etUserName = (EditText) findViewById(R.id.etUsername);
etPassword = (EditText) findViewById(R.id.etPassword);

account = new Account();
account.setSQLiteDatabase(myDatabaseAdapter.getDatabase());
Log.v(getClass().getSimpleName(), "selectedRow=" + selectedRow);
account.setId((int) selectedRow);
if (selectedRow > 0) {
account.load(this);
}
Log.v(getClass().getSimpleName(), "account.getId()=" + account.getId());
if (account.getId() > 0) {
tvId.setText(account.getId() + "");
etServerName.setText(account.getServerName());
etUserName.setText(account.getUsername());
etPassword.setText(account.getPassword());
} else {
tvId.setText("new");
}
btnSave = (Button) findViewById(R.id.btnSave);
btnSave.setOnClickListener(this);
btnCancel = (Button) findViewById(R.id.btnCancel);
btnCancel.setOnClickListener(this);
}


Source Code
If you find something confusing from this tutorial, just post a comment, and I'll fix it for you.


In order to display multiple items on the list, just edit account_row.xml as shown below.


android:layout_width="fill_parent" android:layout_height="wrap_content">
android:layout_width="wrap_content" android:layout_alignParentRight="false"
android:layout_height="wrap_content" android:text="Server Name" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_toRightOf="@id/tfServerName"
android:text="Username"/>



Also modify AccountList.java to display the fields you want.


String[] from = new String[] { Account.COL_SERVER_NAME, Account.COL_USERNAME};
int[] to = new int[] { R.id.tfServerName, R.id.tfUsername };



Email

java.padawan@androidph.com