---
# System prepended metadata

title: Projekt Bericht Multimedia Systeme - MusiQ

---

# Projekt Bericht Multimedia Systeme - MusiQ

## Thema und Team 

### Team

**Front-End Team**: 

- Alexander Pichler - k12046988
- Rene Wimmer - k12110491

**Back-End Team**:

- Fabian Bleck - k12108626
- David Fankhauser - k12108700 

### Thema

In dem Projekt werden folgende Themen behandelt:

- Mobile-App Entwicklung (mit dart).
- Rest & MQTT-Server Entwicklung in C# (ASP.NET und FastEndpoints)
- **Sound** Verwaltung (in Flutter).
- Übertragung von **Sound**-, und **Bild**-Dateien über HTTP (Base64-Strings).
- Verwenden von APIs (YouTube)
- Teilen von Code mittel Git
- Verwalten des Backends über Docker

## Projektbeschreibung

Wenn in einer Gruppe Musik gehört wird, ergibt sich oft die Situation, dass mehrere verschiedene Personen Songs in die Queue hinzufügen wollen. Um das zu tun, muss momentan einer, der mit einem Musikausgabegerät verbunden ist, sein Handy ständig herumreichen. 
Abhilfe schafft eine geteilte Queue, die einer Reihe an Benutzern, das spielen von Liedern ermöglicht. Diese Funktion gibt es zwar in Spotify, andere Musik-Apps werden davon allerdings nicht unterstützt. Zusätzlich ist die Benutzung dieses Features in Spotify kostenpflichtig. 

Weiters, kann mit einem Mobiltelefon meist nur ein Bluetooth Gerät (z.B. Kopfhörer) angesprochen werden. Daher kann kaum zusammen musikgehört werden, wenn man sich als Gruppe an einem öffentlichen Ort befindet, oder gar nicht beisammen ist. 
Auch dieses Problem löst MusiQ. Dadurch, dass die Audioausgabe auf jedem der Clients ein/ausgestellt werden kann, kann pro Client (Handy) ein weiteres Bluetooth gerät den Sound abspielen. 

Oftmals ergibt sich außerdem das Problem, dass es gewisse Songs z.B. nur auf YouTube gibt, wodurch derjenige, der Musik abspielt, ständig zwischen verschiedenen Apps wechseln muss. Mit MusiQ kann Musik von verschiedensten Platformen gespielt werden, und kein Abonnement ist nötig.

Das Projekt besteht aus einer App, die von mehreren Personen verwendet werden kann, um Musik von verschiedenen Musik-Apps in eine gemeinsame Queue hinzuzufügen und diese Queue zu verwalten. In dieser App erstellt einer der Benutzer einen Raum, dem dann andere Personen beitreten können. Diese Räume und die Musik-Queue werden zentral von einem Server gespeichert, also geht die Queue auch nicht verloren, wenn jemand aus Versehen die App schließt oder die Internetverbindung verliert.

Aus Komfortabilitätsgründen ermöglicht MusiQ das Teilen eines Songs mit der App. Das bedeutet, wenn ein Benutzer in einem Musikplayer wie YouTube, oder Spotify(noch nicht implementiert) den Knopf "Teilen" drückt, erscheint unter den Optionen wie "Whatsapp", "SMS", "E-Mail" auch die "MusiQ" app.
Beim Teilen werden Optionen wie "Play Next" oder "Add to Queue" unterstützt.

## Systemarchitektur

