mix_match_logo

Projekt WS25 | MixMatch (Fortsetzung)

Von am 02.03.2026

Um mein Projekt “MixMatch” näher in die Richtung production-ready Zustand zu bringen wurde das 3. Semester von mir genutzt, nahtlos an den zuvor vorhandenen Stand der Entwicklung anzuschließen.
Im Semester habe ich mich im wesentlichen darauf fokussiert, Community Features, wie Sterne-Bewertungen und Kommentier- bzw. Antwortfunktion, in meine Nativescript-App zu integrieren und deutlich mehr Zeit in Bugfixing zu investieren. Nebenbei wurde stellenweise das Design weiter optimiert, sowie das Fehlerhandling überarbeitet. Zur Abrundung wurde im Projektabschnitt eine exportierbare Einkaufliste für Zutaten, die der/die User:in braucht, um die Rezepte nachzumachen, integriert.

Neuerungen seit dem letzten Semester

  • Filteroptionen in der Rezeptliste

Der größte strukturelle Umbau fand in der Rezeptliste statt. Bisher wurden die Rezepte einfach als rohe Liste angezeigt, ohne Möglichkeit zur Filterung. Jetzt gibt es eine eigene Suchleiste mit Autocomplete-Vorschlägen der gefilterten Drinks, eine alphabetische Sortierung mit Drink-Nummer pro Karte, sowie einen Toggle-Filter für Signature Drinks.

  • Kommentar-Sektion bei den Recipes

Das Herzstück des Projektes war die Erweiterung, dass jedes Rezept nun eine vollwertige Community-Section hat. User:innen können Kommentare mit Sternebewertung hinterlassen, auf Kommentare anderer antworten und eigene Kommentare wieder löschen. Die Bewertungen fließen in eine Durchschnittsbewertung ein, die prominent im Header der Rezeptkarte angezeigt wird. Die gesamte Kommunikation läuft in Echtzeit über WebSockets.

  • Benötigte Zutaten einer Einkaufsliste hinzufügen

Bisher wurden alle nicht angegebenen Zutaten bei der Hauptsuche automatisch gesammelt und global in meinem Code zwischengespeichert. Das wurde grundlegend über ein Einkaufslisten-Feature überarbeitet: Pro Rezept kann der/die Nutzer:in nun selbst entscheiden, ob er/sie die fehlenden Zutaten zur Einkaufsliste hinzufügen möchte. Fehlende Zutaten werden in der Zutatenliste farblich in Orange hervorgehoben. Ein einzelner Button pro Rezept überträgt dann gezielt nur die fehlenden Zutaten in die Einkaufsliste. Die Einkaufsliste selbst unterstützt den Export als PDF über den nativen Android PrintManager sowie im CSV-Format.

Neben diesen Änderungen konnten viele Bugs behoben werden. Speziell einige Bugs, die die Funktionalität rund um die Erstellung von “Signature Drinks” (anlegen/löschen) als auch den Umgang mit gekennzeichneten Favoriten-Rezepten, eingeschränkt haben, existieren nicht mehr.

Technischer Part

Der Großteil an Technologien ist gleich geblieben. Im Frontend setze ich weiterhin auf NativeScript Angular bzw. Tailwind für das Design und im Backend SpringBoot. Das Backend habe ich dieses Semester erstmalig über den Online-Service Railway gehostet. Dazugekommen sind vor allem die WebSocket-Bibliothek @stomp/stompjs im Frontend sowie die entsprechende Spring WebSocket-Abhängigkeit im Backend.

Für die Kommentar-Funktion war von Anfang an klar, dass klassische HTTP-Requests nicht ausreichen , sonst wäre ein ständiges manuelles Aktualisieren notwendig gewesen, um neue Kommentare zu sehen. Die Wahl fiel auf WebSockets mit STOMP, einem einfachen Messaging-Protokoll das auf WebSocket aufsetzt und ein Publish-Subscribe-Modell ermöglicht. Im Spring Boot Backend wird dabei ein Message-Broker konfiguriert der eingehende Nachrichten an alle Subscriber des jeweiligen Rezept-Topics weiterleitet. Jedes Rezept hat seinen eigenen Kanal, zum Beispiel /topic/recipe/42/comments. Nur Nutzer:innen die gerade dieses Rezept offen haben, empfangen die Kommentar, die zu diesem Rezept abgegeben wurden. Der Controller nimmt neue Kommentare entgegen, speichert sie und schickt sie sofort an alle aktiven Subscriber:

@MessageMapping("/comments/create")
public void createComment(CreateCommentRequestDto request, Principal principal) {
    User author = userRepository.findByUsername(principal.getName())...;
    CommentResponseDto saved = commentService.createComment(request, author);
    messagingTemplate.convertAndSend(
        "/topic/recipe/" + request.getRecipeId() + "/comments", 
        saved
    );
}

Im Frontend übernimmt ein dedizierter CommentWebSocketService die gesamte Verbindungslogik. Er verwendet die @stomp/stompjs Bibliothek, schickt den JWT-Token beim Verbindungsaufbau mit und subscribt auf den zum Rezept passenden Topic-Kanal:

this.stompClient = new Client({
    webSocketFactory: () => new WebSocket(`wss://...railway.app/ws`),
    connectHeaders: { Authorization: `Bearer ${this.authToken}` },
    reconnectDelay: 5000,
});

this.stompClient.onConnect = () => {
    this.stompClient.subscribe(
        `/topic/recipe/${this.recipeId}/comments`,
        (message) => {
            const comment = JSON.parse(message.body);
            onCommentReceived(comment);
        }
    );
};

Herausforderungen & Learning(s) daraus

Die größte Herausforderung des Semesters waren eindeutig die WebSockets. Theoretisch klingt es simpel (Verbindung öffnen und Nachrichten empfangen), jedoch in der Praxis gab es einige Hürden. Zuerst die Authentifizierung: anders als bei normalen HTTP-Requests musste der JWT-Token über die STOMP connectHeaders mitgeschickt und im Backend extra validiert werden. Dann das Problem mit inaktiven Verbindungen, denn ohne der sogenannten “Heartbeat-Konfiguration” auf beiden Seiten kamen Nachrichten irgendwann einfach nicht mehr an (Fehlermeldungen wurden nicht geworfen). Zu guter Letzt das Aufräumen der Verbindungen, denn ohne explizites disconnect() blieben im Hintergrund immer mehr aktive Verbindungen offen.

Was das Aufräumen so schwierig gemacht hat, war der Pager, mit dem man zwischen den Rezepten swipen kann. NativeScript’s Pager-Komponente erstellt alle Rezept-Items gleichzeitig beim Laden, bei 14 Rezepten also sofort 14 WebSocket-Verbindungen und 14 HTTP-Requests, obwohl der/die Nutzer:in nur ein einziges Rezept sieht. Noch dazu wird ngOnDestroy beim Wischen zwischen den Items nicht zuverlässig aufgerufen, weil der Pager seine Views intern recycelt statt sie wirklich zu zerstören. Dadurch hat die Cleanup-Logik nie gefeuert.

Die Lösung war letztlich ein Umdenken: statt auf ngOnDestroy zu vertrauen, steuert jetzt ein Flag, ob die Kommentar-Sektion überhaupt gerendert wird. Sobald der Nutzer wegwischt, verschwindet die Komponente via *ngIf aus dem DOM, damit das Cleanup zuverlässig durchläuft. Das wesentliche Learning dabei, war ein gut ausgebautes Debugging zu haben.

Fazit und zukünftige TODOs

In diesem Projektabschnitt konnte ich mich erstmalig herausfordernd damit befassen, wie WebSockets technisch umgesetzt werden. Das war gleichzeitig eines der interessantesten und aufwändigsten Themen des Semesters. Die vielen Debugging-Sessions haben mich aber auch am meisten weitergebracht. Rückblickend bin ich sehr zufrieden damit, was ich mir vorgenommen hatte auch tatsächlich vollständig umgesetzt zu haben. Gerade das Bugfixing war diesmal besonders aufwändig, hat aber mit großem Erfolg geendet und manche Features meiner App brauchbarer gemacht.

An diesen Dingen sollte nun weitergearbeitet werden: Performance-seitig gibt es noch Luft nach oben, sowohl beim initialen Laden als auch bei den Screen-Wechseln merkt man stellenweise noch Verzögerungen die es zu optimieren gilt. Auch das Design ist noch nicht wirklich durchgängig einheitlich, was ich in einem nächsten Schritt konsequenter angehen möchte. Dazu kommt, dass ich bisher kaum echtes User-Feedback eingeholt habe, das soll sich ändern. Auf technischer Seite fehlen noch automatisierte Tests, die zur noch besseren Stabilität der App beitragen sollen. Schlussendlich wäre das große Ziel die App veröffentlichen zu können.


Beitragsbild: dieses wurde mittels Einsatz von KI erzeugt

Beitrag kommentieren

(*) Pflichtfeld