[{"data":1,"prerenderedAt":1998},["ShallowReactive",2],{"blog-flutter-app-entwicklung-leitfaden-de":3,"blog-related-flutter-app-entwicklung-leitfaden-de":1997},{"id":4,"title":5,"body":6,"canonicalUrl":1948,"category":1949,"ctaLabel":1948,"ctaLink":1948,"date":1950,"description":1951,"extension":1952,"faq":1953,"featured":1975,"heroImage":1976,"leadMagnet":1977,"meta":1981,"navigation":87,"noIndex":1975,"ogImage":1976,"path":1982,"readingTime":157,"relatedCaseStudy":1983,"seo":1984,"seoDescription":1985,"seoTitle":1986,"stem":1987,"tags":1988,"updatedAt":1948,"__hash__":1996},"blog_de\u002Fde\u002Fblog\u002Fflutter-app-entwicklung-leitfaden.md","Flutter App-Entwicklung 2026: Architektur, Performance & Deployment",{"type":7,"value":8,"toc":1917},"minimark",[9,16,21,29,34,37,40,44,47,50,55,58,212,215,219,222,284,288,291,295,298,306,310,313,507,511,514,666,670,677,858,862,865,993,997,1000,1004,1007,1054,1134,1137,1141,1148,1275,1286,1290,1297,1303,1307,1310,1314,1367,1386,1390,1393,1397,1400,1404,1407,1676,1680,1683,1699,1703,1706,1711,1715,1718,1738,1858,1861,1865,1868,1900,1903,1907,1910,1913],[10,11],"blog-cta",{"button":12,"text":13,"title":14,"variant":15},"Kontakt aufnehmen","Wir entwickeln produktionsreife Flutter-Apps mit Clean Architecture, nativer Performance und skalierbarer CI\u002FCD. Sprechen wir über Ihre Anforderungen.","Planen Sie ein Flutter-Projekt?","inline",[17,18],"div",{"className":19},[20],"blog-tldr",[22,23,24,28],"p",{},[25,26,27],"strong",{},"TL;DR"," — Flutter ist 2026 ein ausgereiftes, produktionsreifes Framework. Impeller 2.0 eliminiert Rendering-Jank durch AOT-Shader-Compilation. Clean Architecture mit BLoC und GetIt hält große Codebasen wartbar. FFI ersetzt Method Channels für performante native Integration. WebAssembly wird zum Standard für Web-Builds. Und automatisierte CI\u002FCD-Pipelines mit GitHub Actions deployen mit einem einzigen Merge in beide App Stores.\n:",[30,31,33],"h2",{"id":32},"warum-flutter-zum-standard-für-cross-platform-entwicklung-geworden-ist","Warum Flutter zum Standard für Cross-Platform-Entwicklung geworden ist",[22,35,36],{},"Die Cross-Platform-Landschaft hat sich konsolidiert. Während React Native für JavaScript-affine Teams eine starke Option bleibt, hat sich Flutter als Framework der Wahl für Teams etabliert, die Rendering-Performance, pixelgenaue Designkontrolle und echte Multi-Plattform-Reichweite priorisieren. Mit Flutter 3.41 hat das Framework eine Reife erreicht, bei der die Frage nicht mehr lautet, ob Flutter produktionsreif ist — sondern wie man Flutter-Anwendungen architektonisch so aufbaut, dass sie skalieren.",[22,38,39],{},"Dieser Leitfaden behandelt die technischen Entscheidungen, die Hobby-Projekte von produktionsreifen Flutter-Anwendungen unterscheiden: Architektur-Patterns, Rendering-Pipeline-Optimierung, native Integrationsstrategien und Deployment-Automatisierung.",[30,41,43],{"id":42},"die-rendering-revolution-impeller-20","Die Rendering-Revolution: Impeller 2.0",[22,45,46],{},"Die folgenreichste Veränderung im Flutter-Ökosystem ist die Fertigstellung der Impeller-Rendering-Engine. Seit Flutters Anfängen setzte das Framework auf Skia für GPU-Rendering — eine leistungsfähige Engine, die Shader jedoch zur Laufzeit kompilierte. Das verursachte den gefürchteten „Shader-Compilation-Jank\" — sichtbare Ruckler beim ersten Auftreten bestimmter Animationen oder Übergänge.",[22,48,49],{},"Impeller löst dieses Problem grundlegend. Anstatt Shader bei Bedarf zu kompilieren, nutzt Impeller Ahead-of-Time (AOT) Shader-Compilation. Jeder Shader, den die Anwendung benötigen könnte, wird beim Build-Schritt vorkompiliert. Das Ergebnis: null First-Run-Jank, konsistentes Frame-Timing und eine Reduktion der Frame-Rasterisierungszeit um fast 50% bei komplexen Szenen.",[51,52,54],"h3",{"id":53},"wie-impeller-das-erreicht","Wie Impeller das erreicht",[22,56,57],{},"Impeller integriert direkt mit der nativen Grafik-API der jeweiligen Plattform — Metal auf iOS und Vulkan auf Android. Das ist eine signifikante Abkehr von Skias abstraktererem Rendering-Ansatz:",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"language-dart shiki shiki-themes github-light","\u002F\u002F Impeller-Vorteile sind automatisch — keine Code-Änderungen nötig.\n\u002F\u002F Das Verständnis der Pipeline hilft aber bei Performance-Optimierung.\n\n\u002F\u002F Aufwändiges Custom Painting? Impellers Tessellator verarbeitet\n\u002F\u002F komplexe Pfade deutlich effizienter als Skias CPU-basierte Rasterisierung.\nclass OptimizedPainter extends CustomPainter {\n  @override\n  void paint(Canvas canvas, Size size) {\n    final path = Path()\n      ..moveTo(0, size.height)\n      ..cubicTo(\n        size.width * 0.25, size.height * 0.1,\n        size.width * 0.75, size.height * 0.9,\n        size.width, 0,\n      );\n\n    \u002F\u002F Impeller tesselliert diesen Pfad auf der GPU, nicht der CPU.\n    \u002F\u002F Komplexe Gradienten und Blur-Effekte bleiben ebenfalls GPU-gebunden.\n    canvas.drawPath(path, Paint()..color = Colors.blue);\n  }\n\n  @override\n  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;\n}\n","dart","",[66,67,68,76,82,89,95,101,107,113,119,125,131,137,143,149,155,161,166,172,178,184,190,195,200,206],"code",{"__ignoreMap":64},[69,70,73],"span",{"class":71,"line":72},"line",1,[69,74,75],{},"\u002F\u002F Impeller-Vorteile sind automatisch — keine Code-Änderungen nötig.\n",[69,77,79],{"class":71,"line":78},2,[69,80,81],{},"\u002F\u002F Das Verständnis der Pipeline hilft aber bei Performance-Optimierung.\n",[69,83,85],{"class":71,"line":84},3,[69,86,88],{"emptyLinePlaceholder":87},true,"\n",[69,90,92],{"class":71,"line":91},4,[69,93,94],{},"\u002F\u002F Aufwändiges Custom Painting? Impellers Tessellator verarbeitet\n",[69,96,98],{"class":71,"line":97},5,[69,99,100],{},"\u002F\u002F komplexe Pfade deutlich effizienter als Skias CPU-basierte Rasterisierung.\n",[69,102,104],{"class":71,"line":103},6,[69,105,106],{},"class OptimizedPainter extends CustomPainter {\n",[69,108,110],{"class":71,"line":109},7,[69,111,112],{},"  @override\n",[69,114,116],{"class":71,"line":115},8,[69,117,118],{},"  void paint(Canvas canvas, Size size) {\n",[69,120,122],{"class":71,"line":121},9,[69,123,124],{},"    final path = Path()\n",[69,126,128],{"class":71,"line":127},10,[69,129,130],{},"      ..moveTo(0, size.height)\n",[69,132,134],{"class":71,"line":133},11,[69,135,136],{},"      ..cubicTo(\n",[69,138,140],{"class":71,"line":139},12,[69,141,142],{},"        size.width * 0.25, size.height * 0.1,\n",[69,144,146],{"class":71,"line":145},13,[69,147,148],{},"        size.width * 0.75, size.height * 0.9,\n",[69,150,152],{"class":71,"line":151},14,[69,153,154],{},"        size.width, 0,\n",[69,156,158],{"class":71,"line":157},15,[69,159,160],{},"      );\n",[69,162,164],{"class":71,"line":163},16,[69,165,88],{"emptyLinePlaceholder":87},[69,167,169],{"class":71,"line":168},17,[69,170,171],{},"    \u002F\u002F Impeller tesselliert diesen Pfad auf der GPU, nicht der CPU.\n",[69,173,175],{"class":71,"line":174},18,[69,176,177],{},"    \u002F\u002F Komplexe Gradienten und Blur-Effekte bleiben ebenfalls GPU-gebunden.\n",[69,179,181],{"class":71,"line":180},19,[69,182,183],{},"    canvas.drawPath(path, Paint()..color = Colors.blue);\n",[69,185,187],{"class":71,"line":186},20,[69,188,189],{},"  }\n",[69,191,193],{"class":71,"line":192},21,[69,194,88],{"emptyLinePlaceholder":87},[69,196,198],{"class":71,"line":197},22,[69,199,112],{},[69,201,203],{"class":71,"line":202},23,[69,204,205],{},"  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;\n",[69,207,209],{"class":71,"line":208},24,[69,210,211],{},"}\n",[22,213,214],{},"2026 wird das Legacy-Skia-Backend für Android 10 und höher entfernt. Wenn Ihre App Android 9 oder niedriger unterstützt, bleibt Skia als Fallback verfügbar. Für alle praktischen Zwecke ist Impeller jetzt der einzige relevante Rendering-Pfad.",[51,216,218],{"id":217},"performance-profiling-mit-impeller","Performance-Profiling mit Impeller",[22,220,221],{},"Impeller verändert, wie Flutter-Anwendungen profiliert werden. Frame-Rasterisierung ist nicht mehr der Flaschenhals — Widget-Building und Layout-Berechnung sind es oft. Das Flutter DevTools Performance-Overlay hebt jetzt Widget-Rebuild-Zähler und Layout-Pass-Dauer als primäre Optimierungsmetriken hervor.",[59,223,225],{"className":61,"code":224,"language":63,"meta":64,"style":64},"\u002F\u002F RepaintBoundary strategisch einsetzen, um teure Subtrees zu isolieren\nclass ExpensiveAnimatedWidget extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return RepaintBoundary(\n      child: CustomPaint(\n        painter: _ComplexAnimationPainter(),\n        size: const Size(400, 400),\n      ),\n    );\n  }\n}\n",[66,226,227,232,237,241,246,251,256,261,266,271,276,280],{"__ignoreMap":64},[69,228,229],{"class":71,"line":72},[69,230,231],{},"\u002F\u002F RepaintBoundary strategisch einsetzen, um teure Subtrees zu isolieren\n",[69,233,234],{"class":71,"line":78},[69,235,236],{},"class ExpensiveAnimatedWidget extends StatelessWidget {\n",[69,238,239],{"class":71,"line":84},[69,240,112],{},[69,242,243],{"class":71,"line":91},[69,244,245],{},"  Widget build(BuildContext context) {\n",[69,247,248],{"class":71,"line":97},[69,249,250],{},"    return RepaintBoundary(\n",[69,252,253],{"class":71,"line":103},[69,254,255],{},"      child: CustomPaint(\n",[69,257,258],{"class":71,"line":109},[69,259,260],{},"        painter: _ComplexAnimationPainter(),\n",[69,262,263],{"class":71,"line":115},[69,264,265],{},"        size: const Size(400, 400),\n",[69,267,268],{"class":71,"line":121},[69,269,270],{},"      ),\n",[69,272,273],{"class":71,"line":127},[69,274,275],{},"    );\n",[69,277,278],{"class":71,"line":133},[69,279,189],{},[69,281,282],{"class":71,"line":139},[69,283,211],{},[30,285,287],{"id":286},"clean-architecture-flutter-für-skalierung-strukturieren","Clean Architecture: Flutter für Skalierung strukturieren",[22,289,290],{},"Eine Flutter-App mit fünf Screens funktioniert mit jeder Architektur. Eine Flutter-App mit fünfzig Screens, drei API-Integrationen, Offline-Caching und einem Team von fünf Entwicklern erfordert Disziplin. Clean Architecture — angepasst an Flutters reaktives Paradigma — bietet diese Struktur.",[51,292,294],{"id":293},"das-drei-schichten-modell","Das Drei-Schichten-Modell",[22,296,297],{},"Die Architektur trennt Zuständigkeiten in drei Schichten mit strikten Abhängigkeitsregeln. Abhängigkeiten zeigen nur nach innen: Presentation hängt von Domain ab, Domain hängt von nichts ab, und Data implementiert Domain-Verträge.",[59,299,304],{"className":300,"code":302,"language":303},[301],"language-text","lib\u002F\n├── core\u002F\n│   ├── error\u002F           # Failure-Klassen, Exceptions\n│   ├── network\u002F         # HTTP-Client, Interceptors\n│   └── di\u002F              # GetIt Dependency-Injection-Setup\n├── features\u002F\n│   ├── authentication\u002F\n│   │   ├── domain\u002F\n│   │   │   ├── entities\u002F       # AuthUser, Session\n│   │   │   ├── repositories\u002F   # Abstraktes AuthRepository\n│   │   │   └── usecases\u002F       # LoginUseCase, LogoutUseCase\n│   │   ├── data\u002F\n│   │   │   ├── models\u002F         # AuthUserModel (JSON-Mapping)\n│   │   │   ├── datasources\u002F    # AuthRemoteDataSource, AuthLocalDataSource\n│   │   │   └── repositories\u002F   # AuthRepositoryImpl\n│   │   └── presentation\u002F\n│   │       ├── bloc\u002F           # AuthBloc, AuthState, AuthEvent\n│   │       ├── pages\u002F          # LoginPage, ProfilePage\n│   │       └── widgets\u002F        # LoginForm, SocialLoginButton\n│   └── dashboard\u002F\n│       └── ...\n└── main.dart\n","text",[66,305,302],{"__ignoreMap":64},[51,307,309],{"id":308},"domain-layer-reine-geschäftslogik","Domain Layer: Reine Geschäftslogik",[22,311,312],{},"Die Domain-Schicht enthält Entities, Use-Case-Klassen und abstrakte Repository-Interfaces. Sie hat null Flutter-Imports und null externe Abhängigkeiten. Das macht sie testbar mit reinen Dart-Unit-Tests und portabel zwischen Projekten.",[59,314,316],{"className":61,"code":315,"language":63,"meta":64,"style":64},"\u002F\u002F Domain Entity — immutable, keine Serialisierungslogik\nclass Project {\n  final String id;\n  final String name;\n  final ProjectStatus status;\n  final DateTime deadline;\n  final List\u003CString> memberIds;\n\n  const Project({\n    required this.id,\n    required this.name,\n    required this.status,\n    required this.deadline,\n    required this.memberIds,\n  });\n\n  bool get isOverdue =>\n      status != ProjectStatus.completed &&\n      DateTime.now().isAfter(deadline);\n}\n\n\u002F\u002F Abstraktes Repository — der Vertrag, den die Data-Schicht erfüllen muss\nabstract class ProjectRepository {\n  Future\u003CEither\u003CFailure, List\u003CProject>>> getProjects();\n  Future\u003CEither\u003CFailure, Project>> getProjectById(String id);\n  Future\u003CEither\u003CFailure, void>> updateStatus(String id, ProjectStatus status);\n}\n\n\u002F\u002F Use Case — eine Verantwortung, ein Einstiegspunkt\nclass GetProjectsUseCase {\n  final ProjectRepository repository;\n\n  const GetProjectsUseCase(this.repository);\n\n  Future\u003CEither\u003CFailure, List\u003CProject>>> call() =>\n      repository.getProjects();\n}\n",[66,317,318,323,328,333,338,343,348,353,357,362,367,372,377,382,387,392,396,401,406,411,415,419,424,429,434,440,446,451,456,462,468,474,479,485,490,496,502],{"__ignoreMap":64},[69,319,320],{"class":71,"line":72},[69,321,322],{},"\u002F\u002F Domain Entity — immutable, keine Serialisierungslogik\n",[69,324,325],{"class":71,"line":78},[69,326,327],{},"class Project {\n",[69,329,330],{"class":71,"line":84},[69,331,332],{},"  final String id;\n",[69,334,335],{"class":71,"line":91},[69,336,337],{},"  final String name;\n",[69,339,340],{"class":71,"line":97},[69,341,342],{},"  final ProjectStatus status;\n",[69,344,345],{"class":71,"line":103},[69,346,347],{},"  final DateTime deadline;\n",[69,349,350],{"class":71,"line":109},[69,351,352],{},"  final List\u003CString> memberIds;\n",[69,354,355],{"class":71,"line":115},[69,356,88],{"emptyLinePlaceholder":87},[69,358,359],{"class":71,"line":121},[69,360,361],{},"  const Project({\n",[69,363,364],{"class":71,"line":127},[69,365,366],{},"    required this.id,\n",[69,368,369],{"class":71,"line":133},[69,370,371],{},"    required this.name,\n",[69,373,374],{"class":71,"line":139},[69,375,376],{},"    required this.status,\n",[69,378,379],{"class":71,"line":145},[69,380,381],{},"    required this.deadline,\n",[69,383,384],{"class":71,"line":151},[69,385,386],{},"    required this.memberIds,\n",[69,388,389],{"class":71,"line":157},[69,390,391],{},"  });\n",[69,393,394],{"class":71,"line":163},[69,395,88],{"emptyLinePlaceholder":87},[69,397,398],{"class":71,"line":168},[69,399,400],{},"  bool get isOverdue =>\n",[69,402,403],{"class":71,"line":174},[69,404,405],{},"      status != ProjectStatus.completed &&\n",[69,407,408],{"class":71,"line":180},[69,409,410],{},"      DateTime.now().isAfter(deadline);\n",[69,412,413],{"class":71,"line":186},[69,414,211],{},[69,416,417],{"class":71,"line":192},[69,418,88],{"emptyLinePlaceholder":87},[69,420,421],{"class":71,"line":197},[69,422,423],{},"\u002F\u002F Abstraktes Repository — der Vertrag, den die Data-Schicht erfüllen muss\n",[69,425,426],{"class":71,"line":202},[69,427,428],{},"abstract class ProjectRepository {\n",[69,430,431],{"class":71,"line":208},[69,432,433],{},"  Future\u003CEither\u003CFailure, List\u003CProject>>> getProjects();\n",[69,435,437],{"class":71,"line":436},25,[69,438,439],{},"  Future\u003CEither\u003CFailure, Project>> getProjectById(String id);\n",[69,441,443],{"class":71,"line":442},26,[69,444,445],{},"  Future\u003CEither\u003CFailure, void>> updateStatus(String id, ProjectStatus status);\n",[69,447,449],{"class":71,"line":448},27,[69,450,211],{},[69,452,454],{"class":71,"line":453},28,[69,455,88],{"emptyLinePlaceholder":87},[69,457,459],{"class":71,"line":458},29,[69,460,461],{},"\u002F\u002F Use Case — eine Verantwortung, ein Einstiegspunkt\n",[69,463,465],{"class":71,"line":464},30,[69,466,467],{},"class GetProjectsUseCase {\n",[69,469,471],{"class":71,"line":470},31,[69,472,473],{},"  final ProjectRepository repository;\n",[69,475,477],{"class":71,"line":476},32,[69,478,88],{"emptyLinePlaceholder":87},[69,480,482],{"class":71,"line":481},33,[69,483,484],{},"  const GetProjectsUseCase(this.repository);\n",[69,486,488],{"class":71,"line":487},34,[69,489,88],{"emptyLinePlaceholder":87},[69,491,493],{"class":71,"line":492},35,[69,494,495],{},"  Future\u003CEither\u003CFailure, List\u003CProject>>> call() =>\n",[69,497,499],{"class":71,"line":498},36,[69,500,501],{},"      repository.getProjects();\n",[69,503,505],{"class":71,"line":504},37,[69,506,211],{},[51,508,510],{"id":509},"data-layer-api-verträge-und-caching","Data Layer: API-Verträge und Caching",[22,512,513],{},"Die Data-Schicht implementiert die im Domain Layer definierten Repository-Interfaces. Sie kümmert sich um JSON-Serialisierung, API-Aufrufe, lokales Caching und Error-Mapping.",[59,515,517],{"className":61,"code":516,"language":63,"meta":64,"style":64},"class ProjectRepositoryImpl implements ProjectRepository {\n  final ProjectRemoteDataSource remote;\n  final ProjectLocalDataSource local;\n  final NetworkInfo networkInfo;\n\n  const ProjectRepositoryImpl({\n    required this.remote,\n    required this.local,\n    required this.networkInfo,\n  });\n\n  @override\n  Future\u003CEither\u003CFailure, List\u003CProject>>> getProjects() async {\n    if (await networkInfo.isConnected) {\n      try {\n        final models = await remote.fetchProjects();\n        await local.cacheProjects(models);\n        return Right(models.map((m) => m.toEntity()).toList());\n      } on ServerException catch (e) {\n        return Left(ServerFailure(e.message));\n      }\n    } else {\n      try {\n        final cached = await local.getCachedProjects();\n        return Right(cached.map((m) => m.toEntity()).toList());\n      } on CacheException {\n        return Left(const CacheFailure('Keine gecachten Daten verfügbar'));\n      }\n    }\n  }\n}\n",[66,518,519,524,529,534,539,543,548,553,558,563,567,571,575,580,585,590,595,600,605,610,615,620,625,629,634,639,644,649,653,658,662],{"__ignoreMap":64},[69,520,521],{"class":71,"line":72},[69,522,523],{},"class ProjectRepositoryImpl implements ProjectRepository {\n",[69,525,526],{"class":71,"line":78},[69,527,528],{},"  final ProjectRemoteDataSource remote;\n",[69,530,531],{"class":71,"line":84},[69,532,533],{},"  final ProjectLocalDataSource local;\n",[69,535,536],{"class":71,"line":91},[69,537,538],{},"  final NetworkInfo networkInfo;\n",[69,540,541],{"class":71,"line":97},[69,542,88],{"emptyLinePlaceholder":87},[69,544,545],{"class":71,"line":103},[69,546,547],{},"  const ProjectRepositoryImpl({\n",[69,549,550],{"class":71,"line":109},[69,551,552],{},"    required this.remote,\n",[69,554,555],{"class":71,"line":115},[69,556,557],{},"    required this.local,\n",[69,559,560],{"class":71,"line":121},[69,561,562],{},"    required this.networkInfo,\n",[69,564,565],{"class":71,"line":127},[69,566,391],{},[69,568,569],{"class":71,"line":133},[69,570,88],{"emptyLinePlaceholder":87},[69,572,573],{"class":71,"line":139},[69,574,112],{},[69,576,577],{"class":71,"line":145},[69,578,579],{},"  Future\u003CEither\u003CFailure, List\u003CProject>>> getProjects() async {\n",[69,581,582],{"class":71,"line":151},[69,583,584],{},"    if (await networkInfo.isConnected) {\n",[69,586,587],{"class":71,"line":157},[69,588,589],{},"      try {\n",[69,591,592],{"class":71,"line":163},[69,593,594],{},"        final models = await remote.fetchProjects();\n",[69,596,597],{"class":71,"line":168},[69,598,599],{},"        await local.cacheProjects(models);\n",[69,601,602],{"class":71,"line":174},[69,603,604],{},"        return Right(models.map((m) => m.toEntity()).toList());\n",[69,606,607],{"class":71,"line":180},[69,608,609],{},"      } on ServerException catch (e) {\n",[69,611,612],{"class":71,"line":186},[69,613,614],{},"        return Left(ServerFailure(e.message));\n",[69,616,617],{"class":71,"line":192},[69,618,619],{},"      }\n",[69,621,622],{"class":71,"line":197},[69,623,624],{},"    } else {\n",[69,626,627],{"class":71,"line":202},[69,628,589],{},[69,630,631],{"class":71,"line":208},[69,632,633],{},"        final cached = await local.getCachedProjects();\n",[69,635,636],{"class":71,"line":436},[69,637,638],{},"        return Right(cached.map((m) => m.toEntity()).toList());\n",[69,640,641],{"class":71,"line":442},[69,642,643],{},"      } on CacheException {\n",[69,645,646],{"class":71,"line":448},[69,647,648],{},"        return Left(const CacheFailure('Keine gecachten Daten verfügbar'));\n",[69,650,651],{"class":71,"line":453},[69,652,619],{},[69,654,655],{"class":71,"line":458},[69,656,657],{},"    }\n",[69,659,660],{"class":71,"line":464},[69,661,189],{},[69,663,664],{"class":71,"line":470},[69,665,211],{},[51,667,669],{"id":668},"presentation-layer-bloc-pattern","Presentation Layer: BLoC-Pattern",[22,671,672,673,676],{},"BLoC (Business Logic Component) trennt UI-Interaktion von State-Transformation. Events gehen rein, States kommen raus. Die UI abonniert State-Änderungen und baut sich entsprechend neu auf. Kombiniert mit ",[66,674,675],{},"freezed"," für immutable State-Klassen entsteht vorhersagbares, testbares State Management.",[59,678,680],{"className":61,"code":679,"language":63,"meta":64,"style":64},"\u002F\u002F Events\nsealed class ProjectEvent {}\nclass LoadProjects extends ProjectEvent {}\nclass UpdateProjectStatus extends ProjectEvent {\n  final String projectId;\n  final ProjectStatus newStatus;\n  UpdateProjectStatus(this.projectId, this.newStatus);\n}\n\n\u002F\u002F States\nsealed class ProjectState {}\nclass ProjectInitial extends ProjectState {}\nclass ProjectLoading extends ProjectState {}\nclass ProjectLoaded extends ProjectState {\n  final List\u003CProject> projects;\n  ProjectLoaded(this.projects);\n}\nclass ProjectError extends ProjectState {\n  final String message;\n  ProjectError(this.message);\n}\n\n\u002F\u002F BLoC\nclass ProjectBloc extends Bloc\u003CProjectEvent, ProjectState> {\n  final GetProjectsUseCase getProjects;\n\n  ProjectBloc({required this.getProjects}) : super(ProjectInitial()) {\n    on\u003CLoadProjects>((event, emit) async {\n      emit(ProjectLoading());\n      final result = await getProjects();\n      result.fold(\n        (failure) => emit(ProjectError(failure.message)),\n        (projects) => emit(ProjectLoaded(projects)),\n      );\n    });\n  }\n}\n",[66,681,682,687,692,697,702,707,712,717,721,725,730,735,740,745,750,755,760,764,769,774,779,783,787,792,797,802,806,811,816,821,826,831,836,841,845,850,854],{"__ignoreMap":64},[69,683,684],{"class":71,"line":72},[69,685,686],{},"\u002F\u002F Events\n",[69,688,689],{"class":71,"line":78},[69,690,691],{},"sealed class ProjectEvent {}\n",[69,693,694],{"class":71,"line":84},[69,695,696],{},"class LoadProjects extends ProjectEvent {}\n",[69,698,699],{"class":71,"line":91},[69,700,701],{},"class UpdateProjectStatus extends ProjectEvent {\n",[69,703,704],{"class":71,"line":97},[69,705,706],{},"  final String projectId;\n",[69,708,709],{"class":71,"line":103},[69,710,711],{},"  final ProjectStatus newStatus;\n",[69,713,714],{"class":71,"line":109},[69,715,716],{},"  UpdateProjectStatus(this.projectId, this.newStatus);\n",[69,718,719],{"class":71,"line":115},[69,720,211],{},[69,722,723],{"class":71,"line":121},[69,724,88],{"emptyLinePlaceholder":87},[69,726,727],{"class":71,"line":127},[69,728,729],{},"\u002F\u002F States\n",[69,731,732],{"class":71,"line":133},[69,733,734],{},"sealed class ProjectState {}\n",[69,736,737],{"class":71,"line":139},[69,738,739],{},"class ProjectInitial extends ProjectState {}\n",[69,741,742],{"class":71,"line":145},[69,743,744],{},"class ProjectLoading extends ProjectState {}\n",[69,746,747],{"class":71,"line":151},[69,748,749],{},"class ProjectLoaded extends ProjectState {\n",[69,751,752],{"class":71,"line":157},[69,753,754],{},"  final List\u003CProject> projects;\n",[69,756,757],{"class":71,"line":163},[69,758,759],{},"  ProjectLoaded(this.projects);\n",[69,761,762],{"class":71,"line":168},[69,763,211],{},[69,765,766],{"class":71,"line":174},[69,767,768],{},"class ProjectError extends ProjectState {\n",[69,770,771],{"class":71,"line":180},[69,772,773],{},"  final String message;\n",[69,775,776],{"class":71,"line":186},[69,777,778],{},"  ProjectError(this.message);\n",[69,780,781],{"class":71,"line":192},[69,782,211],{},[69,784,785],{"class":71,"line":197},[69,786,88],{"emptyLinePlaceholder":87},[69,788,789],{"class":71,"line":202},[69,790,791],{},"\u002F\u002F BLoC\n",[69,793,794],{"class":71,"line":208},[69,795,796],{},"class ProjectBloc extends Bloc\u003CProjectEvent, ProjectState> {\n",[69,798,799],{"class":71,"line":436},[69,800,801],{},"  final GetProjectsUseCase getProjects;\n",[69,803,804],{"class":71,"line":442},[69,805,88],{"emptyLinePlaceholder":87},[69,807,808],{"class":71,"line":448},[69,809,810],{},"  ProjectBloc({required this.getProjects}) : super(ProjectInitial()) {\n",[69,812,813],{"class":71,"line":453},[69,814,815],{},"    on\u003CLoadProjects>((event, emit) async {\n",[69,817,818],{"class":71,"line":458},[69,819,820],{},"      emit(ProjectLoading());\n",[69,822,823],{"class":71,"line":464},[69,824,825],{},"      final result = await getProjects();\n",[69,827,828],{"class":71,"line":470},[69,829,830],{},"      result.fold(\n",[69,832,833],{"class":71,"line":476},[69,834,835],{},"        (failure) => emit(ProjectError(failure.message)),\n",[69,837,838],{"class":71,"line":481},[69,839,840],{},"        (projects) => emit(ProjectLoaded(projects)),\n",[69,842,843],{"class":71,"line":487},[69,844,160],{},[69,846,847],{"class":71,"line":492},[69,848,849],{},"    });\n",[69,851,852],{"class":71,"line":498},[69,853,189],{},[69,855,856],{"class":71,"line":504},[69,857,211],{},[51,859,861],{"id":860},"dependency-injection-mit-getit","Dependency Injection mit GetIt",[22,863,864],{},"GetIt verbindet alles miteinander. Registrieren Sie abstrakte Typen mit konkreten Implementierungen — die Presentation-Schicht weiß nie, welche Datenquelle tatsächlich verwendet wird. Das macht das Austauschen echter APIs mit Fakes beim Testen trivial.",[59,866,868],{"className":61,"code":867,"language":63,"meta":64,"style":64},"final sl = GetIt.instance;\n\nvoid initDependencies() {\n  \u002F\u002F BLoCs\n  sl.registerFactory(() => ProjectBloc(getProjects: sl()));\n\n  \u002F\u002F Use Cases\n  sl.registerLazySingleton(() => GetProjectsUseCase(sl()));\n\n  \u002F\u002F Repositories\n  sl.registerLazySingleton\u003CProjectRepository>(\n    () => ProjectRepositoryImpl(\n      remote: sl(),\n      local: sl(),\n      networkInfo: sl(),\n    ),\n  );\n\n  \u002F\u002F Data Sources\n  sl.registerLazySingleton\u003CProjectRemoteDataSource>(\n    () => ProjectRemoteDataSourceImpl(client: sl()),\n  );\n  sl.registerLazySingleton\u003CProjectLocalDataSource>(\n    () => ProjectLocalDataSourceImpl(sharedPreferences: sl()),\n  );\n}\n",[66,869,870,875,879,884,889,894,898,903,908,912,917,922,927,932,937,942,947,952,956,961,966,971,975,980,985,989],{"__ignoreMap":64},[69,871,872],{"class":71,"line":72},[69,873,874],{},"final sl = GetIt.instance;\n",[69,876,877],{"class":71,"line":78},[69,878,88],{"emptyLinePlaceholder":87},[69,880,881],{"class":71,"line":84},[69,882,883],{},"void initDependencies() {\n",[69,885,886],{"class":71,"line":91},[69,887,888],{},"  \u002F\u002F BLoCs\n",[69,890,891],{"class":71,"line":97},[69,892,893],{},"  sl.registerFactory(() => ProjectBloc(getProjects: sl()));\n",[69,895,896],{"class":71,"line":103},[69,897,88],{"emptyLinePlaceholder":87},[69,899,900],{"class":71,"line":109},[69,901,902],{},"  \u002F\u002F Use Cases\n",[69,904,905],{"class":71,"line":115},[69,906,907],{},"  sl.registerLazySingleton(() => GetProjectsUseCase(sl()));\n",[69,909,910],{"class":71,"line":121},[69,911,88],{"emptyLinePlaceholder":87},[69,913,914],{"class":71,"line":127},[69,915,916],{},"  \u002F\u002F Repositories\n",[69,918,919],{"class":71,"line":133},[69,920,921],{},"  sl.registerLazySingleton\u003CProjectRepository>(\n",[69,923,924],{"class":71,"line":139},[69,925,926],{},"    () => ProjectRepositoryImpl(\n",[69,928,929],{"class":71,"line":145},[69,930,931],{},"      remote: sl(),\n",[69,933,934],{"class":71,"line":151},[69,935,936],{},"      local: sl(),\n",[69,938,939],{"class":71,"line":157},[69,940,941],{},"      networkInfo: sl(),\n",[69,943,944],{"class":71,"line":163},[69,945,946],{},"    ),\n",[69,948,949],{"class":71,"line":168},[69,950,951],{},"  );\n",[69,953,954],{"class":71,"line":174},[69,955,88],{"emptyLinePlaceholder":87},[69,957,958],{"class":71,"line":180},[69,959,960],{},"  \u002F\u002F Data Sources\n",[69,962,963],{"class":71,"line":186},[69,964,965],{},"  sl.registerLazySingleton\u003CProjectRemoteDataSource>(\n",[69,967,968],{"class":71,"line":192},[69,969,970],{},"    () => ProjectRemoteDataSourceImpl(client: sl()),\n",[69,972,973],{"class":71,"line":197},[69,974,951],{},[69,976,977],{"class":71,"line":202},[69,978,979],{},"  sl.registerLazySingleton\u003CProjectLocalDataSource>(\n",[69,981,982],{"class":71,"line":208},[69,983,984],{},"    () => ProjectLocalDataSourceImpl(sharedPreferences: sl()),\n",[69,986,987],{"class":71,"line":436},[69,988,951],{},[69,990,991],{"class":71,"line":442},[69,992,211],{},[30,994,996],{"id":995},"native-plattformintegration-ffi-vs-method-channels","Native Plattformintegration: FFI vs. Method Channels",[22,998,999],{},"Flutter-Anwendungen müssen gelegentlich plattformspezifische APIs aufrufen — biometrische Authentifizierung, Bluetooth-Protokolle, Kamera-Pipelines oder proprietäre SDKs. Flutter bietet zwei Integrationsmechanismen, und die richtige Wahl wirkt sich auf Performance und Wartbarkeit aus.",[51,1001,1003],{"id":1002},"method-channels-die-traditionelle-brücke","Method Channels: Die traditionelle Brücke",[22,1005,1006],{},"Method Channels sind asynchrone Message-Passing-Brücken zwischen Dart und nativem Code. Sie serialisieren Argumente, senden sie über eine Plattform-Grenze und deserialisieren die Antwort.",[59,1008,1010],{"className":61,"code":1009,"language":63,"meta":64,"style":64},"\u002F\u002F Dart-Seite\nclass NativeBatteryService {\n  static const _channel = MethodChannel('com.example.app\u002Fbattery');\n\n  Future\u003Cint> getBatteryLevel() async {\n    final level = await _channel.invokeMethod\u003Cint>('getBatteryLevel');\n    return level ?? -1;\n  }\n}\n",[66,1011,1012,1017,1022,1027,1031,1036,1041,1046,1050],{"__ignoreMap":64},[69,1013,1014],{"class":71,"line":72},[69,1015,1016],{},"\u002F\u002F Dart-Seite\n",[69,1018,1019],{"class":71,"line":78},[69,1020,1021],{},"class NativeBatteryService {\n",[69,1023,1024],{"class":71,"line":84},[69,1025,1026],{},"  static const _channel = MethodChannel('com.example.app\u002Fbattery');\n",[69,1028,1029],{"class":71,"line":91},[69,1030,88],{"emptyLinePlaceholder":87},[69,1032,1033],{"class":71,"line":97},[69,1034,1035],{},"  Future\u003Cint> getBatteryLevel() async {\n",[69,1037,1038],{"class":71,"line":103},[69,1039,1040],{},"    final level = await _channel.invokeMethod\u003Cint>('getBatteryLevel');\n",[69,1042,1043],{"class":71,"line":109},[69,1044,1045],{},"    return level ?? -1;\n",[69,1047,1048],{"class":71,"line":115},[69,1049,189],{},[69,1051,1052],{"class":71,"line":121},[69,1053,211],{},[59,1055,1059],{"className":1056,"code":1057,"language":1058,"meta":64,"style":64},"language-kotlin shiki shiki-themes github-light","\u002F\u002F Android-Seite (Kotlin)\nclass BatteryMethodHandler(private val context: Context)\n    : MethodChannel.MethodCallHandler {\n    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {\n        if (call.method == \"getBatteryLevel\") {\n            val manager = context.getSystemService(Context.BATTERY_SERVICE)\n                as BatteryManager\n            result.success(manager.getIntProperty(\n                BatteryManager.BATTERY_PROPERTY_CAPACITY\n            ))\n        } else {\n            result.notImplemented()\n        }\n    }\n}\n","kotlin",[66,1060,1061,1066,1071,1076,1081,1086,1091,1096,1101,1106,1111,1116,1121,1126,1130],{"__ignoreMap":64},[69,1062,1063],{"class":71,"line":72},[69,1064,1065],{},"\u002F\u002F Android-Seite (Kotlin)\n",[69,1067,1068],{"class":71,"line":78},[69,1069,1070],{},"class BatteryMethodHandler(private val context: Context)\n",[69,1072,1073],{"class":71,"line":84},[69,1074,1075],{},"    : MethodChannel.MethodCallHandler {\n",[69,1077,1078],{"class":71,"line":91},[69,1079,1080],{},"    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {\n",[69,1082,1083],{"class":71,"line":97},[69,1084,1085],{},"        if (call.method == \"getBatteryLevel\") {\n",[69,1087,1088],{"class":71,"line":103},[69,1089,1090],{},"            val manager = context.getSystemService(Context.BATTERY_SERVICE)\n",[69,1092,1093],{"class":71,"line":109},[69,1094,1095],{},"                as BatteryManager\n",[69,1097,1098],{"class":71,"line":115},[69,1099,1100],{},"            result.success(manager.getIntProperty(\n",[69,1102,1103],{"class":71,"line":121},[69,1104,1105],{},"                BatteryManager.BATTERY_PROPERTY_CAPACITY\n",[69,1107,1108],{"class":71,"line":127},[69,1109,1110],{},"            ))\n",[69,1112,1113],{"class":71,"line":133},[69,1114,1115],{},"        } else {\n",[69,1117,1118],{"class":71,"line":139},[69,1119,1120],{},"            result.notImplemented()\n",[69,1122,1123],{"class":71,"line":145},[69,1124,1125],{},"        }\n",[69,1127,1128],{"class":71,"line":151},[69,1129,657],{},[69,1131,1132],{"class":71,"line":157},[69,1133,211],{},[22,1135,1136],{},"Method Channels funktionieren gut für seltene Request-Response-Interaktionen. Sie serialisieren jedoch alle Argumente durch einen Codec, was bei hochfrequenten Aufrufen oder großen Datentransfers Latenz verursacht.",[51,1138,1140],{"id":1139},"ffi-direkte-zero-copy-native-aufrufe","FFI: Direkte, Zero-Copy native Aufrufe",[22,1142,1143,1144,1147],{},"Seit Flutter 3.38 ist FFI (Foreign Function Interface) via ",[66,1145,1146],{},"dart:ffi"," der empfohlene Ansatz für performance-sensitive native Integration. FFI-Aufrufe sind synchron, vermeiden Serialisierungs-Overhead und können Speicher direkt zwischen Dart und nativem Code teilen.",[59,1149,1151],{"className":61,"code":1150,"language":63,"meta":64,"style":64},"\u002F\u002F Native Library laden und Funktionen direkt aufrufen\nimport 'dart:ffi';\nimport 'dart:io';\n\ntypedef NativeImageProcess = Int32 Function(\n    Pointer\u003CUint8> data, Int32 width, Int32 height);\ntypedef DartImageProcess = int Function(\n    Pointer\u003CUint8> data, int width, int height);\n\nclass NativeImageProcessor {\n  late final DartImageProcess _process;\n\n  NativeImageProcessor() {\n    final lib = Platform.isAndroid\n        ? DynamicLibrary.open('libimage_processor.so')\n        : DynamicLibrary.process();\n\n    _process = lib\n        .lookupFunction\u003CNativeImageProcess, DartImageProcess>(\n            'process_image');\n  }\n\n  int processImage(Pointer\u003CUint8> data, int width, int height) {\n    return _process(data, width, height);\n  }\n}\n",[66,1152,1153,1158,1163,1168,1172,1177,1182,1187,1192,1196,1201,1206,1210,1215,1220,1225,1230,1234,1239,1244,1249,1253,1257,1262,1267,1271],{"__ignoreMap":64},[69,1154,1155],{"class":71,"line":72},[69,1156,1157],{},"\u002F\u002F Native Library laden und Funktionen direkt aufrufen\n",[69,1159,1160],{"class":71,"line":78},[69,1161,1162],{},"import 'dart:ffi';\n",[69,1164,1165],{"class":71,"line":84},[69,1166,1167],{},"import 'dart:io';\n",[69,1169,1170],{"class":71,"line":91},[69,1171,88],{"emptyLinePlaceholder":87},[69,1173,1174],{"class":71,"line":97},[69,1175,1176],{},"typedef NativeImageProcess = Int32 Function(\n",[69,1178,1179],{"class":71,"line":103},[69,1180,1181],{},"    Pointer\u003CUint8> data, Int32 width, Int32 height);\n",[69,1183,1184],{"class":71,"line":109},[69,1185,1186],{},"typedef DartImageProcess = int Function(\n",[69,1188,1189],{"class":71,"line":115},[69,1190,1191],{},"    Pointer\u003CUint8> data, int width, int height);\n",[69,1193,1194],{"class":71,"line":121},[69,1195,88],{"emptyLinePlaceholder":87},[69,1197,1198],{"class":71,"line":127},[69,1199,1200],{},"class NativeImageProcessor {\n",[69,1202,1203],{"class":71,"line":133},[69,1204,1205],{},"  late final DartImageProcess _process;\n",[69,1207,1208],{"class":71,"line":139},[69,1209,88],{"emptyLinePlaceholder":87},[69,1211,1212],{"class":71,"line":145},[69,1213,1214],{},"  NativeImageProcessor() {\n",[69,1216,1217],{"class":71,"line":151},[69,1218,1219],{},"    final lib = Platform.isAndroid\n",[69,1221,1222],{"class":71,"line":157},[69,1223,1224],{},"        ? DynamicLibrary.open('libimage_processor.so')\n",[69,1226,1227],{"class":71,"line":163},[69,1228,1229],{},"        : DynamicLibrary.process();\n",[69,1231,1232],{"class":71,"line":168},[69,1233,88],{"emptyLinePlaceholder":87},[69,1235,1236],{"class":71,"line":174},[69,1237,1238],{},"    _process = lib\n",[69,1240,1241],{"class":71,"line":180},[69,1242,1243],{},"        .lookupFunction\u003CNativeImageProcess, DartImageProcess>(\n",[69,1245,1246],{"class":71,"line":186},[69,1247,1248],{},"            'process_image');\n",[69,1250,1251],{"class":71,"line":192},[69,1252,189],{},[69,1254,1255],{"class":71,"line":197},[69,1256,88],{"emptyLinePlaceholder":87},[69,1258,1259],{"class":71,"line":202},[69,1260,1261],{},"  int processImage(Pointer\u003CUint8> data, int width, int height) {\n",[69,1263,1264],{"class":71,"line":208},[69,1265,1266],{},"    return _process(data, width, height);\n",[69,1268,1269],{"class":71,"line":436},[69,1270,189],{},[69,1272,1273],{"class":71,"line":442},[69,1274,211],{},[22,1276,1277,1278,1281,1282,1285],{},"Für neue Projekte generieren Sie FFI-Bindings automatisch mit ",[66,1279,1280],{},"flutter create --template=package_ffi",". Das ",[66,1283,1284],{},"ffigen","-Tool liest C-Header-Dateien und erzeugt typsichere Dart-Bindings — kein manueller Boilerplate-Code nötig.",[51,1287,1289],{"id":1288},"wann-welchen-ansatz-verwenden","Wann welchen Ansatz verwenden",[22,1291,1292,1293,1296],{},"Nutzen Sie ",[25,1294,1295],{},"Method Channels"," für asynchrone Plattforminteraktionen, bei der Integration mit plattformspezifischen Lifecycle-Events oder wenn der Serialisierungs-Overhead vernachlässigbar gegenüber der eigentlichen Operation ist (z.B. Berechtigungen anfordern, Geräteeinstellungen lesen).",[22,1298,1292,1299,1302],{},[25,1300,1301],{},"FFI"," für synchrone Ausführung, bei der Verarbeitung großer Datenpuffer (Bilder, Audio, Sensordaten) oder beim Wrappen einer bestehenden C\u002FC++-Bibliothek für rechenintensive Operationen.",[30,1304,1306],{"id":1305},"flutter-für-web-webassembly-als-standard","Flutter für Web: WebAssembly als Standard",[22,1308,1309],{},"Flutter Web hat sich deutlich weiterentwickelt. Der Übergang von DOM-basiertem Rendering zu WebAssembly ist die definierende Veränderung für 2026. Wasm liefert nahezu native Ausführungsgeschwindigkeit für Dart-Code im Browser und ersetzt das langsamere JavaScript-Compilation-Target.",[51,1311,1313],{"id":1312},"für-wasm-bauen","Für Wasm bauen",[59,1315,1319],{"className":1316,"code":1317,"language":1318,"meta":64,"style":64},"language-bash shiki shiki-themes github-light","# Build mit WebAssembly (wird 2026 zum Standard)\nflutter build web --wasm\n\n# Wasm-Readiness der Dependencies prüfen\nflutter build web --wasm --wasm-opt\n","bash",[66,1320,1321,1327,1344,1348,1353],{"__ignoreMap":64},[69,1322,1323],{"class":71,"line":72},[69,1324,1326],{"class":1325},"sAwPA","# Build mit WebAssembly (wird 2026 zum Standard)\n",[69,1328,1329,1333,1337,1340],{"class":71,"line":78},[69,1330,1332],{"class":1331},"s7eDp","flutter",[69,1334,1336],{"class":1335},"sYBdl"," build",[69,1338,1339],{"class":1335}," web",[69,1341,1343],{"class":1342},"sYu0t"," --wasm\n",[69,1345,1346],{"class":71,"line":84},[69,1347,88],{"emptyLinePlaceholder":87},[69,1349,1350],{"class":71,"line":91},[69,1351,1352],{"class":1325},"# Wasm-Readiness der Dependencies prüfen\n",[69,1354,1355,1357,1359,1361,1364],{"class":71,"line":97},[69,1356,1332],{"class":1331},[69,1358,1336],{"class":1335},[69,1360,1339],{"class":1335},[69,1362,1363],{"class":1342}," --wasm",[69,1365,1366],{"class":1342}," --wasm-opt\n",[22,1368,1369,1370,1373,1374,1377,1378,1381,1382,1385],{},"Noch sind nicht alle Dart-Packages Wasm-kompatibel. Packages, die ",[66,1371,1372],{},"dart:js"," oder ",[66,1375,1376],{},"dart:html"," direkt verwenden, müssen auf ",[66,1379,1380],{},"package:web"," und ",[66,1383,1384],{},"dart:js_interop"," migriert werden. Führen Sie früh im Projekt eine Wasm-Probe-Compilation durch, um inkompatible Dependencies zu identifizieren, bevor sie zu Blockern werden.",[51,1387,1389],{"id":1388},"stateful-hot-reload-im-web","Stateful Hot Reload im Web",[22,1391,1392],{},"Seit Flutter 3.35 funktioniert Stateful Hot Reload im Web standardmäßig. Das eliminiert die Produktivitätslücke zwischen Mobile- und Web-Entwicklung. Widget ändern, speichern, Ergebnis sofort sehen — mit erhaltenem Anwendungsstate.",[30,1394,1396],{"id":1395},"cicd-vom-commit-zum-app-store","CI\u002FCD: Vom Commit zum App Store",[22,1398,1399],{},"Ein produktives Flutter-Projekt braucht automatisiertes Testing, Building und Deployment. Manuelle Builds führen zu menschlichen Fehlern und verlangsamen Release-Zyklen. GitHub Actions bietet eine solide Grundlage für Flutter CI\u002FCD.",[51,1401,1403],{"id":1402},"pipeline-architektur","Pipeline-Architektur",[22,1405,1406],{},"Eine produktionsreife Pipeline hat drei Stufen: Validieren, Bauen und Deployen.",[59,1408,1412],{"className":1409,"code":1410,"language":1411,"meta":64,"style":64},"language-yaml shiki shiki-themes github-light","# .github\u002Fworkflows\u002Fflutter-ci-cd.yml\nname: Flutter CI\u002FCD\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.41.0'\n          channel: 'stable'\n      - run: flutter pub get\n      - run: flutter analyze --fatal-infos\n      - run: flutter test --coverage\n\n  build-android:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.41.0'\n      - run: flutter build appbundle --release\n      - uses: r0adkll\u002Fupload-google-play@v1\n        with:\n          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}\n          packageName: com.example.app\n          releaseFiles: build\u002Fapp\u002Foutputs\u002Fbundle\u002Frelease\u002Fapp-release.aab\n          track: internal\n\n  build-ios:\n    needs: test\n    runs-on: macos-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.41.0'\n      - run: flutter build ipa --release --export-options-plist=ios\u002FExportOptions.plist\n      - uses: apple-actions\u002Fupload-testflight-build@v1\n        with:\n          app-path: build\u002Fios\u002Fipa\u002F*.ipa\n          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}\n          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}\n          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}\n","yaml",[66,1413,1414,1419,1424,1428,1433,1438,1443,1448,1452,1456,1461,1466,1471,1476,1481,1486,1491,1496,1501,1506,1511,1516,1520,1525,1530,1534,1538,1542,1546,1550,1554,1559,1564,1568,1573,1578,1583,1588,1593,1599,1604,1610,1615,1620,1625,1630,1635,1641,1647,1652,1658,1664,1670],{"__ignoreMap":64},[69,1415,1416],{"class":71,"line":72},[69,1417,1418],{},"# .github\u002Fworkflows\u002Fflutter-ci-cd.yml\n",[69,1420,1421],{"class":71,"line":78},[69,1422,1423],{},"name: Flutter CI\u002FCD\n",[69,1425,1426],{"class":71,"line":84},[69,1427,88],{"emptyLinePlaceholder":87},[69,1429,1430],{"class":71,"line":91},[69,1431,1432],{},"on:\n",[69,1434,1435],{"class":71,"line":97},[69,1436,1437],{},"  push:\n",[69,1439,1440],{"class":71,"line":103},[69,1441,1442],{},"    branches: [main]\n",[69,1444,1445],{"class":71,"line":109},[69,1446,1447],{},"  pull_request:\n",[69,1449,1450],{"class":71,"line":115},[69,1451,1442],{},[69,1453,1454],{"class":71,"line":121},[69,1455,88],{"emptyLinePlaceholder":87},[69,1457,1458],{"class":71,"line":127},[69,1459,1460],{},"jobs:\n",[69,1462,1463],{"class":71,"line":133},[69,1464,1465],{},"  test:\n",[69,1467,1468],{"class":71,"line":139},[69,1469,1470],{},"    runs-on: ubuntu-latest\n",[69,1472,1473],{"class":71,"line":145},[69,1474,1475],{},"    steps:\n",[69,1477,1478],{"class":71,"line":151},[69,1479,1480],{},"      - uses: actions\u002Fcheckout@v4\n",[69,1482,1483],{"class":71,"line":157},[69,1484,1485],{},"      - uses: subosito\u002Fflutter-action@v2\n",[69,1487,1488],{"class":71,"line":163},[69,1489,1490],{},"        with:\n",[69,1492,1493],{"class":71,"line":168},[69,1494,1495],{},"          flutter-version: '3.41.0'\n",[69,1497,1498],{"class":71,"line":174},[69,1499,1500],{},"          channel: 'stable'\n",[69,1502,1503],{"class":71,"line":180},[69,1504,1505],{},"      - run: flutter pub get\n",[69,1507,1508],{"class":71,"line":186},[69,1509,1510],{},"      - run: flutter analyze --fatal-infos\n",[69,1512,1513],{"class":71,"line":192},[69,1514,1515],{},"      - run: flutter test --coverage\n",[69,1517,1518],{"class":71,"line":197},[69,1519,88],{"emptyLinePlaceholder":87},[69,1521,1522],{"class":71,"line":202},[69,1523,1524],{},"  build-android:\n",[69,1526,1527],{"class":71,"line":208},[69,1528,1529],{},"    needs: test\n",[69,1531,1532],{"class":71,"line":436},[69,1533,1470],{},[69,1535,1536],{"class":71,"line":442},[69,1537,1475],{},[69,1539,1540],{"class":71,"line":448},[69,1541,1480],{},[69,1543,1544],{"class":71,"line":453},[69,1545,1485],{},[69,1547,1548],{"class":71,"line":458},[69,1549,1490],{},[69,1551,1552],{"class":71,"line":464},[69,1553,1495],{},[69,1555,1556],{"class":71,"line":470},[69,1557,1558],{},"      - run: flutter build appbundle --release\n",[69,1560,1561],{"class":71,"line":476},[69,1562,1563],{},"      - uses: r0adkll\u002Fupload-google-play@v1\n",[69,1565,1566],{"class":71,"line":481},[69,1567,1490],{},[69,1569,1570],{"class":71,"line":487},[69,1571,1572],{},"          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}\n",[69,1574,1575],{"class":71,"line":492},[69,1576,1577],{},"          packageName: com.example.app\n",[69,1579,1580],{"class":71,"line":498},[69,1581,1582],{},"          releaseFiles: build\u002Fapp\u002Foutputs\u002Fbundle\u002Frelease\u002Fapp-release.aab\n",[69,1584,1585],{"class":71,"line":504},[69,1586,1587],{},"          track: internal\n",[69,1589,1591],{"class":71,"line":1590},38,[69,1592,88],{"emptyLinePlaceholder":87},[69,1594,1596],{"class":71,"line":1595},39,[69,1597,1598],{},"  build-ios:\n",[69,1600,1602],{"class":71,"line":1601},40,[69,1603,1529],{},[69,1605,1607],{"class":71,"line":1606},41,[69,1608,1609],{},"    runs-on: macos-latest\n",[69,1611,1613],{"class":71,"line":1612},42,[69,1614,1475],{},[69,1616,1618],{"class":71,"line":1617},43,[69,1619,1480],{},[69,1621,1623],{"class":71,"line":1622},44,[69,1624,1485],{},[69,1626,1628],{"class":71,"line":1627},45,[69,1629,1490],{},[69,1631,1633],{"class":71,"line":1632},46,[69,1634,1495],{},[69,1636,1638],{"class":71,"line":1637},47,[69,1639,1640],{},"      - run: flutter build ipa --release --export-options-plist=ios\u002FExportOptions.plist\n",[69,1642,1644],{"class":71,"line":1643},48,[69,1645,1646],{},"      - uses: apple-actions\u002Fupload-testflight-build@v1\n",[69,1648,1650],{"class":71,"line":1649},49,[69,1651,1490],{},[69,1653,1655],{"class":71,"line":1654},50,[69,1656,1657],{},"          app-path: build\u002Fios\u002Fipa\u002F*.ipa\n",[69,1659,1661],{"class":71,"line":1660},51,[69,1662,1663],{},"          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}\n",[69,1665,1667],{"class":71,"line":1666},52,[69,1668,1669],{},"          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}\n",[69,1671,1673],{"class":71,"line":1672},53,[69,1674,1675],{},"          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}\n",[51,1677,1679],{"id":1678},"code-signing-und-secrets","Code Signing und Secrets",[22,1681,1682],{},"Committen Sie niemals Signing-Keys oder Service-Account-Credentials in Ihr Repository. Speichern Sie sie als verschlüsselte Secrets in Ihrer CI-Plattform:",[1684,1685,1686,1693],"ul",{},[1687,1688,1689,1692],"li",{},[25,1690,1691],{},"Android",": Keystore als Base64-codiertes Secret hochladen und während des Build-Schritts dekodieren. Den Google Play Service Account JSON als Secret speichern.",[1687,1694,1695,1698],{},[25,1696,1697],{},"iOS",": Fastlane Match oder manuell verwaltete Provisioning Profiles als Secrets. App Store Connect API Keys ersetzen Apple-ID-Credentials für automatisierte Uploads.",[51,1700,1702],{"id":1701},"stufenweise-rollouts","Stufenweise Rollouts",[22,1704,1705],{},"Deployen Sie zuerst in internes Testing, promoten nach manuellem QA zu Beta, dann Rollout auf 10%, 50%, 100% der Nutzer. Google Play Console unterstützt prozentuale Rollouts nativ. App Store Connect nutzt TestFlight-Gruppen für gestuftes Ausliefern.",[10,1707],{"button":1708,"text":1709,"title":1710,"variant":15},"Gespräch vereinbaren","Wir haben Flutter-Apps für Enterprise-Kunden in Versicherung, Bildung und öffentlichem Sektor ausgeliefert. Sprechen wir über Ihr Projekt.","Unterstützung bei Ihrer Flutter-Architektur?",[30,1712,1714],{"id":1713},"teststrategie-für-produktive-flutter-apps","Teststrategie für produktive Flutter-Apps",[22,1716,1717],{},"Testing ist bei produktiver Flutter-Entwicklung nicht optional. Eine geschichtete Teststrategie spiegelt die Clean Architecture wider:",[1684,1719,1720,1726,1732],{},[1687,1721,1722,1725],{},[25,1723,1724],{},"Unit Tests"," für Domain-Logik, Use Cases und BLoC-State-Transitionen",[1687,1727,1728,1731],{},[25,1729,1730],{},"Widget Tests"," für Komponentenverhalten und Interaktion",[1687,1733,1734,1737],{},[25,1735,1736],{},"Integrationstests"," für komplette User Flows auf echten Geräten",[59,1739,1741],{"className":61,"code":1740,"language":63,"meta":64,"style":64},"\u002F\u002F BLoC-Test Beispiel\nvoid main() {\n  late ProjectBloc bloc;\n  late MockGetProjectsUseCase mockGetProjects;\n\n  setUp(() {\n    mockGetProjects = MockGetProjectsUseCase();\n    bloc = ProjectBloc(getProjects: mockGetProjects);\n  });\n\n  blocTest\u003CProjectBloc, ProjectState>(\n    'emittiert [Loading, Loaded] wenn LoadProjects erfolgreich ist',\n    build: () {\n      when(() => mockGetProjects())\n          .thenAnswer((_) async => Right(testProjects));\n      return bloc;\n    },\n    act: (bloc) => bloc.add(LoadProjects()),\n    expect: () => [\n      isA\u003CProjectLoading>(),\n      isA\u003CProjectLoaded>(),\n    ],\n  );\n}\n",[66,1742,1743,1748,1753,1758,1763,1767,1772,1777,1782,1786,1790,1795,1800,1805,1810,1815,1820,1825,1830,1835,1840,1845,1850,1854],{"__ignoreMap":64},[69,1744,1745],{"class":71,"line":72},[69,1746,1747],{},"\u002F\u002F BLoC-Test Beispiel\n",[69,1749,1750],{"class":71,"line":78},[69,1751,1752],{},"void main() {\n",[69,1754,1755],{"class":71,"line":84},[69,1756,1757],{},"  late ProjectBloc bloc;\n",[69,1759,1760],{"class":71,"line":91},[69,1761,1762],{},"  late MockGetProjectsUseCase mockGetProjects;\n",[69,1764,1765],{"class":71,"line":97},[69,1766,88],{"emptyLinePlaceholder":87},[69,1768,1769],{"class":71,"line":103},[69,1770,1771],{},"  setUp(() {\n",[69,1773,1774],{"class":71,"line":109},[69,1775,1776],{},"    mockGetProjects = MockGetProjectsUseCase();\n",[69,1778,1779],{"class":71,"line":115},[69,1780,1781],{},"    bloc = ProjectBloc(getProjects: mockGetProjects);\n",[69,1783,1784],{"class":71,"line":121},[69,1785,391],{},[69,1787,1788],{"class":71,"line":127},[69,1789,88],{"emptyLinePlaceholder":87},[69,1791,1792],{"class":71,"line":133},[69,1793,1794],{},"  blocTest\u003CProjectBloc, ProjectState>(\n",[69,1796,1797],{"class":71,"line":139},[69,1798,1799],{},"    'emittiert [Loading, Loaded] wenn LoadProjects erfolgreich ist',\n",[69,1801,1802],{"class":71,"line":145},[69,1803,1804],{},"    build: () {\n",[69,1806,1807],{"class":71,"line":151},[69,1808,1809],{},"      when(() => mockGetProjects())\n",[69,1811,1812],{"class":71,"line":157},[69,1813,1814],{},"          .thenAnswer((_) async => Right(testProjects));\n",[69,1816,1817],{"class":71,"line":163},[69,1818,1819],{},"      return bloc;\n",[69,1821,1822],{"class":71,"line":168},[69,1823,1824],{},"    },\n",[69,1826,1827],{"class":71,"line":174},[69,1828,1829],{},"    act: (bloc) => bloc.add(LoadProjects()),\n",[69,1831,1832],{"class":71,"line":180},[69,1833,1834],{},"    expect: () => [\n",[69,1836,1837],{"class":71,"line":186},[69,1838,1839],{},"      isA\u003CProjectLoading>(),\n",[69,1841,1842],{"class":71,"line":192},[69,1843,1844],{},"      isA\u003CProjectLoaded>(),\n",[69,1846,1847],{"class":71,"line":197},[69,1848,1849],{},"    ],\n",[69,1851,1852],{"class":71,"line":202},[69,1853,951],{},[69,1855,1856],{"class":71,"line":208},[69,1857,211],{},[22,1859,1860],{},"Streben Sie 80%+ Code-Coverage für Domain- und Data-Layer an. Widget-Test-Coverage kann niedriger sein — fokussieren Sie auf kritische Nutzerinteraktionen statt auf pixelgenaue Assertions.",[30,1862,1864],{"id":1863},"die-entscheidung-wann-flutter-die-richtige-wahl-ist","Die Entscheidung: Wann Flutter die richtige Wahl ist",[22,1866,1867],{},"Flutter ist die richtige Technologie, wenn Ihr Projekt folgendes erfordert:",[1684,1869,1870,1876,1882,1888,1894],{},[1687,1871,1872,1875],{},[25,1873,1874],{},"Multi-Plattform-Reichweite"," aus einer einzigen Codebasis (Mobile, Web, Desktop)",[1687,1877,1878,1881],{},[25,1879,1880],{},"Individuelles, gebrandetes UI",", das nicht Plattformkonventionen folgt",[1687,1883,1884,1887],{},[25,1885,1886],{},"Hochfrequente Animationen"," und komplexe visuelle Interaktionen",[1687,1889,1890,1893],{},[25,1891,1892],{},"Schnelle Time-to-Market"," mit einem kleinen Team",[1687,1895,1896,1899],{},[25,1897,1898],{},"Langfristige Wartbarkeit"," durch Clean Architecture und starke Typisierung",[22,1901,1902],{},"Flutter ist nicht die ideale Wahl, wenn Ihre App primär ein dünner Wrapper um plattformspezifische APIs ist (tiefe AR-Integration, spezialisierte Bluetooth-Protokolle) oder wenn Ihr gesamtes Team tiefe React\u002FJavaScript-Expertise mitbringt und keine Dart-Erfahrung hat.",[30,1904,1906],{"id":1905},"fazit","Fazit",[22,1908,1909],{},"Flutter 2026 ist nicht dasselbe Framework wie vor drei Jahren. Impeller hat die Frage der Rendering-Performance gelöst. Clean Architecture mit BLoC bietet ein kampferprobtes Pattern für komplexe Anwendungen. FFI ermöglicht native Performance bei Plattformintegration. WebAssembly erschließt ernsthaftes Web-Deployment. Und GitHub Actions automatisiert den gesamten Weg vom Commit zum App Store.",[22,1911,1912],{},"Die technologischen Entscheidungen sind getroffen. Was bleibt, ist die Engineering-Disziplin, sie korrekt anzuwenden — die richtige Architektur-Granularität für die Teamgröße wählen, vor dem Optimieren profilen, auf jeder Schicht testen und alles automatisieren, was automatisiert werden kann. Das ist es, was Flutter-Projekte, die ausliefern, von Flutter-Projekten unterscheidet, die stocken.",[1914,1915,1916],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}",{"title":64,"searchDepth":78,"depth":78,"links":1918},[1919,1920,1924,1931,1936,1940,1945,1946,1947],{"id":32,"depth":78,"text":33},{"id":42,"depth":78,"text":43,"children":1921},[1922,1923],{"id":53,"depth":84,"text":54},{"id":217,"depth":84,"text":218},{"id":286,"depth":78,"text":287,"children":1925},[1926,1927,1928,1929,1930],{"id":293,"depth":84,"text":294},{"id":308,"depth":84,"text":309},{"id":509,"depth":84,"text":510},{"id":668,"depth":84,"text":669},{"id":860,"depth":84,"text":861},{"id":995,"depth":78,"text":996,"children":1932},[1933,1934,1935],{"id":1002,"depth":84,"text":1003},{"id":1139,"depth":84,"text":1140},{"id":1288,"depth":84,"text":1289},{"id":1305,"depth":78,"text":1306,"children":1937},[1938,1939],{"id":1312,"depth":84,"text":1313},{"id":1388,"depth":84,"text":1389},{"id":1395,"depth":78,"text":1396,"children":1941},[1942,1943,1944],{"id":1402,"depth":84,"text":1403},{"id":1678,"depth":84,"text":1679},{"id":1701,"depth":84,"text":1702},{"id":1713,"depth":78,"text":1714},{"id":1863,"depth":78,"text":1864},{"id":1905,"depth":78,"text":1906},null,"App-Entwicklung","2026-03-10","Ein tiefgehender technischer Leitfaden für produktionsreife Flutter-Apps — von Clean Architecture und Impeller-Rendering bis zu CI\u002FCD-Pipelines und.","md",[1954,1957,1960,1963,1966,1969,1972],{"question":1955,"answer":1956},"Ist Flutter für Enterprise-Anwendungen geeignet?","Ja. Google Pay, Alibaba, BMW und eBay betreiben produktive Flutter-Apps mit Millionen von Nutzern. Mit Clean Architecture, sauberem State Management und gründlichem Testing skaliert Flutter bis zur Enterprise-Komplexität.",{"question":1958,"answer":1959},"Wie steht Flutter im Performance-Vergleich zu nativer Entwicklung?","Mit Impeller 2.0 und AOT-Compilation erreicht Flutter konsistente 60 FPS mit Cold Starts unter 200ms. Für die meisten Anwendungen ist der Unterschied zur vollständig nativen Entwicklung für Nutzer nicht wahrnehmbar.",{"question":1961,"answer":1962},"Flutter oder React Native — was ist die bessere Wahl 2026?","Flutter eignet sich besonders für performance-kritische Apps, individuelles UI-Design und echte Multi-Plattform-Deployments (Mobile, Web, Desktop aus einer Codebasis). React Native ist besser, wenn das Team tiefe JavaScript-Expertise mitbringt oder Zugriff auf ein größeres Paket-Ökosystem benötigt.",{"question":1964,"answer":1965},"Kann Flutter native iOS- und Android-Entwicklung vollständig ersetzen?","Für 90% der Anwendungsfälle ja. Die verbleibenden 10% — etwa AR\u002FVR, fortgeschrittene Bluetooth-LE-Protokolle oder tiefe OS-Integration — erfordern möglicherweise native Module. Flutters FFI und Platform Channels machen solche Hybrid-Architekturen unkompliziert.",{"question":1967,"answer":1968},"Wie lange dauert die Entwicklung eines MVP mit Flutter?","Ein typisches MVP mit Authentifizierung, API-Integration und 8–12 Screens benötigt ca. 300 Entwicklungsstunden — rund 13% schneller als React Native und 40% schneller als separate native Apps.",{"question":1970,"answer":1971},"Was ist Impeller und warum ist es wichtig?","Impeller ist Flutters Rendering-Engine der nächsten Generation, die Skia ablöst. Sie nutzt Ahead-of-Time-Shader-Compilation, um First-Run-Jank zu eliminieren, reduziert die Frame-Rasterisierungszeit um fast 50% und integriert direkt mit Metal (iOS) und Vulkan (Android).",{"question":1973,"answer":1974},"Unterstützt Flutter WebAssembly?","Ja. WebAssembly (Wasm) wird 2026 zum Standard-Build-Target für Flutter Web und liefert nahezu native Performance im Browser — ein enormer Sprung gegenüber dem älteren DOM-basierten Ansatz.",false,"\u002Fimages\u002Fblog\u002Fflutter-hero.jpg",{"title":1978,"description":1979,"url":1980},"Flutter Architecture Decision Matrix (PDF)","Ein druckbares Entscheidungsframework für die richtige Architektur, State Management und Deployment-Strategie für Ihr Flutter-Projekt.","\u002Fde\u002Fkontakt",{},"\u002Fde\u002Fblog\u002Fflutter-app-entwicklung-leitfaden","lehrer-online",{"title":5,"description":1951},"Flutter App-Entwicklung: Architektur, Impeller, State Management und Deployment. Praxisleitfaden für 2026.","Flutter App-Entwicklung Leitfaden (2026) | reflect.media","de\u002Fblog\u002Fflutter-app-entwicklung-leitfaden",[1989,1990,1949,1991,1992,1993,1994,1995,1697,1691],"Flutter","Dart","Cross-Platform","Mobile Development","Impeller","Clean Architecture","CI\u002FCD","LbKSmBG6rmS9QENAevthMNcFfyNEaXUNWFHvhvIO8kI",[],1776448571416]