![](https://i.imgur.com/o3bCaOB.png)


Prinzipiell wurde die Arbeit wie für solche Projekte üblich auf Front-, und Backend aufgeteilt.

- Im Front End werden unter Anderem die Application, das User Interface und das Aufrufen von REST-Calls implementiert.
- Im Back End werden zum Beispiel die API-Calls auf YT-DLP gehandled, aber auch das Verwalten der REST-Calls, und das Generieren der Antworten implementiert.

Um die Zusammenarbeit zwischen Front und Back End zu verstehen, wird hier der Ablauf beschrieben, in dem ein Song in die Queue hinzugefügt wird.

1. Der Benutzer wählt in einer Musik App (Bisher YouTube) einen Song, und drückt auf "Teilen".
2. Als App mit welcher der Song geteilt wird, wählt der Benutzer "MusiQ".
3. Die App stellt eine Anfrage an den Server, um den Link in eine "Song"-Klasse zu wandeln.
4. Der Server gibt die Anfrage weiter an den "YT-DLP" Server, und löst dadurch Informationen auf, die zum Queuen benötigt werden:
   1. id - String
   2. name - String
   3. link - String
   4. interpret - String
   5. imageData- (Base64 encoded) String
5. Nun platziert die App den Song in der Queue, und updatet das UI. **Wichtig**: Hier wurde noch keine Information über das wirkliche AudioFile übertragen! 

Erreicht nun der Song den Index 1 (next to play) in der Queue der App, so wird autonom eine "GetSongRequest" an den Server gesendet. Dann wird ein Base64-encoded String mit den entsprechenden Audio-Daten empfangen. Dadurch wird verhindert, dass die Daten zwischen dem Abspielen zweier Songs erst heruntergeladen werden müssen und, sofern nicht mehrere Songs auf einmal übersprungen werden.

Mit den erhaltenen Daten wird nun der Song über den Standard-Medienausgang des Mobiltelefons ausgegeben. Sollte ein BT-Gerät verbunden sein, so wird dieses zum Abspielen der Musik verwendet.

## Front End
### Flutter
Flutter ist ein Multi-Platform-Framework für App-Entwicklung und verwendet grundsätzlich Dart als Programmiersprache.

Flutter zielt darauf ab, auf möglichst vielen Platformen zu laufen. Dabei werden aus einer Codebase native Apps für die verschiedenen Platformen (Android, iOS, macOS, etc.) generiert.

Flutter konstruiert Apps durch einen sogenannten Widget-Tree, welcher mit einem Root-Widget beginnt.
Jedes der Child-Widgets kann je nach Implementierung beliebig viele eigene Child-Widgets haben.

### Dart
Dart ist eine moderne Programmiersprache, die hauptsächlich in der App-Entwicklung Verwendung findet.

Einige Unterschiede zu Java sind:
* Parallelisierung:
    * async/await Pattern für kleine Tasks
    * Isolate Threads mit eigenem Speicherbereich für aufwendigere Aufgaben
* Null-Safety: Variablen, die `null` sein dürfen, müssen mit ? markiert und immer explizit überprüft werden, damit keine `NullPointerException` auftreten kann.
* Kein `new` Keyword beim Aufrufen eines Konstruktors
* Private Methoden/Felder beginnen mit "_"

Genauere Informationen zu Dart sind in der Dokumentation auf "dart.dev" zu finden.

### User Interface
#### Eigene Widgets
##### action_bar.dart
Die ActionBar repräsentiert den Bereich der App, welcher den Play-Button, und die zwei Skip-Buttons beinhaltet. Jeder dieser Buttons ruft, wenn er gedrückt wird, die jeweilige Methode in der Queue auf, um die gewünschte Aktion auszuführen.

##### song_overview.dart
Das SongOverview Widget enthält das Album-Cover, den Titel, und den Interpreten des aktuell spielenden Songs. Es ist ein StatefulWidget, das immer neu erzeugt wird, wenn sich der aktuelle Song ändert.

##### queue_item.dart
QueueItems werden verwendet, um alle Songs in der Queue (außer dem Ersten) mithilfe einer ReorderableList darzustellen. Die QueueItem Klasse enthält ein kleines Album-Cover, den Titel, den Interpreten, sowie einen GestureDetector, der den gedrückten Song in der Queue nach vorne bewegt und abspielt.

#### Wichtige vorgefertigte Widgets
Flutter bietet eine große Auswahl an vorgefertigten Widgets (Alle Widgets: https://docs.flutter.dev/development/ui/widgets). Hier finden sich einige Beispiele, die auch in der App verwendet werden.

##### StatelessWidget/StatefulWidget
Hierbei handelt es sich um Base-Klassen, von denen andere Widgets erben. Stateless Widgets sind einfache Widgets wie zum Beispiel statische Boxen und Layout-Widgets. Diese Widgets werden zu Beginn einmal initialisiert und ändern sich danach nicht mehr. Stateful Widgets haben immer eine zugehörige State-Klasse, die den Zustand des Widgets speichern und dieses neu erzeugen kann, falls sich der Zustand ändert. Ein Beispiel wäre das SongOverview Widget, da es immer neu erzeugt werden muss, wenn ein anderer Song abgespielt wird.

##### Reorderable List
Ein ReorderableList Widget stellt eine Liste von Widgets, in unserem Fall QueueItems, visuell dar. Die ReorderableList wurde gewählt

##### Row/Column
In einer Row wird eine beliebige Anzahl an Child-Widgets horizontal angeordnet.
In einer Column wird eine beliebige Anzahl an Child-Widgets vertikal angeordnet.
Die Aufteilung des freien Platzes erfolgt dabei ähnlich wie bei einer CSS flex-box.

##### CustomScrollView
Ein CustomScrollView Widget enthält eine Status-L

### Logik
#### Queue
Eine Queue ist die Realisierung des FIFO-Konzepts. Im Falle der MusiQ App ist diese statisch implementiert, gespeichert werden zwar multiple Queues am Server, jedoch kann ein Benutzer immer nur in einem Raum sein, und daher auch nur eine Queue gleichzeitig verwalten.

Die Klasse beinhaltet zahlreiche Methoden, die folgende Aufgaben erfüllen müssen:
* Manipulation der Queue aufgrund von UI-Input (z.B. Skip-Button, etc.)
* Synchronisation zwischen Client und Server (mit REST)
* Synchronisation zwischen verschiedenen Clients (mit MQTT)

In den meisten dieser Methoden muss beachtet werden, dass sie sowohl von einem UI-Input, als auch von einem MQTT-Event aufgerufen werden können. Falls der Aufruf aus dem UI kommt, muss ein MQTT-Event ausgesendet werden, um die anderen Clients in den gleichen Zustand zu bringen. Das darf allerdings nicht passieren, wenn eine MQTT-Nachricht den Aufruf auslöst, weil es sonst dazu kommen kann, dass sich Clients gegenseitig immer wieder dieselbe Nachricht zuschicken und in einer Endlosschleife stecken bleiben.

#### APIClient (REST)
Der APIClient ist eine Abstraktion, die es ermöglicht, alle REST-API-Endpoints des MusiQ-Backend einfach anzusprechen. Dabei erfolgt der Verbindungsaufbau, die Konfiguration von User und Raum, sowie ein Teil des Fehlerhandlings vollkommen im Hintergrund.
Andere Klassen (hauptsächlich Queue) können diese Endpoints einfach verwenden, wobei die Serialisierung und Deserialisierung der Parameter für die Requests auch im APIClient abgearbeitet wird. Zentral dafür ist die folgende Methode:

```
static Future send<T>(T request, {String arg = ""}) async {
    if (T == GetSongRequest && arg == "") {
      throw ArgumentError("Cant send GetSongRequest without songId", "arg");
    }

    var uri = _server.resolve(_endpoints[T]! + arg);
    var httpReq = Request(_methods[T]!.name, uri);

    if (_requiresJson(T)) {
      httpReq.headers.addAll(_headers);
      httpReq.body = jsonEncode(request);
    }

    var httpRes = await _client.send(httpReq);

    if (httpRes.statusCode < 200 || httpRes.statusCode >= 300) {
      throw HttpException("$T failed", uri: uri);
    }

    Map<String, dynamic> jsonMap =
        jsonDecode(await httpRes.stream.bytesToString());

    switch (T) {
      case CreateUserRequest:
        return CreateUserResponse.fromJson(jsonMap);
      case CreateRoomRequest:
        return CreateRoomResponse.fromJson(jsonMap);
      case GetRoomsRequest:
        return GetRoomsResponse.fromJson(jsonMap);
      case QueueSongRequest:
        return QueueSongResponse.fromJson(jsonMap);
      case GetSongRequest:
        return !jsonMap.values.contains(null)
            ? GetSongResponse.fromJson(jsonMap) 
            : null;
            // API responds with null if the audio data is not downloaded yet 
            // -> dont create response object
      case FinishedSongRequest:
        return FinishedSongResponse.fromJson(jsonMap);
      case GetQueueRequest:
        return GetQueueResponse.fromJson(jsonMap);
      case ReorderSongRequest:
        return ReorderSongResponse.fromJson(jsonMap);
      case JoinRoomRequest:
        return JoinRoomResponse.fromJson(jsonMap);
      default:
        throw TypeError();
    }
  }
```

#### MessageClient (MQTT)
Die Klasse MessageClient bietet ein einfaches Interface, um die folgenden Events an alle anderen Clients zu verteilen:
* play - Fordert alle Clients auf, den ersten Song in der Queue abzuspielen
* pause - Fordert alle Clients auf, das Abspielen zu pausieren
* skipNext - Fordert alle Clients auf, den aktuellen Song zu überspringen
* skipPrev - Fordert alle Clients auf, an den Anfang des aktuellen Songs zu springen
* skipTo / queueUpdate - Fordert alle Clients auf, die gesamte Queue vom Server zu laden

Für ein solches System ist REST keine Option, weil es einem REST-Server nicht erlaubt ist, Clients unaufgefordert anzusprechen.
MQTT hingegen verteilt jede Nachricht an alle Subscriber eines Topics, wobei sich die (einzigartige) Raum-Id dafür sehr gut eignet.

### Packages
Packages können mit dem im Flutter-SDK inkludierten Package-Manager zum Projekt hinzugefügt werden (`flutter pub get <package-name>`).

#### receive_sharing_intent
Das Package "receive_sharing_intent" ist dazu da, die aus anderen Apps geteilten Inhalte in Dart Code bereitzustellen.

#### json_annotation/json_serializable
"json_annotation" enthält nur eine Annotation, die damit markierte Klassen als serialisierbar deklariert. Für solche Klassen werden mit "json_serializable" im Hintergrund automatisch Methoden generiert, die die Serialisierung und Deserialisierung implementieren.

#### perfect_volume_control
"perfect_volume_control" bietet einige Methoden, um mit dem Lautstärkeregler des Betriebssystems zu interagieren.

#### audioplayers
Dieses Package enthält die AudioPlayer Klasse, die verwendet wird, um die Songs abzuspielen.

#### http
"http" enthält eine Client-Klasse, die für die REST-API-Calls verwendet wird.

#### mqtt_client
"mqtt-client" wird für die MQTT-basierte Synchronisation zwischen mehreren Clients verwendet.


## Back End

### Technologien und Motivation

#### C#

C# ist eine objektorientierte Programmiersprache von Microsoft. C# ist sehr innovativ und flexibel verwendbar und dadurch auch immer beliebter bei Entwickler. Vorallem in der Web- und Backend-Entwicklung glänzt C# mit ASP. NET. Des weiteren wurde versucht sich durch dieses Projekt sich weiterzubilden welches einer von vielen Gründen war nicht Java zu benützen.

##### ASP. NET

Webservices mit C# lassen sich am besten mit ASP. NET lösen, dass soll nicht heißen, das ASP. NET nur durch C# lebt. ASP. NET ist das zweit meist verwendete Framework für Webseiten auf Serverseite, das liegt an der unglaublichen Leistung, welche ASP. NET auf mehreren Ebenen bringt. Zum Einen ist das Framework von Microsoft sehr schnell, zum Anderen sehr flexibel in der Anwendung. 

##### FastEndpoints

FastEndpoint ist eine bessere Alternative zur C# hauseigenen Minimal Api. Nicht nur das die Strukturierung von FastEndpoints "schöner" (= vielleicht subjektiv) ist, sonder es ist gleich schnell, verwendet weniger Speicher und kann mehr *requests per second* verwalten wie ein MVC Controller.

##### Swagger

Ein mächtiges Werkzeug jedes Backend-Entwicklers ist entweder Postman oder Swagger. Eines von beiden ist ein "must-have" um seine Endpoints zu testen. In unserem Fall wurde Swagger verwendet, da FastEndpoints direkt ein Paket zur verfügung stellt. Swagger baut einen Webseite mit Server auf wo man jeden Endpoint seiner API und deren Request- und Response-bodys genau inspizieren kann.

##### FluentValidation

Damit man incoming Requests angenehm überprüfen kann, ob diese auch halbwegs korrekt sind, ist FluentValidation die perfekte Bibliothek für ASP.NET. Mit FluentValidation kann man Regeln für RequestObjekte und deren properties erstellen welche erfüllt sein müssen. Später dazu mehr im Aufbau.

#### Rest

Representational State Transfer ist wahrscheinlich einer der bekanntesten Paradigmen für Webservices. REST verwendet HTTP um Daten zu senden. GET/POST/PUT/UPDATE/DELETE sind request Typen, welche verwendet werden können um Daten anzufragen oder Daten an den Server zu senden.
Rest wird in unserem Fall dazu verwendet Lieder zur Wiedergabeliste hinzuzufügen, Benutzer zu erstellen, Räume zu erstellen, ...

#### MQTT

MQTT ist ein Messaging-Protokoll welches das Publisher-Subscriber-Pattern verwendet um Daten über das Netz zu senden. MQTT steht für Message Queuing Telemetry Transport und wurde dazu entwickelt Bandbreite und Leistung einzusparen. Des weiteren sind hohe Latenzen und Verbindungsverlust ebenfalls kein Problem für MQTT und somit optimal für die Verbindung mit einem mobilen Gerät.

#### YTDLP

Eine bekannte API um Videos und Lieder von YouTube zu laden ist youtube-dl. YT-DLP ist ein "fork" von youtube-dl welches neue features und fixes bietet. Des weiteren konnte über yt-dlp höhere Downloadgeschwindigkeit erreicht werden was für uns eine hohe Priorität hatte.

### Aufbau

Die Struktur folgt einer etwas vereinfachten Version der Clean Architecture für ASP. NET, welche sich überwiegend als Standard etabliert. Der Grundgedanke ist das Domain Centric Design.
Im Zentrum stehen die Domain Objekte. Diese Objekte, sind jene, die für den gesammten Bereich Gültigkeit haben und nicht nur für den aktuellen Microservice. Wir haben aktuell aus Zeitgründen jedoch Entities und Domainonjekte nicht getrennt, da wir aktuell nicht mehrere Services gebaut haben.
Der Teil der bei uns hauptsächlich verwendet wurde ist der Rest. Die Aufteilung in Presentation Layer, Application Layer und Infrastructure Layer.
Der Presentation Layer ist bei uns die API, welche über ASP. NET realisiert wurde. Alles wofür die API zuständig ist, ist die Requests zu validieren, das Mapping zwischen Entities und DTOs und der eigentliche workload wird dann an den Application Layer propagiert. Normalerweise würde das über das MediatR package und dem CQRS Pattern stattfinden, wobei wir die Services aktuell direkt über den DI Container auflösen um Entwicklungszeit einzusparen.
Sobald die Request dann vom Application Layer übernommen wird, arbeitet dieser die Request ab. Allerdings soll der Application Layer nicht über Implementations der Infrastruktur bescheid wissen sondern sich nur mit Business Logik auseinandersetzen. Deshalb wird die Abstraktion (Interface) der Infrastruktur im Application Layer erstellt wobei der Application Layer diese blind verwendet ohne zu wissen wie z.B. die Datenbank wirklich funktioniert.
Diese Infrastruktur wird dann im Infrastructure Layer erstellt. Hier werden einfach alle Interfaces die der Application Layer bereit stellt für Infrastruktur implementiert. Dies ermöglicht, im späteren Verlauf nicht nur kleine Änderungen im Code der Infrastruktur vorzunehmen sondern sogar, dass z.B. MySql mit CosmosDB getauscht wird ohne, dass sich aus sicht der Business Logik etwas ändert. 

#### Middleware

Um Validierung in ASP. NET zu verstehen, muss man erst über Middlewares sprechen. Es kann bei jeder Request eine oder mehrere Middlewares registriert werden. Diese werden dann nacheinander, in der Reihenfolge, in welcher sie registriert wurde ausgeführt. Jede Middleware ist auch dafür verantwortlich die nächste Middleware aufzurufen. Die bedeutet, dass eine Request auch schon bevor diese im eigentlichen Handler bearbeitet wird abgebrochen werden kann, ohne dass die danach folgenden Methoden etwas davon mitbekommen, dass es überhaupt eine Request gab.

#### Validators

Das Konzept der Middlewares macht sich die Validators aus Fluent Validation zu nutze. Man generiert eine Klasse, in der man die Regeln definiert, welche entscheiden, ob eine Request den Validen Inhalt hat oder nicht. Fluent Validation fügt die Klasse, dann als Middleware hinzu und bricht die Request ab, falls die Request nicht mit den Regeln vereinbar ist.

#### Mapper

Mapper in FastEndpoints sind ein sehr einfaches Konstrukt. Jeder Mapper muss von der Klasse Mapper ableiten und dabei den generischen Typen der Request, Response und Entity übergeben. Hier wird nun einfach jeweils eine Methode implementier, welche die Request auf das Entity Objekt wandelt und die Entity dann wieder zu einer Response.

#### Endpoints

Die Endpoints sind die Klassen, in denen wirklich definiert wird wie sich die API verhält. Dies passiert einersets dadruch, dass man von Endpoint erbt und dabei den generischen Typ der Request, Response und des Mappers übergibt und durch das Konfigurieren in der Configure Methode.

In der Configure Methode wird der Http Type (GET, POST ...) festgelegt, und die Route des Endpoints. In unserem Fall sind alle unsere Endpoint aktuell noch so konfiguriert, dass diese keine Authentifizierung benötigen und die Version des Endpoints wird auch noch gesetzt.

In der Methode HandleAsync wird dann die eigentliche Request, welche ankommt, abgearbeitet. Hier wird dann der eigentliche workload an die Services des Application Layers übergeben.

#### Services

Services sind dafür da, die Funktionalität, welche die Applikation eigentlich wirklich ausmacht abzubilden. In diesen Services verbirgt sich die Business Logik. Jedoch nicht die Anbindung an Infrastruktur. Die Services verwenden lediglich die Abstraktionen der Infrastruktur ohne direkte Abhänigkeit zu der Implementierung.

#### Repositories

Die Repositories sind eine abstrakte weise State Stores zu betrachten. Es ist klar, dass irgendetwas gespeichert werden muss allerdings ist es sinnvoll, sich dabei aus Sicht der Schnittstelle darauf zu beschränken, zu definieren, dass irgendetwas bitte das Objekt speichern soll bzw. dieses Objekt löschen können will etc. Dies ermöglicht es gut die Implementierung und die Abhängigkeiten auszutauschen, ohne die Schnittstelle ändern zu müssen.

### Docker

Docker ermöglicht es eine Applikation zusammen mit allen sine Abhängigkeiten in ein Image zu kapseln. Dies hat den Vorteil, dass sich beim Einsatz die Applikation starten lässt ohne nenneswert vorarbeit leisten zu müssen.

Bei uns kommt Docker einerseits zum Einsatz um das Projekt an sich zu kompilieren, was dringend notwendig ist für unsere ci pipeline, und auch die kompilierte Anwendung selbst läuft dann in einem Container, da die Applikation ca. 700Mb an Abhängigkeiten hat.

## Mögliche Erweiterungen

1. **"Queue Playlist" - Feature**
   Das abspielen gesammter Playlists erleichtert die Benutzung der App erheblich.
2. **Unterstützung von Spotify/SoundCloud/AppleMusic**
   Die implementierung dieser verschiedenen anbieter Funktioniert jeweils anders, und ist dementsprechend einiges an Arbeit.
3. **Reihenfolge der Songs ändern erlauben**
    In Arbeit! (API✅)
5. **Bereits gespielte Lieder anzeigen**
    Möglicherweise in einem Eigenen Fenster, eventuell Grundlage für Autoplay.
7. **Autoplay implementieren**
    Das Ende der Queue stoppt momentan einfach die Queue. Wesentlich schöner wäre das weiterspielen von änhlichen Liedern.
8. **Zeitleiste des derzeitigen Songs**
    Erfordert Synchronisierung des Zeitleiste, erlaubt jedoch das Zurückspulen in einem Song.
9.  **Speichern der Daten in einer Datenbank**
Derzeit nur lokal am Server gespeichert und nicht in Datenbanken.
11. **Authentifizierung der Benutzer**

## Verwendung
Um MusiQ zu testen, oder zu verwenden, wird entweder ein Android-Gerät, oder ein Android-Emulator (Android Studio) benötigt. (https://www.alphr.com/run-android-emulator/)
Sollte ein echtes Gerät verwendet werden, muss dafür Entwicklermodus eingeschalten werden, und USB-Debuggung aktiviert sein. Die Verbindung erfolgt über USB.

Es wird beim Start der App automatisch eine Verbindung zum Server aufgebaut, dieser läuft innerhalb der nächsten Monate weiter. Sollte die Verbindung zum Server fehlschlagen, kontaktieren Sie Fabian Bleck.

## Referenzen
### Frontend

**Dart:** https://dart.dev/guides
**Flutter:** https://flutter.dev/learn
**Flutter Examples:** https://flutter.github.io/samples/#

### Backend

**C#:** https://docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/
**MQTT:** https://mqtt.org/
**Fluent Validation:** https://docs.fluentvalidation.net/en/latest/
**FastEndpoints:** https://fast-endpoints.com/
**REST:** https://de.wikipedia.org/wiki/Representational_State_Transfer
**ASP. NET:** https://dotnet.microsoft.com/en-us/apps/aspnet
**.NET Clean Architecture:** https://github.com/jasontaylordev/CleanArchitecture