Monday, December 31, 2012

Move map to my location - Google Maps Android API v2

On request, here's a very small tutorial on how to move the map to your location in the new API.
Have a look at this code:

private void moveMapToMyLocation() {

  LocationManager locMan = (LocationManager)getActivity().getSystemService(Context.LOCATION_SERVICE);

  Criteria crit = new Criteria();

  Location loc = locMan.getLastKnownLocation(locMan.getBestProvider(crit, false));

  

  CameraPosition camPos = new CameraPosition.Builder()

   .target(new LatLng(loc.getLatitude(), loc.getLongitude()))

   .zoom(12.8f)

   .build();

  CameraUpdate camUpdate = CameraUpdateFactory.newCameraPosition(camPos);

  getMap().moveCamera(camUpdate);

  

 }

This is a method I made, you can place it somewhere in your custom MapFragment or in your Activity.

It's important to call this moment at the right time. We have to make sure that the map is loaded before we ask it to move to somewhere else:

  • in custom MapFragment: call the method somewhere in onActivityCreated(Bundle)
  • in Activity: call the method somewhere in onStart()
Moving the camera (your view of the map) around is possible by making a CameraPosition object, and using that to make the right CameraUpdate object.

Once we have our CameraUpdate object, we can choose to use moveCamera(CameraUpdate) or animateCamera(CameraUpdate). With moveCamera, the view will instantly change and with animateCamera you'll see the map moving on the screen towards the destination that we want. There are multiple arguments possible for these methods, please check the android API here for more information.

Good luck!

Thursday, December 27, 2012

Using InfoWindowAdapter Part 2: getInfoWindow

As promised, here's the second part on how to use the InfoWindowAdapter in Google Maps Android API v2. This is a sequel to my last blog post and also to the post before that. So be sure to check that out first.

In my last blog I told you how to change the contents of your InfoWindow without changing the default window layout. Now I'll show you how to change that window as well. First of all let me quote something that I read on the Android developers site:
The API will first call getInfoWindow(Marker) and if null is returned, it will then callgetInfoContents(Marker). If this also returns null, then the default info window will be used.
So this means you have to implement the contents of your InfoWindow in getInfoWindow(Marker) as well, if you want to change the window. The API will ignore what's in getInfoContents(Marker) if getInfoWindow(Marker) does not return null.

This is a screenshot of what our InfoWindow should look like:

You'll see I've copied the code of getInfoContents(Marker) of my last post and used this in getInfoWindow(Marker). I also did this with my xml layout file.

First of all, we're going to create this oval shape with the gradient blue background. We'll use this as background of our window. I've named it circle_window.xml and placed it in (one of) my drawable folder(s).


<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" >

    <gradient android:startColor="#FFA1A4E3" android:endColor="#AFA1CDED"

            android:angle="270"/>

</shape>

Next I made a new layout file called custom_window.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="wrap_content"

  android:layout_height="wrap_content"

  android:orientation="horizontal"

  android:background="@drawable/circle_window"

  android:padding="15dp">

  <ImageView

    android:id="@+id/ivInfoWindowMain"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_marginRight="5dp"

    android:adjustViewBounds="true"

    android:src="@drawable/ic_launcher">

  </ImageView>

  <LinearLayout

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:orientation="vertical">

    <TextView

      android:id="@+id/txtInfoWindowTitle"

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:layout_gravity="center_horizontal"

      android:ellipsize="end"

      android:singleLine="true"

      android:textColor="#ff000000"

      android:textSize="14dp"

      android:textStyle="bold"/>

    <TextView

      android:id="@+id/txtInfoWindowEventType"

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:ellipsize="end"

      android:singleLine="true"

      android:textColor="#ff7f7f7f"

      android:textSize="14dp"/>

  </LinearLayout>

</LinearLayout>

It's basically exactly the same as in my previous post, except for two things.

  • set the oval shape as background of my layout.
  • set a padding of about 15dp so that our contents is placed nicely within our oval shape
To finish of we need to implement our InfoWindowAdapter like this:

mapFragment.getMap().setInfoWindowAdapter(new InfoWindowAdapter() {

   

   private final View window = getLayoutInflater().inflate(R.layout.custom_window, null);

   

   @Override

   public View getInfoWindow(Marker marker) {

    EventInfo eventInfo = eventMarkerMap.get(marker);

    

    String title = marker.getTitle();

             TextView txtTitle = ((TextView) window.findViewById(R.id.txtInfoWindowTitle));

             if (title != null) {

                 // Spannable string allows us to edit the formatting of the text.

                 SpannableString titleText = new SpannableString(title);

                 titleText.setSpan(new ForegroundColorSpan(Color.RED), 0, titleText.length(), 0);

                 txtTitle.setText(titleText);

             } else {

                 txtTitle.setText("");

             }

             

             TextView txtType = ((TextView) window.findViewById(R.id.txtInfoWindowEventType));

             txtType.setText(eventInfo.getType());

             

    return window;

   }

   

   @Override

   public View getInfoContents(Marker marker) {

    //this method is not called if getInfoWindow(Marker) does not return null

    return null;

   }

  });

