Insight

Insight

Insight

Insight

Von BLoC bis Clean Architecture - Maximiere die Leistung und Skalierbarkeit deiner Flutter-Apps durch eine effektive Systemarchitektur

Von BLoC bis Clean Architecture - Maximiere die Leistung und Skalierbarkeit deiner Flutter-Apps durch eine effektive Systemarchitektur

04.03.2024

App Architecture
App Architecture
App Architecture
App Architecture

Foto - Skaletz Photography


Systemarchitekturen sind ein strukturelles Framework, das die Organisation und das Design einer Software-Anwendung festlegt. In Flutter-Apps ermöglichen sie eine klare Trennung von Anwendungslogik und Benutzeroberfläche, was zu besserer Wartbarkeit, Skalierbarkeit und Testbarkeit führt.


Die Bedeutung von Systemarchitekturen in Flutter-Apps liegt in ihrer Fähigkeit, komplexe Anwendungen zu strukturieren und zu organisieren. Sie helfen dabei, Code besser zu organisieren, Abhängigkeiten zu verwalten und die Wiederverwendbarkeit von Komponenten zu fördern. Durch den Einsatz einer geeigneten Architektur können Entwickler effizienter arbeiten und hochwertige Apps entwickeln, die den Anforderungen ihrer Nutzer gerecht werden.

 


Grundlagen der Systemarchitektur


Warum ist eine solide Systemarchitektur wichtig?


Eine gut durchdachte Systemarchitektur legt das Fundament für eine robuste, skalierbare und wartbare App. Sie ermöglicht es, die Anwendungslogik klar von der Benutzeroberfläche zu trennen, was eine bessere Strukturierung und Organisation des Codes ermöglicht. Durch eine solide Architektur wird die App leichter verständlich, einfacher zu warten und flexibler für zukünftige Änderungen.


Kriterien für die Auswahl einer geeigneten Architektur


Bei der Auswahl einer geeigneten Architektur für deine Flutter-App solltest du verschiedene Kriterien berücksichtigen. Dazu gehören die Komplexität deiner App, die Anforderungen an die Skalierbarkeit und Wartbarkeit, das Team-Know-how und die Präferenzen der Entwickler. Es ist wichtig, eine Architektur zu wählen, die gut zu den Anforderungen deines Projekts passt und die Entwicklung erleichtert, anstatt sie zu beinträchtigen.


Einfluss auf die Entwicklung, Wartung und Skalierbarkeit von Apps


Eine solide Systemarchitektur hat einen direkten Einfluss auf die gesamte Lebensdauer einer App. Während der Entwicklung erleichtert sie die Zusammenarbeit im Team, fördert eine strukturierte Code-Basis und ermöglicht eine schnellere Implementierung neuer Funktionen. In der Wartungsphase vereinfacht sie es, Fehler zu finden und zu beheben, und erleichtert die Skalierung der App, um den steigenden Anforderungen gerecht zu werden.

 


BLoC-Architektur


Die BLoC-Architektur, oder Business Logic Component, ist eine leistungsfähige Architektur, die in der Flutter-Entwicklung weitverbreitet ist. Sie ermöglicht eine klare Trennung von Geschäftslogik und Benutzeroberfläche, was zu besser strukturiertem Code und einer verbesserten Wartbarkeit führt.


Funktionsweise und Kernprinzipien


Das zentrale Konzept der BLoC-Architektur ist die Verwendung von Streams und Events, um den Zustand der Anwendung zu verwalten und zu aktualisieren. Eine BLoC-Komponente empfängt Eingaben in Form von Events, verarbeitet diese und gibt neue Zustände über einen Stream zurück. Dadurch bleibt die Benutzeroberfläche reaktiv und kann auf Änderungen im Zustand der Anwendung reagieren. Die Kernprinzipien der BLoC-Architektur sind Entkoppelung, Wiederverwendbarkeit und Testbarkeit.


