From 722f9cd465470a3688a12d3045e4999740770f27 Mon Sep 17 00:00:00 2001 From: wiecktobi Date: Thu, 11 Jun 2020 13:48:12 +0200 Subject: [PATCH] Finish Android chapter --- android/.idea/misc.xml | 2 +- documentation/img/android/btn_no_fence.png | Bin 0 -> 9592 bytes documentation/parts/android.tex | 176 +++++++++++---------- 3 files changed, 94 insertions(+), 84 deletions(-) create mode 100644 documentation/img/android/btn_no_fence.png diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 7bfef59..b6ea2b1 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/documentation/img/android/btn_no_fence.png b/documentation/img/android/btn_no_fence.png new file mode 100644 index 0000000000000000000000000000000000000000..39003a4327a9e1d5e137080fbab0c13290897df6 GIT binary patch literal 9592 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 UCvy?jPQ - - @GET("whoami") - fun getUser(): Call - - @GET("accounts/search/findByUsername") - fun getAccounts(@Query("username") username : String): Call +@POST("/login") +fun login(@Body login_data: ValuesUserLogin): Call + +@GET("whoami") +fun getUser(): Call + +@GET("accounts/search/findByUsername") +fun getAccounts(@Query("username") username : String): Call \end{lstlisting} Der Rückgabewert der Methoden ist immer vom Typ \verb|Call|. Wenn aus dem Body Werte gelesen werden sollen, muss eine Art Skelett-Klasse angelegt werden mit den relevanten Feldern. Die Klasse \verb|ValuesUser| stellt Werte der Antwort bereit, wie z. B. den Vornamen. \begin{lstlisting}[language=Kotlin] - class ValuesUser(firstname: String) { - @SerializedName("firstname") - var firstname = firstname - } +class ValuesUser(firstname: String) { + @SerializedName("firstname") + var firstname = firstname +} \end{lstlisting} Der Aufruf der Methode erfolgt Asynchron. Deshalb darf sich nicht auf das Ergebnis des Aufrufs direkt danach verlassen werden, sonst bekommt man eine Null-Pointer-Excetion. Die Methode \verb|enqueue| besitzt ein Callback-Objekt als Parameter, welches \verb|onResponse| und \verb|onFailure| überschreibt. Dort wird entsprechend definiert was in den jeweiligen Fällen ausgeführt werden soll. \begin{lstlisting}[language=Kotlin] - val call = service.getUser() - call.enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val firstname = response.body()?.firstname - lbl_username.text = "Hello " + firstname - } else { - println("Response not successful: ${response.code()}") - } - } - override fun onFailure(call: Call, t: Throwable) { - println("Response 'whoami' failed. " + t.message) - } - }) +val call = service.getUser() +call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val firstname = response.body()?.firstname + lbl_username.text = "Hello " + firstname + } else { + println("Response not successful: ${response.code()}") + } + } + override fun onFailure(call: Call, t: Throwable) { + println("Response 'whoami' failed. " + t.message) + } +}) \end{lstlisting} \bigskip In dieser Art und Weise werden alle Anfragen ans Backend gehandhabt. Dazu zählen: @@ -131,37 +130,37 @@ \item Einloggen \end{itemize} - \subsection{Geofencing} + \subsection{Geofencing}\label{subsec:geofence} Die Geofencing-Funktion ist die zentrale Funktion für die App und auch für das gesamte Projekt. Deshalb war es wichtig, dass sie frühzeitig funktioniert. \\ Um die Position eines Gerätes zu bestimmen bedarf es einer Berechtigung, die vom Benutzer bestätigt werden muss. Für Geräte mit API-Level 28 und niedriger muss dafür die \verb|ACCESS_FINE_LOCATION|-Berechtigung gesetzt werden und für API-Level 29 und höher \verb|ACCESS_BACKGROUND_LOCATION|. \\ Der Geofence wird initialisiert, wenn für den Benutzer Geo-Daten gespeichert sind. Ist dies der Fall, so wird ein \verb|GeofencingClient| angelegt, dem dann der Geofence hinzugefügt wird. Der Geofence wird erzeugt mit den Parametern: Breitengrad, Längengrad, Radius, der Lebenszeit des Fence und den Übergangstypen. Die Typen sind in unserem Fall \verb|GEOFENCE_TRANSITION_ENTER| und \verb|GEOFENCE_TRANSITION_EXIT|, da wir immer reagieren wollen, wenn der Nutzer den Bereich verlässt oder betritt. \begin{lstlisting}[language=Kotlin] - geofencingClient = LocationServices.getGeofencingClient(this) - geofence = Geofence.Builder().setRequestId("Geofence") - .setCircularRegion(lat, long, rad) - .setExpirationDuration(Geofence.NEVER_EXPIRE) - .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT) - .build() +geofencingClient = LocationServices.getGeofencingClient(this) +geofence = Geofence.Builder().setRequestId("Geofence") + .setCircularRegion(lat, long, rad) + .setExpirationDuration(Geofence.NEVER_EXPIRE) + .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT) + .build() \end{lstlisting} Um den Geofence-Client zu starten wird auf das Objekt die \verb|addGeofences|-Methode ausgeführt mit einem \verb|GeofencingRequest|-Objekt und einem \verb|PendingIntent|-Objekt als Parameter. \begin{lstlisting}[language=Kotlin] - geofencingClient.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run { - addOnSuccessListener { ... } - addOnFailureListener { ... } - } +geofencingClient.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run { + addOnSuccessListener { ... } + addOnFailureListener { ... } +} \end{lstlisting} In der \verb|getGeofencingRequest|-Methode wird festgelegt auf welches initiale Event reagiert werden soll und der oben erstellte Geofence wird hinzugefügt. Als initiales Event habe ich \verb|INITIAL_TRIGGER_ENTER| gewählt, da es ausgelöst wird wenn man sich bereits im Bereich befindet und die App startet. Denn erst mit dem Eintrittsevent wird der Button zum Starten der Aufzeichnung freigeschaltet. Das \verb|geofencePendingIntent| definiert die BroadcastReceiver-Klasse, welche bei jedem Event aufgerufen wird. \begin{lstlisting}[language=Kotlin] - private fun getGeofencingRequest(): GeofencingRequest { - return GeofencingRequest.Builder().apply { - setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) - addGeofence(geofence) - }.build() - } - private val geofencePendingIntent: PendingIntent by lazy { - val intent = Intent(this, GeofenceBroadcastReceiver::class.java) - PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) - } +private fun getGeofencingRequest(): GeofencingRequest { + return GeofencingRequest.Builder().apply { + setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) + addGeofence(geofence) + }.build() +} +private val geofencePendingIntent: PendingIntent by lazy { + val intent = Intent(this, GeofenceBroadcastReceiver::class.java) + PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) +} \end{lstlisting} Die \verb|GeofenceBroadcastReceiver|-Klasse definiert was bei den jeweiligen Events ausgeführt werden soll. In unserem Fall ist dies das Verändern einer boolean Shared-Prefrences-Variable, je nachdem ob der Bereich betreten oder verlassen wurde. Warum diese Art und Weise gewählt wurde lesen Sie in Kapitel \ref{sec:Probleme}.\\ Das Code-Beispiel zeigt die Aktion beim Betreten des Bereichs. @@ -173,14 +172,11 @@ context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE) \end{lstlisting} In der \verb|MainActivity| wird ein Listener für diese Shared-Prefrences-Variable definiert. Je nachdem zu welchem Wert sich die Variable ändert wird der Start/Stopp-Button freigeschalten oder gesperrt und wenn der Benutzer den Bereich verlässt, aber noch aufzeichnet, wird diese gestoppt und gespeichert. \\ \\ - Unerwartet hierbei war, dass die Geofence-Funktion die normale Positionsbestimmung zusätzlich benötigt. Denn zuerst, hatte ich die Positionsbestimmung implementiert und dann die Geofence-Funktion, was funktioniert hat. Da in der Geofence-Funktion keine Code der normalen Positionsbestimmung referenziert wurde, dachte ich man könne diesen weglassen, was ein Trugschluss war. Auch der Versuch Teile der Positionsbestimmung wegzulassen war ohne Erfolg. Deshalb beinhaltet die App auch Code für die normale Positionsbestimmung. - - \section{Funktionen der App} - Wie oben beschrieben besteht die Android-App aus den vier Activities. Die Register- und Settings-Activity sind aus zeitlichen Gründen ohne Funktion und layouttechnisch nicht überarbeitet. Der Fokus lag stark auf der Main-Activity, das Kernstück der App darstellt. Im Folgenden die Funktionalitäten der Activities Login und Main. + Wie oben beschrieben besteht die Android-App aus vier Activities. Die Register- und Settings-Activity sind aus zeitlichen Gründen ohne Funktion und layouttechnisch nicht überarbeitet. Der Fokus lag stark auf der Main-Activity, das Kernstück der App darstellt. Im Folgenden die Funktionalitäten der Activities Login und Main. \subsection{Login Screen} In der Abbildung \ref{Abb:login} ist der Login Screen zu sehen. Er besteht aus der Top-Action-Bar mit Logo und App-Name, den Eingabefeldern und zwei Buttons. Alle Komponenten sind aus der Material-Design-Bibliothek. \\ - Zum Einloggen werden die Daten in die jeweiligen Felder eingegeben. Wenn ein Feld markiert ist, wird das ausgewählte Feld blau umrandet und der Hinweis wird auf die obere Linie verschoben. Das Passwortfeld zeigt nur kurz den eingegebenen Buchstaben an und wird dann zu einem \verb|*|, sodass das Passwort nicht offen lesbar ist. \\ + Zum Einloggen werden die Daten in die jeweiligen Felder eingegeben. Wenn ein Feld markiert ist, wird das ausgewählte Feld blau umrandet und der Hinweis wird auf die obere Linie verschoben. Das Passwortfeld zeigt nur kurz den eingegebenen Buchstaben an und wird dann zu einem '*', sodass das Passwort nicht offen lesbar ist. \\ Der Login-Button sendet die Daten an das Backend und prüft ob die Daten korrekt sind. Wenn dies der Fall ist, enthält die Antwort das Token, welches in den privaten Speicher abgelegt wird, und die App wechselt zum Hauptbildschirm. War der Login nicht erfolgreich, wird dem Benutzer eine Pop-Up-Meldung angezeigt und nichts weiter unternommen. Mit dem betätigen des Registrieren-Knopfes wird man auf die Register-Activity weitergeleitet. \subsection{Main Activity}\label{subsec:main} @@ -201,7 +197,7 @@ context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE) \caption{Laufende Aufzeichnung} \label{Abb:menu} \end{figure} - Der Start-Stop-Button schält die Aufzeichnung um, in dem ein Backend-Endpunkt angesprochen wird. In der App habe ich eine boolean-Variable \verb|running|, welche gespeichert hält ob die Aufzeichnung aktiv ist. Anhand ihr wird entschieden wie der Start-Stop-Button aussieht und ob beim Verlassen des Geofence noch gestoppt werden muss. Der Button ist nicht Auswählbar wenn sich der Nutzer außerhalb seines Arbeitsplatzes befindet und zeigt dies auch an (Abb.: \ref{Abb:outside}). Ist der Nutzer dann im Bereich, wird "Start" angezeigt und der Button ist freigeschaltet. Während der Aufzeichnung trägt der Button die Schrift "Stop". + Der Start-Stop-Button schält die Aufzeichnung um, in dem ein Backend-Endpunkt angesprochen wird. In der App habe ich eine boolean-Variable \verb|running| definiert, welche gespeichert hält ob die Aufzeichnung aktiv ist. Anhand ihr wird entschieden wie der Start-Stop-Button aussieht und ob beim Verlassen des Geofence noch gestoppt werden muss. Der Button ist nicht Auswählbar wenn sich der Nutzer außerhalb seines Arbeitsplatzes befindet und zeigt dies auch an (Abb.: \ref{Abb:outside}). Ist der Nutzer dann im Bereich, wird "Start" angezeigt und der Button ist freigeschaltet. Während der Aufzeichnung trägt der Button die Schrift "Stop". Hat der Nutzer noch keine Geo-Daten für seinen Arbeitsplatz definiert, wird auch das auf dem Button angezeigt. \begin{figure}[H] \centering \includegraphics[width=0.4\linewidth]{img/android/btn_outside} @@ -212,13 +208,16 @@ context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE) \centering \includegraphics[width=0.4\linewidth]{img/android/btn_start} \caption{Aufzeichnung kann gestartet werden} - \label{Abb:menu} \end{figure} \begin{figure}[H] \centering \includegraphics[width=0.4\linewidth]{img/android/btn_stop} \caption{Laufende Aufzeichnung} - \label{Abb:menu} + \end{figure} + \begin{figure}[H] + \centering + \includegraphics[width=0.4\linewidth]{img/android/btn_no_fence} + \caption{Nutzer hat noch keinen Geo-Daten} \end{figure} Um versehentliches stoppen der Aufzeichnung zu verhindern, muss der Nutzer in einem Pop-Up-Dialog seine Aktion bestätigen. \begin{figure}[H] @@ -229,4 +228,15 @@ context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE) \end{figure} \section{Probleme und Lösungen}\label{sec:Probleme} - \section{Deployment} \ No newline at end of file + Damit die App auch die aktuellste Android Version unterstützt, mussten einige zusätzliche Punkte berücksichtigt werden. Neben der Berechtigung aus Kapitel \ref{subsec:geofence} musste in der \verb|build.gradle|-Datei Kompilierungsoptionen gesetzt werden. \\ + + Zu beginn wollte ich alle Activities mit Fragments realisieren, sodass es nur eine Activity gibt und alles weitere Fragments sind. Allerdings war es schwieriger zwischen den Fragments zu wechseln, als in den Tutorials beschrieben. Deshalb wurde mir von einem Teamkollegen empfohlen auf nur Activities umzusteigen. Zwischen diesen ist das hin- und herschalten deutlich einfacher, hat jedoch kein Zugriff auf Elemente der anderen Activities. \\ + + Das wurde bemerkbar, als ich aus der Klasse \verb|GeofenceBroadcastReceiver| eine Methode der \\\verb|MainActivity| zur Änderung der Oberfläche aufrufen wollte. Das hat den Grund, dass Android nicht sicher sagen kann das diese Activity gerade auch aktiv ist. Deshalb habe ich den Weg über die Shared Preferences gewählt mit einem Listener in der \verb|MainActivity|. \\ + + Initial wollte ich das Token in einer Datenklasse abspeichern, welche beim Einloggen befüllt wird. Dazu müsste allerdings das Objekt oder die Referenz zu jeder anderen Activity übergeben werden. Eine andere Möglichkeit stellen erneut die Shared Preferences dar. Wäre auch eine gute Lösung gewesen, welche ich aber dazu zu spät kennengelernt habe. Deshalb habe ich das Problem mit dem privaten Speicher gelöst. Er ist auch durch andere Apps und den Benutzer nicht einsehbar, bildet deshalb also keine Sicherheitslücke.\\ + + Unerwartet war, dass die Geofence-Funktion die normale Android Positionsbestimmung zusätzlich benötigt. Denn zuerst, hatte ich die Positionsbestimmung implementiert und dann die Geofence-Funktion, was funktioniert hat. Da in der Geofence-Funktion kein Code der normalen Positionsbestimmung referenziert wurde, dachte ich man könne diesen weglassen, was ein Trugschluss war. Auch der Versuch Teile der Positionsbestimmung wegzulassen war ohne Erfolg. Deshalb beinhaltet die App auch Code für die normale Positionsbestimmung. + + \section{Deployment} + Das Deployment spielte im Entwicklungsprozess der App keine große Rolle, da es Android-Studio benötigt um die App zu starten. Zum Abschluss habe ich allerdings den aktuellen Stand des Projekts in einer APK-Datei persistiert. Damit lässt sich die App auf andere Geräte installieren und in den App-Store laden. Zur Erstellung einer solchen APK muss ein Key zur Signatur angegeben werden. \ No newline at end of file