As always, if you have any questions, feel free to leave a comment below and I'll see what I can do.

Download source code here

Good luck!

Wednesday, December 26, 2012

Using InfoWindowAdapter Part 1: getInfoContents

This is the first part of my next tutorial for Google Maps Android API v2.
It's about using the InfoWindowAdapter to make awesome looking InfoWindows for your Marker.

Here I'm going to show you how to implement this adapter and how to implement the method getInfoContents(Marker marker). This will only change what's inside the InfoWindow and not how the window itself looks. In this simple example I'm simply going to add an image to the InfoWindow, change the title a bit and add a String from a custom object to it.

You might want to check my previous post on adding an object to a marker if you haven't seen it yet, I'll continue using that project for this tutorial as well.

This is how it should look when you've correctly implemented everything:

I've changed my EventInfo class a bit to add an String called "type" to it. Some of you asked for this code so I'm posting the class here:

public class EventInfo {

 

 private LatLng latLong;

 private String name;

 private Date someDate;

 private String type;

 

 public EventInfo(LatLng latLong, String name, Date someDate, String type) {

  super();

  this.latLong = latLong;

  this.name = name;

  this.someDate = someDate;

  this.type = type;

 }

 

 public LatLng getLatLong() {

  return latLong;

 }

 public void setLatLong(LatLng latLong) {

  this.latLong = latLong;

 }

 public String getName() {

  return name;

 }

 public void setName(String name) {

  this.name = name;

 }

 public Date getSomeDate() {

  return someDate;

 }

 public void setSomeDate(Date someDate) {

  this.someDate = someDate;

 }



 public String getType() {

  return type;

 }



 public void setType(String type) {

  this.type = type;

 }

}
So I've also made a small change in the setUpEventSpots() to initiate the EventInfo objects:
EventInfo firstEventInfo = new EventInfo(new LatLng(50.154, 4.35), "Right now - event", new Date(), "Party");

  EventInfo secondEventInfo = new EventInfo(new LatLng(51.25, 4.15), "Future Event", new Date(1032, 5, 25), "Convention");

Now let's setup a layout file for our contents. It's just a normal xml-file. I've placed an ImageView in it, with the launcer icon. You can change this picture if you want. For example use a picture of the object that the marker represents. Here's the code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="wrap_content"

  android:layout_height="wrap_content"

  android:orientation="horizontal">

  <ImageView

    android:id="@+id/ivInfoWindowMain"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_marginRight="5dp"

    android:adjustViewBounds="true"

    android:src="@drawable/ic_launcher">

  </ImageView>

  <LinearLayout

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:orientation="vertical">

    <TextView

      android:id="@+id/txtInfoWindowTitle"

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:layout_gravity="center_horizontal"

      android:ellipsize="end"

      android:singleLine="true"

      android:textColor="#ff000000"

      android:textSize="14dp"

      android:textStyle="bold"/>

    <TextView

      android:id="@+id/txtInfoWindowEventType"

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:ellipsize="end"

      android:singleLine="true"

      android:textColor="#ff7f7f7f"

      android:textSize="14dp"/>

  </LinearLayout>

</LinearLayout>

Just have a good look at this code and check the screenshot above to compare.

Next up, let's finally use the InfoWindowAdapter :-)
I've added this code at the end of my setUpEventSpots() method:

mapFragment.getMap().setInfoWindowAdapter(new InfoWindowAdapter() {

   

   private final View contents = getLayoutInflater().inflate(R.layout.content, null);

   

   @Override

   public View getInfoWindow(Marker marker) {

    //Only changing the content for this tutorial

    //if you return null, it will just use the default window

    return null;

   }

   

   @Override

   public View getInfoContents(Marker marker) {

    

    EventInfo eventInfo = eventMarkerMap.get(marker);

    

    String title = marker.getTitle();

             TextView txtTitle = ((TextView) contents.findViewById(R.id.txtInfoWindowTitle));

             if (title != null) {

                 // Spannable string allows us to edit the formatting of the text.

                 SpannableString titleText = new SpannableString(title);

                 titleText.setSpan(new ForegroundColorSpan(Color.RED), 0, titleText.length(), 0);

                 txtTitle.setText(titleText);

             } else {

                 txtTitle.setText("");

             }

             

             TextView txtType = ((TextView) contents.findViewById(R.id.txtInfoWindowEventType));

             txtType.setText(eventInfo.getType());

             

    return contents;

   }

  });

