App-Entwicklung zum CampCockpit

Zur kürzlich ins Leben gerufenen Verwaltungssoftware für Campingplätze namens CampCockpit, haben wir eine Android-App entwickelt. Mit dieser App sollen Platzverwalter direkt auf dem Zelt- oder Campingplatz Informationen aus der Webapplikation abrufen können und jederzeit über ein Smartphone oder Tablet auf dem aktuellen Stand sein, Daten abrufen und Vorkommnisse einsehen.

Bei der AS infotrack habe ich meine vierjährige Lehre als Informatikerin mit der Fachrichtung Applikationsentwicklung absolviert. Meine IPA (Interdisziplinäre Arbeit, betriebliche Lehrabschluss-Prüfung) bestand darin, eine App zur bestehenden Webapplikation CampCockpit zu erstellen. Wie bereits in einem Blog erwähnt, dient das CampCockpit zur Verwaltung von Campingplätzen. Damit der Verwalter unabhängig und flexibel ist, soll die App eine Übersicht über die Belegung des Campingplatzes, sowie zu den anreisenden und abreisenden Kunden liefern.

In diesem Blog-Beitrag erfahren Sie, wie ich bei der Umsetzung meiner IPA vorgegangen bin. Vorgängig wurde ich von Pascal Müller in die Welt der Android-App-Entwicklung eingeführt. Er hat mir als Fachvorgesetzten alle Basics beigebracht und mich so auf die bevorstehende Aufgabe geschult. Ausserdem las ich mich mithilfe von online Tutorials noch tiefer in die jeweiligen Unterthemen ein.

Das grundlegende Ziel des Projekts ist eine einfache, übersichtliche und schnelle App als Erweiterung der Webapplikation CampCockpit zu programmieren. Die App ist nicht für den Kunden gedacht sondern nur für den Verwalter des Campingplatzes.



1.Planung der App-Entwicklung

Die Planungsphase war ein wichtiger Bestandteil meiner IPA. Mithilfe eines Zeitplans und einer Checkliste versuchte ich den Aufwand der einzelnen Implementationen einzuschätzen. Ausserdem musste ich ausreichende Reserveblöcke einrechnen, um über genügend Zeit für Unvorhergesehenes zu verfügen. Während der Planung setzte ich mich nicht nur mit dem Aussehen der App auseinander, sondern auch mit den möglichen Techniken und den Funktionalitäten der App.

Implementation des Prototyps

Zu Beginn musste ich mir klar werden, wie meine App am Schluss aussehen sollte und wie ich die Bedienung am benutzerfreundlichsten umsetzen kann. Ich hatte verschiedene Möglichkeiten meine App zu gestalten. Darum habe ich verschiedene Prototypen skizziert. Ich zeige hier zwei mögliche Prototypen mit ihren Vor- und Nachteilen auf.

Die Vorteile des ersten Prototyps sind die folgenden: Durch die Icons auf den Buttons ist die App auch für Personen mit Sehschwäche oder Analphabeten einfach zu bedienen. Man muss sich nicht auf den Text fixieren. Da die Buttons gross sind, trifft man schnell den gewünschten Knopf. Der Wiedererkennungswert zur Webapplikation ist gegeben, weil dieselben Icons verwendet werden. Zudem ist die Kachelansicht der Buttons bekannt, da Android Betriebssysteme ebenfalls so aufgebaut sind.
Es gibt jedoch auch Nachteile: Die Buttons nehmen sehr viel Platz ein und bieten darum kaum eine Möglichkeit mehr Informationen auf dem Startbildschirm anzuzeigen.

Der zweite Prototyp hat ebenfalls seine Stärken und Schwächen. Die App ist ergonomisch für Links- und Rechtshänder. Zudem kann die App mit beiden Händen bedient werden. Da die Buttons sehr nahe beieinander sind, ist die Bedienung jedoch erschwert. Für Icons hat es kaum Platz.

2.Technische Umsetzung des Prototyps

Webservice

Webservices bieten die Möglichkeit, mithilfe des Internets, Anfragen zu stellen. Man kann Daten senden, abfragen oder auch ändern. Eine Anfrage an einen Webservice nennt man Request.

Der grosse Vorteil ist, dass jede Anfrage in sich geschlossen ist. Jeder Request enthält alle Informationen, die der Server benötigt. So ist man Client (setzt Request ab) – Server (Webservice) unabhängig. Folgende standardisierte HTTP-Methoden werden genutzt:

Damit ein konkreter Webservice angesprochen werden kann, gibt es bestimmte URI (Uniform Ressource Identifier). Die Kommunikation zwischen Client und Server ist auf Abruf. Der Client (aktiv) adressiert über URIs die Anfrage direkt an den Server (passiv). Zudem sind Webservices mit jeder Plattform nutzbar, da sie mit den standardisierten HTTP-Codes arbeiten.

Wie bereits erklärt, kann man nur einen Request absetzen, wenn man auch ein Webservice besteht, der den Request behandelt. In meinem Fall musste ich drei neue Webservices in PHP auf dem Webserver implementieren. Ich teilte die Webservices nach den verschiedenen Daten auf: Plätze (SiteApi), Kunden (ClientApi) und Aufenthalte (StayApi).

Schema des Web-Service

Da ich die Daten gleich serverseitig Filtern wollte, erweiterte ich die Webservices.

So spare ich Traffic und es finden keine ressourcenaufwändigen Filterungen auf den mobilen Geräten statt. Die Umsetzung eines solchen Webservices sehen Sie im nächsten Codeblock. Ich kümmere mich hier um eine allgemeine Abfrage der Daten.

/**
 * (non-PHPdoc)
 * @see PlmRestApiController::actionGet()
 */
public function actionGet()
{
	//try to get id by request otherwise $id is null
	$id = Yii::app()->request->getParam('id', null);

	$crit = new CDbCriteria();

//when id is set by request, return one object , otherwise return all objects
	if ($id !== null) {
		//compare request id with stay id in database
		$crit->compare('id', $id);
		$data = Stay::model()->find($crit);
	} else {
		//get all models
		$data = Stay::model()->findAll($crit);
	}

	//when no data has been found
	if ($data === null) {
		//statuscode 404
		$this->throwNotFound();
	} else {
		//everything is fine, send statuscode 200 and the data
		$this->sendResponse(200, $data);
	}
}

Benachrichtigungen

Bei einer Andorid-App haben wir viele Möglichkeiten, unsere Daten darzustellen. Wichtig ist hier, dass wir die richtigen Elemente auswählen, damit ein benutzerfreundlicher Umgang gewährleistet ist. Ich musste mich entscheiden, wie ich den Benutzer informieren will, wenn beispielsweise keine Internetverbindung besteht.

Android bietet einige Möglichkeiten, den Benutzer über Probleme oder Abläufe zu informieren. Es gibt zum einen sogenannte Toasts, ersichtlich auf dem ersten Bild. Diese eignen sich vor allem für kurze Nachrichten, denn die Toasts werden nur für eine bestimmte Zeit eingeblendet.
Eine weitere Möglichkeit ist die Benachrichtigungen via Andorid-Drawer abzusetzen. Das ist das Menu, welches Sie von oben herunterziehen können. Auf der rechten Abbildung sehen Sie einen Printscreen von einem heruntergezogenen Drawer.

Die Benachrichtigung wird in der App stattfinden, darum kommt der Android Drawer nicht in Frage. Dieser wäre ideal gewesen, wenn der Benutzer auch informiert werden soll, wenn die App geschlossen ist. In meinem Fall reichen kleine und kurze Toasts vollkommen aus, um alle wichtigen Informationen anzuzeigen.

3.MVC-Pattern

In der Entwicklung von Android-Apps ist das MVC-Pattern sehr verbreitet. Durch die Aufteilung des App-Aufbaus in Model, View und Controller hat man voneinander unabhängige Programmteile und kann diese individuell ändern. Der Programmcode ist übersichtlicher und strukturierter. Ausserdem können die Komponenten ausgetauscht und wiederverwendet werden. Die Wartbarkeit ist deshalb umso besser.

Um Ihnen eine kleine Übersicht über die Komponenten zu geben, sehen Sie auf der Skizze unten wie View, Controller und Model zusammenarbeiten. Im Anschluss werden diese im Detail erläutert.

MVC-Pattern

Model

Das Model übernimmt die Datenverwaltung, d.h. die Daten werden mithilfe des Models geändert und zur Verfügung gestellt. In der CampCockpit-App gibt es das Stay-, Site- und Client-Model. Das Staymodel kümmert sich dann beispielsweise um die Aufenthaltsdaten.

View

