SnuggleMe: Erfahrungsbericht bei der Migration auf Google Maps Android API Version 2

In unserem letzten Lab haben wir in unserer App (SnuggleMe) die Google Maps Android API auf Version v2 migriert. Informationen zum bisherigen Stand können im Blogpost Find someone to snuggle with or “The ultimate Snuggle Finder” nachgelesen werden.

Die wichtigsten Funktionalitäten der neuen API werden im Folgenden erläutert.

Anforderungen

Die bisherigen Anforderungen an die Map sind unverändert geblieben und lauteten wie folgt:
Es werden Marker auf der Karte angezeigt, die Aktivitäten (eigene und von anderen Benutzern) darstellen. Diese Aktivitäten werden nur innerhalb eines bestimmten Radius um die aktuelle Position angezeigt. Wird ein Marker angeklickt, öffnet sich ein Info-Fenster mit weiteren Informationen zur Aktivität.

Initialisierung

Die Karte zum Anzeigen der Marker ist in der neuen V2 API nun ein MapFragment statt einer MapActivity. Das Fragment muss hierzu in einem Layout defniert werden:


<fragment
   android:id="@+id/map"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   class="com.google.android.gms.maps.MapFragment" />

Von diesem Fragment erhalten wir in unserer Activity eine Instanz der Klasse GoogleMap.


GoogleMap map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();

Mit Hilfe dieser Instanz lässt sich die angezeigte Karte steuern und verändern.

Funktionalität

Die aktuelle Position des Benutzers auf der Map darstellen

Um die aktuelle Position auf der Karte darzustellen, muss GoogleMap.setMyLocationEnabled(true) aufgerufen werden.

Letzte Position ermitteln und auf der Karte darstellen

Um die letzte Position auf der Karte zu ermitteln muss man den LocationClient verwenden. Der LocationClient wird folgendermaßen initalisiert.

LocationClient locationClient = new LocationClient(this, this, this);

An den LocationClient müssen drei Objekte übergeben werden:

  • Context
  • GooglePlayServicesClient.ConnectionCallbacks
  • GooglePlayServicesClient.OnConnectionFailedListener

Unsere Activity, die die Location benötigt, ist bereits ein Context und die übrigen Interfaces werden implementiert. Deshalb wurde hier dreimal this übergeben. Interessant war in diesem Fall nur das Interface GooglePlayServicesClient.ConnectionCallbacks. Hier wurden die beiden Methoden onConnected(Bundle bundle) und onDisconnected wie folgt implementiert:


@Override
public void onConnected(Bundle bundle) {
    Location location = locationClient.getLastLocation();
    zoomToLocation(location);
}

Um auf die aktuelle Position zu zoomen muss folgende Methode implementiert werden.


private void zoomToLocation(Location location) {
    LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
    CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, 14);
    map.animateCamera(cameraUpdate);
}

Zusätzlich muss von der Activity das Interface GooglePlayServicesClient.OnConnectionFailedListener implementiert werden. Dieses Interface wird benötigt, um z.B. eine Fehlermeldung anzuzeigen, wenn keine Verbindung zum LocationClient hergestellt werden konnte.

Darstellung der Marker auf der Karte

Damit die Marker auf der Karte dargestellt werden können muss die Activity, auf der die Karte dargestellt werden soll, als OnCameraChangeListener registriert werden.

map.setOnCameraChangeListener(this);

Dazu muss die Activity folgende Methode implementieren:

@Override
public void onCameraChange(CameraPosition cameraPosition) {
    loadModelsAndShowOnMap(cameraPosition.target);
}

Die Methode loadModelsAndShowOnMap erstellt einen neuen Thread in dem die anzuzeigenden Aktivitäten vom Server geladen und zur Karte als Marker hinzugefügt werden. Das Anzeigen selbst wird durch die Methode runOnUiThread vorgenommen, da eine direkte Interaktion mit Objekten der Oberfläche nicht aus einem separaten Thread möglich ist.

private void loadModelsAndShowOnMap(final LatLng center) {

    this.locationUpdateThread = new Thread(new Runnable() {

        @Override
        public void run() {
            synchronized (ExampleActivity.this) {
            double mapLongitude = center.longitude;
            double mapLatitude = center.latitude;
            ExampleActivity.this.models =
                getModelManagerApplication().getModelService()
                .searchModels(mapLongitude, mapLatitude, SEARCH_DISTANCE);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    displayModels(ExampleActivity.this.models);
                }
            });
         }
       }
    });

    locationUpdateThread.start();
}

Das Hinzufügen zur Karte geschieht letztendlich in der Methode displayModels mit dem Aufruf map.addMarker(getMarker(model, modelBitmap));


private void displayModels(List models) {
    for (Model model : models) {
        if (map != null) {
            if(!isModelAlreadyShownOnMap(model)){
                // Eine eigene Grafik als Marker auf der Map darstellen
                Bitmap modelBitmap =
                 categoryDrawables.get(CategoryType.valueOf(model.getCategory()));
                Marker modelMarker = map.addMarker(getMarker(model, modelBitmap));
                addModel(modelMarker, model);
            }
        }
    }
}

Management der Marker

Jetzt wird in der Methode addModel das Model der ModelMarkerMap hinzugefügt. Diese Map merkt sich alle bereits hinzugefügten Marker/Aktivitäten. Mit dieser prüft die Methode isModelAlreadyShownOnMap ob der Marker bereits auf der Map dargestellt wird. Außerdem können über diese Map die hinzugefügten Models abgerufen werden.


private void addModel(Marker modelMarker, Model model) {
    modelsShownOnMap.add(model.getId());
    modelMarkerMap.put(modelMarker, model);
}

private boolean isModelAlreadyShownOnMap(Model model) {
    if(modelsShownOnMap.contains(model.getId())){
        return true;
    }else{
        return false;
    }
}

Darstellung eines Info-Fensters

Wenn ein Marker auf der Karte angeklickt wurde, dann soll ein Info-Fenster dargestellt werden. Um das zu erreichen muss die Activity als InfoWindowAdapter registiert werden.

map.setInfoWindowAdapter(this);

Die Activity muss dafür die Methoden View getInfoWindow(Marker marker) und View getInfoContents(Marker marker) implementieren.

@Override
public View getInfoWindow(Marker marker) {
    Party model = modelMarkerMap.get(marker);
    View view = inflater.inflate(R.layout.party_overlay_info, null);
    TextView size = (TextView)view.findViewById(R.id.sizeValue);
    size.setText(model.getText());
    return view;
}

Fazit

Durch die Umstellung auf die GoogleMaps Version 2 konnten wir einiges an Code entsorgen und der übrig gebliebene Code ist klarer und selbsterklärend.

  • Es muss keine MapActivity mehr verwendet werden. Somit ist es möglich die Karte als Fragment zu benutzen.
  • Marker können ohne Overlays der GoogleMap hinzugefügt werden.
  • Der recht komplizierte Code der Map V1 API mit seinen Overlays und Items wurde mittels der Marker stark vereinfacht.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s