Try this out and if you're having any problems, just leave a comment below and I'll see what I can do.
Be sure to check back for part 2 where I'm going to make a custom window for the InfoWindow, perhaps I'll try something like a circle or something. I don't know yet.
It should be online somewhere in the next two days.

Happy Programming

Sunday, December 23, 2012

Add Information/Object to Marker in Google Maps Android API v2

As some of you might know, Google recently launched a completely new API for Google Maps on Android. Therefor, I'm going to post some tutorials on this. I'm going to talk about linking an object to a Marker. If you haven't tried the new API yet, get started by reading this post on the Android Developers Blog.

Let's start by making a quick example application that works with a self-made class called EventInfo. This class holds a LatLng-object for the location, a String for the name and let's put in a Date.
I've created a class called MainMapFragment that extends the MapFragment of Google Play Services. Here's the code for that:
public class MainMapFragement extends MapFragment {

 

 public Marker placeMarker(EventInfo eventInfo) {

  Marker m  = getMap().addMarker(new MarkerOptions()

   .position(eventInfo.getLatLong())

   .title(eventInfo.getName()));

  

  return m;

 }

}
I'm going to show you why i've made the placeMarker() method in just a second.
First let us have a look at our xml layout file:
<?xml version="1.0" encoding="utf-8"?>

 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:id="@+id/map"

  android:layout_width="match_parent"

  android:layout_height="match_parent"/>
It's just that simple, so let's have a look at our MainActivity where every thing is managed:
public class MainActivity extends Activity {



 private MainMapFragement mapFragment;

 private HashMap<Marker, EventInfo> eventMarkerMap;



 @Override

 protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_main);

  mapFragment = new MainMapFragement();

  FragmentTransaction ft = getFragmentManager().beginTransaction();

  ft.add(R.id.map, mapFragment);

  ft.commit();

  

 }

 

 @Override

 protected void onStart() {

  super.onStart();

  setUpEventSpots();

 }







 private void setUpEventSpots() {

  // I'm going to make 2 EventInfo objects and place them on the map

  EventInfo firstEventInfo = new EventInfo(new LatLng(50.154, 4.35), "Right now - event", new Date());

  EventInfo secondEventInfo = new EventInfo(new LatLng(51.25, 4.15), "Future Event", new Date(1032, 5, 25));

  //this date constructor is deprecated but it's just to make a simple example

  

  

  

  Marker firstMarker = mapFragment.placeMarker(firstEventInfo);

  Marker secondMarker = mapFragment.placeMarker(secondEventInfo);

  

  eventMarkerMap = new HashMap<Marker, EventInfo>();

  eventMarkerMap.put(firstMarker, firstEventInfo);

  eventMarkerMap.put(secondMarker, secondEventInfo);

  

  //add the onClickInfoWindowListener

  mapFragment.getMap().setOnInfoWindowClickListener(new OnInfoWindowClickListener() {

   

   @Override

   public void onInfoWindowClick(Marker marker) {

    EventInfo eventInfo = eventMarkerMap.get(marker);

    Toast.makeText(getBaseContext(),

      "The date of " + eventInfo.getName() + " is " + eventInfo.getSomeDate().toLocaleString(),

      Toast.LENGTH_LONG).show();

    

   }

  });

  

 }



}
So in the onCreate() method, I'm making a private MainMapFragment object that I place in my FrameLayout of the Activity. Then I'm setting up 2 simple EventInfo objects and call the method placeMarker() for each object. This places a Marker with the name as title and returns that Marker.

Next up is the important part. We're going to make a HashMap that stores the EventInfo object with the Marker as key. That way, we have linked the Marker with the object. To show you what we can do with this, we have to implement the onClickInfoWindowListener on the map of our MainMapFragment.
We are expected to implement the method onInfoWindowClick() that gives us the Marker object. We get the clicked marker and search the correct EventInfo object in the HashMap.
To show a result, I just made a simple Toast message that tells us the name of the EventInfo and the date.

Here are some screenshots:


Important note: I've called the setUpEventSpots() method in the onStart() method. This is because the map needs to get loaded first and you'd get a NullPointerException on getMap() if I would called it in the onCreate() method.

Feel free to leave comments below

Wednesday, December 19, 2012

Using AsyncTask

A very usefull class in Android is the AsyncTask.

It's an easy way to make your application do something in background and bringing the results of this action to the UI. In this post, I'm going to show you how you can use AsyncTask in your application.
I made a simple App that generates a random number between 0 and 10 every second. After 7 times, it returns the sum of these generated values. In the meanwhile, each time a number is generated, the user will be informed about it.

First of all

Before we start the AsyncTask to do its job, we have to give it some parameters to work with.
While working, we want to see the progress of the AsyncTask.
And of course, eventually, we want to have a result.