Die View übernimmt mithilfe des Controllers die ganze Darstellung der Daten aus den Models. Hier ist keine Logik vorhanden. Benutzereingaben werden entgegengenommen und an den Controller weitergeleitet. Das können Buttonklicks, Textänderungen im Einstellungsmenü oder andere Eingaben sein.

Controller

Die Activities übernehmen die Aufgaben des Controller. Die Kommunikation zwischen View und Model wird sichergestellt. In der Activity holen wir die Daten per GET-Request und übergeben sie dem Model. Die aufbereiteten Daten werden später dem individuellen Adapter, bzw. View, übergeben und dargestellt.

4.Listen implementieren

Ich gehe auf den Aufbau und die Implementation einer meiner Listen ein. Die Listen übernehmen einen zentralen Teil meiner Arbeit, da die Daten immer in Form einer Liste in der App dargestellt werden. Android lädt automatisch immer nur die Anzahl Daten, die auch auf dem Display sichtbar sind. Das heisst obwohl vielleicht 1000 Datensätze vorhanden sind, werden nur die sichtbaren (abhängig von Displaygrösse, Schriftgrösse, etc.) dargestellt.

Schema einer Listen-Activity

Activity

Damit das Layout in der App dargestellt werden kann, braucht es im Hintergrund eine Anwendungslogik. Dieser Teil übernimmt die jeweilige Activity. Jede dieser Activity ist mit einer View (Darstellung des Layouts in der App) verbunden und kann so auf Benutzereingaben reagieren.

Main Activity

Beim Start der App wird die onCreate-Methode aufgerufen. In dieser Methode verknüpfe ich meine Main Activity (MainActivity.java) mithilfe der setContentView()-Methode mit dem Main Layout (activity_main.xml).

Die Elemente im Layout werden in Variablen abgelegt. Dies vereinfacht die Arbeit und den Umgang mit den Elementen enorm. Damit wir auf einen Buttonklick reagieren können, benötigen wir OnClickListeners. Sobald beispielsweise der Button _btnArrive betätigt wird, reagiert dieser und erstellt einen neuen Intent. Ein Intent ist der Zwischenspieler von einer Activity und dem dazugehörigen Layout. Ohne Intent könnte man gar keine neue Activity öffnen.

List Activity

Der Intent in der Main Activty verweist auf die ListArriveActivity.java. Diese Klasse leitet von der ListActivity ab und hat ebenfalls den IRestRequestResultHandler implementiert. Auf den IRestRequestResultHandler komme ich später zurück, da wir diesen für den Request benötigen.

Die Activity wird wieder durch das onCreate() gestartet. Die ArrayList vom Typ String und auch der Adapter habe ich bereits als Klassenvariabel definiert. Initialisiert werden sie erst im onCreate(). Der Adapter benötigt das Layout der Zeilen und die Daten, die in der Liste dargestellt werden sollen.

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	//fill array with data
	_listData = new ArrayList<Stay>();
	_adapter = new StayAdapter(this, R.layout.activity_list_stay_row, _listData);
	setListAdapter(_adapter);

	loadData();
}

Per Rest-Request werden die aktuellen Daten aus der Datenbank geholt. Dafür gibt es die Funktion loadData(). Mithilfe der Rest Api von Pascal Müller können die Requests einfach umgesetzt werden. Ich benötigte eine Instanz der Factory und ein RestRequest Model. Diesem übergebe ich den aktuellen Kontext, den Pfad für den Request und den Handler.

/**
* fetch the Arrive Data per Request
*/
public void loadData() {
	RestRequestFactory rrf = RestRequestFactory.getInstance();
	RestRequest rr = rrf.createGet(this, "stayApi/getArrive/", this);
	rr.execute();
}

Weil ich den IRestRequestResultHandler implementiert hatte, musste ich zwingend den Handler überschreiben. In der Funktion handleRestResponse() konnte ich definieren, was mit den Daten vom Request passiert. Die Daten bekomme ich in einem RestResponseContainer. Der Code ist im Codeblock 7 ersichtlich.

Weil ich nur die neusten Daten speichern will, wird die Liste sicherheitshalber jedes Mal zuerst geleert.

Im Objekt rrc habe ich die ganze Antwort des Requests und darum auch alle Daten zur Verfügung. Durch die Kontrolle des HTTP-Codes auf 401, kann ich den User benachrichtigen, falls er gar nicht berechtigt wäre, auf diese Informationen zuzugreifen.

