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

9 comments:

Nadine said...

Thanks for the nice tutorials!
Maybe this link could be helpful for your conversion problem:

http://www.movable-type.co.uk/scripts/latlong.html

Nadine

Nadine said...

Thanks for the nice tutorial. Maybe this link could be meaningful for your conversion problem:

http://www.movable-type.co.uk/scripts/latlong.html

Nadine

carterson2 said...

Q: How do you get the emulator to attach inside that eclipse window?

Mine is always separate so I spend all day moving windows around so I can see it.

thanks
gpscruise@gmail.com

java.padawan said...

Hi carterson2,

I never had this issue before. Might be worth to ask this in Android Forum.

Regards

lizh said...

I loaded the source code, but keep getting an error that forces it to quit. What am I doing wrong? Thanks.

ComputerTutorials said...

Very well explained.

Anonymous said...

umb, the source code link is broken

Anonymous said...

this is a good tutorial can you please post the full code because the link for the full code is broken.thank you

Anonymous said...

Hi can you share the full source code for this.the link is broke.

Thanks

Email

java.padawan@androidph.com