Implementierung in Flutter-Apps


In Flutter wird die BLoC-Architektur oft mit den BLoC-eigenen Klassen BlocProvider, BlocBuilder, BlocListener und BlocConsumer implementiert. Eine BLoC-Komponente wird als separate Klasse definiert, die einen Stream von Zuständen erzeugt. Diese Komponente kann dann in Widgets verwendet werden, um die Benutzeroberfläche zu aktualisieren, sobald sich der Zustand ändert. Durch diese Entkoppelung bleibt die Benutzeroberfläche unabhängig von der Geschäftslogik und kann leicht wiederverwendet werden.


Hier ist ein einfaches Beispiel für die Implementierung der BLoC-Architektur in Flutter unter Verwendung der BLoC-eigenen Klassen:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Definiere den Zustand
enum CounterEvent { increment, decrement }

// Definiere die BLoC-Klasse
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        yield state + 1;
        break;
      case CounterEvent.decrement:
        yield state - 1;
        break;
    }
  }
}

// Definiere das Widget, das die BLoC-Komponente verwendet
class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Zugriff auf die BLoC-Instanz
    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('BLoC Beispiel')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // Anzeige des aktuellen Zählerwerts
            BlocBuilder<CounterBloc, int>(
              builder: (context, count) {
                return Text(
                  'Zähler: $count',
                  style: TextStyle(fontSize: 24),
                );
              },
            ),
            SizedBox(height: 20),
            // Schaltflächen zum Inkrementieren und Dekrementieren des Zählers
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () =>
                      _counterBloc.add(CounterEvent.increment),
                ),
                IconButton(
                  icon: Icon(Icons.remove),
                  onPressed: () =>
                      _counterBloc.add(CounterEvent.decrement),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BLoC Beispiel',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: BlocProvider(
        create: (context) => CounterBloc(),
        child: CounterWidget(),
      ),
    );
  }
}


Dieses Beispiel zeigt die Verwendung von BLoC (Business Logic Component) in Flutter. Es enthält eine BLoC-Klasse (CounterBloc), die den Zustand der Anwendung verarbeitet, und ein Flutter-Widget (CounterWidget), das die Benutzeroberfläche darstellt und mit der BLoC-Komponente interagiert. Die Benutzeroberfläche wird über den BlocBuilder aktualisiert, der auf Änderungen im Zustand der BLoC-Komponente reagiert.


Vorteile und Anwendungsfälle


Die BLoC-Architektur bietet eine Vielzahl von Vorteilen, darunter eine verbesserte Wartbarkeit, Skalierbarkeit und Testbarkeit der Anwendung. Sie eignet sich besonders gut für komplexe Anwendungen, bei denen eine klare Trennung von Geschäftslogik und Benutzeroberfläche erforderlich ist. Durch die Verwendung von Streams und Events können Änderungen im Zustand der Anwendung effizient und reaktiv verarbeitet werden.


Wie du BLoC in deiner App-Entwicklung einsetzen kannst


Um die BLoC-Architektur in deiner Flutter-App zu nutzen, solltest du zunächst deine Geschäftslogik identifizieren und in separate BLoC-Komponenten auslagern. Diese Komponenten können dann in Widgets eingebunden werden, um die Benutzeroberfläche zu aktualisieren. Es ist wichtig, die Kommunikation zwischen BLoC-Komponenten und Widgets klar zu definieren und den Zustand der Anwendung effizient zu verwalten.

 


MVC-Architektur


Die MVC-Architektur, oder Model-View-Controller, ist eine bewährte Methode zur Strukturierung von Software-Anwendungen, die in verschiedenen Bereichen der Softwareentwicklung eingesetzt wird.


Aufbau und Bestandteile