If we want to create an AsyncTask, we need to specify these 3 things. Our AsyncTask class will extend AsyncTask<Params, Progress, Result>

In this case, we will make a RandomCountingTask.
The parameters we want to give to the task are the number of times it should generate a number and how large this generated number may be. These are both Integers, in this case 10 the maximum value of a number and 7 for the amount of numbers we want.

The Progress in this case is the generated number, so this is also an Integer.

The Result will also be an Integer, the sum of all generated numbers.

So our task has to look like this:

class RandomCountingTask extends AsyncTask<Integer, Integer, Integer>{ }

If we would write this in Eclipse, we would have to add unimplemented methods. This is the method doInBackground(Integer... params){}


AsyncTask 'Lifecycle'

Since the AsyncTask is created to do stuff in background and bring results and progress to the UI, it has methods on the Main Thread (UI) and methods on a single Worker Thread (Background).
This is how it goes:



Code

Now without further ado, here's the code of this simple example:


package com.bonappetit.demos.asynctask;

import java.util.Random;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class AsyncTaskDemo extends Activity implements OnClickListener {

 private TextView txtMain;
 private Button btnStartStop;
 private AsyncTask<Integer, Integer, Integer> countingTask;
 private boolean isCounting;



 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_async_task_demo);
  setUpViews(); 

 }

 private void setUpViews() {
  txtMain = (TextView)findViewById(R.id.txtAsyncTaskDemoMainText);
  btnStartStop = (Button)findViewById(R.id.btnAsyncTaskDemoStartStop);
  btnStartStop.setOnClickListener(this);
  btnStartStop.setText("Start"); 

 }



 @Override
 public void onClick(View v) {
  if(v == btnStartStop){
   if(!isCounting){
    txtMain.setText("");//clear the text
    countingTask = new RandomCountingTask(); // create a new task
    //a task can be executed only once;
    Integer[] values = new Integer[2];
    values[0] = 10; //number between 0 and 10
    values[1] = 7; //generate 7 times
    countingTask.execute(values);
   }else{
    countingTask.cancel(true);
   }
  }  

 }

 public boolean isCounting() {
  return isCounting;
 }

 public void setCounting(boolean isCounting) {
  this.isCounting = isCounting;
 }


 class RandomCountingTask extends AsyncTask<Integer, Integer, Integer>{


  @Override
  protected void onPreExecute() {
   super.onPreExecute();
   Toast.makeText(AsyncTaskDemo.this, "Started counting", Toast.LENGTH_SHORT).show();
   setCounting(true);
   btnStartStop.setText("Stop");
  }

  @Override
  protected Integer doInBackground(Integer... params) {
   int maxValue = params[0];
   int maxTimes = params[1];
   int sum = 0;
   int randomNumber;
   Random r = new Random();
   for(int i = 0; i<maxTimes; i++){
    randomNumber = r.nextInt(maxValue);
    sum += randomNumber;
    publishProgress(randomNumber);   

    try {
     Thread.sleep(1000); //Sleep for 1 second
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   return sum;
  }  

  @Override
  protected void onProgressUpdate(Integer... values) {
   super.onProgressUpdate(values);
   txtMain.append("Added " + values[0] + " to the sum\n");
  }



  @Override
  protected void onPostExecute(Integer result) {
   super.onPostExecute(result);
   setCounting(false);
   Toast.makeText(AsyncTaskDemo.this, "Stopped counting", Toast.LENGTH_SHORT).show();
   txtMain.append("Result is " + result);
  }  

  @Override
  protected void onCancelled() {
   super.onCancelled();
   setCounting(false);
   Toast.makeText(AsyncTaskDemo.this, "Counting was cancelled", Toast.LENGTH_SHORT).show();  
  }
 }

}


Important notes

  • There is a limit of AsyncTasks to use in your application (another post on this will follow)
  • An instance of an AsyncTask can only be executed once.
  • Something like Integer... can be one or more integers

Tuesday, December 18, 2012

Hi there!

Hi and welcome to Bon App-etit!

Let me introduce myself:
My name is Fré Dumazy and I'm a 22-year old computer science student from Belgium.
Lately I've been programming quite a lot in Android and that's why I wanted to start this blog.
Since Android names all its versions after snacks, I think "Bon App-etit" would be a good name for the blog.

I've been looking for a lot of good Android tutorials on the web, regarding specific subjects such as using the ActionBar, Google Maps integration, connecting to a remote database, etc.
I've found some good tutorials on these subjects but not on all of them.

I will try to write some good posts on specific subjects with example code, screenshots, etc. whenever I've got the time. So stay tuned!

PS: If there is a certain subject you would like to see in this blog, please place a comment and I'll see what I can do.