Flutter App Development - Teil 2 - Libraries und Architektur
Womit geht es in Teil zwei weiter habe ich mich gefragt. Sowohl die UI, wie auch die eigentliche Architektur könnten ein Thema sein. Ich persönlich bevorzuge erst die Logik, dann die UI und entsprechend machen wir heute auch weiter. Bevor wir jetzt aber alles selber schreiben, binden wir erst einmal relevante Libraries ein. Denn es gibt ein paar Dinge die man nicht unbedingt selber schreiben möchte.
pubspec.yaml
(Code auf GitHub open_in_new)
name: fss
description: Fluttery Site Summaries - RSS the Flutter way
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
bloc: ^4.0.0
flutter_bloc: ^4.0.0
flutter_html: ^0.11.1
http: ^0.12.0+4
intl: ^0.16.1
path_provider: ^1.6.7
sembast: ^2.4.2
url_launcher: ^5.4.5
webfeed: ^0.4.2
dev_dependencies:
flutter_test:
sdk: flutter
pedantic: ^1.9.0
flutter:
uses-material-design: true
- BloC open_in_new und Flutter BloC open_in_new - Beginnen möchte ich mit dem State Management. Hier setzte ich auf das BloC Pattern open_in_new und die gleichnamige Library. Ebenfalls nutze ich flutter_bloc, welche diverse Mappings und Vereinfachungen für die Nutzung des Patterns im Flutter UI Kontext bietet. Hierzu sei gesagt, Flutter bietet diverse State Management Patterns open_in_new an und natürlich auch die dazugehörigen Libraries, doch ich fahre aktuell mit BloC sehr gut und möchte dabei bleiben. Auch wenn ein kleiner RSS Reader vielleicht sogar ohne komplexen State Management Layer auskommen würde.
- sembast open_in_new - Weiter geht es mit der Datenbank. Hier wollte ich etwas Neues ausprobieren und bin initial über Hive open_in_new gestolpert. Nach einigen weiteren Recherchen blieb ich allerdings an sembast hängen und werde diese Library nun nutzen. Der geneigte Entwickler hat hier natürlich die Qual der Wahl und je nach Einsatzgebiet und potentiellen Datenmengen + Performance Anforderungen, sollte man pro Projekt entscheiden.
- path_provider open_in_new - Damit die benötigte Datei für die Speicherung der Datenbank erstellt werden kann, wird zusätzlich die path_provider Library genutzt, sie erlaubt es uns passende Speicherorte vom jeweiligen System abzufragen.
- webfeed open_in_new und http open_in_new - Um nun auch in der Lage zu sein RSS Feeds zu verarbeiten, nutzen wir die webfeed Library, in Kombination mit der http Library. Diese Kombination erlaubt es uns RSS Feeds abzurufen (http) und zu parsen (webfeed), denn dies ist wahrlich keine Aufgabe die wir selber erledigen wollen, vor allem nicht im Rahmen einer kleinen Tutorialreihe. Die webfeed Library wurde zwar leider seit längerem nicht mehr aktualisiert, ist aber im Flutter RSS Bereich die einzige mir bekannte. Da sie Open Source ist können wir ja vielleicht auch selber bei Problemen eingreifen und im Rahmen dieses Tutorials der Community noch etwas Gutes tun. Die http Library wird direkt vom Dart Core Entwicklerteam angeboten und entsprechend gut gepflegt.
- flutter_html open_in_new - Da es sich bei Beiträgen in RSS Feeds um HTML handelt und wir die Inhalte ordentlich anzeigen wollen, benötigen wir eine HTML Library. Dafür setze ich auf flutter_html. Die Library ist simpel in der Nutzung und funktional, entsprechend ist sie meine erste Wahl.
- intl open_in_new - Da wir an einigen Stellen Datums- und Zeitangaben verarbeiten müssen, ist die intl Library mit an Bord.
- url_launcher open_in_new - Sobald ein Nutzer auf einen Link in einem Beitrag klickt oder aber auf einen Beitrag generell, wollen wir die dazugehörige Webseite öffnen und dies erreichen wir mit der url_launcher Library.
- pedantic open_in_new - Abschließend gibt es noch etwas spezielles, denn im Rahmen dieses Tutorials habe ich das erste Mal die pedantic Library ausprobiert. Diese erlaubt uns das Projekt gegen die gleichen Lint open_in_new Regeln zu testen, wie es intern bei Google der Fall ist. Sinn und Zweck ist es eine bessere Code Qualität zu erreichen.
Nachdem die Abhängigkeiten nun definiert sind geht es in die Vollen. Als Erstes würde ich gerne einen RSS Feed abrufen können und schauen wie die webfeed Library funktioniert. Mit diesem Wissen kann dann die erste von der UI getrennte Logik gebaut werden, die dann auf meine kleine sembast Datenbank zugreift, welche quasi ad-hoc während des Ausprobierens kreiert wird. Wäre dies übrigens ein großes Projekt würde die Erstellung der Datenbankstruktur vor der eigentlichen Implementierung kommen, inklusive diverser Planungen, Nachforschungen und Tests. Denn eine Datenbank sollte gut strukturiert sein, da spätere Änderungen teils gravierende Aufwände bedeuten.
Konkret erstellte ich etwas Logik, welche meinen Blog RSS Feed open_in_new lädt und pickte mir relevante Felder heraus. Mit diesem Wissen und meinen Dart + Flutter + BloC Grundkenntnissen geht es nur an die Strukturierung der Logik, der App-Architektur und der Datenbankstruktur.
Dazu sei gesagt das ich im Laufe der Entwicklung noch diverse kleinere Optimierungen vornahm, denn egal wie gut man eine Architektur plant, kleinere Anpassungen sind basierend auf neuen oder veränderten Anforderungen an der Tagesordnung. Der folgende Aufbau beschreibt die aktuelle finale Struktur.
Datenbankstruktur:
In diesem Bereich verzichte ich auf Auto-Increment Ids und werde die URL als Hash nutzen, um eine eindeutige Zuordnung zu ermöglichen. Bei einer SQL Datenbank würde ich vermutlich anders vorgehen, aber im sembast Kontext erscheint mir dieses Vorgehen durchaus sinnvoll.
- RSS Feed (
lib/types/rss_feed.dart
- Code auf GitHub open_in_new)- Hash der URL als Unique Identifier
- URL
- Zeitpunkt der letzten Aktualisierung
- Name
- Beitrag innerhalb eines RSS Feed -
lib/types/rss_entry.dart
(Code auf GitHub open_in_new)- Hash der URL als Unique Identifier
- URL
- Datum der Erstellung
- Titel
- Content
App-Struktur
- Screen 1 - RSS Feed Liste - (Code auf GitHub open_in_new)
- Möglichkeit neue RSS Feeds einzutragen (via Screen 2)
- Lädt die Liste der vorhandenen RSS Feeds
- Refresh Möglichkeit, um die gesamte Liste zu aktualisieren
- Feed Updates können mehrere Sekunden dauern, entsprechend wird ein Fortschrittsindikator benötigt
- Tap auf einen Eintrag öffnet Screen 2
- Screen 2 - RSS Feed Detailansicht - (Code auf GitHub open_in_new)
- Möglichkeit den Feed zu bearbeiten oder zu löschen (via Screen 3)
- Lädt alle Beiträge eines RSS Feeds
- Refresh Möglichkeit, um die Beiträge dieses Feeds zu aktualisieren
- Feed Updates können mehrere Sekunden dauern, entsprechend wird ein Fortschrittsindikator benötigt
- Aktualisieren aller involvierten Screens, beim Refresh
- Tap auf das Edit-Icon öffnet Screen 3
- Screen 3 - RSS Feed List bearbeiten - (Code auf GitHub open_in_new)
- Hinzufügen, Bearbeiten und Löschen eines RSS Feeds
- Aktualisieren aller involvierten Screens, beim Hinzufügen, Ändern oder Löschen
- Explizite Abfrage bei der Löschaktion
- Information für den Nutzer wenn eine Feed URL geändert oder ein Feed gelöscht wurde
Logik
lib/main/main_bloc.dart
(Code auf GitHub open_in_new)- Verwaltet den globalen Zustand der App
- Lädt die eigentliche Datenbank
- Lädt eventuelle Konfigurationsdaten und weitere globale Informationen
lib/feed_list/feed_list_bloc.dart
(Code auf GitHub open_in_new)- Verwaltet die RSS Feed Liste und ist das zentrale Bindeglied zwischen Persistenz und UI
- Wird über alle RSS Feed Updates informiert
- Zeigt in der UI den letzten Stand der Datenbank an (RSS Feeds) und ermöglicht Aktualisierungen aller RSS Feeds
- Ermöglicht das Hinzufügen, Bearbeiten und Löschen von RSS Feeds
lib/entry_list/entry_list_bloc.dart
(Code auf GitHub open_in_new)- Lädt einen Feed und liest alle Beiträge aus der Datenbank ein
- Zeigt in der UI den letzten Stand der Datenbank an (Beiträge innerhalb eines RSS Feeds) und ermöglicht Aktualisierungen eines RSS Feeds
- Informiert auch andere interessierte Parteien (z.B. den feed_list_bloc open_in_new) über Updates
Bloc Management
- BloCs werden so häufig wie nötig, aber so selten wie möglich erstellt. Sobald ein BloC erstellt wurde, sollte er nach Möglichkeit wiederverwendet werden. Dies kann z.B. durch Nutzung der Provider open_in_new Library geschehen, wodurch vorhandene Objekte (z.B. ein BloC) den darunterliegenden UI Elementen zur Verfügung gestellt werden.
- feed_list_bloc open_in_new - Dieser BloC wird einmalig in der main.dart open_in_new angelegt. Darunterliegende Objekte erhalten den BloC über einen BlocProvider open_in_new, wodurch alle tieferen Strukturen auf demselben Informationstand aufsetzen können und auch der Stand der Liste bei Aktualisierungen an einer zentralen Stelle verwaltet wird.
- entry_list_bloc open_in_new - Dieser BloC wird pro Detailansicht erstellt und erhält als Parameter die Instanz des zuvor erwähnten feed_list_bloc open_in_new. Dadurch kann direkt in diesem BloC die Informationskette gepflegt werden, ohne dass weitere Zwischenschritte über die UI nötig sind.
Nachdem nun alle grundlegenden Dinge geplant, die Libraries importiert und die nötigen Setup-Schritte erledigt sind, geht es im nächsten Teil mit einigem Code weiter. Fragen und ähnliches bitte wie gewohnt in den Kommentaren hinterlassen.