{"id":34589,"date":"2024-10-18T17:00:00","date_gmt":"2024-10-19T00:00:00","guid":{"rendered":"https:\/\/clickup.com\/blog\/?p=34589"},"modified":"2024-10-23T07:55:04","modified_gmt":"2024-10-23T14:55:04","slug":"separation-of-concerns-in-flutter-applications","status":"publish","type":"post","link":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/","title":{"rendered":"Separation of Concerns in Flutter Applications"},"content":{"rendered":"\n<p>Recently, I had to implement onboarding walkthroughs for <a href=\"https:\/\/clickup.com\/\">ClickUp<\/a> newcomers! This was a really important task because a lot of new users were about to discover the platform with the <a href=\"https:\/\/www.youtube.com\/watch?v=AirnL1NxOAw\" rel=\"noreferrer noopener\" target=\"_blank\">incredibly funny ad we premiered at the Super Bowl<\/a>! \u2728<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.34%\"><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.34%\">\n<figure class=\"wp-block-video\"><video height=\"1888\" style=\"aspect-ratio: 922 \/ 1888;\" width=\"922\" controls src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/Onboarding-Demo-2.mov\"><\/video><figcaption><p style=\"text-align:center\">via ClickUp<\/p><\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.33%\"><\/div>\n<\/div>\n\n\n\n<p>The walkthrough allows our numerous new users, who maybe don&#8217;t know ClickUp yet, to quickly understand how to use several functionalities from the application. It is an ongoing effort, just like the new <a href=\"https:\/\/university.clickup.com\/\" rel=\"noreferrer noopener\" target=\"_blank\">ClickUp University<\/a> resource we&#8217;re pursuing! \ud83d\ude80<\/p>\n\n\n\n<p>Fortunately, the software architecture behind the ClickUp Flutter mobile application allowed me to implement this functionality quite quickly, even by reusing the real widgets from the application! This means that the walkthrough is dynamic, responsive, and exactly matches the real application screens of the app\u2014and will continue to, even when the widgets will evolve.<\/p>\n\n\n\n<p>I was also able to implement the functionality because of the right separation of concerns.<\/p>\n\n\n\n<p>Let&#8217;s see what I mean here. \ud83e\udd14<\/p>\n\n\n<div class=\"wp-block-ub-table-of-contents-block ub_table-of-contents\" id=\"ub_table-of-contents-5b0d341f-a7d8-4c08-b768-274f8765c939\" data-linktodivider=\"false\" data-showtext=\"show\" data-hidetext=\"hide\" data-scrolltype=\"auto\" data-enablesmoothscroll=\"false\" data-initiallyhideonmobile=\"false\" data-initiallyshow=\"true\"><div class=\"ub_table-of-contents-header-container\" style=\"\">\n\t\t\t<div class=\"ub_table-of-contents-header\" style=\"text-align: left; \">\n\t\t\t\t<div class=\"ub_table-of-contents-title\">This Article Contains:<\/div>\n\t\t\t\t\n\t\t\t<\/div>\n\t\t<\/div><div class=\"ub_table-of-contents-extra-container\" style=\"\">\n\t\t\t<div class=\"ub_table-of-contents-container ub_table-of-contents-1-column \">\n\t\t\t\t<ul style=\"\"><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#0-separation-of-concerns\" style=\"\">Separation of concerns<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#1-a-step-by-step-guide\" style=\"\">A step-by-step guide<\/a><ul><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#2-the-application\" style=\"\">The application<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#3-step-1-naive-approach\" style=\"\">Step 1. Naive approach<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#8-step-2-inversion-of-control\" style=\"\">Step 2. Inversion of control<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#15-step-3-state-management\" style=\"\">Step 3. State management<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#20-step-4-visual-dedicated-layout-widget\" style=\"\">Step 4. Visual dedicated layout widget<\/a><\/li><\/ul><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#24-conclusion\" style=\"\">Conclusion<\/a><\/li><\/ul>\n\t\t\t<\/div>\n\t\t<\/div><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"0-separation-of-concerns\">Separation of concerns<\/h2>\n\n\n\n<p>Designing a software architecture is one of the most complex topics for engineering teams. Among all responsibilities, it is always difficult to anticipate future software evolutions. That&#8217;s why creating a well-layered and decoupled architecture can help you and your teammates with a lot of things!<\/p>\n\n\n\n<p>The main benefit of creating small decoupled systems is undoubtedly the&nbsp;<strong>testability<\/strong>! And this is what helped me create a demo alternative of the existing screens from the app!<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"1-a-step-by-step-guide\">A step-by-step guide<\/h2>\n\n\n\n<p>Now, how could we apply those principles to a Flutter application?<\/p>\n\n\n\n<p>We will share a few technics we use to build ClickUp with a simple walkthrough example.<\/p>\n\n\n\n<p>The example is so simple that it may not enlighten all of the advantages behind it, but believe me, it will help you create a lot more maintainable Flutter applications with complex codebases. \ud83d\udca1<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"2-the-application\">The application<\/h3>\n\n\n\n<p>As an example, we will create an application that displays the population of the USA for each year.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.34%\"><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.34%\">\n<figure class=\"wp-block-video aligncenter\"><video height=\"1888\" style=\"aspect-ratio: 922 \/ 1888;\" width=\"922\" controls src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/Example-Demo.mov\"><\/video><figcaption><p style=\"text-align:center\">via ClickUp<\/p><\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33.33%\"><\/div>\n<\/div>\n\n\n\n<p>We have two screens here :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>HomeScreen<\/code>&nbsp;: simply lists all years from 2000 to now. When the users taps on a year tile, they will navigate to the&nbsp;<code>DetailScreen<\/code>&nbsp;with a navigation argument set to the selected year.<\/li><li><code>DetailScreen<\/code>&nbsp;: gets the year from the navigation argument, calls the&nbsp;<a href=\"https:\/\/datausa.io\/api\/data?drilldowns=Nation&amp;measures=Population&amp;year=2018\" rel=\"noreferrer noopener\" target=\"_blank\">datausa.io API<\/a>&nbsp;for this year, and parses the JSON data to extract the associated population value. If data is available, a label is displayed with the population.<\/li><\/ul>\n\n\n\n<p>We will focus on the&nbsp;<code>DetailScreen<\/code>&nbsp;implementation since it is the most interesting with its asynchronous call.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"3-step-1-naive-approach\">Step 1. Naive approach<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"607\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-1400x607.png\" alt=\"Naive approach Stateful Widget\" class=\"wp-image-34594\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-1400x607.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-300x130.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-768x333.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-1536x666.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5-700x304.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-5.png 1928w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption>via ClickUp<\/figcaption><\/figure><\/div>\n\n\n\n<p>The most obvious implementation for our app, is by using a single\u00a0<code>StatefulWidget<\/code>\u00a0for the whole logic.<\/p>\n\n\n<div style=\"gap: 20px;\" class=\"align-button-center ub-buttons orientation-button-row ub-flex-wrap wp-block-ub-button\" id=\"ub-button-ac5697e0-0540-4cda-9408-5e8ef0391aab\"><div class=\"ub-button-container\">\n\t\t\t<a href=\"https:\/\/dartpad.dev\/embed-flutter.html?id=b37071f4386ecb27c29332e788999612&amp;split=80&amp;theme=dark\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"ub-button-block-main  \" role=\"button\" style=\"--ub-button-background-color: #9b51e0; --ub-button-color: #ffffff; --ub-button-border: none; --ub-button-hover-background-color: #eb3dae; --ub-button-hover-color: #ffffff; --ub-button-hover-border: none; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; \">\n\t\t\t\t<div class=\"ub-button-content-holder\" style=\"flex-direction: row\">\n\t\t\t\t\t<span class=\"ub-button-block-btn\">Dart Source Code &amp; Demo<\/span>\n\t\t\t\t<\/div>\n\t\t\t<\/a>\n\t\t<\/div><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"4-accessing-the-year-navigation-argument\">Accessing the&nbsp;<code>year<\/code>&nbsp;navigation argument<\/h4>\n\n\n\n<p>To access the requested year, we read the&nbsp;<code>RouteSettings<\/code>&nbsp;from the&nbsp;<code>ModalRoute<\/code>&nbsp;inherited widget.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void didChangeDependencies() {\n    super.didChangeDependencies();\n    final year = ModalRoute.of(context)!.settings.arguments as int;\n    \/\/ ...\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"5-http-call\">HTTP call<\/h4>\n\n\n\n<p>This example invokes the&nbsp;<code>get<\/code>&nbsp;function from the&nbsp;<code>http<\/code>&nbsp;package to get the data from the&nbsp;<a href=\"https:\/\/datausa.io\/api\/data?drilldowns=Nation&amp;measures=Population&amp;year=2018\" rel=\"noreferrer noopener\" target=\"_blank\">datausa.io API<\/a>, parses the resulting JSON with the&nbsp;<code>jsonDecode<\/code>&nbsp;method from the&nbsp;<code>dart:convert<\/code>&nbsp;library, and keep&nbsp;<code>Future<\/code>&nbsp;as part of the state with a property named&nbsp;<code>_future<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>late Future&lt;Map&lt;dynamic, dynamic&gt;?&gt; _future;\n\n\nvoid didChangeDependencies() {\n    super.didChangeDependencies();\n    final year = ModalRoute.of(context)!.settings.arguments as int;\n    if (_year != year) {\n      _future = _loadMeasure(year);\n    }\n}\n\n\nFuture&lt;Map&lt;dynamic, dynamic&gt;?&gt; _loadMeasure(int year) async {\n    _year = year;\n    final uri = Uri.parse(\n        'https:\/\/datausa.io\/api\/data?drilldowns=Nation&amp;measures=Population&amp;year=$year');\n    final result = await get(uri);\n    final body = jsonDecode(result.body);\n    final data = body&#91;'data'] as List&lt;dynamic&gt;;\n    if (data.isNotEmpty) {\n      return data.first;\n    }\n    return null;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"6-rendering\">Rendering<\/h4>\n\n\n\n<p>To create the widget tree, we&#8217;re using a&nbsp;<code>FutureBuilder<\/code>, which rebuilds itself regarding the current state of our&nbsp;<code>_future<\/code>&nbsp;asynchronous call.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@override\nWidget build(BuildContext context) {\nreturn Scaffold(\n    appBar: AppBar(\n    title: Text('Year $_year'),\n    ),\n    body: FutureBuilder&lt;Map&lt;dynamic, dynamic&gt;?&gt;(\n    future: _future,\n    builder: (context, snapshot) {\n        switch (snapshot.connectionState) {\n        case ConnectionState.done:\n            final error = snapshot.error;\n            if (error != null) {\n                \/\/ return \"error\" tree.\n            }\n            final data = snapshot.data;\n            if (data != null) {\n                \/\/ return \"result\" tree.\n            }\n            \/\/ return \"empty\" data tree.case ConnectionState.none:\n        case ConnectionState.waiting:\n        case ConnectionState.active:\n            \/\/ return \"loading\" data tree.\n        }\n    },\n    ),\n);\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"7-review\">Review<\/h4>\n\n\n\n<p>Okay, the implementation is short and uses only built-in widgets, but now think of our initial intention: building demo alternatives (or tests) for this screen. It is very difficult to control the result of the HTTP call to force the application to render in a certain state. <\/p>\n\n\n\n<p>That&#8217;s where the concept of&nbsp;<strong>inversion of control<\/strong>&nbsp;will help us. \ud83e\uddd0<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"8-step-2-inversion-of-control\">Step 2. Inversion of control<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"607\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-1400x607.png\" alt=\"Inversion of control to Provider and api Client\" class=\"wp-image-34595\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-1400x607.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-300x130.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-768x333.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-1536x666.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6-700x304.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-6.png 1928w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption>via ClickUp<\/figcaption><\/figure><\/div>\n\n\n\n<p>This principle can be hard to understand for new developers (<em>and also hard to explain<\/em>), but the overall idea is to&nbsp;<strong>extract the concerns outside of our components<\/strong>\u2014so that they aren&#8217;t responsible for choosing the behavior\u2014and delegate it instead.<\/p>\n\n\n\n<p>In a more common situation, it simply consists of creating abstractions and injecting implementations into our components so their implementation can be changed later on if needed.<\/p>\n\n\n\n<p>But don&#8217;t worry, it will make more sense after our next example! \ud83d\udc40<\/p>\n\n\n<div style=\"gap: 20px;\" class=\"align-button-center ub-buttons orientation-button-row ub-flex-wrap wp-block-ub-button\" id=\"ub-button-070cce52-127e-40bd-ba49-a9b25908c9c1\"><div class=\"ub-button-container\">\n\t\t\t<a href=\"https:\/\/dartpad.dev\/embed-flutter.html?id=d68322aa5a6e6bdd4d1fbbf8f86aebd3&amp;split=80&amp;theme=dark\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"ub-button-block-main  \" role=\"button\" style=\"--ub-button-background-color: #9b51e0; --ub-button-color: #ffffff; --ub-button-border: none; --ub-button-hover-background-color: #eb3dae; --ub-button-hover-color: #ffffff; --ub-button-hover-border: none; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; \">\n\t\t\t\t<div class=\"ub-button-content-holder\" style=\"flex-direction: row\">\n\t\t\t\t\t<span class=\"ub-button-block-btn\">Dart Source Code &amp; Demo<\/span>\n\t\t\t\t<\/div>\n\t\t\t<\/a>\n\t\t<\/div><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"9-creating-an-api-client-object\">Creating an API client object<\/h4>\n\n\n\n<p>In order to control the HTTP call to our API, we&#8217;ve isolated our implementation into a dedicated&nbsp;<code>DataUsaApiClient<\/code>&nbsp;class. We&#8217;ve also created a&nbsp;<code>Measure<\/code>&nbsp;class to make data easier to manipulate and maintain.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class DataUsaApiClient {\n  const DataUsaApiClient({\n    this.endpoint = 'https:\/\/datausa.io\/api\/data',\n  });\n\n\n  final String endpoint;\n\n\n  Future&lt;Measure?&gt; getMeasure(int year) async {\n    final uri =\n        Uri.parse('$endpoint?drilldowns=Nation&amp;measures=Population&amp;year=$year');\n    final result = await get(uri);\n    final body = jsonDecode(result.body);\n    final data = body&#91;'data'] as List&lt;dynamic&gt;;\n    if (data.isNotEmpty) {\n      return Measure.fromJson(data.first as Map&lt;String, Object?&gt;);\n    }\n    return null;\n  }<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"10-provide-an-api-client\">Provide an API client<\/h4>\n\n\n\n<p>For our example, we&#8217;re using the well-known&nbsp;<a href=\"https:\/\/file+.vscode-resource.vscode-webview.net\/Users\/alois\/Desktop\/article\/clickup-separation_of_concern.md\" rel=\"noreferrer noopener\" target=\"_blank\">provider<\/a> package to inject a&nbsp;<code>DataUsaApiClient<\/code>&nbsp;instance at the root of our tree.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Provider&lt;DataUsaApiClient&gt;(\n    create: (context) =&gt; const DataUsaApiClient(),\n        child: const MaterialApp(\n        home: HomePage(),\n    ),\n)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"11-using-the-api-client\">Using the API client<\/h4>\n\n\n\n<p>The provider allows any descendent widget&nbsp;<em>(<\/em><em>like<\/em><em> our&nbsp;<\/em><code><em>DetailScreen<\/em><\/code><em>)<\/em>&nbsp;to read the nearest&nbsp;<code>DataUsaApiClient<\/code>&nbsp;upper in the tree. We can then use its&nbsp;<code>getMeasure<\/code>&nbsp;method to start our&nbsp;<code>Future<\/code>, in place of the actual HTTP implementation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@overridevoid didChangeDependencies() {\n    super.didChangeDependencies();\n    final year = ModalRoute.of(context)!.settings.arguments as int;\n    if (_year != year) {\n      _year = year;\n      final api = context.read&lt;DataUsaApiClient&gt;();\n      _future = api.getMeasure(year);\n    }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"12-demo-api-client\">Demo API client<\/h4>\n\n\n\n<p>Now we can take advantage of this!<\/p>\n\n\n\n<p>In case you didn&#8217;t know: any classes&nbsp;<a href=\"https:\/\/dart.dev\/samples#interfaces-and-abstract-classes\" rel=\"noreferrer noopener\" target=\"_blank\">in dart also implicitly define an associated interface<\/a>. This allows us to provide an alternative implementation of&nbsp;<code>DataUsaApiClient<\/code>&nbsp;which always returns the same instance from its&nbsp;<code>getMeasure<\/code>&nbsp;method calls.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>This method<\/p><\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>class DemoDataUsaApiClient implements DataUsaApiClient {\n  const DemoDataUsaApiClient(this.measure);\n\n\n  final Measure measure;\n\n\n  @overrideString get endpoint =&gt; '';\n\n\n  @override\n  Future&lt;Measure?&gt; getMeasure(int year) {\n    return Future.value(measure);\n  }<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"13-displaying-a-demo-page\">Displaying a demo page<\/h4>\n\n\n\n<p>We now have all the keys to display a demo instance of the&nbsp;<code>DetailPage<\/code>!<\/p>\n\n\n\n<p>We simply override the currently provided&nbsp;<code>DataUsaApiClient<\/code>&nbsp;instance by wrapping our&nbsp;<code>DetailScreen<\/code>&nbsp;in a provider that creates a&nbsp;<code>DemoDataUsaApiClient<\/code>&nbsp;instance instead!<\/p>\n\n\n\n<p>And that&#8217;s it\u2014our&nbsp;<code>DetailScreen<\/code> reads this demo instance instead, and uses our&nbsp;<code>demoMeasure<\/code>&nbsp;data instead of an HTTP call.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ListTile(\n    title: const Text('Open demo'),\n    onTap: () {\n        const demoMeasure = Measure(\n            year: 2022,\n            population: 425484,\n            nation: 'United States',\n        );\n        Navigator.push(\n            context,\n            MaterialPageRoute(\n                settings: RouteSettings(arguments: demoMeasure.year),\n                builder: (context) {\n                    return Provider&lt;DataUsaApiClient&gt;(\n                        create: (context) =&gt;\n                            const DemoDataUsaApiClient(demoMeasure),\n                        child: const DetailScreen(),\n                    );\n                },\n            ),\n        );\n    },\n)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"14-review\">Review<\/h4>\n\n\n\n<p>This is a great example of&nbsp;<em>Inversion of control.<\/em> Our&nbsp;<code>DetailScreen<\/code>&nbsp;widget isn&#8217;t responsible for the logic of getting the data anymore, but delegates it instead to a dedicated client object. And we&#8217;re now able to create demo instances of the screen, or to implement widget tests for our screen! Awesome! \ud83d\udc4f<\/p>\n\n\n\n<p>But we can do even better!<\/p>\n\n\n\n<p>Since we&#8217;re not able to simulate a loading state, for example, we don&#8217;t have full control of any state change at our widget level.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"15-step-3-state-management\">Step 3. State management<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"607\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-1400x607.png\" alt=\"Statement management in flutter applications\" class=\"wp-image-34596\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-1400x607.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-300x130.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-768x333.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-1536x666.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7-700x304.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-7.png 1928w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption>via ClickUp<\/figcaption><\/figure><\/div>\n\n\n\n<p>This is a hot topic in Flutter! <\/p>\n\n\n\n<p>I&#8217;m sure you&#8217;ve already read long threads of people who try to elect&nbsp;<em>the best state-management<\/em>&nbsp;solution for Flutter. And to be clear, that&#8217;s not what we will do in this article. In our opinion, as long as you separate your business logic from your visual logic, you&#8217;re fine! Creating these layers is really important for maintainability. Our example is simple, but in real applications, logic can quickly become complex and such separation makes it a lot easier to find your pure logic algorithms. This subject is often summarized as&nbsp;<em>state management<\/em>.<\/p>\n\n\n\n<p>In this example, we&#8217;re using a basic&nbsp;<code>ValueNotifier<\/code>&nbsp;alongside a&nbsp;<code>Provider<\/code>. But we could have also used&nbsp;<a href=\"https:\/\/pub.dev\/packages\/flutter_bloc\" rel=\"noreferrer noopener\" target=\"_blank\">flutter_bloc<\/a>&nbsp;or&nbsp;<a href=\"https:\/\/pub.dev\/packages\/riverpod\" rel=\"noreferrer noopener\" target=\"_blank\">riverpod<\/a><em> (or another solution)<\/em>, and it would have worked great, too. The principles stay the same, and as long as you separated your states and your logic, it is even possible to port your codebase from one of the other solutions.<\/p>\n\n\n\n<p>This separation also helps us to control any state of our widgets so we can mock it in every possible way!<\/p>\n\n\n<div style=\"gap: 20px;\" class=\"align-button-center ub-buttons orientation-button-row ub-flex-wrap wp-block-ub-button\" id=\"ub-button-3f64a2cf-8193-4376-a154-d5359cc119e5\"><div class=\"ub-button-container\">\n\t\t\t<a href=\"https:\/\/dartpad.dev\/embed-flutter.html?id=e148980534dae261ef1174a15f7ac5cf&amp;split=80&amp;theme=dark\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"ub-button-block-main  \" role=\"button\" style=\"--ub-button-background-color: #9b51e0; --ub-button-color: #ffffff; --ub-button-border: none; --ub-button-hover-background-color: #eb3dae; --ub-button-hover-color: #ffffff; --ub-button-hover-border: none; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; \">\n\t\t\t\t<div class=\"ub-button-content-holder\" style=\"flex-direction: row\">\n\t\t\t\t\t<span class=\"ub-button-block-btn\">Dart Source Code &amp; Demo<\/span>\n\t\t\t\t<\/div>\n\t\t\t<\/a>\n\t\t<\/div><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"16-creating-a-dedicated-state\">Creating a dedicated state<\/h4>\n\n\n\n<p>Instead of relying on the&nbsp;<code>AsyncSnapshot<\/code>&nbsp;from the framework, we now represent our screen state as a&nbsp;<code>DetailState<\/code>&nbsp;object.<\/p>\n\n\n\n<p>It is also important to implement the&nbsp;<code>hashCode<\/code>&nbsp;and&nbsp;<code>operator ==<\/code>&nbsp;methods to make our object comparable by value. This allows us to detect if two instances should be considered different.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>\ud83d\udca1 The&nbsp;<a href=\"https:\/\/pub.dev\/packages\/equatable\" rel=\"noreferrer noopener\" target=\"_blank\">equatable<\/a>&nbsp;or&nbsp;<a href=\"https:\/\/pub.dev\/packages\/freezed\" rel=\"noreferrer noopener\" target=\"_blank\">freezed<\/a>&nbsp;packages are great options to help you implement the&nbsp;<code>hashCode<\/code>&nbsp;and&nbsp;<code>operator ==<\/code>&nbsp;methods!<\/p><\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>abstract class DetailState {\n  const DetailState(this.year);\n  final int year;\n\n\n  @overridebool operator ==(Object other) =&gt;\n      identical(this, other) ||\n      (other is DetailState &amp;&amp;\n          runtimeType == other.runtimeType &amp;&amp;\n          year == other.year);\n\n\n  @overrideint get hashCode =&gt; runtimeType.hashCode ^ year;<\/code><\/pre>\n\n\n\n<p>Our state is always associated with a&nbsp;<code>year<\/code>, but we also have four distinct possible states regarding what we want to show to the user:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>NotLoadedDetailState<\/code>: the data update hasn&#8217;t started yet<\/li><li><code>LoadingDetailState<\/code>: the data is currently being loaded<\/li><li><code>LoadedDetailState<\/code>: the data has been successfully loaded with an associated&nbsp;<code>measure<\/code><\/li><li><code>NoDataDetailState<\/code>: the data has been loaded, but there&#8217;s no available data<\/li><li><code>UnknownErrorDetailState<\/code>: the operation failed because of an unknown&nbsp;<code>error<\/code><\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>class NotLoadedDetailState extends DetailState {\n  const NotLoadedDetailState(int year) : super(year);\n}\n\n\nclass LoadedDetailState extends DetailState {\n  const LoadedDetailState({\n    required int year,\n    required this.measure,\n  }) : super(year);\n\n\n  final Measure measure;\n\n\n  @overridebool operator ==(Object other) =&gt;\n      identical(this, other) ||\n      (other is LoadedDetailState &amp;&amp; measure == other.measure);\n\n\n  @overrideint get hashCode =&gt; runtimeType.hashCode ^ measure.hashCode;\n}\n\n\nclass NoDataDetailState extends DetailState {\n  const NoDataDetailState(int year) : super(year);\n}\n\n\nclass LoadingDetailState extends DetailState {\n  const LoadingDetailState(int year) : super(year);\n}\n\n\nclass UnknownErrorDetailState extends DetailState {\n  const UnknownErrorDetailState({\n    required int year,\n    required this.error,\n  }) : super(year);\n\n\n  final dynamic error;\n\n\n  @overridebool operator ==(Object other) =&gt;\n      identical(this, other) ||\n      (other is UnknownErrorDetailState &amp;&amp;\n          year == other.year &amp;&amp;\n          error == other.error);\n\n\n  @overrideint get hashCode =&gt; Object.hash(super.hashCode, error.has<\/code><\/pre>\n\n\n\n<p>Those states are clearer than an&nbsp;<code>AsyncSnapshot<\/code>, since it really represents our use cases. And again, this makes our code more maintainable!<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>\ud83d\udca1 We highly recommend the&nbsp;<a href=\"https:\/\/pub.dev\/packages\/freezed#unionssealed-classes\" rel=\"noreferrer noopener\" target=\"_blank\">Union types from the freezed package<\/a>&nbsp;to represent your logic states! It adds a lot of utilities like the&nbsp;<code>copyWith<\/code>&nbsp;or&nbsp;<code>map<\/code>&nbsp;methods.<\/p><\/blockquote>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"17-putting-the-logic-in-a-notifier\">Putting the logic in a Notifier<\/h4>\n\n\n\n<p>Now that we have a representation of our state, we need to store an instance of it somewhere\u2014that&#8217;s the purpose of the&nbsp;<code>DetailNotifier<\/code>. It will keep the current&nbsp;<code>DetailState<\/code>&nbsp;instance in its&nbsp;<code>value<\/code>&nbsp;property and will provide methods to update the state.<\/p>\n\n\n\n<p>We provide a&nbsp;<code>NotLoadedDetailState<\/code>&nbsp;initial state, and a&nbsp;<code>refresh<\/code>&nbsp;method to load data from the&nbsp;<code>api<\/code> and update the current&nbsp;<code>value<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class DetailNotifier extends ValueNotifier&lt;DetailState&gt; {\n  DetailNotifier({\n    required int year,\n    required this.api,\n  }) : super(DetailState.notLoaded(year));\n\n\n  final DataUsaApiClient api;\n\n\n  int get year =&gt; value.year;\n\n\n  Future&lt;void&gt; refresh() async {\n    if (value is! LoadingDetailState) {\n      value = DetailState.loading(year);\n      try {\n        final result = await api.getMeasure(year);\n        if (result != null) {\n          value = DetailState.loaded(\n            year: year,\n            measure: result,\n          );\n        } else {\n          value = DetailState.noData(year);\n        }\n      } catch (error) {\n        value = DetailState.unknownError(\n          year: year,\n          error: error,\n        );\n      }\n    }\n  }<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"18-provide-a-state-for-the-view\">Provide a state for the view<\/h4>\n\n\n\n<p>To instantiate and observe the state of our screen, we also rely on the provider and its&nbsp;<code>ChangeNotifierProvider<\/code>. This kind of provider automatically looks for any&nbsp;<code>ChangeListener<\/code>&nbsp;created and will trigger a rebuild from the&nbsp;<code>Consumer<\/code>&nbsp;each time it is notified of a change&nbsp;<em>(when our notifier value is different from the previous one).<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class DetailScreen extends StatelessWidget {\n  const DetailScreen({\n    Key? key,\n  }) : super(key: key);\n\n\n  @override\n  Widget build(BuildContext context) {\n    final year = ModalRoute.of(context)!.settings.arguments as int;\n    return ChangeNotifierProvider&lt;DetailNotifier&gt;(\n      create: (context) {\n        final notifier = DetailNotifier(\n          year: year,\n          api: context.read&lt;DataUsaApiClient&gt;(),\n        );\n        notifier.refresh();\n        return notifier;\n      },\n      child: Consumer&lt;DetailNotifier&gt;(\n        builder: (context, notifier, child) {\n             final state = notifier.value;\n            \/\/ ...\n        },\n      ),\n    );\n  }\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"19-review\">Review<\/h4>\n\n\n\n<p>Great! Our application architecture is starting to look pretty good. Everything is separated into well-defined layers, with specific concerns! \ud83e\udd17<\/p>\n\n\n\n<p>One thing is still missing for testability though, we want to define the current&nbsp;<code>DetailState<\/code>&nbsp;to control the state of our associated&nbsp;<code>DetailScreen<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"20-step-4-visual-dedicated-layout-widget\">Step 4. Visual dedicated layout widget<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"607\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-1400x607.png\" alt=\"Visual dedicated layout widgets in flutter applications\" class=\"wp-image-34597\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-1400x607.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-300x130.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-768x333.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-1536x666.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8-700x304.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/image-8.png 1928w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption>via ClickUp<\/figcaption><\/figure><\/div>\n\n\n\n<p>In the last step, we gave a bit too much responsibility to our&nbsp;<code>DetailScreen<\/code>&nbsp;widget: it was responsible for instantiating the&nbsp;<code>DetailNotifier<\/code>. And like what we&#8217;ve seen previously, we try to avoid any logic responsibility at the view layer!<\/p>\n\n\n\n<p>We can easily solve this by creating another layer for our screen widget: we will split our&nbsp;<code>DetailScreen<\/code>&nbsp;widget into two:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>DetailScreen<\/code> is responsible for setting up the various dependencies of our screen from the current application state (navigation, notifiers, state, services, &#8230;),<\/li><li><code>DetailLayout<\/code> simply converts a&nbsp;<code>DetailState<\/code>&nbsp;into a dedicated tree of widgets.<\/li><\/ul>\n\n\n\n<p>By combining the two, we will be able to simply create\u00a0<code>DetailLayout<\/code>\u00a0demo\/test instances, but having\u00a0<code>DetailScreen<\/code>\u00a0for the real use case in our application.<\/p>\n\n\n<div style=\"gap: 20px;\" class=\"align-button-center ub-buttons orientation-button-row ub-flex-wrap wp-block-ub-button\" id=\"ub-button-64050d17-113d-4261-933c-c2d2408f8aa1\"><div class=\"ub-button-container\">\n\t\t\t<a href=\"https:\/\/dartpad.dev\/embed-flutter.html?id=6e4075480df18bfe42b932f419a231ac&amp;split=80&amp;theme=dark\" target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"ub-button-block-main  \" role=\"button\" style=\"--ub-button-background-color: #9b51e0; --ub-button-color: #ffffff; --ub-button-border: none; --ub-button-hover-background-color: #eb3dae; --ub-button-hover-color: #ffffff; --ub-button-hover-border: none; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; \">\n\t\t\t\t<div class=\"ub-button-content-holder\" style=\"flex-direction: row\">\n\t\t\t\t\t<span class=\"ub-button-block-btn\">Dart Source Code &amp; Demo<\/span>\n\t\t\t\t<\/div>\n\t\t\t<\/a>\n\t\t<\/div><\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"21-dedicated-layout\">Dedicated layout<\/h4>\n\n\n\n<p>To achieve a better separation of concerns, we moved everything under the&nbsp;<code>Consumer<\/code>&nbsp;widget to a dedicated&nbsp;<code>DetailLayout<\/code>&nbsp;widget. This new widget only consumes data and isn&#8217;t responsible for any instantiation. It just converts the read state to a specific widget tree.<\/p>\n\n\n\n<p>The&nbsp;<code>ModalRoute.of<\/code>&nbsp;call and&nbsp;<code>ChangeNotifierProvider<\/code>&nbsp;instance remains in the&nbsp;<code>DetailScreen<\/code>, and this widget simply returns the&nbsp;<code>DetailLayout<\/code>&nbsp;with a pre-configured dependency tree!<\/p>\n\n\n\n<p>This minor improvement is specific to provider usage, but you will notice that we&#8217;ve also added a&nbsp;<code>ProxyProvider<\/code>&nbsp;so that any descendant widget can directly consume a&nbsp;<code>DetailState<\/code>. This will make it easier to mock data.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class DetailScreen extends StatelessWidget {\n  const DetailScreen({\n    Key? key,\n  }) : super(key: key);\n\n\n  @override\n  Widget build(BuildContext context) {\n    final year = ModalRoute.of(context)!.settings.arguments as int;\n    return ChangeNotifierProvider&lt;DetailNotifier&gt;(\n      create: (context) {\n        final notifier = DetailNotifier(\n          year: year,\n          api: context.read&lt;DataUsaApiClient&gt;(),\n        );\n        notifier.refresh();\n        return notifier;\n      },\n      child: child: ProxyProvider&lt;DetailNotifier, DetailState&gt;(\n        update: (context, value, previous) =&gt; value.value,\n        child: const DetailLayout(),\n      ),\n    );\n  }\n}\n\n\nclass DetailLayout extends StatelessWidget {\n  const DetailLayout({\n    Key? key,\n  }) : super(key: key);\n\n\n  @override\n  Widget build(BuildContext context) {\n    return Consumer&lt;DetailState&gt;(\n      builder: (context, state, child) {\n        return Scaffold(\n          appBar: AppBar(\n            title: Text('Year ${state.year}'),\n          ),\n          body: () {\n              \/\/ ...\n          }(),\n        );\n      },\n    );\n  }<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"22-extracting-widgets-as-dedicated-classes\">Extracting widgets as dedicated classes<\/h4>\n\n\n\n<p>Never hesitate to extract a widget tree into a dedicated class! It will <a href=\"https:\/\/clickup.com\/blog\/performance-improvement-plan-templates\/\">improve performance<\/a>, and make the code more maintainable.<\/p>\n\n\n\n<p>In our example we created one visual layout widget for each one of the associated state types:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (state is NotLoadedDetailState || state is LoadingDetailState) {\n    return const LoadingDetailLayout();\n}\nif (state is LoadedDetailState) {\n    return LoadedDetailLayout(state: state);\n}\nif (state is UnknownErrorDetailState) {\n    return UnknownErrorDetailLayout(state: state);\n}\nreturn const NoDataDetailLayout();<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"23-demo-instances\">Demo instances<\/h4>\n\n\n\n<p>Now we have full control over what we can mock and display onto our screen!<\/p>\n\n\n\n<p>We just have to wrap a&nbsp;<code>DetailLayout<\/code>&nbsp;with a&nbsp;<code>Provider&lt;DetailState&gt;<\/code>&nbsp;to simulate the state of the layout.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ListTile(\n    title: const Text('Open \"loaded\" demo'),\n    onTap: () {\n        Navigator.push(\n        context,\n        MaterialPageRoute(\n            builder: (context) {\n            return Provider&lt;DetailState&gt;.value(\n                    value: const DetailState.loaded(\n                    year: 2022,\n                    measure: Measure(\n                        year: 2022,\n                        population: 425484,\n                        nation: 'United States',\n                    ),\n                    ),\n                    child: const DetailLayout(),\n                );\n            },\n        ),\n        );\n    },\n),\nListTile(\n    title: const Text('Open \"loading\" demo'),\n    onTap: () {\n        Navigator.push(\n        context,\n        MaterialPageRoute(\n            builder: (context) {\n                    return Provider&lt;DetailState&gt;.value(\n                        value: const DetailState.loading(2022),\n                        child: const DetailLayout(),\n                    );\n                },\n            ),\n        );\n    },\n),<\/code><\/pre>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"24-conclusion\">Conclusion<\/h2>\n\n\n\n<p>Creating a maintainable software architecture definitively isn&#8217;t easy! Anticipating future scenarios can demand a lot of effort, but I hope the few tips I shared will help you in the future! <\/p>\n\n\n\n<p>The examples may look simple\u2014it might even seem like we&#8217;re over-engineering\u2014but as your app&#8217;s complexity grows, having those standards will help you a lot! \ud83d\udcaa<\/p>\n\n\n\n<p>Have fun with Flutter, and follow the blog to get more technical articles like this one! Stay tuned!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I had to implement onboarding walkthroughs for ClickUp newcomers! This was a really important task because a lot of new users were about to discover the platform with the incredibly funny ad we premiered at the Super Bowl! \u2728 The walkthrough allows our numerous new users, who maybe don&#8217;t know ClickUp yet, to quickly [&hellip;]<\/p>\n","protected":false},"author":63,"featured_media":34593,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"cu_sticky_sidebar_cta_is_visible":true,"cu_sticky_sidebar_cta_title":"Start using ClickUp today","cu_sticky_sidebar_cta_bullet_1":"Manage all your work in one place","cu_sticky_sidebar_cta_bullet_2":"Collaborate with your team","cu_sticky_sidebar_cta_bullet_3":"Use ClickUp for FREE\u2014forever","cu_sticky_sidebar_cta_button_text":"Get Started","cu_sticky_sidebar_cta_button_link":"","_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[909],"tags":[],"class_list":["post-34589","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering"],"featured_image_src":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","author_info":{"display_name":"Alo\u00efs Deniel","author_link":"https:\/\/clickup.com\/blog\/author\/alois\/"},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Separation of Concerns in Flutter Applications | The ClickUp Blog<\/title>\n<meta name=\"description\" content=\"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Separation of Concerns in Flutter Applications | The ClickUp Blog\" \/>\n<meta property=\"og:description\" content=\"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/\" \/>\n<meta property=\"og:site_name\" content=\"The ClickUp Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/clickupprojectmanagement\" \/>\n<meta property=\"article:published_time\" content=\"2024-10-19T00:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-10-23T14:55:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1400\" \/>\n\t<meta property=\"og:image:height\" content=\"1050\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Alo\u00efs Deniel\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@clickup\" \/>\n<meta name=\"twitter:site\" content=\"@clickup\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Alo\u00efs Deniel\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/\"},\"author\":{\"name\":\"Alo\u00efs Deniel\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#\\\/schema\\\/person\\\/fcd40f8e9f4945441a66c6b6fa5b5736\"},\"headline\":\"Separation of Concerns in Flutter Applications\",\"datePublished\":\"2024-10-19T00:00:00+00:00\",\"dateModified\":\"2024-10-23T14:55:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/\"},\"wordCount\":1983,\"publisher\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/03\\\/separation-of-concerns-in-flutter-applications-2.png\",\"articleSection\":[\"Engineering at ClickUp\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/\",\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/\",\"name\":\"Separation of Concerns in Flutter Applications | The ClickUp Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/03\\\/separation-of-concerns-in-flutter-applications-2.png\",\"datePublished\":\"2024-10-19T00:00:00+00:00\",\"dateModified\":\"2024-10-23T14:55:04+00:00\",\"description\":\"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#primaryimage\",\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/03\\\/separation-of-concerns-in-flutter-applications-2.png\",\"contentUrl\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2022\\\/03\\\/separation-of-concerns-in-flutter-applications-2.png\",\"width\":1400,\"height\":1050},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/separation-of-concerns-in-flutter-applications\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/clickup.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Engineering at ClickUp\",\"item\":\"https:\\\/\\\/clickup.com\\\/blog\\\/engineering\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Separation of Concerns in Flutter Applications\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/\",\"name\":\"The ClickUp Blog\",\"description\":\"The ClickUp Blog\",\"publisher\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/clickup.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#organization\",\"name\":\"ClickUp\",\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/logo-v3-clickup-light.jpg\",\"contentUrl\":\"https:\\\/\\\/clickup.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/logo-v3-clickup-light.jpg\",\"width\":503,\"height\":125,\"caption\":\"ClickUp\"},\"image\":{\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/clickupprojectmanagement\",\"https:\\\/\\\/x.com\\\/clickup\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/clickup-app\",\"https:\\\/\\\/en.wikipedia.org\\\/wiki\\\/ClickUp\",\"https:\\\/\\\/tiktok.com\\\/@clickup\",\"https:\\\/\\\/instagram.com\\\/clickup\",\"https:\\\/\\\/www.youtube.com\\\/@ClickUpProductivity\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/clickup.com\\\/blog\\\/#\\\/schema\\\/person\\\/fcd40f8e9f4945441a66c6b6fa5b5736\",\"name\":\"Alo\u00efs Deniel\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g\",\"caption\":\"Alo\u00efs Deniel\"},\"url\":\"https:\\\/\\\/clickup.com\\\/blog\\\/author\\\/alois\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Separation of Concerns in Flutter Applications | The ClickUp Blog","description":"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/","og_locale":"en_US","og_type":"article","og_title":"Separation of Concerns in Flutter Applications | The ClickUp Blog","og_description":"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.","og_url":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/","og_site_name":"The ClickUp Blog","article_publisher":"https:\/\/www.facebook.com\/clickupprojectmanagement","article_published_time":"2024-10-19T00:00:00+00:00","article_modified_time":"2024-10-23T14:55:04+00:00","og_image":[{"width":1400,"height":1050,"url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","type":"image\/png"}],"author":"Alo\u00efs Deniel","twitter_card":"summary_large_image","twitter_creator":"@clickup","twitter_site":"@clickup","twitter_misc":{"Written by":"Alo\u00efs Deniel","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#article","isPartOf":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/"},"author":{"name":"Alo\u00efs Deniel","@id":"https:\/\/clickup.com\/blog\/#\/schema\/person\/fcd40f8e9f4945441a66c6b6fa5b5736"},"headline":"Separation of Concerns in Flutter Applications","datePublished":"2024-10-19T00:00:00+00:00","dateModified":"2024-10-23T14:55:04+00:00","mainEntityOfPage":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/"},"wordCount":1983,"publisher":{"@id":"https:\/\/clickup.com\/blog\/#organization"},"image":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#primaryimage"},"thumbnailUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","articleSection":["Engineering at ClickUp"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/","url":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/","name":"Separation of Concerns in Flutter Applications | The ClickUp Blog","isPartOf":{"@id":"https:\/\/clickup.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#primaryimage"},"image":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#primaryimage"},"thumbnailUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","datePublished":"2024-10-19T00:00:00+00:00","dateModified":"2024-10-23T14:55:04+00:00","description":"Techniques ClickUp engineers use to build our app with simple examples to help you create Flutter applications with complex codebases.","breadcrumb":{"@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#primaryimage","url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","contentUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","width":1400,"height":1050},{"@type":"BreadcrumbList","@id":"https:\/\/clickup.com\/blog\/separation-of-concerns-in-flutter-applications\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/clickup.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Engineering at ClickUp","item":"https:\/\/clickup.com\/blog\/engineering\/"},{"@type":"ListItem","position":3,"name":"Separation of Concerns in Flutter Applications"}]},{"@type":"WebSite","@id":"https:\/\/clickup.com\/blog\/#website","url":"https:\/\/clickup.com\/blog\/","name":"The ClickUp Blog","description":"The ClickUp Blog","publisher":{"@id":"https:\/\/clickup.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/clickup.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/clickup.com\/blog\/#organization","name":"ClickUp","url":"https:\/\/clickup.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg","contentUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg","width":503,"height":125,"caption":"ClickUp"},"image":{"@id":"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/clickupprojectmanagement","https:\/\/x.com\/clickup","https:\/\/www.linkedin.com\/company\/clickup-app","https:\/\/en.wikipedia.org\/wiki\/ClickUp","https:\/\/tiktok.com\/@clickup","https:\/\/instagram.com\/clickup","https:\/\/www.youtube.com\/@ClickUpProductivity"]},{"@type":"Person","@id":"https:\/\/clickup.com\/blog\/#\/schema\/person\/fcd40f8e9f4945441a66c6b6fa5b5736","name":"Alo\u00efs Deniel","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8c0ea9f8d0b73248aa6c8f56a771f223d447e248e28c224b5480d204c7607198?s=96&d=retro&r=g","caption":"Alo\u00efs Deniel"},"url":"https:\/\/clickup.com\/blog\/author\/alois\/"}]}},"reading":["12"],"keywords":[["Engineering at ClickUp","engineering",909]],"redirect_params":"","is_translated":"true","author_data":{"name":"Alo\u00efs Deniel","link":"https:\/\/clickup.com\/blog\/author\/alois\/","image":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/Aloi\u0308s-Deniel-headshot.png","position":"Flutter Developer"},"category_data":{"name":"Engineering at ClickUp","slug":"engineering","term_id":909,"url":"https:\/\/clickup.com\/blog\/engineering\/"},"hero_data":{"media_url":"","media_alt_text":"","button":"","template_id":"","youtube_thumbnail_url":"","custom_button_text":"","custom_button_url":""},"featured_media_data":{"id":34593,"url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/03\/separation-of-concerns-in-flutter-applications-2.png","alt":"","mime_type":"image\/png","is_webm":false},"_links":{"self":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/34589","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/users\/63"}],"replies":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/comments?post=34589"}],"version-history":[{"count":14,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/34589\/revisions"}],"predecessor-version":[{"id":229318,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/34589\/revisions\/229318"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/media\/34593"}],"wp:attachment":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/media?parent=34589"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/categories?post=34589"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/tags?post=34589"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}