Dashboard
Das Dashboard der App wurde neugestaltet und zeigt nun Echtdaten anstatt Dummy-Daten an. Ganz oben wird die Möglichkeit geboten, den Channel über ein Dropdown-Menü (in Android Spinner genannt) auszuwählen. Dadurch kann sich der Benutzer das Dashboard für jeden Bienenstock separat anschauen. Das Dashboard zeigt nun die aktuellen Werte aller Sensoren und darunter eine Honigertragstabelle an. Jede Spalte der Tabelle ist auf- und absteigend sortierbar. Hierfür wurde die externe Bibliothek SortableTableView verwendet. Leider zeigt die Honigertragstabelle falsche Werte an, da die Wägezellen nicht richtig eingestellt waren und somit fehlerhafte Messwerte lieferten. Nachfolgende Abbildung zeigt das Dashboard.
AQI-Informationsseite
Falls der Sensor zum Messen der Luftqualität mit dem ThingSpeak-Konto verbunden wurde, erscheint ein Informationssymbol. Wenn dieses angeklickt wird, öffnet sich ein Informationsdialog über Luftqualität und dem Air Quality Index (AQI). Siehe hierzu folgende Abbildung.
Lade- und Fehlerseiten
Außerdem wurden nun zur besseren Usability Lade- und Fehlerseiten eingebaut, die dem Benutzer anzeigen, ob die Daten geladen werden, keine Daten verfügbar sind oder keine Internetverbindung besteht. Nachfolgende drei Lade- bzw. Fehlerseiten wurden in der App integriert.
Benutzerdefinierter Zeitraum
Auf den Detailseiten der einzelnen Messarten funktioniert nun auch der Tab „BENUTZERDEFINIERT“. Hierfür kann der Benutzer ein beliebiges Start- und Enddatum aus der Vergangenheit auswählen. Anschließend wird die übliche Seite mit dem Liniendiagramm sowie Höchst-, Tiefst- und Durchschnittswert für diesen Zeitraum angezeigt. Es ist auch möglich, wie auf nachfolgender Abbildung zu sehen ist, das Start- und Enddatum über einen sogenannten DatePicker auszuwählen.
Einstellungsseite
Die letzten Änderungen in der Android-App waren die Einrichtung einer Einstellungsseite. Folgende Einstellungen sind möglich:
- Den Bienenstock für das Dashboard auswählen.
- Den Aktualisierungsinterval für die Detailseiten einstellen (1 bis 60 Minuten).
- Ein Standardstartdatum für den benutzerdefinierten Tab einstellen.
- Ein Standardenddatum für den benutzerdefinierten Tab einstellen.
- Die Möglichkeit, aus allen Fields aller Channels des ThingSpeak-Kontos, auszuwählen, welche Fields in der App angezeigt werden sollen.
- Die Messart aller ausgewählten Fields ändern (mögliche Messarten: Temperatur, Luftfeuchtigkeit, Luftdruck, Luftqualität, Gewicht oder keine spezifische Messart).
- Die Channels und Fields mit dem ThingSpeak-Konto synchronisieren. Dies kann der Benutzer betätigen, wenn er z. B. Änderungen am ThingSpeak-Konto vorgenommen hat und diese in der App sichtbar werden sollen.
- Eine Schaltfläche zum Öffnen der Über-HoneyPi-Seite. Hier werden Kontaktinfos, Credits und Copyright angezeigt.
- Eine Schaltfläche zum Abmelden aus der App. Danach erscheint wieder die Loginseite zum Eingeben des User-API-Keys.
Problem bei der Einstellungsseite
Die sechste Einstellung hat etwas mehr Zeit gekostet, da die einzelnen Elemente (Fields) zum Bearbeiten dynamisch erzeugt werden müssen. Außerdem soll sich beim Anklicken eines Elements ein Dialog öffnen, in dem schließlich die richtige Messart ausgewählt werden kann. Hierfür kam zunächst die Klasse ListPreference von Android in Frage. Dies kann jedoch nur statisch für eine bekannte Anzahl an Fields verwendet und einzeln in der preferences.xml angegeben werden. Nach einigen fehlgeschlagenen Versuchen ListPreference-Elemente dynamisch im Java-Code zu erzeugen, musste eine andere Lösung gefunden werden.
Lösung des Problems
Die neue Lösung besteht nun daraus, dass beim Anklicken der Schaltfläche zum Ändern der Messart in den Einstellungen eine neue Seite erscheint, auf der jedes aktivierte Field in einer Android-ListView angezeigt wird und selbst jeweils einen OnClickListener besitzt, dessen Callback-Methode ausgeführt wird, wenn das jeweilige Element/Field angeklickt wird. Dadurch erscheint der oben erwähnte Dialog, in dem der Benutzer die richtige Messart auswählen kann. Folgendes Code-Snippet zeigt das oben beschriebene Vorgehen:
// Measurement types final CharSequence[] entries = {"Temperatur", "Luftfeuchtigkeit", "Luftdruck", "Luftqualität", "Gewicht", "Keine spezifische Messart"}; // Create ArrayAdapter and custom view with dialog and radio buttons ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_2, android.R.id.text1, channelFieldItems) { @NonNull @Override public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) { View view = super.getView(position, convertView, parent); TextView textView1 = (TextView) view.findViewById(android.R.id.text1); TextView textView2 = (TextView) view.findViewById(android.R.id.text2); textView1.setText(channelFieldItems.get(position).toString()); textView2.setText(channelFieldItems.get(position).getType()); textView2.setTextColor(getResources().getColor(R.color.textColorSecondary)); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(ChangeMeasurementTypePreferenceActivity.this); builder.setTitle(R.string.button_change_measurement_type_title); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.setSingleChoiceItems(entries, getCheckedItemNumber(channelFieldItems.get(position)), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Set initial selection switch (which) { case 0: channelFieldItems.get(position).setType("Temperatur"); break; case 1: channelFieldItems.get(position).setType("Luftfeuchtigkeit"); break; case 2: channelFieldItems.get(position).setType("Luftdruck"); break; case 3: channelFieldItems.get(position).setType("Luftqualität"); break; case 4: channelFieldItems.get(position).setType("Gewicht"); break; default: channelFieldItems.get(position).setType(""); break; } dialog.dismiss(); String channelFieldItemsString = new Gson().toJson(channelFieldItems); sharedPreferences.edit().putString("channelFieldItems", channelFieldItemsString).apply(); notifyDataSetChanged(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } }); return view; } };
Durch diese Lösung war es möglich, die Elemente dynamisch zu erzeugen und dennoch eine fast identische Optik wie bei der Verwendung von ListPreference zu erzielen. Siehe hierzu nachfolgende zwei Abbildungen.
Wird der Code der App auch auf Github veröffentlicht?
Den Code der App wollten wir nicht auf GitHub veröffentlichen, da wir einen eigenen Git-Server betreiben, auf dem der Code aktuell liegt. Auf der CD, die wir abgeben müssen, erhalten Sie selbstverständlich den Code 😉
Naja, ich frage nur, weil der Backend-Code ja durchaus auf Github liegt. 😉