Im Try Block wurde zuerst getestet, ob überhaupt Daten vorhanden sind. Wenn nicht, wird der Benutzer informiert, ansonsten wird für jedes Element in der Antwort ein Stay-Model erstellt und in meiner Liste gespeichert. Mit _adapter.notifyDataSetChanged() wird der Adapter informiert, dass neue Daten vorhanden sind und die Liste aktualisiert wird.

@Override
public void handleRestResponse(RestResponseContainer rrc) {
Log.d(TAG, String.format("Request beendet, Rückgabecode: %d", rrc.getHttpCode()));

_listData.clear();

	//check if User is authorized, otherwise notify user
	if (rrc.getHttpCode() == 401) {
Toast.makeText(this, "Nicht authentifiziert!", Toast.LENGTH_SHORT).show();
		return;
		}

	try {
		JSONArray arr = new JSONArray(rrc.getBody());
		if (arr.length() < 1) {
			//notify user
				Toast.makeText(this, "keine Anreisenden heute", Toast.LENGTH_SHORT).show();
				throw new JSONException("no arrive today");
			}
		for (int i = 0; i < arr.length(); i++) {
			Stay stay = new Stay(arr.getString(i));

			//add Data to collections
			_listData.add(stay);
			}
	} catch (JSONException e) {
		e.printStackTrace();
	}
	_adapter.notifyDataSetChanged();
}

5.Ergebnis der IPA

Beim Starten der App gelangen Sie als erstes auf den Homescreen (erste Grafik). Mithilfe der vier Buttons gelangen Sie auf jede gewünschte Liste. Die Gestaltung der Listen mit ihren Daten sehen Sie auf den mittleren beiden Bildern. Die Ankunftsliste zeigt an, welche Kunden heute eintreffen. Die rotgeschriebenen Kundennamen haben noch nicht bezahlt. In der Platzbelegungs-Liste sehen wir alle Campingplätze eines Campingplatzes mit den dazugehörigen Kunden auf dem entsprechenden Platz. Plätze ohne Belegung sind mit „Frei“ gekennzeichnet.

Zum Schluss sehen Sie im vierten Bild das Einstellungsfenster. So können Sie alles Wichtige - wie beispielsweise Ihren Mandanten, Benutzername und Passwort - hinterlegen. Da die App mandantenunabhängig ist, das heisst man kann zwischen verschiedenen Campingplätzen mithilfe des jeweiligen Mandanten wechseln, muss auch dieser angegeben werden. Zur Authentifizierung werden auch Benutzername und Passwort hinterlegt.

6.Reflexion der Abschlussarbeit

Das Projekt liess mir sehr viel Platz zur Selbstverwirklichung. Ich konnte im Design- und Umsetzungsteil meine eigenen Entscheidungen treffen. Darum war es wichtig, dass ich eine fundamentale und saubere Planung erstellte. Diesen Teil unterschätzte ich am Anfang, hatte aber durch Reserven Blöcke im Zeitplan keinen Verzug. Die 10 Tage vergingen wie im Fluge, umso beruhigter war ich, dass der Zeitplan aufging und die App funktionierte. Die kleineren Abweichungen vom Zeitplan konnte ich begründen und waren sinnvoll und hilfreich.

Meine IPA war sehr interessant. Die Arbeit empfand ich als abwechslungsreich, motivierend und herausfordernd. Ich konnte den Berufsschulstoff miteinbeziehen und das Gelernte miteinander verknüpfen.

Es freut mich sehr, dass ich ein Projekt umsetzen durfte, das auch einen produktiven Nutzen für meinen Lehrbetrieb stiftet. Dies spornte mich noch mehr an, eine verständliche und alltagsfähige App zu entwickeln.


Tamara Troxler

Senior System- & Software Engineer


Die IPA bildete einen der letzten und grössten Meilensteine in meiner Lehre ab. Die erlernten Fähigkeiten von der Berufsschule und dem Geschäftsalltag konnte ich einfliessen lassen und halfen mir bei der Umsetzung der Arbeit. Ich bin stolz, dass ich ein Projekt alleine umsetzen konnte und freue mich wenn die Campingplatz-Verwalter mit der App arbeiten können. Heute wird schon vieles mithilfe von Apps dargestellt oder komplex Prozesse vereinfacht auf den mobilen Geräten umgesetzt. Dieses Thema wird immer populärer und wichtiger. Es ist spannend, diese Entwicklungen mitzuerleben und jetzt auch das nötige Wissen zu haben.