Die MVC-Architektur besteht aus drei H auptkomponenten: dem Model, der View und dem Controller. Das Model repräsentiert die Daten und die Geschäftslogik der Anwendung, die View ist für die Darstellung der Benutzeroberfläche verantwortlich, und der Controller koordiniert die Interaktionen zwischen dem Model und der View.


Umsetzung in Flutter-Apps


In Flutter-Apps kann die MVC-Architektur durch die Verwendung von separaten Klassen für das Model, die View und den Controller implementiert werden. Das Model kann Datenklassen oder Datenbankzugriffe umfassen, die View kann Flutter-Widgets enthalten, und der Controller kann die Logik zur Verarbeitung von Benutzereingaben und zur Aktualisierung des Models und der View enthalten.


Hier ist ein einfaches Beispiel für die Umsetzung der MVC-Architektur in einer Flutter-App:

import 'package:flutter/material.dart';

// Model
class CounterModel {
  int count = 0;
}

// View
class MyHomePage extends StatelessWidget {
  final CounterController controller = CounterController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MVC Beispiel'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Zähler: ${controller.model.count}',
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                ElevatedButton(
                  onPressed: () => controller.increment(),
                  child: Text('Inkrementieren'),
                ),
                ElevatedButton(
                  onPressed: () => controller.decrement(),
                  child: Text('Dekrementieren'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// Controller
class CounterController {
  CounterModel model = CounterModel();

  void increment() {
    model.count++;
  }

  void decrement() {
    model.count--;
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MVC Beispiel',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}


In diesem Beispiel wird die MVC-Architektur in einer einfachen Flutter-App umgesetzt. Das Model (CounterModel) enthält den Zustand der Anwendung, in diesem Fall einen Zähler. Die View (MyHomePage) ist eine Flutter-Widget-Klasse, die das Benutzerinterface darstellt und auf den Controller zugreift, um Aktionen auszulösen. Der Controller (CounterController) enthält die Geschäftslogik der Anwendung und aktualisiert das Model entsprechend den Benutzeraktionen.


Vor- und Nachteile


Die MVC-Architektur bietet eine klare Trennung von Daten, Benutzeroberfläche und Logik, was die Wartbarkeit und Skalierbarkeit der Anwendung verbessert. Durch die Wiederverwendung von Komponenten und die klare Definition von Verantwortlichkeiten kann die MVC-Architektur die Entwicklung effizienter machen. Allerdings kann sie in komplexen Anwendungen zu einem übermäßigen Controller und zu einer unklaren Zuständigkeitsverteilung führen.


Einsatzmöglichkeiten und bewährte Praktiken


Die MVC-Architektur eignet sich besonders gut für Anwendungen mit einer klaren Trennung von Daten, Benutzeroberfläche und Logik, wie CRUD-Anwendungen oder kleinere Apps mit begrenztem Funktionsumfang. Es ist wichtig, die Verantwortlichkeiten klar zu definieren und sicherzustellen, dass der Controller nicht überladen wird. Außerdem können bewährte Praktiken wie Dependency Injection und Unit-Tests die Implementierung der MVC-Architektur erleichtern.

 


GetX-Architektur


Die GetX-Architektur ist eine moderne Architektur, die speziell für die Flutter-Entwicklung entwickelt wurde. Sie zeichnet sich durch ihre Einfachheit, Flexibilität und Leistungsfähigkeit aus und bietet eine Vielzahl von Funktionen und Konzepten, die die Entwicklung von Flutter-Apps vereinfachen und beschleunigen.


Hauptmerkmale und Konzepte


Die GetX-Architektur basiert auf der Idee der Dependency Injection und reaktiven Programmierung. Sie bietet eine Vielzahl von Funktionen und Konzepten, darunter Zustandsmanagement, Navigation, Dialoge, internationale Lokalisierung, Dependency Injection und vieles mehr. GetX verwendet ein einfaches und intuitives API, das es Entwicklern ermöglicht, schnell und effizient zu arbeiten, ohne sich um komplexe Konfigurationen kümmern zu müssen.


Integration in Flutter-Apps


Die Integration von GetX in Flutter-Apps ist einfach und unkompliziert. Es gibt ein spezielles Paket namens "get" in Flutter, das alle erforderlichen Funktionen und Klassen für die Verwendung von GetX bereitstellt. Durch die Installation des Pakets und die Einbindung der entsprechenden Klassen in deine Flutter-App kannst du sofort von den Funktionen und Konzepten von GetX profitieren.


Hier ist ein einfaches Beispiel, für die Umsetzung von GetX in einer Flutter-App:

import 'package:flutter/material.dart';
import 'package:get/get.dart'; // Importiere das GetX-Paket

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp( // Verwende GetMaterialApp anstelle von MaterialApp
      title: 'GetX Beispiel',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Beispiel'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            GetBuilder<MyController>( // Verwende GetBuilder, um auf die GetX-Komponente zuzugreifen
              init: MyController(), // Initialisiere den Controller
              builder: (controller) {
                return Text(
                  'Zähler: ${controller.counter}',
                  style: TextStyle(fontSize: 24),
                );
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                ElevatedButton(
                  onPressed: () => Get.find<MyController>().increment(), 
                  child: Text('Inkrementieren'),
                ),
                ElevatedButton(
                  onPressed: () => Get.find<MyController>().decrement(),
                  child: Text('Dekrementieren'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// Definiere den Controller
class MyController extends GetxController { 
  var counter = 0.obs; 

  void increment() => counter.value++; 
  void decrement() => counter.value--; 
}


In diesem Beispiel wird das get Paket verwendet, um den MyController zu verwalten und auf seine Daten zuzugreifen. Die GetMaterialApp ersetzt die MaterialApp, um die Funktionen von GetX zu nutzen. Mit GetBuilder kann auf den Controller zugegriffen werden, und mit Get.find können Methoden des Controllers aufgerufen werden, um den Zähler zu inkrementieren und zu dekrementieren. Um auf Get.find zugreifen zu können, muss der Controller mit dem Get.put-Befehl an geeigneter Stelle initialisiert werden.


Stärken und Schwächen


Die Stärken von GetX liegen in seiner Einfachheit, Flexibilität und Leistungsfähigkeit. Es bietet eine Vielzahl von Funktionen und Konzepten, die die Entwicklung von Flutter-Apps vereinfachen und beschleunigen. GetX ermöglicht eine effiziente Verwaltung des Zustands der Anwendung, eine einfache Navigation zwischen Bildschirmen, die einfache Integration von Dialogen und vieles mehr. Eine Schwäche ist die Verwendung von GetX in großen und komplexen Anwendungen, die zu einer unübersichtlichen Code-Basis führen kann und eine sorgfältige Planung und Strukturierung erfordert.


Strategien zur Nutzung von GetX in deiner App-Entwicklung


Um GetX effektiv in deiner App-Entwicklung zu nutzen, solltest du zunächst die Funktionen und Konzepte von GetX kennenlernen und verstehen. Dann kannst du entscheiden, welche Funktionen du in deiner App verwenden möchtest und wie du sie am besten integrieren kannst. Es ist wichtig, die Code-Basis klar zu strukturieren und die Verantwortlichkeiten der verschiedenen Komponenten klar zu definieren, um eine wartbare und skalierbare App zu gewährleisten.

 


Clean Architecture: Konzept und Prinzipien


Die Clean Architecture ist ein Architekturmuster, das darauf abzielt, Software-Anwendungen in verschiedene Schichten zu unterteilen, um die Abhängigkeiten zwischen den einzelnen Komponenten zu minimieren und die Geschäftslogik von technischen Details zu entkoppeln. Das Hauptprinzip der Clean Architecture ist die Abhängigkeitsregel, die besagt, dass Abhängigkeiten immer von außen nach innen zeigen sollten, wobei die inneren Schichten von den äußeren unabhängig sind.


Schichten und Abhängigkeiten


Die Clean Architecture besteht aus verschiedenen Schichten, darunter die äußere Schicht, die Benutzeroberfläche, die Anwendungslogik, die Geschäftsregeln und das Datenbankmodell. Jede Schicht hat ihre spezifischen Aufgaben und Verantwortlichkeiten, und die Abhängigkeiten zwischen den Schichten sollten so organisiert sein, dass Änderungen in einer Schicht keine Auswirkungen auf andere Schichten haben.


Anwendung in Flutter-Apps


In Flutter-Apps kann die Clean Architecture durch die Verwendung von separaten Modulen für die Benutzeroberfläche, die Anwendungslogik und die Datenzugriffsschicht umgesetzt werden. Die Benutzeroberfläche kommuniziert mit der Anwendungslogik über definierte Schnittstellen, und die Anwendungslogik ist von der Datenzugriffsschicht entkoppelt, was eine bessere Testbarkeit und Wartbarkeit der App ermöglicht.


Hier ist ein einfaches Beispiel für die Anwendung der Clean Architecture in einer Flutter-App:

import 'package:flutter/material.dart';

// Datenmodell
class UserData {
  final String id;
  final String name;

  UserData({required this.id, required this.name});
}

// Datenzugriffsschicht
class UserRepository {
  Future<UserData> getUserData() async {
    // Hier würde normalerweise eine Datenbankabfrage oder eine Netzwerkanfrage durchgeführt
    await Future.delayed(Duration(seconds: 2)); // Simuliert eine Verzögerung von 2 Sekunden
    return UserData(id: '1', name: 'Max Mustermann');
  }
}

// Anwendungslogik
class UserInteractor {
  final UserRepository _userRepository;

  UserInteractor(this._userRepository);

  Future<UserData> getUserData() async {
    return await _userRepository.getUserData();
  }
}

// Benutzeroberfläche
class MyApp extends StatelessWidget {
  final UserInteractor _userInteractor = UserInteractor(UserRepository());

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Clean Architecture Beispiel',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Clean Architecture Beispiel'),
        ),
        body: Center(
          child: FutureBuilder<UserData>(
            future: _userInteractor.getUserData(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return CircularProgressIndicator();
              } else if (snapshot.hasError) {
                return Text('Fehler beim Laden der Daten');
              } else {
                return Text('Benutzername: ${snapshot.data!.name}');
              }
            },
          ),
        ),
      ),
    );
  }
}


In diesem Beispiel wird die Clean Architecture in einer Flutter-App angewendet. Die Datenzugriffsschicht (UserRepository) ist verantwortlich für den Zugriff auf die Daten, in diesem Fall die Benutzerdaten. Die Anwendungslogik (UserInteractor) orchestriert die Aktionen und ruft die entsprechenden Methoden in der Datenzugriffsschicht auf. Die Benutzeroberfläche (MyApp) interagiert nur mit der Anwendungslogik und zeigt die erhaltenen Daten an. Durch diese Trennung der Verantwortlichkeiten ist die App besser testbar und wartbar.


Vorzüge und Herausforderungen


Die Clean Architecture bietet eine Vielzahl von Vorteilen, darunter eine verbesserte Testbarkeit, Wartbarkeit und Skalierbarkeit der Anwendung. Sie ermöglicht es, die Geschäftslogik von technischen Details zu entkoppeln und die Anwendung in einzelne austauschbare Module zu unterteilen. Allerdings kann die Umsetzung der Clean Architecture in Flutter-Apps herausfordernd sein, da sie eine sorgfältige Planung und Strukturierung der Code-Basis erfordert.


Tipps zur Umsetzung von Clean Architecture in deinen Projekten


Um die Clean Architecture effektiv in deinen Flutter-Projekten umzusetzen, ist es wichtig, die Verantwortlichkeiten der verschiedenen Schichten klar zu definieren und sicherzustellen, dass die Abhängigkeiten zwischen den Schichten korrekt organisiert sind. Es ist auch hilfreich, bewährte Praktiken wie Dependency Injection und Test-driven Development zu verwenden, um die Implementierung der Clean Architecture zu erleichtern und die Qualität der App zu verbessern.

 


Zusammenfassung


Wir haben die BLoC, MVC, GetX und Clean Architecture untersucht und deren Funktionsweise, Vor- und Nachteile sowie Anwendungsfälle diskutiert. Abschließend findest du einen Überblick der Konzepte.


BLoC: Die BLoC-Architektur bietet eine klare Trennung von Geschäftslogik und Benutzeroberfläche durch die Verwendung von Streams und Events. Sie eignet sich besonders gut für komplexe Anwendungen, die eine reaktive und effiziente Verarbeitung von Benutzereingaben erfordern.


MVC: MVC ist ein bewährtes Architekturmuster, das die Anwendung in verschiedene Schichten unterteilt: Model, View und Controller. Es bietet eine klare Trennung von Daten, Benutzeroberfläche und Logik und eignet sich gut für kleinere bis mittlere Projekte mit klar definierten Anforderungen.


GetX: GetX ist eine moderne Architektur, die speziell für die Flutter-Entwicklung realisiert wurde. Sie zeichnet sich durch ihre Einfachheit, Flexibilität und Leistungsfähigkeit aus und bietet eine Vielzahl von Funktionen und Konzepten, die die Entwicklung von Flutter-Apps vereinfachen und beschleunigen.


Clean Architecture: Die Clean Architecture zielt darauf ab, Software-Anwendungen in verschiedene Schichten zu unterteilen, um die Abhängigkeiten zwischen den Komponenten zu minimieren und die Geschäftslogik von technischen Details zu entkoppeln. Sie bietet eine verbesserte Testbarkeit, Wartbarkeit und Skalierbarkeit der Anwendung und eignet sich besonders gut für große und komplexe Projekte.


Empfehlungen für die Auswahl der passenden Architektur: Bei der Auswahl der passenden Architektur für deine Flutter-App solltest du die Anforderungen deines Projekts sorgfältig abwägen. Berücksichtige die Komplexität der App, die Anforderungen an die Skalierbarkeit und Wartbarkeit, das Team-Know-how und die Präferenzen der Entwickler. Wähle eine Architektur, die gut zu den Anforderungen deines Projekts passt und die Entwicklung erleichtert. Oft ergibt sich auch eine gezielte Kombination von Systemarchitekturen, wie zum Beispiel die Verbindung von BLoC und Clean Architecture.


Insgesamt bieten die verschiedenen Systemarchitekturen für Flutter-Apps eine Vielzahl von Optionen, um effiziente, wartbare und skalierbare Apps zu entwickeln. Indem du die Vor- und Nachteile jeder Architektur sorgfältig abwägst und die beste Lösung für deine spezifischen Anforderungen auswählst, kannst du erfolgreiche Flutter-Apps entwickeln, die den Anforderungen deiner Nutzer gerecht werden.

 

Als Flutter-Experte für Systemarchitekturen stehe ich dir zur Verfügung, um deine App zu analysieren, zu optimieren und skalierbarer zu machen. Buche dir jetzt gerne eine Termin und lass uns gemeinsam dein Projekt systematisch verbessern!

Dein planbarer App-Entwickler

für Flutter Apps

“Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.”

Copyright ©2024. Julian Giesen. Alle Rechte vorbehalten.

Dein planbarer App-Entwickler

für Flutter Apps

Copyright ©2024. Julian Giesen.

Alle Rechte vorbehalten.

“Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.”

Dein planbarer App-Entwickler

für Flutter Apps

“Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.”

Copyright ©2024. Julian Giesen. Alle Rechte vorbehalten.