[{"data":1,"prerenderedAt":1998},["ShallowReactive",2],{"blog-flutter-app-development-guide-en":3,"blog-related-flutter-app-development-guide-en":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_en\u002Fen\u002Fblog\u002Fflutter-app-development-guide.md","Flutter App Development in 2026: Architecture, 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},"Get in touch","We build production-grade Flutter apps with clean architecture, native performance, and scalable CI\u002FCD. Let's talk about your requirements.","Planning a Flutter Project?","inline",[17,18],"div",{"className":19},[20],"blog-tldr",[22,23,24,28],"p",{},[25,26,27],"strong",{},"TL;DR"," — Flutter in 2026 is a mature, production-ready framework. Impeller 2.0 eliminates rendering jank with AOT shader compilation. Clean architecture with BLoC and GetIt keeps large codebases maintainable. FFI replaces method channels for high-performance native integration. WebAssembly is the new default for web builds. And automated CI\u002FCD pipelines with GitHub Actions deploy to both app stores with a single merge.\n:",[30,31,33],"h2",{"id":32},"why-flutter-has-become-the-default-for-cross-platform-development","Why Flutter Has Become the Default for Cross-Platform Development",[22,35,36],{},"The cross-platform landscape has consolidated. While React Native remains a strong contender for JavaScript-heavy teams, Flutter has emerged as the framework of choice for teams that prioritize rendering performance, pixel-perfect design control, and true multi-platform reach. With Flutter 3.41, the framework has reached a level of maturity where the question is no longer whether Flutter is production-ready, but how to architect Flutter applications that scale.",[22,38,39],{},"This guide covers the technical decisions that separate hobby projects from production-grade Flutter applications: architecture patterns, rendering pipeline optimization, native integration strategies, and deployment automation.",[30,41,43],{"id":42},"the-rendering-revolution-impeller-20","The Rendering Revolution: Impeller 2.0",[22,45,46],{},"The most consequential change in the Flutter ecosystem is the completion of the Impeller rendering engine. Since Flutter's inception, the framework relied on Skia for GPU rendering — a capable engine, but one that compiled shaders at runtime. This caused the dreaded \"shader compilation jank\" — visible stutters the first time a user encountered certain animations or transitions.",[22,48,49],{},"Impeller solves this problem fundamentally. Instead of compiling shaders on demand, Impeller uses ahead-of-time (AOT) shader compilation. Every shader the application might need is precompiled during the build step. The result: zero first-run jank, consistent frame timing, and nearly 50% reduction in frame rasterization time for complex scenes.",[51,52,54],"h3",{"id":53},"how-impeller-achieves-this","How Impeller Achieves This",[22,56,57],{},"Impeller integrates directly with the platform's native graphics API — Metal on iOS and Vulkan on Android. This is a significant departure from Skia's more abstract rendering approach:",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"language-dart shiki shiki-themes github-light","\u002F\u002F Impeller benefits are automatic — no code changes needed.\n\u002F\u002F But understanding the pipeline helps with performance optimization.\n\n\u002F\u002F Heavy custom painting? Impeller's tessellator handles complex paths\n\u002F\u002F far more efficiently than Skia's CPU-bound path rasterization.\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 tessellates this path on the GPU, not CPU.\n    \u002F\u002F Complex gradients and blur effects also stay GPU-bound.\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 benefits are automatic — no code changes needed.\n",[69,77,79],{"class":71,"line":78},2,[69,80,81],{},"\u002F\u002F But understanding the pipeline helps with performance optimization.\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 Heavy custom painting? Impeller's tessellator handles complex paths\n",[69,96,98],{"class":71,"line":97},5,[69,99,100],{},"\u002F\u002F far more efficiently than Skia's CPU-bound path rasterization.\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 tessellates this path on the GPU, not CPU.\n",[69,173,175],{"class":71,"line":174},18,[69,176,177],{},"    \u002F\u002F Complex gradients and blur effects also stay GPU-bound.\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],{},"In 2026, the legacy Skia backend is being removed for Android 10 and above. If your app targets Android 9 or below, Skia remains available as a fallback. For all practical purposes, Impeller is now the only rendering path that matters.",[51,216,218],{"id":217},"performance-profiling-with-impeller","Performance Profiling with Impeller",[22,220,221],{},"Impeller changes how you profile Flutter applications. Frame rasterization is no longer the bottleneck — widget building and layout computation often are. The Flutter DevTools performance overlay now highlights widget rebuild counts and layout pass duration as the primary metrics to optimize.",[59,223,225],{"className":61,"code":224,"language":63,"meta":64,"style":64},"\u002F\u002F Use RepaintBoundary strategically to isolate expensive subtrees\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 Use RepaintBoundary strategically to isolate expensive subtrees\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-structuring-flutter-for-scale","Clean Architecture: Structuring Flutter for Scale",[22,289,290],{},"A Flutter app with five screens works fine with any architecture. A Flutter app with fifty screens, three API integrations, offline caching, and a team of five developers requires discipline. Clean architecture — adapted for Flutter's reactive paradigm — provides that structure.",[51,292,294],{"id":293},"the-three-layer-model","The Three-Layer Model",[22,296,297],{},"The architecture separates concerns into three layers with strict dependency rules. Dependencies only point inward: presentation depends on domain, domain depends on nothing, and data implements domain contracts.",[59,299,304],{"className":300,"code":302,"language":303},[301],"language-text","lib\u002F\n├── core\u002F\n│   ├── error\u002F           # Failure classes, 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   # Abstract 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-pure-business-logic","Domain Layer: Pure Business Logic",[22,311,312],{},"The domain layer contains entities, use case classes, and abstract repository interfaces. It has zero Flutter imports and zero external dependencies. This makes it testable with pure Dart unit tests and portable across projects.",[59,314,316],{"className":61,"code":315,"language":63,"meta":64,"style":64},"\u002F\u002F Domain entity — immutable, no serialization logic\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 Abstract repository — the contract data layer must fulfill\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 — single responsibility, single entry point\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, no serialization logic\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 Abstract repository — the contract data layer must fulfill\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 — single responsibility, single entry point\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-contracts-and-caching","Data Layer: API Contracts and Caching",[22,512,513],{},"The data layer implements the repository interfaces defined in the domain layer. It handles JSON serialization, API calls, local caching, and error mapping. Models in this layer extend or map to domain entities but add serialization concerns.",[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('No cached data available'));\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('No cached data available'));\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) separates UI interaction from state transformation. Events go in, states come out. The UI subscribes to state changes and rebuilds accordingly. Combined with ",[66,674,675],{},"freezed"," for immutable state classes, this produces predictable, testable 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-with-getit","Dependency Injection with GetIt",[22,863,864],{},"GetIt wires everything together. Register abstract types with concrete implementations, and the presentation layer never knows which data source it's actually using. This makes swapping real APIs with fakes during testing 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-platform-integration-ffi-vs-method-channels","Native Platform Integration: FFI vs. Method Channels",[22,998,999],{},"Flutter applications occasionally need to call platform-specific APIs — biometric authentication, Bluetooth protocols, camera pipelines, or proprietary SDKs. Flutter provides two integration mechanisms, and choosing the right one matters for both performance and maintainability.",[51,1001,1003],{"id":1002},"method-channels-the-traditional-bridge","Method Channels: The Traditional Bridge",[22,1005,1006],{},"Method channels are asynchronous message-passing bridges between Dart and native code. They serialize arguments, send them across a platform boundary, and deserialize the response.",[59,1008,1010],{"className":61,"code":1009,"language":63,"meta":64,"style":64},"\u002F\u002F Dart side\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 side\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 side (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 side (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 work well for infrequent, request-response interactions. But they serialize all arguments through a codec, which adds latency for high-frequency calls or large data transfers.",[51,1138,1140],{"id":1139},"ffi-direct-zero-copy-native-calls","FFI: Direct, Zero-Copy Native Calls",[22,1142,1143,1144,1147],{},"Since Flutter 3.38, FFI (Foreign Function Interface) via ",[66,1145,1146],{},"dart:ffi"," is the recommended approach for performance-sensitive native integration. FFI calls are synchronous, avoid serialization overhead, and can share memory directly between Dart and native code.",[59,1149,1151],{"className":61,"code":1150,"language":63,"meta":64,"style":64},"\u002F\u002F Load a native library and call functions directly\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 Load a native library and call functions directly\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],{},"For new projects, generate FFI bindings automatically with ",[66,1279,1280],{},"flutter create --template=package_ffi",". The ",[66,1283,1284],{},"ffigen"," tool reads C header files and generates type-safe Dart bindings, eliminating manual boilerplate.",[51,1287,1289],{"id":1288},"when-to-use-each","When to Use Each",[22,1291,1292,1293,1296],{},"Use ",[25,1294,1295],{},"method channels"," when you need asynchronous platform interactions, when you're integrating with platform-specific lifecycle events, or when the overhead of serialization is negligible compared to the operation itself (e.g., requesting permissions, reading device settings).",[22,1298,1292,1299,1302],{},[25,1300,1301],{},"FFI"," when you need synchronous execution, when you're processing large data buffers (images, audio, sensor data), or when you're wrapping an existing C\u002FC++ library that handles the heavy computation.",[30,1304,1306],{"id":1305},"flutter-for-web-webassembly-as-default","Flutter for Web: WebAssembly as Default",[22,1308,1309],{},"Flutter web has matured significantly. The transition from DOM-based rendering to WebAssembly is the defining change for 2026. Wasm delivers near-native execution speed for Dart code in the browser, replacing the slower JavaScript compilation target.",[51,1311,1313],{"id":1312},"building-for-wasm","Building for Wasm",[59,1315,1319],{"className":1316,"code":1317,"language":1318,"meta":64,"style":64},"language-bash shiki shiki-themes github-light","# Build with WebAssembly (becoming default in 2026)\nflutter build web --wasm\n\n# Check Wasm readiness of your dependencies\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 with WebAssembly (becoming default in 2026)\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},"# Check Wasm readiness of your dependencies\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],{},"Not all Dart packages are Wasm-compatible yet. Packages that use ",[66,1371,1372],{},"dart:js"," or ",[66,1375,1376],{},"dart:html"," directly need migration to ",[66,1379,1380],{},"package:web"," and ",[66,1383,1384],{},"dart:js_interop",". Run a Wasm dry compilation early in your project to identify incompatible dependencies before they become blockers.",[51,1387,1389],{"id":1388},"stateful-hot-reload-on-web","Stateful Hot Reload on Web",[22,1391,1392],{},"Since Flutter 3.35, stateful hot reload works on web by default. This eliminates the productivity gap between mobile and web development. Change a widget, save, and see the result instantly — with application state preserved.",[30,1394,1396],{"id":1395},"cicd-from-commit-to-app-store","CI\u002FCD: From Commit to App Store",[22,1398,1399],{},"A production Flutter project needs automated testing, building, and deployment. Manual builds introduce human error and slow down release cycles. GitHub Actions provides a solid foundation for Flutter CI\u002FCD.",[51,1401,1403],{"id":1402},"pipeline-architecture","Pipeline Architecture",[22,1405,1406],{},"A production-ready pipeline has three stages: validate, build, and deploy.",[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-and-secrets","Code Signing and Secrets",[22,1681,1682],{},"Never commit signing keys or service account credentials to your repository. Store them as encrypted secrets in your CI platform:",[1684,1685,1686,1693],"ul",{},[1687,1688,1689,1692],"li",{},[25,1690,1691],{},"Android",": Upload keystore as a base64-encoded secret, decode it during the build step. Store the Google Play service account JSON as a secret.",[1687,1694,1695,1698],{},[25,1696,1697],{},"iOS",": Use Fastlane Match or manual provisioning profiles stored as secrets. App Store Connect API keys replace Apple ID credentials for automated uploads.",[51,1700,1702],{"id":1701},"staged-rollouts","Staged Rollouts",[22,1704,1705],{},"Deploy to internal testing first, promote to beta after manual QA, then roll out to production at 10%, 50%, 100%. Google Play Console supports percentage-based rollouts natively. App Store Connect uses TestFlight groups for staged distribution.",[10,1707],{"button":1708,"text":1709,"title":1710,"variant":15},"Schedule a call","We've shipped Flutter apps for enterprise clients across insurance, education, and public sector. Let's discuss your project.","Need Help with Your Flutter Architecture?",[30,1712,1714],{"id":1713},"testing-strategy-for-production-flutter-apps","Testing Strategy for Production Flutter Apps",[22,1716,1717],{},"Testing is not optional in production Flutter development. A layered testing strategy mirrors the clean architecture:",[1684,1719,1720,1726,1732],{},[1687,1721,1722,1725],{},[25,1723,1724],{},"Unit tests"," for domain logic, use cases, and BLoC state transitions",[1687,1727,1728,1731],{},[25,1729,1730],{},"Widget tests"," for component behavior and interaction",[1687,1733,1734,1737],{},[25,1735,1736],{},"Integration tests"," for complete user flows on real devices",[59,1739,1741],{"className":61,"code":1740,"language":63,"meta":64,"style":64},"\u002F\u002F BLoC test example\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    'emits [Loading, Loaded] when LoadProjects succeeds',\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 example\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],{},"    'emits [Loading, Loaded] when LoadProjects succeeds',\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],{},"Aim for 80%+ code coverage on the domain and data layers. Widget test coverage can be lower — focus on critical user interactions rather than pixel-perfect assertions.",[30,1862,1864],{"id":1863},"making-the-decision-when-flutter-is-the-right-choice","Making the Decision: When Flutter Is the Right Choice",[22,1866,1867],{},"Flutter is the right technology when your project requires:",[1684,1869,1870,1876,1882,1888,1894],{},[1687,1871,1872,1875],{},[25,1873,1874],{},"Multi-platform reach"," from a single codebase (mobile, web, desktop)",[1687,1877,1878,1881],{},[25,1879,1880],{},"Custom, branded UI"," that doesn't follow platform conventions",[1687,1883,1884,1887],{},[25,1885,1886],{},"High frame-rate animations"," and complex visual interactions",[1687,1889,1890,1893],{},[25,1891,1892],{},"Fast time-to-market"," with a small team",[1687,1895,1896,1899],{},[25,1897,1898],{},"Long-term maintainability"," through clean architecture and strong typing",[22,1901,1902],{},"Flutter is not the ideal choice when your app is primarily a thin wrapper around platform-specific APIs (deep AR integration, specialized Bluetooth protocols) or when your entire team has deep React\u002FJavaScript expertise and no Dart experience.",[30,1904,1906],{"id":1905},"conclusion","Conclusion",[22,1908,1909],{},"Flutter in 2026 is not the same framework it was three years ago. Impeller has solved the rendering performance question. Clean architecture with BLoC provides a battle-tested pattern for complex applications. FFI offers native-grade performance for platform integration. WebAssembly unlocks serious web deployment. And GitHub Actions automates the entire path from commit to app store.",[22,1911,1912],{},"The technology choices are settled. What remains is the engineering discipline to apply them correctly — choosing the right architecture granularity for your team size, profiling before optimizing, testing at every layer, and automating everything that can be automated. That is what separates Flutter projects that ship from Flutter projects that stall.",[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 Development","2026-03-10","A deep technical guide to building production-grade Flutter apps — from clean architecture and Impeller rendering to CI\u002FCD pipelines and native platform.","md",[1954,1957,1960,1963,1966,1969,1972],{"question":1955,"answer":1956},"Is Flutter suitable for enterprise applications?","Yes. Google Pay, Alibaba, BMW, and eBay all run production Flutter apps serving millions of users. With clean architecture, proper state management, and thorough testing, Flutter scales to enterprise-grade complexity.",{"question":1958,"answer":1959},"How does Flutter's performance compare to native development?","With Impeller 2.0 and AOT compilation, Flutter achieves consistent 60 FPS rendering with sub-200ms cold starts. For most applications, the performance difference to fully native development is imperceptible to users.",{"question":1961,"answer":1962},"Should I choose Flutter or React Native in 2026?","Flutter excels in performance-critical apps, custom UI design, and true multi-platform deployments (mobile, web, desktop from one codebase). React Native is the better choice when your team has deep JavaScript expertise or you need access to a larger third-party package ecosystem.",{"question":1964,"answer":1965},"Can Flutter replace native iOS and Android development entirely?","For 90% of use cases, yes. The remaining 10% — such as AR\u002FVR, advanced Bluetooth LE protocols, or deep OS integration — may still require native modules. Flutter's FFI and platform channels make these hybrid architectures straightforward.",{"question":1967,"answer":1968},"How long does it take to build an MVP with Flutter?","A typical MVP with authentication, API integration, and 8–12 screens takes approximately 300 development hours — roughly 13% faster than React Native and 40% faster than building separate native apps.",{"question":1970,"answer":1971},"What is Impeller and why does it matter?","Impeller is Flutter's next-generation rendering engine replacing Skia. It uses ahead-of-time shader compilation to eliminate first-run jank, reduces frame rasterization time by nearly 50%, and integrates directly with Metal (iOS) and Vulkan (Android) for GPU-level performance.",{"question":1973,"answer":1974},"Does Flutter support WebAssembly?","Yes. WebAssembly (Wasm) is becoming the default web build target in 2026, delivering near-native performance for Flutter web applications compared to the older DOM-based approach.",false,"\u002Fimages\u002Fblog\u002Fflutter-hero.jpg",{"title":1978,"description":1979,"url":1980},"Flutter Architecture Decision Matrix (PDF)","A printable decision framework for choosing the right architecture, state management, and deployment strategy for your Flutter project.","\u002Fen\u002Fcontact",{},"\u002Fen\u002Fblog\u002Fflutter-app-development-guide","lehrer-online",{"title":5,"description":1951},"Flutter app development guide covering architecture, Impeller, state management and deployment best practices.","Flutter App Development Guide (2026) | reflect.media","en\u002Fblog\u002Fflutter-app-development-guide",[1989,1990,1949,1991,1992,1993,1994,1995,1697,1691],"Flutter","Dart","Cross-Platform","Mobile Development","Impeller","Clean Architecture","CI\u002FCD","0NUwKIzRUOPMQtDTQEyZd8a-X2r3SrLnctnZa_dz9hs",[],1776448562689]