<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: C💙demagic</title>
    <description>The latest articles on Forem by C💙demagic (@codemagicio).</description>
    <link>https://forem.com/codemagicio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F443681%2Faefe0e9f-392d-4abf-84ac-cc9d365228b0.jpg</url>
      <title>Forem: C💙demagic</title>
      <link>https://forem.com/codemagicio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/codemagicio"/>
    <language>en</language>
    <item>
      <title>Java Heap Space error and how Codemagic helps to remediate it</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Thu, 12 Sep 2024 11:15:42 +0000</pubDate>
      <link>https://forem.com/codemagicio/java-heap-space-error-and-how-codemagic-helps-to-remediate-it-24k3</link>
      <guid>https://forem.com/codemagicio/java-heap-space-error-and-how-codemagic-helps-to-remediate-it-24k3</guid>
      <description>&lt;p&gt;Overcoming issues related to Java Heap Space while building Android projects can be quite challenging and frustrating. Understanding the concept of heap memory in Java is crucial, as well as being aware of the available solutions.&lt;/p&gt;

&lt;p&gt;This article will provide a brief explanation of the reasons behind the occurrence of the Java Heap Space issue and present Codemagic’s recommended solutions for addressing it.&lt;/p&gt;

&lt;p&gt;I recently added some gradle tasks to my Android project, which stuffed my project with memory-hungry gradle tasks, e.g. &lt;strong&gt;DexGuard&lt;/strong&gt; and &lt;strong&gt;ProGuard&lt;/strong&gt;, that ended up requiring more heap space memory and the machine fell short of providing it. To put more context into it, while executing tasks, it was either exiting Daemon by shutting it down prematurely or throwing java.lang.OutOfMemoryError :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR:: R8: java.lang.OutOfMemoryError: Java heap space
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:minifyReleaseWithR8'.
&amp;gt; com.android.tools.r8.CompilationFailedException: Compilation failed to complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the project’s construction, I found it unexpected that running DexGuard demanded a significantly larger amount of memory than what some machines could manage. This was already impacting the release pipelines that had to be tested and deployed to the Google Play Store.&lt;/p&gt;

&lt;p&gt;The limitation does not specifically apply only to &lt;strong&gt;DexGuard&lt;/strong&gt; or &lt;strong&gt;ProGuard&lt;/strong&gt;, but any other gradle tasks might restrict build pipelines by requiring more memory. There are some suggestions out there to get the issue resolved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Consider updating the &lt;strong&gt;gradle-wrapper.properties&lt;/strong&gt; file to the latest version of Gradle and the &lt;strong&gt;android/build.gradle&lt;/strong&gt; file to the latest version of the Android Gradle plugin as this may resolve the issue. However, working with their Java version compatibility can be tricky by keeping in mind that projects do not support the latest versions of Gradle and AGP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increasing the &lt;strong&gt;-Xmx&lt;/strong&gt; heap size without confirmation of sufficient available memory from the machine is uncertain. If the assigned heap size exceeds the machine’s capacity, the value will be disregarded.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Codemagic to the rescue
&lt;/h2&gt;

&lt;p&gt;Codemagic as a CI/CD platform did the team a huge favour by speeding up workflow pipelines along with automatically conducting tests, creating builds, and releasing team apps on the Play Store, on top of addressing the Java Heap Space issue. Codemagic’s robust machines have made building, testing, and publishing not just Android but iOS applications incredibly easy. Struggles with not only the Java Heap Space error but also other related issues can be relieved through the powerful machines provided by Codemagic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;macOS with Apple M2, M2 Pro, Max and Ultra chips&lt;/li&gt;
&lt;li&gt;Linux with arm64 and x64 architectures&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Contact &lt;a href="https://codemagic.io/contact/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if your Android builds are affected by OOM (out of memory) error or similar issues, and you are interested in trying any of these machines with extended specifications.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>100% Flakiness-free UI test automation with Kaspresso and Allure TestOps</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Wed, 22 Nov 2023 08:17:00 +0000</pubDate>
      <link>https://forem.com/codemagicio/100-flakiness-free-ui-test-automation-with-kaspresso-and-allure-testops-mkb</link>
      <guid>https://forem.com/codemagicio/100-flakiness-free-ui-test-automation-with-kaspresso-and-allure-testops-mkb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was written by Nihal Agazade and originally posted to the &lt;a href="https://blog.codemagic.io/100-flakiness-free-ui-test-automation-with-kaspresso-and-alluretestops/"&gt;Codemagic blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;UI testing in mobile app development is a challenging task due to multiple factors such as flaky tests, saving screenshots, printing useful logs and readability of test codes. We will be talking about a new UI testing framework Kaspresso for Android apps and how it is different from other frameworks when it comes down to solving these challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kaspresso and why should it be chosen?
&lt;/h2&gt;

&lt;p&gt;Kaspresso is based on Espresso and Kakao DSL which helps it have a great readability of test codes and comes with many built-in features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kakao DSL based test code readability. For our better understanding, let’s quickly check how Kaspresso Kakao DSL wrappers differ from Espresso:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Espresso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
fun LoginActivity() {
    onView(withId(R.id.login_button))
        .check(ViewAssertions.matches(
               ViewMatchers.withEffectiveVisibility(
                       ViewMatchers.Visibility.VISIBLE)))
    onView(withId(R.id.login_button)).perform(click())
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kaspresso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
fun LoginActivity() {
    MainScreen {
        LoginButton {
            isVisible()
            click()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;100% flaky test protection, thanks to its built-in protection rules.&lt;/li&gt;
&lt;li&gt;Allure Test Ops support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To learn more about what else Kasspresso offers, feel free to check the &lt;a href="https://kasperskylab.github.io/Kaspresso/"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get started with Kaspresso?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;First and foremost, &lt;code&gt;mavenCentral()&lt;/code&gt; needs to be declared inside the root &lt;code&gt;build.gradle&lt;/code&gt;. Make sure that &lt;code&gt;jcenter()&lt;/code&gt; is removed from the project along with the dependencies that require &lt;code&gt;jcenter()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Recent native android projects have it defined inside settings.gradle instead:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open app/build.gradle and integrate Kaspresso into your project by adding the following line:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    androidTestImplementation("com.kaspersky.android-components:kaspresso:1.5.3")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By this, we are good to go with Kaspresso unless Allure Testops needs to be integrated which requires additional steps to be followed, and we will be talking about it in the next section.&lt;/p&gt;

&lt;p&gt;We briefly talked about what features Kaspresso introduces and what makes Kaspresso different from other frameworks. Let’s go through some of the key features for further clarification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Painful flaky tests
&lt;/h2&gt;

&lt;p&gt;Test flakiness is something we can hardly avoid considering that our code constantly changes, adding new screens, new functionalities etc. There are multiple reasons that could be the actual cause of flaky tests, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Real devices or emulators&lt;/li&gt;
&lt;li&gt;Test code&lt;/li&gt;
&lt;li&gt;Production code&lt;/li&gt;
&lt;li&gt;General infrastructure&lt;/li&gt;
&lt;li&gt;Or even instability with the testing framework you are using&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kaspresso has it reduced to the very minimum with its DSL wrappers and Interceptors. Interceptors are built-in and help achieve many useful things, e.g:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add custom actions to each framework operation like writing a log or taking a screenshot;&lt;/li&gt;
&lt;li&gt;overcome flaky operations by re-running failed actions, scrolling the parent layout or closing the android system dialog;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an example, &lt;code&gt;ScreenshotStepWatcherInterceptor&lt;/code&gt; is utilised to take pictures whether tests fail or pass and it is part of &lt;code&gt;TestCase()&lt;/code&gt; constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SimpleLoginActivity : TestCase(
    kaspressoBuilder = Kaspresso.Builder.simple().apply {
        stepWatcherInterceptors.add(ScreenshotStepWatcherInterceptor(screenshots))
    }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TestCase()&lt;/code&gt; is a class inherited from &lt;code&gt;com.kaspersky.kaspresso.testcases.api.testcase.TestCase&lt;/code&gt; which is out of the box ready for use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;Kaspresso has made the process of taking screenshots much easier via its interceptors. It is possible to take snapshots of every screen while tests are executed and save these screenshots under a desired file name through the &lt;code&gt;device.screenshots.take("desired_file_name")&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   @Before
   @Test
   fun test() {
       run {
           step ("Products dropdown is available") {
           device.screenshots.take("screenshot_file_name")
           ProductsScreen {
               testActivityButton {
                   isVisible()
                   isClickable()
               }
             }
           }
       }
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions must be granted for screenshots to be taken by importing &lt;code&gt;androidx.test.rule.GrantPermissionRule&lt;/code&gt;, otherwise there will be no &lt;code&gt;READ_EXTERNAL_STORAGE&lt;/code&gt; nor &lt;code&gt;WRITE_EXTERNAL_STORAGE&lt;/code&gt; in place. So, at the top of the Kotlin Class/File where the kotlin test code is stored, &lt;code&gt;GrantPermissionRuleneeds&lt;/code&gt; needs to be imported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.test.rule.GrantPermissionRule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then, within the same file, &lt;code&gt;@Rule&lt;/code&gt; must be declared which is inherited from &lt;code&gt;org.junit.Rule&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@get:Rule
    val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
        android.Manifest.permission.READ_EXTERNAL_STORAGE,
        android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides these changes, &lt;code&gt;AndroidManifest.xml&lt;/code&gt; needs to be configured for a complete permission cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;manifest&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&amp;gt; 
    &amp;lt;application&amp;gt;
    &amp;lt;/application&amp;gt;
&amp;lt;/manifest&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kaspresso &lt;code&gt;device.screenshots.take&lt;/code&gt; object is similar to &lt;code&gt;device.uiDevice.takeScreenshotfrom&lt;/code&gt; the &lt;code&gt;uiautomator&lt;/code&gt; library, however the key difference would be custom file names that can be easily managed through &lt;code&gt;device.screenshots.take&lt;/code&gt;, especially when there are lots of snapshots and it can be tricky to find the appropriate ones.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: A video mp4 file is saved to &lt;code&gt;sdcard/Documents&lt;/code&gt; along with screenshots and you can find an exact file path in the logs which is explained in the following section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Logging
&lt;/h2&gt;

&lt;p&gt;Custom notes can be added to the logs, and for that purpose &lt;code&gt;testLoggerobject&lt;/code&gt; can be used with the &lt;code&gt;i&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun test() {
       run {
           testLogger.i("Either hardcoded or dynamic values through env vars $VALUE")
           MainScreen {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, there is another challenge at this point and it is about splitting test internals into different steps, so that which tests fail and which ones pass can be observed easier.&lt;/p&gt;

&lt;p&gt;What we can do is to wrap our code in &lt;code&gt;try catch&lt;/code&gt; block along with adding manual logs to the output. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
    fun sampleTest() {
        try {
            Log.i("Step 1:")
            MainScreen {
                loginActivityButton {
                    isVisible()
                }
            }
            Log.i("Step 1: Succeeded")
        } catch (e: Throwable) {
            Log.i("Step 1: Failed")
            throw e
        }
     anotherTest(){
      try {
       ...
       Log.i("Step 1:")
     } catch(e: Throwable){
      Log.i("Step 2: Failed")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works! Though with all the &lt;code&gt;try catch&lt;/code&gt; blocks, the code will lose its readability and potentially, lead to flakiness.&lt;/p&gt;

&lt;p&gt;In the Kaspresso world, Steps can be added in order to simplify code readability and more importantly, by keeping its flaky reduction rules. Steps can be used by declaring the &lt;code&gt;run{}&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun test() {
       run {
           step ("Testing user registration auth") { ... }
           step ("Testing user login auth") { ... }
           ...
       }
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then we can observe our crafted logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YbJVNru3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/neh2lzyx6q7kuncq5xpy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YbJVNru3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/neh2lzyx6q7kuncq5xpy.jpeg" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete automation with Codemagic CI/CD and uploading results to Allure Testops
&lt;/h2&gt;

&lt;p&gt;We can further automate the process and upload test results to Allure Testops.&lt;/p&gt;

&lt;p&gt;Thanks to Codemagic, we can automatically run UI tests, collect logs, and deploy results to Allure Testops on every change or daily.&lt;/p&gt;

&lt;p&gt;Allure Testops is a DevOps ready test reporting platform where we can analyse test results through graphical reports and gathering results from servers in real-time while the build job is being executed. In order to achieve it with Codemagic, we will be utilising &lt;code&gt;allurectl&lt;/code&gt; which is a command line wrapper of Allure Testops API. Allure Testops functions in two modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CI mode&lt;/li&gt;
&lt;li&gt;Non-CI mode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;allurectl&lt;/code&gt; detects automatically which one is in use through finding the required environment variables specified in Codemagic as explained below. So, let’s get the steps together for an easy flow of the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up with Allure Testops.&lt;/li&gt;
&lt;li&gt;Go to your profile, and create a secret token&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xYCuNKMB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6a5xkhppgp5ry014c83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xYCuNKMB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6a5xkhppgp5ry014c83.png" alt="Image description" width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As soon as your request for registration is approved by the Allure Testops team, you will receive an active endpoint to your work environment.&lt;/li&gt;
&lt;li&gt;Create a project and fetch its ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The very vital step not to miss is to enable Allure support in our test code via withForcedAllureSupport. In order to enable it, we need to go through a couple of steps in our code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Import &lt;code&gt;com.kaspersky.components.alluresupport.withForcedAllureSupport&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add withForcedAllureSupportto TestCase() constructor which will look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class YOUR_ACTIVITY_TEST : TestCase(
    kaspressoBuilder = Kaspresso.Builder.withForcedAllureSupport()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, as soon as our tests are executed, there will be a directory created (&lt;code&gt;sdcard/Documents/allure-results&lt;/code&gt;) and they will be pulled out of the device and uploaded to Allure Testops by Codemagic.&lt;/p&gt;

&lt;p&gt;If you have not signed up with Codemagic and yet added your repo, you can follow the steps as explained in the Codemagic documentation &lt;a href="https://docs.codemagic.io/getting-started/adding-apps/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to your repo settings in the UI and you will find the Environment variables tab right next to &lt;code&gt;codemagic.yaml&lt;/code&gt; and add your Allure secrets that we went through how to create them above.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VeKphgAB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fvm8pptcl4abdt5ul3wh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VeKphgAB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fvm8pptcl4abdt5ul3wh.jpeg" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following lines in &lt;code&gt;codemagic.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflows:
  run-android-ui-test:
    name: Upload test results to Allue Testops
    instance_type: linux
    environment:
      groups:
        - allure_credentials
    scripts:
      - name: Set Android SDK location
        script: |
                        echo "sdk.dir=$ANDROID_SDK_ROOT" &amp;gt; "$CM_BUILD_DIR/local.properties"
      - name: Launch Emulator
        script: |
            cd $ANDROID_HOME/tools
            emulator -avd emulator &amp;amp;
            adb wait-for-device            
      - name: Install Allure CLI
        script: | 
            set -e
       wget https://github.com/allure-framework/allurectl/releases/download/2.2.1/allurectl_linux_amd64 -O ./allurectl
            chmod +x ./allurectl
      - name: Execute tests
        script: |
                        ./gradlew :app:connectedDebugAndroidTest
      - name: Pull test files from the device and upload to Allure Testops
        script: |
            set -e
            adb pull sdcard/Documents/allure-results
            ./allurectl upload allure-results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: When installing Allure CLI and building with a Mac machine, the platform must be changed in the command line to &lt;code&gt;allurectl_darwin_amd64&lt;/code&gt; for Mac Pro machines. In order to switch the machine type from Linux to Mac Pro in codemagic.yaml, changing &lt;code&gt;instance_type&lt;/code&gt; value to &lt;code&gt;mac_pro&lt;/code&gt; suffices.&lt;/p&gt;

&lt;p&gt;Note: As the Apple Virtualization Framework does not support nested virtualization, Android emulators are not available with Apple silicon (M1 or M2) machines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From this point on, Codemagic will start its automation process and print out the build/test logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LwBVx0L2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l06rars1zrfx1yyhkhkh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LwBVx0L2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l06rars1zrfx1yyhkhkh.jpeg" alt="Image description" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A link to the Allure Testops environment where the test results can be accessed will be printed in the logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Fluxjsf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi5vhxtkvo24udailix0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Fluxjsf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi5vhxtkvo24udailix0.jpeg" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Worth pointing out that Codemagic has a possibility to schedule builds. It means that your tests will be run at a certain time you set up and you will already have had the test results in hand, and ready for review in Allue Testops. Let’s see how we can schedule a build:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your application in Codemagic.&lt;/li&gt;
&lt;li&gt;Switch to the Scheduled builds tab and click the Add new schedule button.&lt;/li&gt;
&lt;li&gt;Select the Branch and the Workflow to run.&lt;/li&gt;
&lt;li&gt;In the Schedule for field, select the days you want to run the build.&lt;/li&gt;
&lt;li&gt;Specify the start time (UTC) of the build by selecting a value from the At field. Note that the build may be delayed up to 15 minutes during peak hours.&lt;/li&gt;
&lt;li&gt;Click Add schedule to save the schedule.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xmNmC6UG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hg5oely3ei3ol9db4vne.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xmNmC6UG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hg5oely3ei3ol9db4vne.jpeg" alt="Image description" width="800" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking test results in Allure Testops
&lt;/h2&gt;

&lt;p&gt;Now, we can observe screenshots, videos, and customised test logs in the Allure Testops UI along with a graphical report of the test results.&lt;/p&gt;

&lt;p&gt;Here Allure UI indicates how many tests passed, which ones failed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9I9T_9q7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hploue3uw66z86f34yzx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9I9T_9q7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hploue3uw66z86f34yzx.jpeg" alt="Image description" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot below, we will find our passing tests along with the test logs, screenshots, and videos:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d-L3YW8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfcsnb9vfbfwh749l1ai.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d-L3YW8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfcsnb9vfbfwh749l1ai.jpeg" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will also have a chance to check failing test logs and which steps the issues occurred with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XCIfnxWB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqjxl5lztlje6g3fnifc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XCIfnxWB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqjxl5lztlje6g3fnifc.jpeg" alt="Image description" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We have gone through the steps regarding how Kaspresso manages to overcome the key issues of UI testing environments by allowing us to automate Android UI tests with flakiness protection and providing test results to Allure Testops platform to better control our manual, and automated testing through graphical analysis of test results including screenshots, videos, and all the logs. Codemagic took the code over, added an extra layer of automation, and completed the whole cycle of the automation process.&lt;/p&gt;

</description>
      <category>android</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Test Native Features in Flutter Apps with Patrol and Codemagic</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Sun, 05 Nov 2023 13:03:36 +0000</pubDate>
      <link>https://forem.com/codemagicio/how-to-test-native-features-in-flutter-apps-with-patrol-and-codemagic-17ke</link>
      <guid>https://forem.com/codemagicio/how-to-test-native-features-in-flutter-apps-with-patrol-and-codemagic-17ke</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was written by Kevin Suhajda and originally posted to the &lt;a href="https://blog.codemagic.io/how-to-test-native-features-in-flutter-apps-with-patrol-and-codemagic/" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"It's no use! I can't run an end-to-end test with Flutter's integration tests", exclaimed one of our customers about 9 months ago. I asked what the problem was and they explained that they were using Google Authentication for logging in and used the &lt;strong&gt;google_sign_in&lt;/strong&gt; package but it wasn't possible to use Flutter's integration tests to interact with the login screens. I still didn't quite understand what the problem was, and then it clicked: this plugin uses &lt;strong&gt;native UI components&lt;/strong&gt; which the integration tests don't work with.&lt;/p&gt;

&lt;p&gt;I was quite disappointed that I couldn't offer a solution at the time and had to leave it at that. However, fast forward to today and an awesome new solution has presented itself called &lt;strong&gt;"Patrol"&lt;/strong&gt; by &lt;a href="https://leancode.co/" rel="noopener noreferrer"&gt;LeanCode&lt;/a&gt;. I'm going to tell you all about it, but before we dive in let's just recap why it's important to run tests, what tools you had available until now, and then talk about how to get started with Patrol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why test Flutter apps anyway?
&lt;/h2&gt;

&lt;p&gt;One of the main reasons development teams use &lt;strong&gt;Continuous Integration&lt;/strong&gt; (CI) services like &lt;a href="https://codemagic.io/" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is to get immediate feedback on the code they are committing to their repo. Tests can be run automatically as part of your pipeline to ensure a level of both quality and stability because bugs or issues can be caught early and rectified straight away. We always encourage customers to implement testing when setting up their workflows, but it's not uncommon to hear "We haven't got time to write tests". Hopefully, you're not in that boat, and use testing as part of your app development cycle to deliver great quality and bug-free apps!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the main automated ways to test Flutter apps?
&lt;/h2&gt;

&lt;p&gt;There are four main testing methods that can be automated as part of your CI workflow. Testing is a topic unto itself, so I'll keep it brief, but using a combination of these testing methods will help you improve the quality of your app and catch problems sooner rather than later.&lt;/p&gt;

&lt;p&gt;Firstly, there is &lt;strong&gt;"Unit testing"&lt;/strong&gt; which is commonly used for testing your functions and methods in isolation to make sure they work as expected. Unit tests can also be written to make sure your business logic works in different scenarios without any unexpected results.&lt;/p&gt;

&lt;p&gt;Next, we have Flutter &lt;strong&gt;"Widget tests"&lt;/strong&gt; which allows you to test your UI components and make sure they render correctly and work as you expect.&lt;/p&gt;

&lt;p&gt;Then there is &lt;strong&gt;"Integration testing"&lt;/strong&gt; which is where you test if the units and components of your application work together as expected.&lt;/p&gt;

&lt;p&gt;Finally, there is &lt;strong&gt;"End-to-End UI testing"&lt;/strong&gt; where you test the application as if it were being used by a real user. In a CI workflow, this is usually automated using simulators or emulators to test different pathways through your app to make sure there aren't any issues after you make changes to your code.&lt;/p&gt;

&lt;p&gt;This is where the customer I was talking about at the beginning was stuck because they couldn't run their End-to-End UI tests because it wasn't possible to log in to the app. At the time they tested a 'dev' version that bypassed the log-in part. &lt;/p&gt;

&lt;p&gt;However, that's no longer required now that &lt;strong&gt;"Patrol"&lt;/strong&gt; is available!&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter, stage left, Patrol!
&lt;/h2&gt;

&lt;p&gt;So first of all, what is Patrol? Well, I think the docs say it best:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Patrol is a new and open-source UI testing framework for Flutter developed by LeanCode. It builds on top of Flutter's existing test tooling to let you do things which were previously impossible. Patrol lets you access native features of the platform that the Flutter app is running on.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most important part here is that it lets you access the &lt;strong&gt;native features&lt;/strong&gt; of the platform your &lt;strong&gt;Flutter&lt;/strong&gt; app is running on.&lt;/p&gt;

&lt;p&gt;This means that you can now do things such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interact with Permission Request dialogs to dismiss or accept requests.&lt;/li&gt;
&lt;li&gt;Interact with WebViews.&lt;/li&gt;
&lt;li&gt;Minimize and maximize your app.&lt;/li&gt;
&lt;li&gt;Interact with authentication flows like Google or Apple authentication.&lt;/li&gt;
&lt;li&gt;Interact with other native features such as opening the Notifications tray, pressing the Home button, turning Wi-Fi connectivity on or off, or changing the device to dark mode, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Okay, this sounds great, but what's the catch?&lt;/p&gt;

&lt;p&gt;Well, there isn't one! And what's more, it's not only &lt;strong&gt;free&lt;/strong&gt; but also &lt;strong&gt;&lt;a href="https://github.com/leancodepl/patrol" rel="noopener noreferrer"&gt;open source&lt;/a&gt;&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Moreover, Patrol also introduces &lt;strong&gt;'custom finders'&lt;/strong&gt; which gives you a more concise syntax for writing your tests. You can read more about them &lt;a href="https://patrol.leancode.co/patrol/finders/overview" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and setting up Patrol
&lt;/h2&gt;

&lt;p&gt;To get started with Patrol you will need to install the CLI, add the &lt;strong&gt;Patrol dependency&lt;/strong&gt; to your &lt;strong&gt;pubspec.yaml&lt;/strong&gt; and set up some configurations in your iOS and Android projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LeanCode&lt;/strong&gt; has created some great documentation &lt;a href="https://patrol.leancode.co/getting-started" rel="noopener noreferrer"&gt;here&lt;/a&gt; which takes you through the process for each platform which you can find here. Their step-by-step guide will take you through the setup for both &lt;strong&gt;iOS&lt;/strong&gt; and &lt;strong&gt;Android&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you run into any problems, the best place to get help is in the &lt;strong&gt;Patrol Community Discord&lt;/strong&gt; server which you can join &lt;a href="https://discord.gg/GGFQMsTM" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you find any bugs, then you can raise an issue &lt;a href="https://github.com/leancodepl/patrol/issues" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing native feature tests with Patrol
&lt;/h2&gt;

&lt;p&gt;Now you are all set up, let's start testing some native features. To try it myself, I set up a simple Flutter app with an elevated button that when clicked opens a &lt;strong&gt;native&lt;/strong&gt; alert dialog.&lt;/p&gt;

&lt;p&gt;Clicking "OK" or "Cancel" simply dismisses the dialog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupbhyj1ckp0tgf31h29w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupbhyj1ckp0tgf31h29w.png" alt="Codemagic Patrol test app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, I would recommend using Patrol's own documentation which you can find &lt;a href="https://patrol.leancode.co/native/overview" rel="noopener noreferrer"&gt;here&lt;/a&gt; to get your first test file added.&lt;/p&gt;

&lt;p&gt;So for my test, I wanted to click on the elevated button that has the text "Click me!". It's a standard Flutter widget so it can be tapped using the following Patrol finder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native dialog should then be displayed, so we can now start interacting with a native UI component. So let's add the native finder that will allow us to tap the "OK" button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was easy! I also want to test the "Cancel" button, so let's tap on the "Click me!" button again and then tap on the native dialog's "Cancel" button by adding a couple more lines as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'Cancel'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your completed test file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:cmpatrol/main.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:patrol/patrol.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;patrolTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'Native tests'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;nativeAutomation:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pumpWidgetAndSettle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'Cancel'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Click me!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'NO'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to run that test on your emulator or a real device using the command to launch the test. My integration test file was called "button_test" so I started the tests from Terminal as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;patrol &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; integration_test/button_test.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see if your tests pass or fail directly in the Terminal. If the tests fail you will get a link to the full test report. Alternatively, If you are running your tests on Android like I did then this should you can access the report by clicking the &lt;strong&gt;index.html&lt;/strong&gt; in the following directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build/app/reports/androidTest/connected&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxnjjwn5r9hycooz18tq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxnjjwn5r9hycooz18tq.png" alt="Gradle index.html"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can experiment further with other native features such as opening the notifications tray, disabling the wifi, enabling dark mode, minimizing and maximizing the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// minimize app&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pressHome&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openNotifications&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disableWifi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enableDarkMode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// maximize app&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Note that it is not possible to completely close your app and then re-open it because doing so would end the whole test and, therefore, make it fail.&lt;/p&gt;

&lt;p&gt;Consult the Patrol &lt;a href="https://patrol.leancode.co/native/overview" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Patrol in your Codemagic workflows
&lt;/h2&gt;

&lt;p&gt;To incorporate Patrol into your workflows, you'll first have to install the &lt;strong&gt;Patrol CLI&lt;/strong&gt; on the build machine. This only takes a few seconds and once that's done, you can run your test script. Below is an example of how you would add these steps to the "scripts" section of your &lt;strong&gt;codemagic.yaml&lt;/strong&gt; configuration file. I would recommend running the script to install the Patrol CLI as one of the first script steps and then you can run your Patrol tests either immediately after that or after any other tests you might also want to run beforehand.&lt;/p&gt;

&lt;p&gt;Before running your Patrol tests you will need to start the emulator so we'll add a script to launch the emulator and wait for it to be fully booted. Note that the Android emulators are not available on machines using Apple Silicon M1 or M2 machines due to the Apple Virtualization Framework not supporting nested virtualization. Therefore, I would recommend using a &lt;strong&gt;linux&lt;/strong&gt; instance when testing Android apps.&lt;/p&gt;

&lt;p&gt;The scripts section of your &lt;strong&gt;codemagic.yaml&lt;/strong&gt; should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Patrol CLI&lt;/span&gt;
    &lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart pub global activate patrol_cli&lt;/span&gt;
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Launch Android emulator&lt;/span&gt;
    &lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;cd $ANDROID_HOME/tools&lt;/span&gt;
      &lt;span class="s"&gt;emulator -avd emulator &amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;adb wait-for-device&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests with Patrol&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;patrol test -t integration_test/your_test.dart&lt;/span&gt;
    &lt;span class="na"&gt;ignore_failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Showing Patrol test results in the Codemagic build logs
&lt;/h2&gt;

&lt;p&gt;The Patrol test results are also available in &lt;strong&gt;JUnit XML&lt;/strong&gt; format, which means they can be displayed in the build logs on the Codemagic build overview screen. You just need to add the &lt;strong&gt;test_report&lt;/strong&gt; property pass in the path to the JUnit XML file that is generated. You can use the &lt;strong&gt;ignore_failure&lt;/strong&gt; property with a boolean to control whether you want the rest of the workflow to continue running or not. If you want to upload your results to a test management system as described in the next section, you should set this to &lt;strong&gt;true&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's an example of what your script should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests with Patrol&lt;/span&gt;
    &lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;patrol test -t integration_test/your_test.dart&lt;/span&gt;
      &lt;span class="s"&gt;test_report: build/app/outputs/androidTest-results/connected/*.xml&lt;/span&gt;
      &lt;span class="s"&gt;ignore_failure: true&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A failing test might look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvt3niybnvvybl92shta3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvt3niybnvvybl92shta3.png" alt="A failing Patrol test in the Codemagic build log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gathering the Patrol test report as a build artifact
&lt;/h2&gt;

&lt;p&gt;One additional thing you might want to do is gather the test report output as a build artifact so you can view the full report should any errors occur. Doing this will make the report available for download as a zip file on the build overview screen in the "Artifacts" section on the left-hand side. The easiest way to do this is to copy the directory the report files are in to the directory Codemagic uses to export artifacts. There's a built-in environment variable called &lt;strong&gt;$CM_EXPORT_DIR&lt;/strong&gt; that references this directory which you can use in your script.&lt;/p&gt;

&lt;p&gt;The script to do this should be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt; 
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export Patrol test report&lt;/span&gt;
    &lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
      &lt;span class="s"&gt;cp  -r build/app/reports/androidTests/connected $CM_EXPORT_DIR/report&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Patrol&lt;/strong&gt; has finally overcome the problem of running UI and integration tests that involve native features. It's now possible to test native features and interact with authentication flows, native dialogs, and even toggle native features like wifi, cellular, dark mode, and even minimize your app and maximize it. Additionally, it's both &lt;strong&gt;free&lt;/strong&gt; and &lt;strong&gt;open source&lt;/strong&gt; and provides a solution to a real problem that's been around since Flutter was launched. What's more, it's straightforward to add and use it in your Codemagic workflows. If you want to support the great work LeanCode is doing, give Patrol a like on pub.dev &lt;a href="https://pub.dev/packages/patrol" rel="noopener noreferrer"&gt;here&lt;/a&gt; and give the Patrol GitHub repository a star &lt;a href="https://github.com/leancodepl/patrol" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;This article is written by Kevin Suhajda, Head of Solutions Engineering at &lt;a href="https://codemagic.io" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt;. You can find Kevin on &lt;a href="https://x.com/kevsuda" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://github.com/kevin-suhajda" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and &lt;a href="https://linkedin.com/in/kevinsuhajda" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Introducing the official Codemagic integration for Runway</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Tue, 31 Jan 2023 13:15:23 +0000</pubDate>
      <link>https://forem.com/codemagicio/introducing-the-official-codemagic-integration-for-runway-1ed4</link>
      <guid>https://forem.com/codemagicio/introducing-the-official-codemagic-integration-for-runway-1ed4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is written by Gabriel Savit and originally posted to &lt;a href="https://blog.codemagic.io/codemagic-runway-integration/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://codemagic.io/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is now officially integrated with &lt;a href="https://www.runway.team/" rel="noopener noreferrer"&gt;Runway&lt;/a&gt;, the DevOps platform for mobile. But wait, you might be wondering, isn't Codemagic already that as a CI/CD for mobile? Aren't the two services interchangeable? In fact, it's quite the opposite --- they're powerfully complementary.&lt;/p&gt;

&lt;p&gt;By pairing the flexibility and ease that Codemagic brings to your build pipeline with Runway's ability to pull the rest of your toolchain and team into a unified control center, you'll be set up for success as you scale your mobile practice. Let's take a look at how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codemagic: An easy way to automate building and distribution
&lt;/h2&gt;

&lt;p&gt;Say you have an app and you're tired of manually building, code signing, and releasing it. Naturally, you look for a CI/CD solution and end up building a Codemagic pipeline that effortlessly churns out signed builds of your app across any and all platforms you support -- Android, iOS, macOS, Windows, etc.&lt;/p&gt;

&lt;p&gt;Maybe you have different flavors configured, from dev builds to poke at while you work on new features to release candidates that your team can run regression on and prepare for release. You may even be taking advantage of Codemagic features that allow you to not only build and sign your artifacts but even distribute new builds automatically to &lt;a href="https://blog.codemagic.io/apple-developer-integration-codemagic/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;App Store Connect&lt;/a&gt; and &lt;a href="https://blog.codemagic.io/the-simple-guide-to-android-code-signing/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Play Console&lt;/a&gt;. You can literally release app updates to users with a single click --- CI/CD at its finest!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're new to CI/CD, check out &lt;a href="https://blog.codemagic.io/the-complete-guide-to-ci-cd/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;this guide on getting started with CI/CD for mobile&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now imagine you're on a team of more than one person. Can you ship updates with a click of a button? You have other engineers around you who might need to finalize their work for the release. You have QA engineers who need to complete testing and sign off on a final RC. OK, that's still possible with Codemagic and some good practices around working with pull requests, but it's already become more complicated.&lt;/p&gt;

&lt;p&gt;Add to the mix PMs who need to plan ahead and understand what will be shipping and when. You have customer service and ops folks who need to know about new versions before they start receiving a flood of feedback from users about it. You have security and compliance reviewers who need to apply their rubber stamp. You have managers and other stakeholders who care about what you're up to and need to weigh in. The list goes on. As your team and product grow, the number of parties involved --- and the number of steps from build to release (and post-release) --- increase. And that's where Runway comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runway: A home to get your entire mobile org on the same page
&lt;/h2&gt;

&lt;p&gt;Runway integrates with your build pipeline (Codemagic, in this case) and the rest of your toolchain to give your entire mobile org a home. By keeping everyone --- and we mean &lt;em&gt;all&lt;/em&gt; stakeholders, not only the technical ones --- on the same page and automating all sorts of tasks that exist outside the realm of CI, Runway ensures your releases stay as streamlined as possible, even as your team and product grow. Instead of bouncing between your Codemagic dashboard and 10 other browser tabs and endlessly pinging people back and forth over Slack, everyone can leverage Runway to see exactly where things stand during your release cycles and collaborate on releases together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafp2tm00q72gnjv67qqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafp2tm00q72gnjv67qqa.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For maximum visibility across the team, Runway surfaces build data alongside info on feature work from version control and project management tools, stability and health metrics from crash reporting and product analytics, and even info on the state of your feature flags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2F_hu0f2192366be52c499d52e8f8282ee20e_0_7cdf33094139fb9ffee64b6c52a635f2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2F_hu0f2192366be52c499d52e8f8282ee20e_0_7cdf33094139fb9ffee64b6c52a635f2.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Runway allows you to release hands-free from kickoff to submission to release --- but not without the input and signoffs needed from various team members. Combine scheduled automations for branch cuts, version bumps, rollouts, and much more with a robust and customizable system of checklist items and approval gates to ensure you don't sacrifice quality even as you move to a level of automation you haven't been able to achieve before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-kickoff-automations_11834165608607617729_hu0b0ba0e349b59e938ad28536e4a0b094_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-kickoff-automations_11834165608607617729_hu0b0ba0e349b59e938ad28536e4a0b094_0_1280x1800_fit_linear_3.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Runway's deep integrations with App Store Connect and Play Console allow for a saner --- and safer --- publishing experience. Instead of constant roundtrips between engineers, PMs, and marketing folks just to get store copy updated, you can give responsible parties scoped access within Runway to update and validate metadata. For cross-platform teams, this is an especially big win --- similar to how keeping your different platforms' build pipelines nicely consolidated in Codemagic reduces overhead, Runway's unified and platform-agnostic treatment of the stores will save your team context switching and make things more approachable for non-technical folks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-metadata-screenshots_5537230177584822270_hu0276bde0a4f7072b5a588da92ba0dab5_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-metadata-screenshots_5537230177584822270_hu0276bde0a4f7072b5a588da92ba0dab5_0_1280x1800_fit_linear_3.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your team's job isn't over once a build lands in the store: Running a successful rollout is a demanding and critical part of the process, and Runway helps with this stage too. With disparate signals of health pulled together from across your stack --- including crash reporting, product analytics, and even user reviews --- your team gets a single source of truth to refer to when making the call to halt or continue a rollout. You can configure granular thresholds that define what "healthy" looks like for your team, then tie those to alerting or even "halt" and "accelerate" automations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-rollout_17719299888298052217_hu41bf313fd47693019c5f20957a1b090f_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-rollout_17719299888298052217_hu41bf313fd47693019c5f20957a1b090f_0_1280x1800_fit_linear_3.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to integrate Codemagic with Runway
&lt;/h2&gt;

&lt;p&gt;It takes minutes to &lt;a href="https://docs.runway.team/integrations/ci-cd/codemagic" rel="noopener noreferrer"&gt;integrate Codemagic&lt;/a&gt; with Runway. After heading to &lt;a href="https://www.runway.team/" rel="noopener noreferrer"&gt;runway.team&lt;/a&gt; to sign up for free, you'll be guided through a simple onboarding flow. When you're on the CI/CD integration step, select the Codemagic option and drop your Personal Access Token in there. (To generate a token, in your Codemagic account, navigate to Teams &amp;gt; Personal Account &amp;gt; Integrations.) Runway is SOC 2 Type 2 certified --- your secrets are &lt;a href="https://www.runway.team/security" rel="noopener noreferrer"&gt;safe with them&lt;/a&gt;! You'll be prompted to select the Codemagic workflows that correspond to your different flavors of builds (where applicable: dev builds, RCs, and release builds), and that's it! You're ready to streamline and democratize releases and rollouts with the combined power of Codemagic and Runway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-onboarding_9203359614984763908_hue7af27ce5743de3e0f49a9f0d8fb13ce_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-onboarding_9203359614984763908_hue7af27ce5743de3e0f49a9f0d8fb13ce_0_1280x1800_fit_linear_3.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once Runway and Codemagic are integrated, you get to decide how you want them to work together. You can continue triggering builds from Codemagic or take advantage of Runway's scheduled release automations to kick off timed RCs for you. Alternatively, you can trigger builds in Runway manually along with extra context from the rest of your toolchain. Everything to do with building artifacts stays in Codemagic, and you don't have to touch anything in your workflows; Runway will plug and play with your current processes and practices.&lt;/p&gt;

&lt;p&gt;However streamlined and powerful your Codemagic pipelines are, you may find the dream of one-click deployment slipping away as your team and product grow and your overall release process gets more complex. By integrating Codemagic in Runway, you can get everyone back on the same page and keep shipping smoothly.&lt;/p&gt;

&lt;p&gt;And there's more good news: Codemagic and Runway are both free to start with. So, if you haven't done so already, &lt;a href="https://codemagic.io/signup/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;sign up for Codemagic&lt;/a&gt; and head to &lt;a href="https://www.runway.team/" rel="noopener noreferrer"&gt;runway.team&lt;/a&gt; to get started for free -- and use both services to create a fully integrated DevOps home for your apps.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Creating WebView Content Blockers with Flutter InAppWebView</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Mon, 09 Jan 2023 11:32:10 +0000</pubDate>
      <link>https://forem.com/codemagicio/creating-webview-content-blockers-with-flutter-inappwebview-3695</link>
      <guid>https://forem.com/codemagicio/creating-webview-content-blockers-with-flutter-inappwebview-3695</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Written by Lorenzo Pichilli and originally posted to &lt;a href="https://blog.codemagic.io/creating-content-blockers-with-flutter-inappwebview/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we are going to learn how to create a custom Content Blocker for our WebView instances using the &lt;a href="https://github.com/pichillilorenzo/flutter_inappwebview" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_inappwebview&lt;/code&gt;&lt;/a&gt; plugin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read &lt;a href="https://blog.codemagic.io/inappwebview-the-real-power-of-webviews-in-flutter/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;this article to get started with Flutter InAppWebView&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Content Blockers are usually used for blocking ads, but you can also use them to block any other content. Blocking behaviors include hiding elements, blocking loads, and, on iOS and macOS, stripping cookies from WebView requests.&lt;/p&gt;

&lt;p&gt;Keep in mind that, in general, Content Blockers cannot achieve the same level of functionality as specialized extensions such as AdBlock or AdBlock Plus. Content Blockers are a set of rules that never receive any callbacks or notifications back from the WebView when it finds content it needs to block.&lt;/p&gt;

&lt;p&gt;Through the &lt;code&gt;contentBlockers&lt;/code&gt; property of the &lt;code&gt;InAppWebViewSettings&lt;/code&gt; class, we can define a list of &lt;code&gt;ContentBlocker&lt;/code&gt; instances that the WebView will use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ContentBlocker class
&lt;/h2&gt;

&lt;p&gt;We define content-blocking behavior in the &lt;code&gt;ContentBlocker&lt;/code&gt; class. Each one contains an action property and a trigger property. The action tells the WebView what to do when it encounters a match for the trigger. The trigger tells the WebView when to perform the corresponding action.&lt;/p&gt;

&lt;p&gt;Here is a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initialSettings: InAppWebViewSettings(contentBlockers: [
 ContentBlocker(
   trigger: ContentBlockerTrigger(
     urlFilter: ".*",
     resourceType: [
       ContentBlockerTriggerResourceType.IMAGE,
       ContentBlockerTriggerResourceType.STYLE_SHEET
     ]
   ),
   action: ContentBlockerAction(
     type: ContentBlockerActionType.BLOCK
   )
 )
]),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the ContentBlocker blocks the loading of every image and stylesheet for every URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add triggers to your Content Blocker
&lt;/h2&gt;

&lt;p&gt;A trigger must define the required &lt;code&gt;urlFilter&lt;/code&gt; property, which specifies a regular expression as a string to match the URL against. The other properties are optional --- they modify the behavior of the trigger. For example, you can limit the trigger to specific domains or have it not apply when the WebView finds a match for a specific domain.&lt;/p&gt;

&lt;p&gt;Here is an example of a Content Blocker with a trigger for image and style sheet resources that the WebView finds on any domain except those specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initialSettings: InAppWebViewSettings(contentBlockers: [
 ContentBlocker(
   trigger: ContentBlockerTrigger(
     urlFilter: ".*",
     resourceType: [
       ContentBlockerTriggerResourceType.IMAGE,
       ContentBlockerTriggerResourceType.STYLE_SHEET
     ],
     unlessDomain: ["example.com", "github.com", "pub.dev"]
   ),
   action: ContentBlockerAction(
     type: ContentBlockerActionType.BLOCK
   )
 )
]),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deeper trigger customization, you can use the other properties of &lt;code&gt;ContentBlockerTrigger&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;urlFilterIsCaseSensitive&lt;/code&gt;: &lt;em&gt;If the URL matching should be case-sensitive. By default, it is case insensitive.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resourceType&lt;/code&gt;: &lt;em&gt;A list of "ContentBlockerTriggerResourceType" representing the resource types (how the browser intends to use the resource) that the rule should match. If it is not specified, the rule matches all resource types.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ifDomain&lt;/code&gt;: &lt;em&gt;A list of strings matched to a URL's domain; it limits action to a list of specific domains. Values must be lowercase ASCII or Punycode for non-ASCII characters. Add `&lt;/em&gt;&lt;code&gt; in front to match the domain and subdomains. It can't be used with &lt;/code&gt;unlessDomain`.*&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;unlessDomain&lt;/code&gt;: &lt;em&gt;A list of strings matched to a URL's domain; acts on any site except domains in a provided list. Values must be lowercase ASCII or Punycode for non-ASCII. Add `&lt;/em&gt;&lt;code&gt; in front to match the domain and subdomains. It can't be used with &lt;/code&gt;ifDomain`.*&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;loadType&lt;/code&gt;: &lt;em&gt;A list of &lt;code&gt;ContentBlockerTriggerLoadType&lt;/code&gt; that can include one of two mutually exclusive values. If not specified, the rule matches all load types. &lt;code&gt;ContentBlockerTriggerLoadType.FIRST_PARTY&lt;/code&gt; triggers only if the resource has the same scheme, domain, and port as the main page resource. &lt;code&gt;ContentBlockerTriggerLoadType.THIRD_PARTY&lt;/code&gt; triggers if the resource isn't from the same domain as the main page resource.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ifTopUrl&lt;/code&gt;: &lt;em&gt;A list of strings matched to the entire main document URL; it limits the action to a specific list of URL patterns. Values must be lowercase ASCII or Punycode for non-ASCII characters. It can't be used with &lt;code&gt;unlessTopUrl&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;unlessTopUrl&lt;/code&gt;: &lt;em&gt;An array of strings matched to the entire main document URL; it acts on any site except URL patterns in the provided list. Values must be lowercase ASCII or Punycode for non-ASCII characters. It can't be used with &lt;code&gt;ifTopUrl&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;loadContext&lt;/code&gt;: &lt;em&gt;An array of strings that specify loading contexts.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ifFrameUrl&lt;/code&gt;: &lt;em&gt;A list of regular expressions to match iframes' URL against.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the code documentation for each specific property to find out which platform supports that feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add actions to your Content Blocker
&lt;/h2&gt;

&lt;p&gt;When a trigger matches a resource, the WebView evaluates all the triggers and executes the actions in order.&lt;/p&gt;

&lt;p&gt;Group the rules with similar actions together to improve performance. For example, first specify rules that block content loading followed by rules that block cookies.&lt;/p&gt;

&lt;p&gt;There are only two valid properties for actions: &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;selector&lt;/code&gt;. An action type is required.&lt;/p&gt;

&lt;p&gt;If the type is &lt;code&gt;ContentBlockerActionType.CSS_DISPLAY_NONE&lt;/code&gt;, a &lt;code&gt;selector&lt;/code&gt; is required as well; otherwise, the &lt;code&gt;selector&lt;/code&gt; is optional.&lt;/p&gt;

&lt;p&gt;Here is a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initialSettings: InAppWebViewSettings(contentBlockers: [
 ContentBlocker(
   trigger: ContentBlockerTrigger(
     urlFilter: "https://flutter.dev/.*",
   ),
   action: ContentBlockerAction(
     type: ContentBlockerActionType.CSS_DISPLAY_NONE,
     selector: '.notification, .media, #developer-story'
   )
 )
]),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid types are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;BLOCK&lt;/code&gt;: &lt;em&gt;Stops the loading of the resource. If the resource was cached, the cache is ignored.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;BLOCK_COOKIES&lt;/code&gt;: &lt;em&gt;Strips cookies from the header before sending it to the server. This only blocks cookies that are otherwise acceptable to WebView's privacy policy. Combining &lt;code&gt;BLOCK_COOKIES&lt;/code&gt; with &lt;code&gt;IGNORE_PREVIOUS_RULES&lt;/code&gt; doesn't override the browser's privacy settings.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CSS_DISPLAY_NONE&lt;/code&gt;: &lt;em&gt;Hides elements of the page based on a CSS selector. A selector field contains the selector list. Any matching element has its display property set to none, which hides it.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;MAKE_HTTPS&lt;/code&gt;: &lt;em&gt;Changes a URL from &lt;code&gt;http&lt;/code&gt; to &lt;code&gt;https&lt;/code&gt;. URLs with a specified (nondefault) port and links using other protocols are unaffected.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IGNORE_PREVIOUS_RULES&lt;/code&gt;: &lt;em&gt;Ignores previously triggered actions.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the code documentation for each specific type to find out which platform supports it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a simple ad blocker
&lt;/h2&gt;

&lt;p&gt;Let's create a simple ad blocker using what we have learned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

Future main() async {
 WidgetsFlutterBinding.ensureInitialized();
 if (!kIsWeb &amp;amp;&amp;amp;
     kDebugMode &amp;amp;&amp;amp;
     defaultTargetPlatform == TargetPlatform.android) {
   await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
 }
 runApp(const MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
 const MyApp({Key? key}) : super(key: key);

 @override
 State&amp;lt;MyApp&amp;gt; createState() =&amp;gt; _MyAppState();
}

class _MyAppState extends State&amp;lt;MyApp&amp;gt; {
 final GlobalKey webViewKey = GlobalKey();

 // list of ad URL filters to be used to block ads from loading
 final adUrlFilters = [
   ".*.doubleclick.net/.*",
   ".*.ads.pubmatic.com/.*",
   ".*.googlesyndication.com/.*",
   ".*.google-analytics.com/.*",
   ".*.adservice.google.*/.*",
   ".*.adbrite.com/.*",
   ".*.exponential.com/.*",
   ".*.quantserve.com/.*",
   ".*.scorecardresearch.com/.*",
   ".*.zedo.com/.*",
   ".*.adsafeprotected.com/.*",
   ".*.teads.tv/.*",
   ".*.outbrain.com/.*"
 ];

 final List&amp;lt;ContentBlocker&amp;gt; contentBlockers = [];
 var contentBlockerEnabled = true;

 InAppWebViewController? webViewController;

 @override
 void initState() {
   super.initState();

   // for each ad URL filter, add a Content Blocker to block its loading
   for (final adUrlFilter in adUrlFilters) {
     contentBlockers.add(ContentBlocker(
         trigger: ContentBlockerTrigger(
           urlFilter: adUrlFilter,
         ),
         action: ContentBlockerAction(
           type: ContentBlockerActionType.BLOCK,
         )));
   }

   // apply the "display: none" style to some HTML elements
   contentBlockers.add(ContentBlocker(
       trigger: ContentBlockerTrigger(
         urlFilter: ".*",
       ),
       action: ContentBlockerAction(
           type: ContentBlockerActionType.CSS_DISPLAY_NONE,
           selector: ".banner, .banners, .ads, .ad, .advert")));
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
       appBar: AppBar(
         title: const Text("Ads Content Blocker"),
         actions: [
           TextButton(
             onPressed: () async {
               contentBlockerEnabled = !contentBlockerEnabled;
               if (contentBlockerEnabled) {
                 await webViewController?.setSettings(
                     settings: InAppWebViewSettings(
                         contentBlockers: contentBlockers));
               } else {
                 await webViewController?.setSettings(
                     settings: InAppWebViewSettings(contentBlockers: []));
               }
               webViewController?.reload();

               setState(() {});
             },
             style: TextButton.styleFrom(foregroundColor: Colors.white),
             child: Text(contentBlockerEnabled ? 'Disable' : 'Enable'),
           )
         ],
       ),
       body: SafeArea(
           child: Column(children: &amp;lt;Widget&amp;gt;[
         Expanded(
           child: Stack(
             children: [
               InAppWebView(
                 key: webViewKey,
                 initialUrlRequest:
                     URLRequest(url: WebUri('https://www.tomshardware.com/')),
                 initialSettings:
                     InAppWebViewSettings(contentBlockers: contentBlockers),
                 onWebViewCreated: (controller) {
                   webViewController = controller;
                 },
               ),
             ],
           ),
         ),
       ])));
 }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using these rules will prevent a bunch of ads from appearing, such as Google Ads.&lt;/p&gt;

&lt;p&gt;Click the &lt;em&gt;Disable/Enable&lt;/em&gt; button to disable or enable the ad blocker feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7r2r6qgvcpwi8g6t27tk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7r2r6qgvcpwi8g6t27tk.gif" alt="Content Blocker example with Flutter InAppWebView" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Content Blockers allow us to write performant rules for blocking content in the WebView while respecting the user's privacy.&lt;/p&gt;

&lt;p&gt;Full project code is available &lt;a href="https://github.com/pichillilorenzo/flutter_inappwebview_examples/tree/main/webview_ad_blocker" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's all for today!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Are you using this plugin? Submit your app through the &lt;a href="https://inappwebview.dev/submit-app/" rel="noopener noreferrer"&gt;Submit Application&lt;/a&gt; page and follow the instructions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check the &lt;a href="https://inappwebview.dev/showcase/" rel="noopener noreferrer"&gt;Showcase&lt;/a&gt; page to see who's already using it!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This project follows the &lt;a href="https://github.com/all-contributors/all-contributors" rel="noopener noreferrer"&gt;all-contributors&lt;/a&gt; specification (&lt;a href="https://github.com/pichillilorenzo/flutter_inappwebview#contributors-" rel="noopener noreferrer"&gt;contributors&lt;/a&gt;). I want to thank all the people who are supporting the project in any way. Thanks a lot to all of you! 💙&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Migrating a Flutter app to Material 3</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Fri, 16 Dec 2022 08:33:53 +0000</pubDate>
      <link>https://forem.com/codemagicio/migrating-a-flutter-app-to-material-3-5fp4</link>
      <guid>https://forem.com/codemagicio/migrating-a-flutter-app-to-material-3-5fp4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is written by &lt;strong&gt;Taha Tesser&lt;/strong&gt; and originally posted to &lt;a href="https://blog.codemagic.io/migrating-a-flutter-app-to-material-3/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=flutter_distribution&amp;amp;utm_content=material3" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At Google I/O 2021, Google announced the next evolution of Material Design, Material You, along with Android 12. The Material Design system's biggest overhaul yet brought redesigned components, new colors, a wide range of shapes, simplified typography, new elevation, better accessibility, and many other tweaks. With this update, Flutter apps can have a consistent design across multiple platforms. Material Design 3 (the technical name for Material You) is sometimes referred to as Material 3 or simply M3 for the sake of brevity, similarly to how Material 2 is referred to as M2.&lt;/p&gt;

&lt;p&gt;In this article, we will discuss everything that you, as a Flutter developer, should know about migrating your Flutter app to Material 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to enable Material 3 in Flutter
&lt;/h2&gt;

&lt;p&gt;Since the announcement of Material 3, Flutter has received a bunch of updates to support it, including support for new typography, shapes, elevation, updated widgets, and new M3 widgets. Most of the M3 components are available in Flutter. You can track the few remaining widgets that are yet to receive Material 3 support and the progress of the Material 3 implementations in Flutter for them in the &lt;a href="https://github.com/flutter/flutter/issues/91605" rel="noopener noreferrer"&gt;Bring Material 3 to Flutter&lt;/a&gt; issue.&lt;/p&gt;

&lt;p&gt;Currently, Material 3 changes are only available when opting in, so you'll need to use the &lt;code&gt;useMaterial3&lt;/code&gt; flag on &lt;code&gt;ThemeData&lt;/code&gt; to enable Material 3 support. (This might change in the future, so be sure to check out the Flutter website for updated documentation. We'll also update this article soon after the change takes place.)&lt;/p&gt;

&lt;p&gt;To use Material 3 in Flutter instead of Material 2, specify the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theme: ThemeData(
  useMaterial3: true,
),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's new in Material Design 3 for Flutter?
&lt;/h2&gt;

&lt;p&gt;Material 3 updates brought revamped typography, an improved &lt;code&gt;ColorScheme&lt;/code&gt; (including the ability to generate a full &lt;code&gt;ColorScheme&lt;/code&gt; from a given seed color), updated elevation, a beautiful Android 12 overscroll stretch effect, and a new ink ripple. A bunch of Material widgets, such as &lt;code&gt;AppBar&lt;/code&gt;, &lt;code&gt;FloatingActionButton&lt;/code&gt; (FAB), &lt;code&gt;ElevatedButton&lt;/code&gt;, &lt;code&gt;OutlinedButton&lt;/code&gt;, &lt;code&gt;IconButton&lt;/code&gt;, and &lt;code&gt;Card&lt;/code&gt;, have been updated to support Material 3 design. There are also new Material 3 widgets, such as &lt;code&gt;NavigationBar&lt;/code&gt;, &lt;code&gt;NavigationDrawer&lt;/code&gt;, and&lt;code&gt;SegmentedButton&lt;/code&gt;, some new M3-style buttons, such as &lt;code&gt;FilledButton&lt;/code&gt; and &lt;code&gt;FilledButton.tonal&lt;/code&gt;, and a whole lot more.&lt;/p&gt;

&lt;p&gt;If you simply migrate the Flutter starter app to Material 3, you'll already notice some changes: The &lt;code&gt;AppBar&lt;/code&gt; has no elevation or background color, and the &lt;code&gt;FAB&lt;/code&gt; has a rounded rectangular shape instead of the more familiar circular shape.&lt;/p&gt;

&lt;p&gt;For a complete overview of M3 in Flutter, check out the official &lt;a href="https://github.com/flutter/samples/tree/main/material_3_demo" rel="noopener noreferrer"&gt;Material 3 Demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the following sections, I'll explain some of the key changes and tweaks you might want to make in your app to support Material 3.&lt;/p&gt;

&lt;p&gt;The key changes in Material 3 are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Dynamic color&lt;/li&gt;
&lt;li&gt;  Typography&lt;/li&gt;
&lt;li&gt;  Shapes&lt;/li&gt;
&lt;li&gt;  Elevation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's go through each of them in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic color
&lt;/h3&gt;

&lt;p&gt;Let's start with Dynamic color, which enables you to apply consistent colors across your app. It contains some key colors and neutral colors related to separate tonal palettes. Colors from these tonal palettes are applied across the UI. Use the &lt;a href="https://m3.material.io/theme-builder#/dynamic" rel="noopener noreferrer"&gt;Material Theme Builder web app&lt;/a&gt; or the &lt;a href="https://www.figma.com/community/plugin/1034969338659738588/Material-Theme-Builder" rel="noopener noreferrer"&gt;Figma plugin&lt;/a&gt; to visualize the dynamic color for your app and create a custom color scheme.&lt;/p&gt;

&lt;p&gt;Flutter uses a low-level &lt;a href="https://pub.dev/packages/material_color_utilities" rel="noopener noreferrer"&gt;&lt;code&gt;material_color_utilities&lt;/code&gt;&lt;/a&gt; package that contains algorithms to create a Material Design 3 color system. You can create color schemes for your apps using &lt;a href="https://pub.dev/packages/dynamic_color" rel="noopener noreferrer"&gt;&lt;code&gt;dynamic_color&lt;/code&gt;&lt;/a&gt; based on a platform's implementation of dynamic color.&lt;/p&gt;

&lt;p&gt;The easiest way to create an M3 &lt;code&gt;ColorScheme&lt;/code&gt; is by providing a &lt;code&gt;seedColor&lt;/code&gt; color in the app's theme.&lt;/p&gt;

&lt;p&gt;For instance, add &lt;code&gt;colorSchemeSeed: Colors.green&lt;/code&gt; to the starter app. Notice that the &lt;code&gt;FAB&lt;/code&gt; is now using a lighter green color instead of the light purple from the default color scheme.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Adding &lt;code&gt;colorSchemeSeed&lt;/code&gt; when &lt;code&gt;primarySwatch&lt;/code&gt; is present will throw &lt;code&gt;assertion&lt;/code&gt; : &lt;code&gt;'package:flutter/src/material/theme_data.dart': Failed assertion: line 477 pos 12: 'colorSchemeSeed == null || primarySwatch == null': is not true.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To fix this, remove &lt;code&gt;primarySwatch: Colors.blue,&lt;/code&gt; from the starter app's theme.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ///...
    primarySwatch: Colors.blue,
  useMaterial3: true,
  colorSchemeSeed: Colors.green,
),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fcolor_scheme_seed.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fcolor_scheme_seed.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, I've created an example that shows all the colors from the M3 &lt;code&gt;ColorScheme&lt;/code&gt;. On the left, we have the default &lt;code&gt;ColorScheme&lt;/code&gt;, which is available when setting the &lt;code&gt;useMaterial&lt;/code&gt; flag to true. And on the right, we're using the custom &lt;code&gt;ColorScheme&lt;/code&gt;, generated using the &lt;code&gt;colorSchemeSeed&lt;/code&gt; parameter, which takes a seed color or key color to generate a full Material 3 &lt;code&gt;ColorScheme&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Default M3 &lt;code&gt;ColorScheme&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Custom M3 &lt;code&gt;ColorScheme&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fdefault_color_scheme.png%23center"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fcustom_color_scheme.png%23center"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can take this to the next level using both &lt;a href="https://pub.dev/packages/flex_seed_scheme" rel="noopener noreferrer"&gt;flex_seed_scheme&lt;/a&gt;, which allows you to create a more flexible version of Flutter's &lt;code&gt;ColorScheme.fromSeed&lt;/code&gt;, and &lt;a href="https://pub.dev/packages/flex_color_scheme" rel="noopener noreferrer"&gt;flex_color_scheme&lt;/a&gt;, which ensures UI components get themed completely by the color schemes and custom colors you provide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typography
&lt;/h3&gt;

&lt;p&gt;Material 3 simplified the typography naming by splitting the typescales into five key groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Display&lt;/li&gt;
&lt;li&gt;  Headline&lt;/li&gt;
&lt;li&gt;  Title&lt;/li&gt;
&lt;li&gt;  Body&lt;/li&gt;
&lt;li&gt;  Label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The role of each key group is more descriptive, and it's much easier to use different sizes in a particular typography group, e.g., &lt;code&gt;BodyLarge&lt;/code&gt;, &lt;code&gt;BodyMedium&lt;/code&gt;, and &lt;code&gt;BodySmall&lt;/code&gt; instead of &lt;code&gt;bodyText1&lt;/code&gt;, &lt;code&gt;bodyText2&lt;/code&gt;, and &lt;code&gt;caption&lt;/code&gt;. This helps when implementing typography for devices with different screen sizes.&lt;/p&gt;

&lt;p&gt;The scaling of the typography has become consistent across the groups. Here is a comparison between the M3 and M2 typescales:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Ftypescale_comparison_16973925797358865740_hu9559f954705c0743cbd660c9e255659f_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Ftypescale_comparison_16973925797358865740_hu9559f954705c0743cbd660c9e255659f_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Elevation
&lt;/h3&gt;

&lt;p&gt;In Material 2, each elevated component gets a shadow. The higher the elevation, the bigger the shadow. Going even further, Material 3 introduces a new &lt;code&gt;surfaceTintColor&lt;/code&gt; color property. When applied to elevated components, the surface of the elevated components gets this color, and its intensity depends on the elevation value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Felevation_surface_16501195752006919127_huc594a07f071ee0e54ec69d7628a99bc9_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Felevation_surface_16501195752006919127_huc594a07f071ee0e54ec69d7628a99bc9_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt; Source: &lt;a href="https://m3.material.io/styles/elevation/overview" rel="noopener noreferrer"&gt;https://m3.material.io/styles/elevation/overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;surfaceTintColor&lt;/code&gt; property is added to all the elevated widgets in Flutter, along with the &lt;code&gt;elevation&lt;/code&gt; and &lt;code&gt;shadow&lt;/code&gt; properties.&lt;/p&gt;

&lt;p&gt;Try out this Material elevation demo: Turn off shadows by tapping on the icon button in the top right and switch between M2 and M3. Notice that elevated surfaces take on the &lt;code&gt;surfaceTintColor&lt;/code&gt; color, which makes them visible even when no shadow is provided.&lt;/p&gt;

&lt;p&gt;When comparing the M2 &lt;code&gt;AppBar&lt;/code&gt; with the M3 &lt;code&gt;AppBar&lt;/code&gt; in the starter app, you'll notice that the &lt;code&gt;AppBar&lt;/code&gt; doesn't have a default &lt;code&gt;elevation&lt;/code&gt; value, &lt;code&gt;surfaceTintColor&lt;/code&gt; color, or &lt;code&gt;shadow&lt;/code&gt; color.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;M2 &lt;code&gt;AppBar&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;M3 &lt;code&gt;AppBar&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_app_bar.png%23center"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_app_bar.png%23center"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Providing a custom &lt;code&gt;elevation&lt;/code&gt; value shows the default &lt;code&gt;surfaceTintColor&lt;/code&gt; color in effect and applies the theme's shadow color to the &lt;code&gt;AppBar&lt;/code&gt; so that the AppBar casts a shadow as well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This &lt;code&gt;AppBar.elevation&lt;/code&gt; property is different from the new &lt;code&gt;AppBar.scrolledUnderElevation&lt;/code&gt; property, which is only in effect when the content is scrolled underneath the &lt;code&gt;AppBar&lt;/code&gt;. Learn more about this further below.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  appBar: AppBar(
    title: Text(widget.title),
    elevation: 4,
  ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_app_bar_elevated.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_app_bar_elevated.png%23center"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  appBar: AppBar(
    title: Text(widget.title),
    elevation: 4,
    shadowColor: Theme.of(context).shadowColor,
  ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_app_bar_elevated_with_shadow.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_app_bar_elevated_with_shadow.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shapes
&lt;/h3&gt;

&lt;p&gt;Material 3 offers a wider range of shapes, including squared, rounded, and rounded rectangular shapes. The &lt;code&gt;FAB&lt;/code&gt;, which was previously circled, now has a rounded rectangular shape, and material buttons went from rounded rectangular to pill shaped. Widgets like &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Dialog&lt;/code&gt;, and &lt;code&gt;BottomSheet&lt;/code&gt; are also more rounded in M3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_fab.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_fab.png%23center"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_buttons.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_buttons.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from Material 2 to Material 3
&lt;/h2&gt;

&lt;p&gt;Here, I'll walk you through the process of migrating a Material 2 demo app to Material 3.&lt;/p&gt;

&lt;p&gt;This demo app contains some Material 2 Flutter widgets. Some of them will be updated when enabling M3, and some of the M2 widgets can be replaced with new M3-style widgets. For example, &lt;code&gt;ElevatedButton&lt;/code&gt; can be replaced with the new &lt;code&gt;FilledButton&lt;/code&gt; to preserve the visual design, and &lt;code&gt;BottomNavigationBar&lt;/code&gt; can be replaced with the new M3-style &lt;code&gt;NavigationBar&lt;/code&gt; widget. The demo app also contains some customization needed in an M2 app for some widgets to stack well. For instance, &lt;code&gt;InputDecorationTheme&lt;/code&gt; applies a custom &lt;code&gt;fillColor&lt;/code&gt; so that the &lt;code&gt;TextField&lt;/code&gt; is visible when placed in the &lt;code&gt;AppBar&lt;/code&gt; in an M2 app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  theme: ThemeData(
    colorScheme: const ColorScheme.light().copyWith(
      primary: Colors.green[700],
      secondary: Colors.green[700],
    ),
    inputDecorationTheme: InputDecorationTheme(
      filled: true,
      fillColor: Theme.of(context).colorScheme.onPrimary,
      hintStyle: TextStyle(
        color: Colors.green[700],
      ),
    ),
    floatingActionButtonTheme: const FloatingActionButtonThemeData(
      foregroundColor: Colors.white,
    ),
  ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_1.png%23center"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_2.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_2.png%23center"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_3.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm2_demo_3.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the best things about Material 3 is that widgets use consistent colors, and the overall theming experience has also been improved. For example, in the demo, we used a custom &lt;code&gt;InputDecorationTheme&lt;/code&gt; to make the &lt;code&gt;TextField&lt;/code&gt; visible in the dark green &lt;code&gt;AppBar&lt;/code&gt;. But when migrating to M3, we can remove this, and the &lt;code&gt;TextField&lt;/code&gt; will be visible without any customization.&lt;/p&gt;

&lt;p&gt;In the demo, remove the existing &lt;code&gt;colorScheme&lt;/code&gt; property, &lt;code&gt;inputDecorationTheme&lt;/code&gt;, and &lt;code&gt;floatingActionButtonTheme&lt;/code&gt;. Then add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    theme: ThemeData(
    useMaterial3: true,
    colorSchemeSeed: Colors.green[700]
  ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a new beautiful &lt;code&gt;ColorScheme&lt;/code&gt; that's applied to all the components in the demo app. Each UI component is using specific tonal palette colors. &lt;code&gt;TextField&lt;/code&gt; uses an appropriate &lt;code&gt;fillColor&lt;/code&gt;, and cards have the default greenish &lt;code&gt;surfaceTintColor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IconButton&lt;/code&gt; now supports the &lt;code&gt;ButtonStyle&lt;/code&gt; property, which you can use to customize the &lt;code&gt;IconButton&lt;/code&gt; to get an M3 visual and a new selected state. Let's update the &lt;code&gt;IconButton&lt;/code&gt; from the &lt;code&gt;AppBar&lt;/code&gt; in the demo app to an M3 filled-style icon button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_icon_button.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_icon_button.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://api.flutter.dev/flutter/material/IconButton-class.html" rel="noopener noreferrer"&gt;https://api.flutter.dev/flutter/material/IconButton-class.html&lt;/a&gt; and &lt;a href="https://m3.material.io/components/icon-buttons/overview" rel="noopener noreferrer"&gt;https://m3.material.io/components/icon-buttons/overview&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;Here's my favorite part: When you scroll through the content, the &lt;code&gt;AppBar&lt;/code&gt;'s default &lt;code&gt;scrollUnderElevation&lt;/code&gt; kicks in. Notice that the &lt;code&gt;surfaceTintColor&lt;/code&gt; intensified when we're scrolling, and as a result, the &lt;code&gt;AppBar&lt;/code&gt; changes color. This is because the &lt;code&gt;AppBar&lt;/code&gt; is elevated based on the &lt;code&gt;scrollUnderElevation&lt;/code&gt; value when the content is scrolled underneath. List views send scroll notifications to the &lt;code&gt;AppBar&lt;/code&gt; that they're being scrolled beneath.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can implement &lt;code&gt;AppBar.notificationPredicate&lt;/code&gt; and listen to scroll notifications from a nested list view for more complex layouts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Material 3 &lt;code&gt;AppBar&lt;/code&gt; is elevated when content is scrolled underneath the &lt;code&gt;AppBar&lt;/code&gt;. You can adjust this elevation using the new &lt;code&gt;scrolledUnderElevation&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Check out the official &lt;code&gt;scrolledUnderElevation&lt;/code&gt; code sample I've added in the &lt;code&gt;AppBar&lt;/code&gt; &lt;a href="https://api.flutter.dev/flutter/material/AppBar-class.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Execute &lt;code&gt;flutter create --sample=material.AppBar.2 mysample&lt;/code&gt; to create a local project with this code sample.&lt;/p&gt;

&lt;p&gt;The migration process for our demo app isn't complete yet. It still uses the M2-style &lt;code&gt;BottomNavigationBar&lt;/code&gt;, so let's replace it with the new M3-style &lt;code&gt;NavigationBar&lt;/code&gt; widget. It's taller and easier to interact with, and it isn't elevated.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;M2-style &lt;code&gt;BottomNavigationBar&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;M3-style &lt;code&gt;NavigationBar&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_bottom_navigation_bar.png%23center"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_navigation_bar.png%23center"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check out the documentation for the &lt;a href="https://api.flutter.dev/flutter/material/NavigationBar-class.html" rel="noopener noreferrer"&gt;NavigationBar class&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Tapping on the &lt;code&gt;FAB&lt;/code&gt; on the home screen opens a dialog with a rounded rectangular shape. Since the dialog is elevated by default, we can see that the default &lt;code&gt;surfaceTintColor&lt;/code&gt; has been applied and the content padding has been slightly modified, as it has an optional &lt;code&gt;icon&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_dialog.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_dialog.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the documentation for the &lt;a href="https://api.flutter.dev/flutter/material/showDialog.html" rel="noopener noreferrer"&gt;showDialog function&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Now, navigate to the details page by tapping on one of the list items. This page contains a &lt;code&gt;SliverAppBar&lt;/code&gt; with &lt;code&gt;expandedHeight&lt;/code&gt; and &lt;code&gt;flexibleSpace&lt;/code&gt; to make the app bar title large and the &lt;code&gt;SliverAppBar&lt;/code&gt; collapsable. This can be replaced with the new &lt;code&gt;SliverAppBar.large&lt;/code&gt;, which supports large titles and is also collapsable. When doing so, we can remove &lt;code&gt;expandedHeight&lt;/code&gt; and &lt;code&gt;flexibleSpace&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    SliverAppBar.large(
    title: const Text('Lorem Ipsum'),
  ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the new M3 &lt;code&gt;AppBar&lt;/code&gt; variants, which support medium and large titles, use the new &lt;code&gt;SliverAppBar.medium&lt;/code&gt; and &lt;code&gt;SliverAppBar.large&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check out the documentation for the &lt;a href="https://api.flutter.dev/flutter/material/SliverAppBar/SliverAppBar.medium.html" rel="noopener noreferrer"&gt;SilverAppBar.medium constructor&lt;/a&gt; and &lt;a href="https://api.flutter.dev/flutter/material/SliverAppBar/SliverAppBar.large.html" rel="noopener noreferrer"&gt;SilverAppBar.large constructor&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Here are the screenshots of the final M3 demo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_1.png%23center"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_2.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_2.png%23center"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_3.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F12%2Fm3_demo_final_3.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The source code for all the demos is available &lt;a href="https://github.com/TahaTesser/material_3_demos" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Material 3 makes it possible to create beautiful, personalized, and accessible designs. When combining Material 3 with Flutter, you can create a consistent and unified UI experience across mobile, web, and desktop platforms. It makes it much easier to create complex algorithmic color schemes and scale typography for devices with varying screen sizes. Furthermore, accessibility has been improved, and visual feedback is clearer.&lt;/p&gt;

&lt;p&gt;The Flutter team has been working hard on adding full support for Material 3 to Flutter. As demonstrated above, you can already migrate your existing Material 2 app to Material 3. If you use some widgets that are yet to receive Material 3 support, you can track their progress in the Material 3 &lt;a href="https://github.com/flutter/flutter/issues/91605" rel="noopener noreferrer"&gt;issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>10 reasons to choose Codemagic CI/CD in 2022–2023</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Mon, 21 Nov 2022 08:33:46 +0000</pubDate>
      <link>https://forem.com/codemagicio/10-reasons-to-choose-codemagic-cicd-in-2022-2023-4a3f</link>
      <guid>https://forem.com/codemagicio/10-reasons-to-choose-codemagic-cicd-in-2022-2023-4a3f</guid>
      <description>&lt;p&gt;&lt;a href="https://codemagic.io/start/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic&lt;/a&gt; is a CI/CD (continuous integration and continuous delivery) tool that is best suited for mobile developers. It can help you speed up your release cycle, get actionable feedback faster, and forget about the pain of manually submitting your apps to stores.&lt;/p&gt;

&lt;p&gt;Codemagic is evolving fast: This year, we added M1 Mac mini build machines and changed our pricing so that everyone can afford to use the friendliest mobile DevOps tool on the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why using a CI/CD tool is especially important for mobile app development
&lt;/h2&gt;

&lt;p&gt;Mobile application development has come a long way over the last decade, and users have developed high expectations for the look, feel, and functionality of the apps they use. They expect apps to work smoothly, be available across multiple platforms, look nice, and be intuitive.&lt;/p&gt;

&lt;p&gt;All of that requires a fast software development life cycle so that you can quickly test concepts, fix issues, and iterate in order to improve the quality of your app and release new features. Building such a release cycle is impossible without adhering to DevOps best practices, which include automating a lot of tasks and improving collaboration between team members by building a continuous integration and continuous delivery pipeline for your app.&lt;/p&gt;

&lt;p&gt;For continuous integration, you'll need a version control system such as GitLab, GitHub, or Bitbucket (Codemagic supports all of these version control systems, by the way) for your source code. You will also require a tool that will grab the source code from your repository, run automated tests, and build it into an actual working app.&lt;/p&gt;

&lt;p&gt;For continuous delivery, you'll usually use the same tool to automatically publish your app to the app stores, such as Google Play and Apple App Store. This means that just committing your code with a change to a repository or approving a pull request is enough to trigger a pipeline that will automatically build, test, and deploy your app with that change and enable users to access it immediately.&lt;/p&gt;

&lt;p&gt;Tools that carry out the above processes are called continuous integration and continuous delivery services, or CI/CD for short.&lt;/p&gt;

&lt;p&gt;There are many CI/CD tools available in the market, from universal solutions such as Jenkins or GitHub Actions to hosted mobile-first cloud services such as Codemagic, Bitrise, or Appcircle. How do you know which CI/CD service is best for building mobile apps?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Download our ebook &lt;a href="https://codemagic.io/ci-cd-ebook/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;"Continuous Integration and Delivery for Mobile Apps"&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below, you'll find ten reasons why you should consider using Codemagic in 2022 (and 2023) for your mobile app development.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Never overspend
&lt;/h2&gt;

&lt;p&gt;Codemagic has a unique billing model that helps you save money by adapting to your needs.&lt;/p&gt;

&lt;p&gt;Firstly, you get 500 free build minutes per month. If you need more, you only pay for the build minutes you have used at the end of the month.&lt;/p&gt;

&lt;p&gt;And if your usage exceeds 50 build-machine hours per month, you won't have to pay more than $299 a month --- Codemagic fixes the costs for you while providing unlimited build minutes. This is done automatically, so there's no need to worry about optimizing the spend --- we'll optimize it for you.&lt;/p&gt;

&lt;p&gt;Finally, if you're sure you will be building extensively every month, you can choose the annual plan with the same unlimited usage but at a 20% discount so that you pay $2,870 per year of unlimited use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more about &lt;a href="https://codemagic.io/pricing/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic's pricing&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Build with Apple silicon machines
&lt;/h2&gt;

&lt;p&gt;This winter, Codemagic &lt;a href="https://blog.codemagic.io/codemagic-mac-mini-m1-release/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;introduced Apple M1 build machines&lt;/a&gt;, which recently replaced the old Intel Mac minis as our default VMs (even though you can still use Intel machines if you need to). That means that you can build with M1s even on a free plan. Once you exceed the limit and need to pay for build minutes, Codemagic still offers M1 Mac minis at a fraction of the price of what other mobile CI/CD services offer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read &lt;a href="https://blog.codemagic.io/how-codemagic-lowered-prices-and-improved-infrastructure/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;how Codemagic managed to lower its pricing while improving its infrastructure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Enjoy good performance
&lt;/h2&gt;

&lt;p&gt;There are many benefits of using Apple silicon build machines in continuous integration/continuous delivery pipelines. The improvements in build speed that they provide are just amazing. Building with other Codemagic machines is also quite fast, primarily because they have all the necessary tools, such as Flutter SDK, Android Studio, Xcode, Unity Editor, and many more preinstalled.&lt;/p&gt;

&lt;p&gt;You can find the build machine specs and information about preinstalled software via the links below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.codemagic.io/specs/versions-macos/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;macOS build machine specs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.codemagic.io/specs/versions-linux/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Linux build machine specs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.codemagic.io/specs/versions-windows/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Windows build machine specs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more about how &lt;a href="https://blog.codemagic.io/flutter-m1-vm-comparison/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Mac M1 build machines increase the build speed of Flutter apps&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Forget about maintaining infrastructure
&lt;/h2&gt;

&lt;p&gt;Perhaps even more importantly, you don't need to worry about maintaining infrastructure and updating dependencies. We always keep all the software up to date so that you don't need to worry about it --- you can just use it, and we'll take care of the maintenance. You don't need to update your environment. We've got you covered, from the latest Xcode versions (actually, we have several versions to choose from) to the latest Flutter versions.&lt;/p&gt;

&lt;p&gt;And with Codemagic, there's no such thing as "our build server is broken" anymore --- there are always build machines in the pool ready to build your apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Embrace flexibility with the visual Workflow Editor or YAML Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;Codemagic offers two different approaches to building CI/CD pipelines, and you can choose the one that suits you best. With our visual Workflow Editor, you can follow a very intuitive (yet well-documented) process to build a CI/CD pipeline with just a few clicks.&lt;/p&gt;

&lt;p&gt;Alternatively, you can utilize the power IaC (Infrastructure as Code) with codemagic.yaml. This method gives you infinite options for customizing your pipeline and enables you to set up new pipelines within a few minutes by simply copying and pasting the contents of your codemagic.yaml file from the previous pipeline.&lt;/p&gt;

&lt;p&gt;Choose whichever approach you like best. What's even better is that you can start with the simple Workflow Editor and then export your configuration as a codemagic.yaml file to continue tweaking it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn how to &lt;a href="https://docs.codemagic.io/flutter-configuration/flutter-projects/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;get started with the Workflow Editor&lt;/a&gt; and &lt;a href="https://docs.codemagic.io/yaml-basic-configuration/yaml-getting-started/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;codemagic.yaml&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Build white-label apps at scale
&lt;/h2&gt;

&lt;p&gt;Have you built a successful app for one service and want to sell it to another service with minor tweaks? This is called white labeling, and Codemagic supports it at no additional cost. No matter which white-labeling approach you choose or whether you're using Flutter, React Native, or another framework, Codemagic can help you build all flavors of your white-label app with a single commit and distribute them all to the stores using their respective accounts.&lt;/p&gt;

&lt;p&gt;Codemagic speeds up release cycles for your white-label applications, helping you make sure your clients are happy with your app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more about &lt;a href="https://docs.codemagic.io/knowledge-others/white-label-apps/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;white labeling apps with Codemagic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  7. Build for all platforms
&lt;/h2&gt;

&lt;p&gt;One of the core benefits of frameworks such as Flutter, React Native, and Unity is their support for many platforms, and Codemagic allows you to enjoy this support at its fullest.&lt;/p&gt;

&lt;p&gt;Codemagic has Linux, Windows, and Mac build machines. (The latter even come with two different architectures --- x86/x64 and ARM64.) We allow you to build apps for the following platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Android&lt;/li&gt;
&lt;li&gt;  iOS&lt;/li&gt;
&lt;li&gt;  Windows&lt;/li&gt;
&lt;li&gt;  Linux&lt;/li&gt;
&lt;li&gt;  Mac&lt;/li&gt;
&lt;li&gt;  Web&lt;/li&gt;
&lt;li&gt;  Standalone Oculus VR headsets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codemagic can not only build your app for all these platforms but also help you automate deployment to the respective stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Enjoy tight integrations with App Store and Google Play
&lt;/h2&gt;

&lt;p&gt;Codemagic is tightly integrated with both App Store Connect and the Google Play Store. This means that setting up automated deployment is easier with Codemagic. It can help you with steps such as code signing, automatic app versioning, distributing apps to TestFlight or Google Play Beta, and more.&lt;/p&gt;

&lt;p&gt;Also, did you know that publishing to the App Store with Codemagic doesn't consume your build minutes? Well, now you do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more about &lt;a href="https://docs.codemagic.io/yaml-publishing/app-store-connect/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic's App Store Connect integration&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. Use integrations with various test automation tools
&lt;/h2&gt;

&lt;p&gt;Codemagic has quite an extensive list of &lt;a href="https://codemagic.io/integrations/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;integrations&lt;/a&gt; that are documented and ready for you to try out. Among those integrations, we'd like to specifically highlight tools that allow you to automate testing, including&lt;a href="https://docs.codemagic.io/integrations/katalon-integration/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt; Katalon&lt;/a&gt; (one of the most powerful engines for no-code test automation),&lt;a href="https://docs.codemagic.io/integrations/kobiton-integration/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt; Kobiton&lt;/a&gt; (for testing on real devices),&lt;a href="https://docs.codemagic.io/integrations/sofy-integration/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt; Sofy&lt;/a&gt; (for low-code test automation), and&lt;a href="https://docs.codemagic.io/yaml-testing/firebase-test-lab/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt; Firebase Test Lab&lt;/a&gt; (for cloud-based testing for Android and iOS apps on various devices).&lt;/p&gt;

&lt;p&gt;And there are many more integrations that can help you improve testing, code coverage, and more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get acquainted with &lt;a href="https://codemagic.io/integrations/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic's integrations&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  10. Get help from our very responsive support
&lt;/h2&gt;

&lt;p&gt;With some SaaS companies, it can be difficult and time-consuming to reach support, and they often don't really solve your issue. Codemagic is different: You can contact our support using the in-app chat (for paying customers) or on&lt;a href="https://slack.codemagic.io/"&gt; GitHub Discussions&lt;/a&gt; (for everybody). We strive to provide help within 24 hours, and it usually takes just a few hours.&lt;/p&gt;

&lt;p&gt;We also offer&lt;a href="https://codemagic.io/onboarding-assistance/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution"&gt; free onboarding assistance&lt;/a&gt;, and our DevOps engineers are here for you to get the most out of Codemagic and build better pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Choosing the right CI/CD for mobile app development might seem like a complicated task, but it essentially boils down to choosing the solution with the best parameters, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Ease of use&lt;/li&gt;
&lt;li&gt;  Cost&lt;/li&gt;
&lt;li&gt;  Features and integrations&lt;/li&gt;
&lt;li&gt;  Build speed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codemagic excels at all of the above. It is easy to set up and use, is much more affordable than its competitors, is feature-rich, and has a lot of useful integrations. And finally, Codemagic's virtual machines are really, really fast.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>flutter</category>
      <category>mobile</category>
      <category>ios</category>
    </item>
    <item>
      <title>How to build a Chrome extension with Flutter Web</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 14:01:18 +0000</pubDate>
      <link>https://forem.com/codemagicio/how-to-build-a-chrome-extension-with-flutter-web-618</link>
      <guid>https://forem.com/codemagicio/how-to-build-a-chrome-extension-with-flutter-web-618</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is written by Hrishikesh Pathak and originally posted to &lt;a href="https://blog.codemagic.io/how-to-build-a-chrome-extension-with-flutter-web/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is a Google Chrome extension?
&lt;/h2&gt;

&lt;p&gt;Google Chrome is the most popular web browser in the world. Chrome extensions are small programs that extend Chrome's functionality. Google Chrome has a standardized API through which extensions can perform various tasks in the browser.&lt;/p&gt;

&lt;p&gt;In our daily lives, we use browser extensions for various tasks, like ad and tracker blocking, grammar correction, and translation. These extensions are made using JavaScript and use the Chrome extension API to interact with the browser. Since &lt;a href="https://blog.codemagic.io/build-and-host-your-flutter-web-apps-on-codemagic/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Flutter Web&lt;/a&gt; projects are compiled in JavaScript, we can build a Chrome extension with Flutter with some small tweaks.&lt;/p&gt;

&lt;p&gt;In this tutorial, we are going to learn how to make a Chrome extension using Flutter Web. We will explore different use cases, tweaks, and build commands to get the best result. To get the most out of this guide, make sure to follow it to the end.&lt;/p&gt;

&lt;p&gt;The final result of the tutorial will be a Flutter application that runs as a Chrome extension. It will look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-exension-1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-exension-1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To create a Chrome extension with Flutter, we'll need to go through the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Configure the &lt;code&gt;manifest.json&lt;/code&gt; file for Flutter Web&lt;/li&gt;
&lt;li&gt; Deal with Content Security Policy to make our Chrome extension actually work in the browser environment&lt;/li&gt;
&lt;li&gt; Configure Flutter Web&lt;/li&gt;
&lt;li&gt; Add optional background scripts to our Flutter Chrome extension&lt;/li&gt;
&lt;li&gt; Build our Flutter Web project&lt;/li&gt;
&lt;li&gt; Build a Codemagic pipeline to build and deploy our Chrome extension to Chrome Web Store&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Add a new manifest.json file
&lt;/h2&gt;

&lt;p&gt;If you look in the &lt;code&gt;web/&lt;/code&gt; folder in a default Flutter project, you will see a &lt;code&gt;manifest.json&lt;/code&gt; file. This file includes all the application configuration and aims to provide Progressive Web App (PWA) capabilities to our Flutter web application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To learn more about building PWAs with Flutter, take a look at this &lt;a href="https://blog.codemagic.io/pwa-in-flutter/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google Chrome also requires a &lt;code&gt;manifest.json&lt;/code&gt; file to register an extension. Therefore, to make our extension work, delete the contents inside the &lt;code&gt;manifest.json&lt;/code&gt; file, and substitute them with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "Chromextension",
  "description": "A Flutter chrome extension",
  "version": "0.0.0.1",
  "manifest_version": 3,
  "content_security_policy": {
    "extension_pages": "script-src 'self' ; object-src 'self'"
  },
  "action": {
    "default_popup": "index.html",
    "default_icon": {
      "16": "icons/chromextension16.png",
      "32": "icons/chromextension32.png",
      "48": "icons/chromextension48.png",
      "128": "icons/chromextension128.png"
    }
  },
  "icons": {
    "16": "icons/chromextension16.png",
    "32": "icons/chromextension32.png",
    "48": "icons/chromextension48.png",
    "128": "icons/chromextension128.png"
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you can change the name and description of the extension. Chrome extensions require icons in four different sizes. Use an image-resizing tool to make four versions of your extension icon with dimensions of 128x128, 48x48, 32x32, and 16x16 pixels. Then place the icons in the adjacent &lt;code&gt;icons/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;action&lt;/code&gt; field in the &lt;code&gt;manifest.json&lt;/code&gt; file, set &lt;code&gt;index.html&lt;/code&gt; as the default pop-up page. This way, when you click on the extension icon in the browser's top bar, the &lt;code&gt;index.html&lt;/code&gt; page will pop up.&lt;/p&gt;

&lt;h2&gt;
  
  
  An explanation of Content Security Policy
&lt;/h2&gt;

&lt;p&gt;Content Security Policy (CSP) is a security layer in the browser that protects users from cross-site scripting and data injection attacks. Normally, browsers trust the content coming from a server. Cross-site scripting misuses this trust and runs malicious code in the browser.&lt;/p&gt;

&lt;p&gt;Using CSP, developers can specify a trusted domain in the HTTP header. A CSP-enabled browser will only execute scripts coming from that specific domain and ignore inline scripts and event-handling HTML attributes.&lt;/p&gt;

&lt;p&gt;Take a look at the CSP in our &lt;code&gt;manifest.json&lt;/code&gt; file. Here, we define &lt;code&gt;script-src&lt;/code&gt; and &lt;code&gt;object-src&lt;/code&gt; as &lt;code&gt;self&lt;/code&gt;. Therefore, the browser won't execute inline scripts and scripts from another origin. In developer mode, you get a CSP error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-error.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-error.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will rectify this error in the next section. If you want to learn more about CSP on the web, you can read this &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="noopener noreferrer"&gt;CSP guide from MDN Web Docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Flutter Web
&lt;/h2&gt;

&lt;p&gt;To rectify the CSP error, remove all the script tags from the &lt;code&gt;index.html&lt;/code&gt; file in the &lt;code&gt;web/&lt;/code&gt; directory. Then link the &lt;code&gt;main.dart.js&lt;/code&gt; file inside the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. The &lt;code&gt;main.dart.js&lt;/code&gt; is generated at build time and contains all the transpiled Dart and Flutter code.&lt;/p&gt;

&lt;p&gt;Now, remove all the unnecessary meta tags from the &lt;code&gt;index.html&lt;/code&gt; file, and add an explicit width and height to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element. After all these changes, the final version of &lt;code&gt;index.html&lt;/code&gt; looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html style="height: 600px; width: 300px"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;title&amp;gt;chromextension&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;script src="main.dart.js" type="application/javascript"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add a custom background script
&lt;/h2&gt;

&lt;p&gt;Background scripts are event-based programs that react to certain events within the browser. These scripts are optional for Chrome extensions. If you're developing some feature in your extension that requires the execution of background code, then having custom background scripts comes in handy.&lt;/p&gt;

&lt;p&gt;To add a background script to a Flutter Chrome extension, add a new file &lt;code&gt;background.js&lt;/code&gt; inside the &lt;code&gt;web/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Then, register the background script in your &lt;code&gt;manifest.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "Chromextension",
  "description": "A flutter chrome extension",
  "version": "0.0.0.1",
  "manifest_version": 3,
  "content_security_policy": {
    "extension_pages": "script-src 'self' ; object-src 'self'"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["contextMenus", "clipboardWrite", "clipboardRead"],
  "action": {
    "default_icon": {
      "16": "/images/scholarr16.png",
      "32": "/images/scholarr32.png",
      "48": "/images/scholarr48.png",
      "128": "/images/scholarr128.png"
    }
  },
  "icons": {
    "16": "/images/scholarr16.png",
    "32": "/images/scholarr32.png",
    "48": "/images/scholarr48.png",
    "128": "/images/scholarr128.png"
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The background script needs explicit permission to run in the browser. This is controlled with the &lt;code&gt;permissions&lt;/code&gt; field in the &lt;code&gt;manifest.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Now, populate the background script with the logic to run upon a browser event. To learn more, check out the &lt;a href="https://developer.chrome.com/docs/extensions/mv3/getstarted/" rel="noopener noreferrer"&gt;Chrome developer documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Flutter Web project
&lt;/h2&gt;

&lt;p&gt;Flutter Web projects can be built with two types of renderers. The first, HTML renderer, uses a combination of HTML elements, CSS, Canvas elements, and SVG elements. The second, CanvasKit renderer, uses Skia as the rendering engine and draws pixel-perfect widgets. CanvasKit renderer adds about 2 MB to the download size of your application.&lt;/p&gt;

&lt;p&gt;If we don't specify the renderer when we build our Flutter Web project using the &lt;code&gt;flutter build web&lt;/code&gt; command, Flutter will use an HTML-rendered web app in mobile browsers and a CanvasKit-rendered web application in desktop browsers.&lt;/p&gt;

&lt;p&gt;However, Chrome extensions don't support CanvasKit-rendered applications. Therefore, add the &lt;code&gt;--web-renderer html&lt;/code&gt; flag at build time to generate only an HTML-rendered application.&lt;/p&gt;

&lt;p&gt;Another issue with Flutter Web is the dynamically generated code in the build output. This behavior gives us CSP errors in the browser. To disable dynamically generated code, use the &lt;code&gt;--csp&lt;/code&gt; flag with the Flutter build command.&lt;/p&gt;

&lt;p&gt;Therefore, to make an optimized Chrome extension using Flutter, the build command should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter build web --web-renderer html --csp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integrate a Codemagic CI/CD pipeline
&lt;/h2&gt;

&lt;p&gt;Codemagic is a CI/CD platform that facilitates the building and deployment of Flutter applications. We can use it to build and deploy our Flutter app for any platform, including the Flutter Web Chrome extension that we've just made. Create a &lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic account&lt;/a&gt; today to get 500 build minutes for free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codemagic.io/signup?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Create a Codemagic account&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to your Codemagic project dashboard and add a new application. Select your repository provider from the pop-up list. My code is hosted on &lt;a href="https://github.com/hrishiksh/flutter-chrome-extension" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, so I am selecting GitHub here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-2.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-2.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, find your project repository in the dropdown menu, and select &lt;em&gt;"Flutter App (via Workflow Editor)"&lt;/em&gt; as the project type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-3.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-3.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the build platform option, select web builds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-chrome-extension-4_16168656978939810064_hubf094ff486635989c26d210464740a09_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-chrome-extension-4_16168656978939810064_hubf094ff486635989c26d210464740a09_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the build argument, add the &lt;code&gt;--web-renderer&lt;/code&gt; and &lt;code&gt;--csp&lt;/code&gt; flags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-5.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-5.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, save your changes and start your Codemagic build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-6.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-6.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can download the generated artifacts and publish to Chrome Web Store. Let's do that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish the Chrome extension to Chrome Web Store
&lt;/h2&gt;

&lt;p&gt;After you've built your Chrome extension, it's possible to automatically publish in Chrome Web Store using a &lt;a href="https://docs.codemagic.io/yaml-publishing/post-publish/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic post-publish script&lt;/a&gt;. Before following the steps in this section, make sure to sign up for a &lt;a href="https://developer.chrome.com/docs/webstore/register/" rel="noopener noreferrer"&gt;Chrome Developer Account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's also important to note that you should publish the extension manually the first time. But afterward, you can automate the deployment of your Chrome extension with Codemagic CI/CD.&lt;/p&gt;

&lt;p&gt;So, let's start by getting the credentials we'll need to publish the extension for the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Acquire the required credentials
&lt;/h3&gt;

&lt;p&gt;Go to the &lt;a href="https://console.developers.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt; and create a new project. In the search bar, search for &lt;em&gt;"Chrome Web Store API"&lt;/em&gt;. Click on the first result and enable the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-webstore-API.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-webstore-API.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the &lt;em&gt;OAuthConsentScreen&lt;/em&gt; and select the user type &lt;em&gt;"External"&lt;/em&gt;. Fill out all the application details in the respective fields, click &lt;em&gt;"Save"&lt;/em&gt;, and continue to the next step. You can skip the "&lt;em&gt;Scopes&lt;/em&gt;" tab and continue. Then add your email as a test user and click &lt;em&gt;"Save and continue"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To get the access key, go to &lt;em&gt;"Credentials"&lt;/em&gt; and select the &lt;em&gt;"Create credentials"&lt;/em&gt; option. From the dropdown list, select &lt;em&gt;"OAuth client ID"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-oauth-clientID.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-oauth-clientID.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the application type, select &lt;em&gt;"Desktop App"&lt;/em&gt;, fill out the name, and click &lt;em&gt;"Create"&lt;/em&gt;. Now, a pop-up containing your &lt;em&gt;client ID&lt;/em&gt; and &lt;em&gt;client secret&lt;/em&gt; should appear. You can download the JSON file and save it in a secure place.&lt;/p&gt;

&lt;p&gt;To work with the Chrome Web Store API, you'll need to have an access token. To get the access token, paste this URL &lt;code&gt;https://accounts.google.com/o/oauth2/auth?response_type=code&amp;amp;scope=https://www.googleapis.com/auth/chromewebstore&amp;amp;client_id=$CLIENT_ID&amp;amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&lt;/code&gt; in your browser, and replace &lt;code&gt;$client_ID&lt;/code&gt; with our app's client ID.&lt;/p&gt;

&lt;p&gt;When your browser resolves the URL, a Google OAuth page appears. Sign in with the email you have set as the test user inside the &lt;em&gt;OAuthConsentScreen&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After you have been successfully authorized, you will get an authorization code. We'll use this code to request an access token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-oauth-success.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-oauth-success.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To generate the access token, you have to make a request with your client ID, client secret, and the authorization code we've found in the previous step. I am using &lt;code&gt;curl&lt;/code&gt; for this purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "https://accounts.google.com/o/oauth2/token" -d\
"client_id=$CLIENT_ID&amp;amp;client_secret=$CLIENT_SECRET&amp;amp;code=$CODE&amp;amp;grant_type=authorization_code&amp;amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;$CLIENT_ID&lt;/code&gt;, &lt;code&gt;$CLIENT_SECRET&lt;/code&gt;, and &lt;code&gt;$CODE&lt;/code&gt; with their respective values. The response to this request should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "access_token" : "ya29.a0Aa4xrXO...",
  "expires_in" : 3599,
  "refresh_token" : "1//0gZyg...",
  "scope": "https://www.googleapis.com/auth/chromewebstore",
  "token_type" : "Bearer",
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use this access token to interact with the Chrome Web Store Publish API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publish the Chrome extension using the Chrome Web Store API
&lt;/h3&gt;

&lt;p&gt;Before publishing, make sure to enable two-step authentication in your Google account.&lt;/p&gt;

&lt;p&gt;Then go to your &lt;a href="https://chrome.google.com/webstore/devconsole/48ec8d0c-d8c0-402a-b6cc-0487d0887366" rel="noopener noreferrer"&gt;Chrome Web Store dashboard&lt;/a&gt; and create a new item. Fill out the store listing and privacy practices page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-storelisting.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-storelisting.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-privacypage.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-chrome-extension-privacypage.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After properly setting things up, we are ready to publish our extension to Chrome Web Store. I am using &lt;code&gt;curl&lt;/code&gt; to make the request to the Chrome Web Store API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; curl\
-H "Authorization: Bearer $TOKEN"\
-H "x-goog-api-version: 2"\
-X POST\
-T $FILE_NAME\
-v\
https://www.googleapis.com/upload/chromewebstore/v1.1/items

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, fill in &lt;code&gt;$TOKEN&lt;/code&gt; and &lt;code&gt;$FILE_NAME&lt;/code&gt; with the access token and the ZIP file with the extension, respectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to automate publishing a Chrome extension with a Codemagic post-publish script
&lt;/h3&gt;

&lt;p&gt;After finishing building our Flutter project with Codemagic, click on the gear icon at the end to reveal the post-publish script section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-chrome-extension-postpublish_16428777477623384463_hu982e41909e18b1cdcc484fc710154e05_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-chrome-extension-postpublish_16428777477623384463_hu982e41909e18b1cdcc484fc710154e05_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To access the generated artifacts from the build stage, add this script inside the post-publish script section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARTIFACT_TYPE=".zip"
ARTIFACT_URL=$(echo $CM_ARTIFACT_LINKS | jq -r '.[] | select(.name | endswith("'"$ARTIFACT_TYPE"'")) | .url')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;code&gt;ARTIFACT_URL&lt;/code&gt; contains the URL to the generated ZIP file in the build stage.&lt;/p&gt;

&lt;p&gt;Now we can use this URL in our &lt;code&gt;curl&lt;/code&gt; script to directly upload to Chrome Web Store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl\
-H "Authorization: Bearer $TOKEN"\
-H "x-goog-api-version: 2"\
-X POST\
--url $ARTIFACT_URL
-v\
https://www.googleapis.com/upload/chromewebstore/v1.1/items

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we replace the &lt;code&gt;-T&lt;/code&gt; flag from the previous curl script with the &lt;code&gt;--url&lt;/code&gt; flag to directly upload the generated artifact. Now, whenever you build your extension, it automatically uploads the project to Chrome Web Store.&lt;/p&gt;

&lt;p&gt;You can use this &lt;a href="https://developer.chrome.com/docs/webstore/using_webstore_api/" rel="noopener noreferrer"&gt;official guide&lt;/a&gt; to explore more use cases of the Chrome Web Store API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is a beginner's guide to making a Google Chrome extension using Flutter Web. If you have followed all the steps, by now, you've learned how to configure the different files of your Flutter Web app so that it can be a Chrome extension, build the app, publish it to Chrome Web Store, and automate the process with Codemagic CI/CD.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://github.com/hrishiksh/flutter-chrome-extension" rel="noopener noreferrer"&gt;sample project for making a Chrome extension with Flutter&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=chrome-ext-fl-we-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is a CI/CD tool for Flutter and other mobile developers.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>flutter</category>
      <category>dart</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dr. Riverpod: How I learned to stop worrying and love state management</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 13:50:46 +0000</pubDate>
      <link>https://forem.com/codemagicio/dr-riverpod-how-i-learned-to-stop-worrying-and-love-state-management-4c4f</link>
      <guid>https://forem.com/codemagicio/dr-riverpod-how-i-learned-to-stop-worrying-and-love-state-management-4c4f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is written by Alessio Salvadorini and originally posted &lt;a href="https://blog.codemagic.io/flutter-state-management-part-1/?utm_source=devto&amp;amp;utm_medium=mr-riverpod-article-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;State management is a very controversial topic in the Flutterverse, with many holding strong opinions on it.&lt;/p&gt;

&lt;p&gt;In the last few weeks, my Twitter feed has been overwhelmed with threads about state management, covering the entire spectrum of opinions, from GetX/BLoC/Riverpod fanatics on one side to a few &lt;a href="https://twitter.com/MatanLurey/status/1569428738764570624?s=20&amp;amp;t=oo0GhEowBANGVZgmtC7ooQ"&gt;rare enlightened ones&lt;/a&gt; on the other side who recommend not using any state management at all.&lt;/p&gt;

&lt;p&gt;While I personally enjoyed almost all of them, the impact on the Flutter community has been quite severe.&lt;/p&gt;

&lt;p&gt;When I asked my readers about writing this article in a typical TDD (Twitter-Driven Development, ©® &lt;a href="https://twitter.com/mkobuolys"&gt;@mkobuolys&lt;/a&gt;) approach, about a third &lt;a href="https://twitter.com/ASalvadorini/status/1572411146501750785?s=20&amp;amp;t=uDcN2vtvMJ4Y9SwoD4mp6A"&gt;voted no&lt;/a&gt;, and half of them were so fed up with state management that they recommended that I shoot myself, metaphorically speaking.&lt;/p&gt;

&lt;p&gt;So, keep all this in mind while I walk you through this article about state management to show you how to choose the best state management solution for Flutter at the end of 2022 (and perhaps in 2023 as well). It's worth noting that this advice is based entirely on my own opinion, and you're always welcome to disagree. However, we first need to understand how and why we ended up in this situation, so bear with me for a few minutes longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state of Flutter state management: How did we get here?
&lt;/h2&gt;

&lt;p&gt;If you haven't done so yet, I strongly recommend you watch these two videos: the &lt;a href="https://www.youtube.com/watch?v=RS36gBEp8OI"&gt;first&lt;/a&gt; shows &lt;a href="https://twitter.com/filiphracek"&gt;Filip Hracek&lt;/a&gt; and &lt;a href="https://twitter.com/mjohnsullivan"&gt;Matt Sullivan&lt;/a&gt; on the Google I/O stage in May 2018 introducing BLoC (the pattern) to all of us; the &lt;a href="https://www.youtube.com/watch?v=d_m5csmrf7I"&gt;second&lt;/a&gt; shows the same Filip and Matt on the very same stage but in May 2019, exactly one year later, introducing pragmatic state management.&lt;/p&gt;

&lt;p&gt;However, in between those two videos, I think something important yet fleeting happened. A big misunderstanding was born, and it impacted our community more than I initially realized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yN4Iq7ss--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/uploads/2022/10/dr-riverpod-sm-how-it-started.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yN4Iq7ss--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/uploads/2022/10/dr-riverpod-sm-how-it-started.png" alt="Filip and Matt at Google I/O 2018" title="Google I/O 2018" width="880" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To understand the context at the time when the first video was released, we need to remember that the community was already asking Google, and the Flutter team in particular, for a recommended approach for state management. Also, Flutter wasn't as relevant as it is nowadays. This meant that very few videos at Google I/O were about Flutter, so one about state management felt even more relevant --- or at least, the community perceived it to be.&lt;/p&gt;

&lt;p&gt;Shortly after the first video was released, a talented developer who needs no introduction, &lt;a href="https://twitter.com/felangelov"&gt;Felix Angelov&lt;/a&gt;, started to develop &lt;a href="https://pub.dev/packages/flutter_bloc"&gt;BLoC&lt;/a&gt; (the package).&lt;/p&gt;

&lt;p&gt;It quickly became the de facto standard for state management, one of the most used packages by individual developers and big companies, and the subject of mandatory questions at job interviews. Basically, it was essential for anyone who wanted to become a Flutter developer to understand it.&lt;/p&gt;

&lt;p&gt;This happened around the time when I was starting to look into Flutter. I quickly reached a point when the more I was coding in Android, the more I wanted to code in Flutter, and the more I was coding in Flutter, the more I wanted to code in Flutter. So, by the time I tried to wrap my head around BLoC (the pattern) and BLoC (the library), the second video was released, introducing what immediately felt like a much simpler approach to state management.&lt;/p&gt;

&lt;p&gt;But, as the demand for a state management solution had already been met (or at least the community thought so), and everyone was already using BLoC (the package), I think the second video went almost unnoticed.&lt;/p&gt;

&lt;p&gt;It does, however, contain this little gem starting at around &lt;a href="https://youtu.be/d_m5csmrf7I?t=1162"&gt;19:20&lt;/a&gt;, where Filip and Matt mention that even though Google also open sourced a similar package based on scope model, they would instead recommend Provider, a library created by a young and talented French developer, &lt;a href="https://twitter.com/remi_rousselet"&gt;Remi Rousselet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And Provider wasn't the only alternative to BLoC (the package) because many other options were also flourishing: Redux (which was already familiar to developers coming from JavaScript), get_it, MobX, the controversial GetX, and many, many more.&lt;/p&gt;

&lt;p&gt;You can find a non-exhaustive list in the &lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/options"&gt;official Flutter docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kKHGYrO9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/uploads/2022/10/dr-riverpod-sm-how-it-is-going.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kKHGYrO9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/uploads/2022/10/dr-riverpod-sm-how-it-is-going.png" alt="Filip and Matt at Google I/O 2019" title="Google I/O 2019" width="880" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given all that, it's quite easy to understand how, by the end of 2022, we're still in a messy and unclear situation.&lt;/p&gt;

&lt;p&gt;It's frustratingly difficult for beginners and new developers approaching Flutter to make a choice unless they have lots of time to spend learning about the topic.&lt;/p&gt;

&lt;p&gt;The Flutter team has been mainly silent about this topic, and I don't blame them, as they can't (and, IMHO, shouldn't) suggest one state management technique that would be suitable for all kinds of apps, nor can they add one directly into the SDK.&lt;/p&gt;

&lt;p&gt;The only way to resolve this situation is to end at the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn't this where we came in?
&lt;/h2&gt;

&lt;p&gt;Believe me or not: Not all states need a state management technique. A state can be classified into one of two distinct types: &lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/ephemeral-vs-app"&gt;ephemeral or application state&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z1UF-zjO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/dr-riverpod-sm-when-to-use-it_14858182863452772886_huf27a411e689d61e3ed97a42a877ace75_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z1UF-zjO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/dr-riverpod-sm-when-to-use-it_14858182863452772886_huf27a411e689d61e3ed97a42a877ace75_0_1280x1800_fit_linear_3.png" alt="When to use state management" title="When to use state management" width="880" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of the ephemeral state as a state that is self-contained into a widget, which doesn't need to be shared or accessed from anywhere else.&lt;/p&gt;

&lt;p&gt;An example of an ephemeral state is the index of the actual page in a page viewer. For an ephemeral state, you don't need any state management technique, and setState() and StatefulWidget work perfectly fine for handling this case.&lt;/p&gt;

&lt;p&gt;Think of the application state as a state that is shared between the different views of your app or that needs to be restored between two separate user sessions.&lt;/p&gt;

&lt;p&gt;In this case, you need to move the state away from your view and into a state holder class. Examples of an application state are the settings or the login.&lt;/p&gt;

&lt;p&gt;For an application state, you do need a state management technique.&lt;/p&gt;

&lt;p&gt;Since you are going to pick only one, you want to choose carefully. And to choose carefully, you need to be able to compare your options. And to be able to compare, we need to agree on the metrics of the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing metrics: Measuring how good a state management solution is
&lt;/h2&gt;

&lt;p&gt;The only way we can choose which one is the "best" state management solution is to agree on how we define "best."&lt;/p&gt;

&lt;p&gt;There are many different types of apps, and more will come in the future that we can't even imagine now. Therefore, it's practically impossible to suggest one state management technique to rule them all.&lt;/p&gt;

&lt;p&gt;So far, I have found only two sources that have helped me compare state management techniques.&lt;/p&gt;

&lt;p&gt;The first source is by my mate &lt;a href="https://twitter.com/RydMike"&gt;Mike Rydstrom&lt;/a&gt;, who created an exhaustive &lt;a href="https://twitter.com/RydMike/status/1578462043593535488?s=20&amp;amp;t=r3QQS6Vd1D5VLGeeY5qbLA"&gt;picture&lt;/a&gt; comparing the top 30 state management packages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hJTT-Ggm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/_hu4a0a44225277f7d1cbe1b0a1831efdec_0_19137d11047704fb55637c1fbae704da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hJTT-Ggm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/_hu4a0a44225277f7d1cbe1b0a1831efdec_0_19137d11047704fb55637c1fbae704da.png" alt="Mike's state managements comparison" title="Mike's state managements comparison" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, this picture ranks the packages by package likes. It also includes extra information, like if a given package is null safe (NS), the percentage of test coverage (CodeCov), the percentage of API docs' completeness (API docs), a summary (Points), the package's popularity, GitHub stars, the ratio of likes to stars, and the ratio of open to closed issues.&lt;/p&gt;

&lt;p&gt;That's a lot of info to crunch for one single picture.&lt;/p&gt;

&lt;p&gt;It's a good start, with Provider, BLoC (the package), get_it, and Riverpod placing in the top rankings (2, 3, 4, and 5, respectively). This is all fine until you look at the highest-ranked package, get (formally known as GetX), which, for me, is an anti-pattern and a no-go.&lt;/p&gt;

&lt;p&gt;The second source is a bachelor's thesis titled &lt;a href="https://www.theseus.fi/bitstream/handle/10024/355086/Dmitrii_Slepnev.pdf"&gt;"State management approaches in Flutter"&lt;/a&gt; by Dmitrii Slepnev, published in December 2020.&lt;/p&gt;

&lt;p&gt;In this thesis, Slepnev categorizes some of the most common state management techniques. He chooses six criteria, some of which are still subject to opinion, but they are well defined and easy to understand: complexity, amount of boilerplate code, code generation, time travel support, scalability, and testability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mLDIkc1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/_hu7cd08fc6850691d36f66e01d29da70da_0_23d2040f0edd22d9e3deb7ebd957ec6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mLDIkc1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/_hu7cd08fc6850691d36f66e01d29da70da_0_23d2040f0edd22d9e3deb7ebd957ec6e.png" alt="Slepnev's state managements comparison" title="Slepnev's state management comparison" width="880" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An intuitive summary is provided in Table 11 of the study.&lt;/p&gt;

&lt;p&gt;As you can see, this table makes comparing state management techniques easy, and it may help you to choose one based on such metrics. Likewise, after you choose your state management technique, this table could help you profile it.&lt;/p&gt;

&lt;p&gt;Now that we have defined at least some metrics based on these two sources, let's see how to apply them.&lt;/p&gt;

&lt;h2&gt;
  
  
  And the winner is...
&lt;/h2&gt;

&lt;p&gt;Just like many of you at this point, I was initially confused and overwhelmed by all the possibilities and alternatives we have when it comes to state management in Flutter.&lt;/p&gt;

&lt;p&gt;This is definitely a healthy sign, as it clearly indicates that Flutter is developing, but it could also lead to a negative downward spiral.&lt;/p&gt;

&lt;p&gt;So, I looked into and tried some of the different options. I strongly recommend you try them out too and not take anyone's opinion for granted, not even mine.&lt;/p&gt;

&lt;p&gt;Try to implement and re-implement a sample app with different state management techniques, compare them, and see which one feels better to you and for what reasons. If possible, try to make the metrics explicit.&lt;/p&gt;

&lt;p&gt;There is also a non-official &lt;a href="https://fluttersamples.com/"&gt;code sample app&lt;/a&gt; that compares some of the alternatives.&lt;/p&gt;

&lt;p&gt;For me, simplicity was the key, as simplicity is one of the most important values in my life.&lt;/p&gt;

&lt;p&gt;After I tried some of them, I decided which one was the best for me: Provider/Riverpod. This choice was supported by Table 11 in Slepnev's study. Since I prefer simplicity, I want the complexity to be as low as possible. Additionally, I want minimal boilerplate code, I don't want any code generation, time travel is irrelevant to me, and scalability and testability must be decently good.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find a tutorial that will help you get started with Riverpod &lt;a href="https://blog.codemagic.io/flutter-state-management-with-riverpod/?utm_source=devto&amp;amp;utm_medium=mr-riverpod-article-devto&amp;amp;utm_campaign=flutter_distribution"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This decision is also confirmed by Mike's picture, as I want a well-maintained package.&lt;/p&gt;

&lt;p&gt;Last but not least, this choice is supported by the official Flutter docs: &lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple"&gt;Simple state management&lt;/a&gt; is the only officially documented state management technique.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is simple state management?
&lt;/h2&gt;

&lt;p&gt;Simple state management, aka pragmatic state management, is based on Provider+ChangeNotifier. It's well documented, and there's a &lt;a href="https://github.com/flutter/samples/tree/main/provider_shopper"&gt;code sample&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In case you don't know, Riverpod is a second take on Provider made by the same author, Remi Rousselet. You can think of Riverpod as a "Provider 2.0," a better version of Provider. If you're still using Provider, you should migrate to Riverpod, as it's very straightforward.&lt;/p&gt;

&lt;p&gt;So, you can easily implement simple state management nowadays with Riverpod and ChangeNotifier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vmRSG6B1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/dr-riverpod-sm-simple-state-management_13268151069072349849_hu8704f1391c75cdccc6f4f8f531148262_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vmRSG6B1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.codemagic.io/dr-riverpod-sm-simple-state-management_13268151069072349849_hu8704f1391c75cdccc6f4f8f531148262_0_1280x1800_fit_linear_3.png" alt="Simple state management" title="Simple state management" width="880" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: ChangeNotifier, ValueNotifier (from SDK), and StateNotifier (from Remi) are all interchangeable. Since they all accomplish the same thing, you can use the one you prefer as long as you know what you're doing and why.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simple state management is very simple. It is built on three pillars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The state holder class encapsulates the application state. It's responsible for changing the state and notifying about state changes; this is done with the ChangeNotifier class.&lt;/li&gt;
&lt;li&gt;  The UI needs to have access to your state holder class (the ChangeNotifier); this is done through Riverpod with its provider classes (ChangeNotifierProvider, ValueNotifierProvider, StateNotifierProvider, etc.).&lt;/li&gt;
&lt;li&gt;  The UI needs to consume the state changes; this is done through Riverpod with its consumer classes (Consumer, ConsumerWidget, ConsumerStatefulWidget).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Build your Flutter apps faster with Apple M1 VMs. &lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=mr-riverpod-article-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Learn more&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article is an extension of part of &lt;a href="https://twitter.com/ASalvadorini/status/1567189799202705411?s=20&amp;amp;t=Ydm_1uhBQBVqz3bbGLHG8A"&gt;my talk&lt;/a&gt; for Flutter Vikings 2022.&lt;/p&gt;

&lt;p&gt;In this article, I gave you some reasons why state management is a complex topic for Flutter and some metrics that can help you decide between the many options available. I recommend that you choose the safest bet (Riverpod) and the metric used (simplicity).&lt;/p&gt;

&lt;p&gt;Now that you've chosen your state management technique, though, keep in mind that it's not enough to create a scalable and robust app in a production environment. You'll need a whole architecture.&lt;/p&gt;

&lt;p&gt;Luckily, Riverpod is much more than a tool for state management; it's &lt;a href="https://riverpod.dev/"&gt;a reactive caching and data-binding framework&lt;/a&gt;, and we can build the whole app architecture around Riverpod. Curious to know how?&lt;/p&gt;

&lt;p&gt;I kept this article codeless on purpose, as I'll follow up with a deep dive into architecture. So if you're interested, keep an eye on this blog. I'll keep you posted!&lt;/p&gt;

&lt;p&gt;To be continued...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=mr-riverpod-article-devto&amp;amp;utm_campaign=flutter_distribution"&gt;Codemagic&lt;/a&gt; is a CI/CD tool for Flutter and other mobile developers.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>Flutter widgets cheat sheet</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 13:43:05 +0000</pubDate>
      <link>https://forem.com/codemagicio/flutter-widgets-cheat-sheet-33an</link>
      <guid>https://forem.com/codemagicio/flutter-widgets-cheat-sheet-33an</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is written by Godwin Alexander Ekainu and originally posted to &lt;a href="https://blog.codemagic.io/flutter-widget-cheat-sheet/?utm_source=devto&amp;amp;utm_medium=flutter-cheatsheet-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flutter is an open-source tool designed to build fast and beautiful applications across multiple platforms. The Flutter SDK has been widely adopted for developing mobile applications, and many developers are learning Flutter every day. It is important to create content that can help them do this, and that's the aim of this blog post! So, we've prepared a simple cheat sheet of different Flutter widgets (and in Flutter, everything is a widget!), which you can use to build your Flutter apps.&lt;/p&gt;

&lt;p&gt;We've grouped the widgets into several categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Interaction widgets. These help make your Flutter app interactive.&lt;/li&gt;
&lt;li&gt; Input widgets. These widgets are used to handle user input.&lt;/li&gt;
&lt;li&gt; Alignment and layout widgets. You use these widgets everywhere to position other widgets on the screen relative to one another and organize them into structures.&lt;/li&gt;
&lt;li&gt; Scrollable widgets. These widgets come in handy when you need to create scrollable lists and galleries.&lt;/li&gt;
&lt;li&gt; Structure widgets. These are the foundational widgets for your app.&lt;/li&gt;
&lt;li&gt; Paint widgets. These widgets allow you to customize the design of your app.&lt;/li&gt;
&lt;li&gt; Button widgets. These are, as you might have guessed, for creating buttons.&lt;/li&gt;
&lt;li&gt; Basic navigation. To navigate between screens, you'll need navigation. We'll provide a basic example of navigation in this article.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you don't understand the concept of widgets, you can refer to our &lt;a href="https://www.youtube.com/watch?v=Y_3BHX9Uww8&amp;amp;list=PLrAxI-YVFPOqjARFSbxWe5_2XDT40fZn6&amp;amp;index=4&amp;amp;t=189s" rel="noopener noreferrer"&gt;&lt;em&gt;Flutter From Scratch&lt;/em&gt;&lt;/a&gt; video on this topic.&lt;/p&gt;

&lt;p&gt;How do you use this Flutter widgets cheat sheet? Just copy-paste the code for the widgets you want to use in your app, and modify it to incorporate the logic you need. Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Interaction widgets
&lt;/h2&gt;

&lt;p&gt;Interaction widgets make your application dynamic and provide a good user experience, so it's essential to understand how to use them.&lt;/p&gt;

&lt;h3&gt;
  
  
  GestureDetector
&lt;/h3&gt;

&lt;p&gt;GestureDetector is a widget that responds to events or gestures that correspond to its non-callbacks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;onTap&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onTapUp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onTapDown&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onLongPress&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onDoubleTap&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onHorizontalDragStart&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onVerticalDragDown&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onPanDown&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;onScaleStart&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fgesturewidget-1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fgesturewidget-1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use this gesture detector to create custom buttons or clickable text or pictures.&lt;/p&gt;

&lt;p&gt;To add a gesture detector, use the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    GestureDetector(

      onTap: () {
        const snackBar = SnackBar(content: Text('Tap'));

        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      },
      // The custom button
      child: Container(
        padding: const EdgeInsets.all(12.0),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          borderRadius: BorderRadius.circular(8.0),
        ),
        child: const Text('My Button'),
      ),
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AlertDialog
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fdialogwidget_13619648723351944144_huebb6c3db760e71da697d69c329e419e5_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fdialogwidget_13619648723351944144_huebb6c3db760e71da697d69c329e419e5_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AlertDialog widget creates a window to display crucial information with options that allow the user to make decisions.&lt;/p&gt;

&lt;p&gt;To add an alert dialog to your app, you can use the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     Future&amp;lt;void&amp;gt; _showMyDialog() async {
      return showDialog&amp;lt;void&amp;gt;(
        context: context,
        barrierDismissible: false, // user must tap button!
        builder: (BuildContext context) {
          return AlertDialog(
            title: const Text('Cheat Sheet'),
            content: SingleChildScrollView(
              child: ListBody(
                children: const &amp;lt;Widget&amp;gt;[
                  Text('This is a demo alert dialog.'),
                  Text('Would you like to approve of this message?'),
                ],
              ),
            ),
            actions: &amp;lt;Widget&amp;gt;[
              TextButton(
                child: const Text('Approve'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        },
      );
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SnackBar
&lt;/h3&gt;

&lt;p&gt;The SnackBar widget is used to briefly tell a user that an action has taken place. For example, you can delete an item that triggers a snackbar, which tells the user that an item has just been deleted.&lt;/p&gt;

&lt;p&gt;To add a snackbar to your application, use the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Center(
            child: ElevatedButton(
              onPressed: () {
              final snackBar =  SnackBar(
                content: const Text('Yay! A SnackBar!'),
                action: SnackBarAction(
                  label: 'Undo',
                  onPressed: () {
                   Text('data');
                  },
                ),
              );
                  ScaffoldMessenger.of(context).showSnackBar(snackBar);
              },
              child: const Text('Show snackbar'),
            ),
          ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dismissable
&lt;/h3&gt;

&lt;p&gt;You can use a Dismissable widget to remove or dismiss items from a list. You can swipe left or right to remove any item.&lt;/p&gt;

&lt;p&gt;Use the code below to implement this feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    List&amp;lt;String&amp;gt; items = &amp;lt;String&amp;gt;[
        'banana',
        'strawberry',
        'apple'
        'orange'
        'cat'
        'bobcat'
      ];
    ListView.builder(
              itemCount: items.length,
              padding: const EdgeInsets.symmetric(vertical: 16),
              itemBuilder: (BuildContext context, index) {
                return Dismissible(
                  background: Container(
                    color: Colors.green,
                  ),
                  key: Key(items[index]),
                  onDismissed: (DismissDirection direction) {
                    setState(() {
                      items.removeAt(index);
                    });
                  },
                  child: ListTile(
                    title: Text(
                      'Item ${items[index]}',
                    ),
                  ),
                );
              },
            ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  InteractiveViewer
&lt;/h3&gt;

&lt;p&gt;If you have a big picture that doesn't fit on the screen, you can use the InteractiveViewer widget to enable it to fit on the screen by allowing the user to zoom in and out of the picture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    InteractiveViewer(
            boundaryMargin: const EdgeInsets.all(20.0),
            child: Container(
              decoration: const BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: &amp;lt;Color&amp;gt;[Colors.blue, Colors.yellow],
                  stops: &amp;lt;double&amp;gt;[0.0, 1.0],
                ),
              ),
            ),
          ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Input widgets
&lt;/h2&gt;

&lt;p&gt;Input widgets are a very important part of modern-day Flutter applications. For example, they are used to create an account, log in, or even perform a simple search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter forms
&lt;/h3&gt;

&lt;p&gt;You can create a login and signup form using these widgets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fformfieldwidget-1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fformfieldwidget-1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try creating a form using the Forms widget and TextFormFields widget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        const appTitle = 'Flutter Form Demo';
        return MaterialApp(
          title: appTitle,
          home: Scaffold(
            appBar: AppBar(
              title: const Text(appTitle),
            ),
            body: const MyCustomForm(),
          ),
        );
      }
    }
    // Create a Form widget
    class MyCustomForm extends StatefulWidget {
      const MyCustomForm({Key? key}) : super(key: key);
      @override
      MyCustomFormState createState() {
        return MyCustomFormState();
      }
    }
    class MyCustomFormState extends State&amp;lt;MyCustomForm&amp;gt; {
      final formKey = GlobalKey&amp;lt;FormState&amp;gt;();
      @override
      Widget build(BuildContext context) {
        // Build a Form widget using the formKey created above
        return Form(
          key: formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: &amp;lt;Widget&amp;gt;[
              TextFormField(
                decoration: const InputDecoration(
                  icon:  Icon(Icons.person),
                  hintText: 'Enter your name',
                  labelText: 'Name',
                ),
              ),
              TextFormField(
                decoration: const InputDecoration(
                  icon: Icon(Icons.calendar_today),
                  hintText: 'Enter your date of birth',
                  labelText: 'Dob',
                ),
              ),
              const ElevatedButton(
                child:  Text('Submit'),
                onPressed: null,
              ),
            ],
          ),
        );
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Autocomplete
&lt;/h3&gt;

&lt;p&gt;This widget can come in handy in search boxes.&lt;/p&gt;

&lt;p&gt;To add autocomplete to your Flutter application, you can use the following code snippet. The options to be displayed are determined by the &lt;code&gt;optionsBuilder&lt;/code&gt; and rendered by the &lt;code&gt;optionsViewBuilder&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // have a list of words or names from a remote source or package
    const List&amp;lt;String&amp;gt; _kOptions = &amp;lt;String&amp;gt;[
        'aardvark',
        'bobcat',
        'chameleon',
      ];
    // use the Autocomplete widget to make selections
    Autocomplete&amp;lt;String&amp;gt;(
          optionsBuilder: (TextEditingValue textEditingValue) {
            if (textEditingValue.text == '') {
              return const Iterable&amp;lt;String&amp;gt;.empty();
            }
            return _kOptions.where((String option) {
              return option.contains(textEditingValue.text.toLowerCase());
            });
          },
          onSelected: (String selection) {
            debugPrint('You just selected $selection');
          },
        );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TextField
&lt;/h3&gt;

&lt;p&gt;TextField is one of the most fundamental and commonly used widgets for keyboard input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    TextField(
      obscureText: true,
      decoration: InputDecoration(
        border: OutlineInputBorder(),
        labelText: 'Password',
      ),
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Alignment and layout widgets cheat sheet
&lt;/h2&gt;

&lt;p&gt;These widgets are very important for building UIs in Flutter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Center
&lt;/h3&gt;

&lt;p&gt;This widget centers a child within itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Center( child: Text('Center me'))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expanded
&lt;/h3&gt;

&lt;p&gt;The Expanded widget expands the child of a Row or Column to fill any available space.&lt;/p&gt;

&lt;p&gt;For example, you can use it to build a UI that fills the screen with just three containers or pictures. Theoretically, we can just specify their heights, but remember that there are different screen sizes for different devices, including tablets such as iPad devices. The expanded widget can help you create a more responsive design that can easily fit all screen sizes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Row(
              children: &amp;lt;Widget&amp;gt;[
                Expanded(
                  flex: 2,
                  child: Container(
                    color: Colors.amber,
                    height: 100,
                  ),
                ),
                Container(
                  color: Colors.blue,
                  height: 100,
                  width: 50,
                ),
                Expanded(
                  child: Container(
                    color: Colors.amber,
                    height: 100,
                  ),
                ),
              ],
            ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Align
&lt;/h3&gt;

&lt;p&gt;The Align widget aligns its child within itself. This widget is very flexible and can take the size of its child.&lt;/p&gt;

&lt;p&gt;The child can be placed at different points within its parent widget using the &lt;code&gt;alignment property&lt;/code&gt;, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Container(
        height: 120.0,
        width: 120.0,
        color: Colors.blue[50],
        child: const Align(
          alignment: Alignment.topRight,
          child: FlutterLogo(
            size: 60,
          ),
        ),
      ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Row
&lt;/h3&gt;

&lt;p&gt;The Row widget takes a list of widgets and arranges them horizontally. You will likely use this widget a lot when making layouts in your code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Row(
            children: [
              ElevatedButton(
                child: const Text('Widget 1'),
                onPressed: () =&amp;gt; Navigator.pushNamed(context, '/second'),
              ),
               ElevatedButton(
                child: const Text('Widget 2'),
                onPressed: () =&amp;gt; Navigator.pushNamed(context, '/third'),
              ),
               ElevatedButton(
                child: const Text('Widget 3'),
                onPressed: () =&amp;gt; Navigator.pushNamed(context, '/fourth'),
              ),
            ],
          ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Column
&lt;/h3&gt;

&lt;p&gt;The Column widget allows you to arrange a list of widgets vertically, similar to how the Row widget aligns them horizontally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Column(
      children: const &amp;lt;Widget&amp;gt;[
        Text('Deliver features faster'),
        Text('Craft beautiful UIs'),
        Expanded(
          child: FittedBox(
            fit: BoxFit.contain,
            child: FlutterLogo(),
          ),
        ),
      ],
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SizedBox
&lt;/h3&gt;

&lt;p&gt;Use SizedBox to give a specific size to its child widget or provide some white space between widgets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const SizedBox(
      width: 200.0,
      height: 300.0,
      child: Card(child: Text('Size me!')),
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BaseLine
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fbaselinewidget-1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fbaselinewidget-1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the BaseLine widget to position the child widget according to the starting point of the parent widget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Center(
          child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.green,
                  child: Baseline(
                    baseline: 50,
                    baselineType: TextBaseline.ideographic,
                    child: Container(
                      width: 50,
                      height: 50,
                      color: Colors.purple,
                    ),
                  ),
                ),
        );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  LimitedBox
&lt;/h3&gt;

&lt;p&gt;Use this widget to assign a default size to a list of widgets that are unconstrained.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    LimitedBox(
             maxHeight: 150,
             maxWidth: 150,
             child: Container(
             color: Colors.red,
            )
           )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Padding
&lt;/h3&gt;

&lt;p&gt;Use the Padding widget to provide space around its child. The padding widget adds space around its child using the abstract &lt;code&gt;EdgeInsetsGeometry&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;The padding property of the padding widget accepts the &lt;code&gt;EdgeInsets&lt;/code&gt; object, which allows you to add different types of padding, including padding only in certain places using &lt;code&gt;EdgeInsets.only&lt;/code&gt; or from different angles using &lt;code&gt;EdgeInsets.fromLTRB&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const Card(
      child: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text('Hello World!'),
      ),
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Scrollable widgets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ListView Builder
&lt;/h3&gt;

&lt;p&gt;ListView Builder is the most used scrollable widget. It displays its children in the scroll direction one after the other. For example, if you have a long list that is dynamically created, you can use &lt;code&gt;Listview.builder&lt;/code&gt; to display it.&lt;/p&gt;

&lt;p&gt;Use the following code to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    final List&amp;lt;String&amp;gt; entries = &amp;lt;String&amp;gt;['A', 'B', 'C'];
    ListView.builder(
      padding: const EdgeInsets.all(8),
      itemCount: entries.length,
      itemBuilder: (BuildContext context, int index) {
        return Container(
          height: 50,
          color: Colors.amber[colorCodes[index]],
          child: Center(child: Text('Entry ${entries[index]}')),
        );
      }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SingleChildScrollView
&lt;/h3&gt;

&lt;p&gt;The SingleChildScrollView widget will come in handy if you have a list of items that you initially didn't intend for the user to scroll through, but the requirements change upon implementation. In this case, you can quickly wrap the parent widget with a &lt;code&gt;SingleChildScrollView&lt;/code&gt; to make it scrollable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: &amp;lt;Widget&amp;gt;[
              Container(
                color:  Colors.red,
                height: 300.0,
                alignment: Alignment.center,
                child: const Text('Fixed Height Content'),
              ),
              Container(
                color: Colors.blue,
                height: 250.0,
                alignment: Alignment.center,
                child: const Text('Fixed Height Content'),
              ),
              Container(
                color: Colors.green,
                height: 250.0,
                alignment: Alignment.center,
                child: const Text('Fixed Height Content'),
              ),
              Container(
                color: Colors.purple,
                height: 250.0,
                alignment: Alignment.center,
                child: const Text('Fixed Height Content'),
              ),
            ],
          ),
        );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Structure widgets
&lt;/h2&gt;

&lt;p&gt;These are the widgets that compose your app. In this Flutter widget cheat sheet, we will only be highlighting two of them: Scaffold and Loader.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffold
&lt;/h3&gt;

&lt;p&gt;Use Scaffold widgets for all your application layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Scaffold(
          appBar: AppBar(),
          body: Row(
            children: [
              ElevatedButton(
                child: const Text('Widget 1'),
                onPressed: () =&amp;gt; Navigator.pushNamed(context, '/second'),
              ),
          ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Loader
&lt;/h3&gt;

&lt;p&gt;Create Loaders for your app when making an API request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    class SomeWidget extends StatefulWidget {
      @override
      _SomeWidgetState createState() =&amp;gt; _SomeWidgetState();
    }
    class _SomeWidgetState extends State&amp;lt;SomeWidget&amp;gt; {
      late Future future;
      @override
      void initState() {
        future = Future.delayed(const Duration(seconds: 8));
        super.initState();
      }
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: future,
          builder: (context, snapshot) {
            return snapshot.connectionState == ConnectionState.done
                ? const Text('Loaded')
                : const CircularProgressIndicator();
          },
        );
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Paint widgets
&lt;/h2&gt;

&lt;p&gt;Painting effects can give a finished look to your app's design.&lt;/p&gt;

&lt;h3&gt;
  
  
  InkWell
&lt;/h3&gt;

&lt;p&gt;With the InkWell widget, you can create ink splash effects on images or custom buttons when you click on them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    class _SomeWidgetState extends State&amp;lt;SomeWidget&amp;gt; {
      @override
      Widget build(BuildContext context) {
        Image img = Image.network(
            'https://images.unsplash.com/photo-1592194996308-7b43878e84a6?ixlib=rb-1.2.1&amp;amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;amp;auto=format&amp;amp;fit=crop&amp;amp;w=687&amp;amp;q=80');
        return Material(
          child: Ink.image(
            fit: BoxFit.fill,
            width: 300,
            height: 300,
            image: img.image,
            child: InkWell(
              onTap: () {
                print('image');
              },
              child: const Align(
                child: Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Text(
                    'PUFFIN',
                    style: TextStyle(
                      fontWeight: FontWeight.w900,
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DecoratedBox
&lt;/h3&gt;

&lt;p&gt;DecoratedBox, together with BoxDecoration, helps you add more styling to your container widget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Container(
          decoration: const BoxDecoration(
            borderRadius: BorderRadius.all(
              Radius.circular(12),
            ),
            gradient:RadialGradient(
          center: Alignment(-0.5, -0.6),
          radius: 0.15,
          colors: &amp;lt;Color&amp;gt;[
            Color(0xFFEEEEEE),
            Color(0xFF111133),
          ],
          stops: &amp;lt;double&amp;gt;[0.9, 1.0],
        )));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ClipRect
&lt;/h3&gt;

&lt;p&gt;Use ClipRect to clip the child within different shapes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    class DecorateContainer extends StatelessWidget {
       const DecorateContainer(Key? key, this.image) : super(key: key);
     final Image image;
      @override
      Widget build(BuildContext context) {
        return ClipRect(
      child: Container(
        child: Align(
          alignment: Alignment.center,
            widthFactor: 0.4,
            heightFactor: 1.0,
            child: image
        ),
      ),
    );
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ClipPath
&lt;/h3&gt;

&lt;p&gt;With the ClipPath widget, you can give any shape to the child widget. This means you can use it to draw any shape of your choice or according to your UI requirements with less code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     // body of your app
    ClipPath(
            child: Container(
              width: MediaQuery.of(context).size.width,
              height: 250,
              color: Colors.red,
            ),
            clipper: CustomClipPath(),
          ),

    // custom class
     class CustomClipPath extends CustomClipper&amp;lt;Path&amp;gt; {
      var radius=5.0;
      @override
      Path getClip(Size size) {
        Path path = Path();
        path.lineTo(size.width / 2, size.height);
        path.lineTo(size.width, 0.0);
        return path;
      }
      @override
      bool shouldReclip(CustomClipper&amp;lt;Path&amp;gt; oldClipper) =&amp;gt; false;
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Buttons
&lt;/h2&gt;

&lt;p&gt;There are many types of buttons in Flutter, so we'll only suggest the most useful ones in this cheat sheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  TextButton
&lt;/h3&gt;

&lt;p&gt;This is a Material button with text that has no borders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     TextButton(
                      style: TextButton.styleFrom(
                        padding: const EdgeInsets.all(16.0),
                        primary: Colors.white,
                        textStyle: const TextStyle(fontSize: 20),
                      ),
                      onPressed: () {},
                      child: const Text('Gradient'),
                    ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ElevatedButton
&lt;/h3&gt;

&lt;p&gt;ElevatedButton is a Material button that elevates when pressed. Avoid using elevated buttons on widgets that are already elevated, like dialogs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ElevatedButton(
                style: ElevatedButton.styleFrom(),
                onPressed: () {},
                child: const Text('Enabled'),
              ),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FloatingActionButton
&lt;/h3&gt;

&lt;p&gt;Use FloatingActionButton to create a primary action on the screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Scaffold(
          appBar: AppBar(
            title: const Text('Floating Action Button'),
          ),
          body: const Center(child: Text('Press the button below!')),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              // on pressed
            },
            backgroundColor: Colors.green,
            child: const Icon(Icons.navigation),
          ),
        );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Basic navigation
&lt;/h2&gt;

&lt;p&gt;Named routing can be used for small to medium-sized projects. Alternatively, you can use the Navigator API, which provides various routing functions.&lt;/p&gt;

&lt;p&gt;Flutter provides named routing to enable users to navigate your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    void main() {
      runApp(MaterialApp(
        initialRoute: '/',
        routes: {
          '/' : ((context) =&amp;gt; const FirstScreen()),
          '/second':(context) =&amp;gt; const SecondScreen()
        },
        home: const MyApp()));
    }
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: const Text('Navigation'),
            ),
            body:const FirstScreen()

        );
      }
    }

    class FirstScreen extends StatelessWidget {
      const FirstScreen({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Center(
          child: ElevatedButton(
            child: const Text('Go to SecondScreen'),
            onPressed: () =&amp;gt; Navigator.pushNamed(context, '/second'),
          ),
        );
      }
    }
    // can add this in a new file
    class SecondScreen extends StatelessWidget {
      const SecondScreen({Key? key}) : super(key: key);

      @override
      Widget build(BuildContext context) {
        return ElevatedButton(
          child:const Text('Go back!'),
          onPressed: () =&amp;gt; Navigator.pop(context),
        );
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;To learn more about Flutter Navigator 2.0, read &lt;a href="https://blog.codemagic.io/flutter-navigator2/?utm_source=devto&amp;amp;utm_medium=flutter-cheatsheet-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We have created this list of Flutter widgets as a quick reference for you while you build your Flutter app. We hope you have learned something new about these widgets that can help you improve the functionality of your Flutter application.&lt;/p&gt;

&lt;p&gt;We have an &lt;a href="https://codemagic.io/flutter-libraries-ebook/?utm_source=devto&amp;amp;utm_medium=flutter-cheatsheet-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;ebook&lt;/a&gt; that covers a list of libraries commonly used by Flutter developers, including networking, state management, and more. You can also watch our &lt;a href="https://blog.codemagic.io/tags/flutter-from-scratch/?utm_source=devto&amp;amp;utm_medium=flutter-cheatsheet-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Flutter From Scratch tutorial&lt;/a&gt;, which goes into detail about how to use these libraries and widgets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=flutter-cheatsheet-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is a CI/CD tool for Flutter and other mobile developers. &lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>devops</category>
      <category>mobile</category>
    </item>
    <item>
      <title>A beginner’s guide to go_router in Flutter</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 13:29:54 +0000</pubDate>
      <link>https://forem.com/codemagicio/a-beginners-guide-to-gorouter-in-flutter-24ec</link>
      <guid>https://forem.com/codemagicio/a-beginners-guide-to-gorouter-in-flutter-24ec</guid>
      <description>&lt;p&gt;&lt;em&gt;&amp;gt; Written by Hrishikesh Pathak and originally published to &lt;a href="https://blog.codemagic.io/flutter-go-router-guide/?utm_source=devto&amp;amp;utm_medium=go-router-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Routing is a crucial aspect of an app. Just like when you manage the application state or build the UI, you should give sufficient attention to optimizing the routing of your application. An optimized routing system helps users navigate your app and can handle the user state efficiently.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go_Router&lt;/code&gt; is a declarative and minimal routing system built on top of Flutter's Router API. &lt;code&gt;go_router&lt;/code&gt; provides a convenient URL-based API to navigate between different screens.&lt;/p&gt;

&lt;p&gt;In this article, we'll help you learn how to use &lt;code&gt;go_router&lt;/code&gt; in Flutter. We will discuss how to use route parameters, navigate using named routes, handle 404 errors, and much more. To get the most out of this article, be sure to read it to the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding go_router to your Flutter project
&lt;/h2&gt;

&lt;p&gt;Open a terminal in your Flutter project. Then run the following command to install the &lt;a href="https://pub.dev/packages/go_router" rel="noopener noreferrer"&gt;go_router&lt;/a&gt; package in your Flutter project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

flutter pub add go_router



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command installs the latest version of &lt;code&gt;go_router&lt;/code&gt; in your project. If you want to install a different version, just replace the version number in the &lt;code&gt;pubspec.yaml&lt;/code&gt; file, and run &lt;code&gt;flutter pub get&lt;/code&gt; to get your desired version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up basic routing using go_router
&lt;/h2&gt;

&lt;p&gt;To integrate &lt;code&gt;go_router&lt;/code&gt; in your app, change your &lt;code&gt;MaterialApp&lt;/code&gt; widget to &lt;code&gt;MaterialApp.router&lt;/code&gt;. This constructor accepts a &lt;code&gt;routerConfig&lt;/code&gt; property.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

MaterialApp.router(
  routerConfig: _router,
  title: "Go router",
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we'll add a &lt;code&gt;GoRouter&lt;/code&gt; object &lt;code&gt;_router&lt;/code&gt; to the &lt;code&gt;routerConfig&lt;/code&gt; property. Let's configure our &lt;code&gt;GoRouter&lt;/code&gt; and add the &lt;em&gt;home&lt;/em&gt; and &lt;em&gt;settings&lt;/em&gt; routes.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
    ),
    GoRoute(
      path: "/settings",
      builder: (context, state) =&amp;gt; const SettingsPage(),
    )
  ],
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When someone visits the &lt;code&gt;/&lt;/code&gt; route, &lt;code&gt;GoRouter&lt;/code&gt; returns the &lt;code&gt;HomePage&lt;/code&gt; widget. Similarly, if someone visits the &lt;code&gt;/settings&lt;/code&gt; route, &lt;code&gt;GoRouter&lt;/code&gt; returns the &lt;code&gt;SettingsPage&lt;/code&gt; widget.&lt;/p&gt;

&lt;p&gt;Let's see what &lt;code&gt;HomePage&lt;/code&gt; and &lt;code&gt;SettingsPage&lt;/code&gt; look like in the code.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Homepage"),
      ),
      body: Center(
        child: ElevatedButton(
        ),
      ),
    );
  }
}

class SettingsPage extends StatelessWidget {
  const SettingsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: true,
        title: const Text("Settings"),
      ),
      body: const Center(
        child: Text("Settings Page"),
      ),
    );
  }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  How to navigate between routes with go_router
&lt;/h2&gt;

&lt;p&gt;To navigate between routes, we can use the &lt;code&gt;GoRouter.of(context).go()&lt;/code&gt; method or the more concise &lt;code&gt;context.go()&lt;/code&gt; method. Let's add this method in the &lt;code&gt;ElevatedButton&lt;/code&gt; in our &lt;code&gt;HomePage&lt;/code&gt; widget to navigate to the &lt;code&gt;/settings&lt;/code&gt; route.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

child: ElevatedButton(
  onPressed: () =&amp;gt; context.go("/settings"),
  child: const Text("Go to Settings page"),
),



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Similarly, add an &lt;code&gt;ElevatedButton&lt;/code&gt; in the &lt;code&gt;SettingsPage&lt;/code&gt; to come back to the home &lt;code&gt;/&lt;/code&gt; route.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

child: ElevatedButton(
  onPressed: () =&amp;gt; context.go("/"),
  child: const Text("Go to home page"),
),



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Subroutes in go_router
&lt;/h2&gt;

&lt;p&gt;In the above section, we define our &lt;code&gt;GoRouter&lt;/code&gt; object. There, you can observe that we put a leading &lt;code&gt;/&lt;/code&gt; in every route. If you need deeply nested routes, then you have to type the whole route with the leading &lt;code&gt;/&lt;/code&gt; every time. This also makes the routes less organized.&lt;/p&gt;

&lt;p&gt;GoRouter provides a &lt;code&gt;routes&lt;/code&gt; argument in every &lt;code&gt;GoRoute&lt;/code&gt; object to group subroutes and define nested routes more easily.&lt;/p&gt;

&lt;p&gt;If we convert our previous &lt;code&gt;GoRouter&lt;/code&gt; object to a subroutes structure, then the new &lt;code&gt;GoRouter&lt;/code&gt; should look like this.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
      routes: [
        GoRoute(
          path: "settings",
          builder: (context, state) =&amp;gt; const SettingsPage(),
        )
      ],
    ),
  ],
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Can you see the difference? Now, the &lt;code&gt;settings&lt;/code&gt; routes live inside the &lt;code&gt;/&lt;/code&gt; routes. Therefore, there's no need to specify a leading &lt;code&gt;/&lt;/code&gt; in subroutes.&lt;/p&gt;

&lt;p&gt;Let's look at another example so that we can grasp the concept completely. If you want to define the nested route &lt;code&gt;/a/b/c&lt;/code&gt;, then the GoRouter object should look like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _newRouter = GoRouter(
  routes: [
    GoRoute(
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
      routes: [
        GoRoute(
          path: "a",
          builder: (context, state) =&amp;gt; const PageA(),
          routes: [
            GoRoute(
              path: "b",
              builder: (context, state) =&amp;gt; PageB(),
              routes: [
                GoRoute(
                  path: "c",
                  builder: (context, state) =&amp;gt; PageC(),
                )
              ],
            )
          ],
        )
      ],
    )
  ],
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Adding route parameters in go_router
&lt;/h2&gt;

&lt;p&gt;It is very easy to add route parameters in &lt;code&gt;go_router&lt;/code&gt;. To define a route parameter, add a trailing &lt;code&gt;:&lt;/code&gt; with the parameter name in the &lt;code&gt;path&lt;/code&gt; argument of &lt;code&gt;GoRoute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if you want to add a &lt;code&gt;name&lt;/code&gt; parameter in the settings route, the &lt;code&gt;path&lt;/code&gt; argument should be &lt;code&gt;/settings:name&lt;/code&gt;. You can access the route parameter with the &lt;code&gt;state.params["name"]&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;Let's see how to implement this in our Flutter app. The modified &lt;code&gt;GoRouter&lt;/code&gt; with a route parameter in the settings route should look like this.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
      routes: [
        GoRoute(
          path: "settings/:name",
          builder: (context, state) =&amp;gt; SettingsPage(
            name: state.params["name"]!,
          ),
        )
      ],
    ),
  ],
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To receive the route parameter in the settings screen, let's modify our code and add a property called &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class SettingsPage extends StatelessWidget {
  final String name;

  const SettingsPage({super.key, required this.name});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: true,
        title: Text(name),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () =&amp;gt; context.go("/"),
          child: const Text("Go to home page"),
        ),
      ),
    );
  }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, when we visit &lt;code&gt;/settings/codemagic&lt;/code&gt;, the &lt;code&gt;codemagic&lt;/code&gt; route parameter is passed to the &lt;code&gt;SettingsPage&lt;/code&gt; and displays the variable on the screen. Similarly, if you visit the &lt;code&gt;/settings/hrishikesh&lt;/code&gt; route, &lt;code&gt;hrishikesh&lt;/code&gt; is displayed in the &lt;code&gt;SettingsPage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Named routes in go_router
&lt;/h2&gt;

&lt;p&gt;Writing route paths manually is cumbersome and error prone. Therefore, &lt;code&gt;go_router&lt;/code&gt; offers a named route feature to navigate around the app, and &lt;code&gt;go_router&lt;/code&gt; will automatically resolve the path itself.&lt;/p&gt;

&lt;p&gt;To generate named routes, let's change our &lt;code&gt;GoRouter&lt;/code&gt; configuration with the name parameter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      name: "home",
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
      routes: [
        GoRoute(
          name: "settings",
          path: "settings/:name",
          builder: (context, state) =&amp;gt; SettingsPage(
            name: state.params["name"]!,
          ),
        )
      ],
    ),
  ],
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Passing route parameters to a named route is different from what you normally see. You have to define a &lt;code&gt;params&lt;/code&gt; parameter in the &lt;code&gt;context.goNamed()&lt;/code&gt; function. Here's how to set up navigation to &lt;code&gt;SettingsPage&lt;/code&gt; from &lt;code&gt;HomePage&lt;/code&gt; with a route parameter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

child: ElevatedButton(
  onPressed: () =&amp;gt; context.goNamed(
    "settings",
     params: {"name": "codemagic"},
     ),
  child: const Text("Go to Settings page"),
),



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Passing query parameters in go_router
&lt;/h2&gt;

&lt;p&gt;You have access to &lt;code&gt;queryParams&lt;/code&gt; in the &lt;code&gt;context.goNamed()&lt;/code&gt; function. The best thing about &lt;code&gt;queryParams&lt;/code&gt; is that you don't have to explicitly define them in your route path and can easily access them using the &lt;code&gt;state.queryParams&lt;/code&gt; method. You can add miscellaneous user-related data as a query parameter.&lt;/p&gt;

&lt;p&gt;Let's look at an example to illustrate this concept. In the &lt;code&gt;ElevatedButton&lt;/code&gt; in &lt;code&gt;HomePage&lt;/code&gt;, let's add some query parameters to the settings route.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

child: ElevatedButton(
  onPressed: () =&amp;gt; context.goNamed("settings", params: {
    "name": "codemagic"
  }, queryParams: {
    "email": "example@gmail.com",
    "age": "25",
    "place": "India"
    }),
    child: const Text("Go to Settings page"),
),



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can access these query parameters inside the &lt;code&gt;GoRouter&lt;/code&gt; configurations and pass them to the page. In this example, I am just printing out the values for demonstration.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

GoRoute(
  name: "settings",
  path: "settings/:name",
  builder: (context, state) {
    state.queryParams.forEach(
      (key, value) {
        print("$key:$value");
       },
     );
   return SettingsPage(
     name: state.params["name"]!,
   );
 },
)



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, when you click the button to navigate to the &lt;code&gt;SettingsPage&lt;/code&gt;, you can see the following output on the console screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fqueryparams.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fqueryparams.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling 404 errors with go_router
&lt;/h2&gt;

&lt;p&gt;When a user visits a screen that is not defined in the &lt;code&gt;GoRouter&lt;/code&gt; configuration, it causes an exception. But &lt;code&gt;go_router&lt;/code&gt; provides a very easy way to handle these errors, and you can provide a custom page to show to the user when this type of exception occurs.&lt;/p&gt;

&lt;p&gt;We can define an &lt;code&gt;errorBuilder&lt;/code&gt; argument in the &lt;code&gt;GoRouter&lt;/code&gt; object to handle these 404 errors. Let's make an &lt;code&gt;ErrorScreen&lt;/code&gt; page that includes a button to navigate back to the &lt;code&gt;HomePage&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class ErrorScreen extends StatelessWidget {
  const ErrorScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: true,
        title: const Text("Error Screen"),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () =&amp;gt; context.go("/"),
          child: const Text("Go to home page"),
        ),
      ),
    );
  }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now add this &lt;code&gt;ErrorScreen&lt;/code&gt; to the &lt;code&gt;errorBuilder&lt;/code&gt; argument of the &lt;code&gt;GoRouter&lt;/code&gt; object.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  errorBuilder: (context, state) =&amp;gt; const ErrorScreen(),
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it. Try to launch your Flutter app in the Chrome browser and navigate to a random path. You will see that &lt;code&gt;ErrorScreen&lt;/code&gt; will appear as a response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirecting routes using go_router
&lt;/h2&gt;

&lt;p&gt;Sometimes, you want to programmatically navigate your user from one page to another based on certain logic. You can enable that feature using redirects in &lt;code&gt;go_router&lt;/code&gt;. You can define a redirect for every path and a global redirect for all pages.&lt;/p&gt;

&lt;p&gt;For example, when a new user opens your application, you can navigate them to the login screen instead of the home screen. Similarly, when a logged-in user opens your application, you can navigate them to the home page. You can implement this type of feature very easily using &lt;code&gt;go_router&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how to implement a redirect in your app. Inside the route, define a &lt;code&gt;redirect&lt;/code&gt; argument. Inside the &lt;code&gt;redirect&lt;/code&gt; builder, check if the user is logged in or not. If they are not logged in, navigate them to the &lt;code&gt;/login&lt;/code&gt; page. Otherwise, show them the home page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      name: "home",
      path: "/",
      builder: (context, state) =&amp;gt; const HomePage(),
      redirect: (context, state) {
        if (userIsNotLoggedIn){
          return "/login"
        }
        return "/"
      },
    ),
  ],
  errorBuilder: (context, state) =&amp;gt; const ErrorScreen(),
);



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  GoRouter navigation comparison: go vs. push
&lt;/h2&gt;

&lt;p&gt;GoRouter has two methods for navigating between pages: &lt;code&gt;GoRouter.of(context).go()&lt;/code&gt; and &lt;code&gt;GoRouter.of(context).push()&lt;/code&gt;. On the surface, they look very similar, but they function differently. The &lt;code&gt;push()&lt;/code&gt; method stacks one page on another in navigation, while the &lt;code&gt;go()&lt;/code&gt; method directly navigates from one path to another without any stacking.&lt;/p&gt;

&lt;p&gt;Here's an example to demonstrate this concept. Assume you have three pages: A, B, and C. If you navigate from A to B to C using the &lt;code&gt;push&lt;/code&gt; method, then C is stacked on top of B on top of A. Take a look at this diagram to understand better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fgo_router_1_10551958591501476036_hu69eed796f406b2984cadfb8954afe09d_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fgo_router_1_10551958591501476036_hu69eed796f406b2984cadfb8954afe09d_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, if you navigate from A to B to C using the &lt;code&gt;go&lt;/code&gt; method, then C is only stacked on top of A and only if A is the home page with the route &lt;code&gt;/&lt;/code&gt;. B is eliminated from the navigation stack altogether. Therefore, the &lt;code&gt;go()&lt;/code&gt; method leaves no routing history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fgo_router_2_4356671897497888218_hu3d348e154913586ba8b8cc6831d84e3c_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fgo_router_2_4356671897497888218_hu3d348e154913586ba8b8cc6831d84e3c_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can choose which strategy works best for your apps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus: Remove the # prefix from the web URL in Flutter
&lt;/h2&gt;

&lt;p&gt;If you launch your app on the web, you can see a trailing &lt;code&gt;#&lt;/code&gt; in the URL. Sometimes it looks very annoying, and you may want to remove it. You can easily achieve this with the help of a package called &lt;a href="https://pub.dev/packages/url_strategy" rel="noopener noreferrer"&gt;url_strategy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, install this package in your Flutter project.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

flutter pub add url_strategy



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then import this package in your &lt;code&gt;main.dart&lt;/code&gt; file, and add the &lt;code&gt;setPathUrlStrategy()&lt;/code&gt; function before the &lt;code&gt;runApp()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Now, run your application. You can see that the trailing &lt;code&gt;#&lt;/code&gt; is removed from the URL of your Flutter app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing between go_router, Navigator 2.0, and Beamer for navigation in Flutter
&lt;/h2&gt;

&lt;p&gt;Flutter has several packages you can use for routing. The most well-known ones are go_router, Navigator, and Beamer. How do you choose which navigation to use for your app?&lt;/p&gt;

&lt;p&gt;If you are primarily targeting mobile platforms (Android and iOS) and don't have time to learn a new package for routing, it's completely fine to use Navigator 1.0. It is capable of meeting all your routing needs in Android/iOS apps.&lt;/p&gt;

&lt;p&gt;Flutter's core strength is in its multiplatform support, which spans beyond just Android and iOS. If you want to make a real multiplatform app that supports desktop and web platforms in addition to Android/iOS, then you should migrate to Navigator 2.0. Navigator 2.0 provides a very flexible and fully customizable routing solution for truly multiplatform apps.&lt;/p&gt;

&lt;p&gt;But a huge chunk of developers don't like the verbose nature of Navigator 2.0, which usually requires you to write boilerplate code to create a simple route. If you want to avoid these issues, you can use go_router, which is declarative and a significantly simpler solution for Flutter. If you have a medium-sized to large application and want to handle dynamic routes, &lt;code&gt;go_router&lt;/code&gt; can help you minimize your effort and maximize the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/beamer" rel="noopener noreferrer"&gt;Beamer&lt;/a&gt; is the new kid on the Flutter navigation stack. Like &lt;code&gt;go_router&lt;/code&gt;, Beamer is also based on the &lt;a href="https://api.flutter.dev/flutter/widgets/Router-class.html" rel="noopener noreferrer"&gt;Flutter router API&lt;/a&gt;. It makes developers' lives easier by simplifying Navigator 2.0. If you are already using &lt;code&gt;go_router&lt;/code&gt;, then there is no need to switch to Beamer unless you are missing some features and Beamer can satisfy your needs.&lt;/p&gt;

&lt;p&gt;If you are a beginner Flutter dev, you can go with either &lt;code&gt;go_router&lt;/code&gt; or Beamer. Both are good and focus on developer productivity by removing complexity and boilerplate code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build your Flutter app using Codemagic
&lt;/h2&gt;

&lt;p&gt;Our app isn't completely ready for release at this point, but let's set up a CI/CD pipeline for it anyway. Ideally, you should do this right from the start of the development process to make it easier to collaborate on the app with other developers and deliver new features to testers and customers while avoiding falling into the "it works on my computer" trap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flutterci.com/?utm_source=devto&amp;amp;utm_medium=go-router-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is a CI/CD platform for Flutter applications. You can trigger a new Flutter build when you push your code to your GitHub/GitLab/Bitbucket repository. Then just download the generated artifacts from the Codemagic dashboard and run them on your devices (unless you want to publish them to stores, which Codemagic can also help you with). Let's see step by step how to set up a CI/CD pipeline with Codemagic for your Flutter application.&lt;/p&gt;

&lt;p&gt;In case you are not a Codemagic user yet, you can sign up here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codemagic.io/signup/?utm_source=devto&amp;amp;utm_medium=go-router-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Create a new Codemagic project and connect your repository.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-codemagic-demo-1.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-codemagic-demo-1.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Select Flutter as the project type.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-codemagic-demo-2.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fflutter-codemagic-demo-2.png%23center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Under Automatic build triggering, check Trigger on push.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-codemagic-demo-3_12360258576554782698_hud67a11eaddc47b7c12a09d03e5934ff8_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-codemagic-demo-3_12360258576554782698_hud67a11eaddc47b7c12a09d03e5934ff8_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; In the Build tab, select your Flutter version, build format, and custom build arguments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-codemagic-demo-4_16945714986341555965_huea6a11c7175a0126411e2861416487c4_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fflutter-codemagic-demo-4_16945714986341555965_huea6a11c7175a0126411e2861416487c4_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click on the Start new build button to initiate the first build of your application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the build completes, you can download the artifacts and run the app on your devices.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-success_1743323141458607701_hub49bc8c935556c908a329cfa5abcf637_0_1280x1800_fit_linear_3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fcodemagic-success_1743323141458607701_hub49bc8c935556c908a329cfa5abcf637_0_1280x1800_fit_linear_3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go_router&lt;/code&gt; is a very minimalistic but powerful routing package for Flutter. It removes all the complexity that &lt;a href="https://blog.codemagic.io/flutter-navigator2/?utm_source=devto&amp;amp;utm_medium=go-router-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Flutter Navigator 2.0&lt;/a&gt; brings to the table and provides a very developer-friendly API to work with.&lt;/p&gt;

&lt;p&gt;In this article, we have discussed how to use &lt;code&gt;go_router&lt;/code&gt; in your Flutter app as well as some of its features, like route parameters, query parameters, named routes, handling errors, and redirection. This is just a beginner's guide for getting started with &lt;code&gt;go_router&lt;/code&gt;. If you want to learn more, visit the official &lt;code&gt;go_router&lt;/code&gt; &lt;a href="https://github.com/flutter/packages/tree/main/packages/go_router" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions, you can ask me on &lt;a href="https://twitter.com/hrishikshpathak" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. Have a good day.&lt;/p&gt;

&lt;p&gt;You can find the sample app on &lt;a href="https://github.com/hrishiksh/flutter-gorouter" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>devops</category>
      <category>dart</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Creating a Progressive Web App (PWA) with Flutter</title>
      <dc:creator>C💙demagic</dc:creator>
      <pubDate>Fri, 07 Oct 2022 07:27:00 +0000</pubDate>
      <link>https://forem.com/codemagicio/creating-a-progressive-web-app-pwa-with-flutter-385c</link>
      <guid>https://forem.com/codemagicio/creating-a-progressive-web-app-pwa-with-flutter-385c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Written by Jahswill Essien and originally posted to &lt;a href="https://blog.codemagic.io/pwas-vs-native-apps/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;Codemagic blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, you just finished building that application that's going to change the world, you've submitted the Android &lt;code&gt;apk&lt;/code&gt; to the Google Play Store and the iOS &lt;code&gt;ipa&lt;/code&gt; to the Apple App Store, and you're awaiting review. But when you check your email, you discover that your iOS app has been rejected. Given the urgency of releasing the app, what do you do now?&lt;/p&gt;

&lt;p&gt;Well, the good news is that your application was built with Flutter, which is a great tool for creating Progressive Web Apps (PWAs). But what is a PWA?&lt;/p&gt;

&lt;p&gt;This tutorial discusses what PWAs are, how to create a Flutter PWA, what to consider while creating one, handling web navigation efficiently, building and publishing a Flutter web app with Codemagic, how to install a PWA, and the benefits of PWAs.&lt;/p&gt;

&lt;p&gt;To get the most out of this Flutter PWA tutorial, you should at least be proficient in the Flutter framework and reading Flutter code. At least some familiarity with Navigator 2.0 is needed for the &lt;a href="https://blog.codemagic.io/pwa-in-flutter/#navigation-for-flutter-web" rel="noopener noreferrer"&gt;Navigation&lt;/a&gt; section of this post.&lt;/p&gt;

&lt;p&gt;Also, you will need a Codemagic account to complete the building and deployment part. If you don't have one, you can &lt;a href="https://codemagic.io/signup?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;sign up&lt;/a&gt;, it's free.&lt;/p&gt;

&lt;p&gt;By the time you finish this article, you will have completed this simple demo Flutter PWA that displays job openings. &lt;em&gt;(Please note that the figures and data displayed below are just dummy data.)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The design used for this app was inspired by the design I found on Dribbble &lt;a href="https://dribbble.com/shots/17092342-Job-Finder-App" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is PWA?
&lt;/h2&gt;

&lt;p&gt;A Progressive Web App, or PWA for short, is simply a web page that can be saved &lt;em&gt;(installed)&lt;/em&gt; from the web to provide a native app feel. PWAs work offline and have support for notifications, just like native applications.&lt;/p&gt;

&lt;p&gt;Flutter allows you to build multiple apps with one code base, so you can build a web app, or a PWA, if you already have your codebase for Android and iOS, quite quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read &lt;a href="https://blog.codemagic.io/pwas-vs-native-apps/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;this article about PWAs vs native apps&lt;/a&gt; to understand the pros and cons of PWAs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating a Flutter PWA
&lt;/h2&gt;

&lt;p&gt;Creating a PWA in Flutter is as simple as creating a Flutter web application. What better time than now is there to set up our demo Flutter project?&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Clone the starter code by running the command &lt;code&gt;git clone -b starters-code git@github.com:JasperEssien2/pwa_demo.git&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dependencies used in this project are shown in the table below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dependency&lt;/th&gt;
&lt;th&gt;Use cases&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/font_awesome_flutter" rel="noopener noreferrer"&gt;&lt;code&gt;font_awesome_flutter&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;To access icons that aren't available in the material icon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/beamer" rel="noopener noreferrer"&gt;&lt;code&gt;Beamer&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Used for efficient, complicated navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You should do your best to navigate the codebase to understand what each component does, but here's a breakdown of the main components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;/lib/widgets/detail_widgets.dart&lt;/code&gt;: contains the detail page's widget code&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/lib/widgets/job_list.dart&lt;/code&gt;: houses the job list's widget code&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib/app_provider.dart&lt;/code&gt;: contains an implementation of &lt;code&gt;InheritedWidget&lt;/code&gt; named &lt;code&gt;AppProvider&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;InheritedWidget&lt;/code&gt; provides a good way to pass data down Flutter's widget tree. &lt;code&gt;AppProvider&lt;/code&gt; will be utilized to pass down dummy jobs data and a &lt;code&gt;childBeamerkey&lt;/code&gt; (more on this in the navigation section).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib/extensions.dart&lt;/code&gt;: defines the &lt;code&gt;isLargeScreen&lt;/code&gt;, &lt;code&gt;isExpanded&lt;/code&gt;, and &lt;code&gt;provider&lt;/code&gt; getter functions in the &lt;code&gt;BuildContext&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dart's &lt;code&gt;extensions&lt;/code&gt; make it possible to define custom functionalities in existing libraries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lib/job_model.dart&lt;/code&gt;: defines the model classes that the app uses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lib/main.dart&lt;/code&gt;: contains our app entry point and the home page widgets&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this out of the way, let's look at some things to consider while building a web application in Flutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations while building a web app with Flutter
&lt;/h2&gt;

&lt;p&gt;There are a few important things to consider while building a web app with Flutter. This article covers two important aspects: &lt;a href="https://blog.codemagic.io/pwa-in-flutter/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution#responsiveness-in-flutter" rel="noopener noreferrer"&gt;&lt;em&gt;responsiveness&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://blog.codemagic.io/pwa-in-flutter/#navigation-in-flutter/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution#responsiveness-in-flutter" rel="noopener noreferrer"&gt;&lt;em&gt;navigation&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsiveness in Flutter
&lt;/h3&gt;

&lt;p&gt;Given that our web application may be run on various screen sizes, as Flutter developers, we have to do our best to account for them. A &lt;em&gt;responsive&lt;/em&gt; app is an app that is tuned for different screen sizes. So, let's make our Flutter Progressive Web App responsive!&lt;/p&gt;

&lt;p&gt;In this project, we will use a &lt;em&gt;list-detail&lt;/em&gt; design pattern. To make it responsive, we will take the following approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  On a smaller screen size, the list view occupies the entire screen, and tapping on a list item pushes a new screen to display the job details&lt;/li&gt;
&lt;li&gt;  On a larger screen size, the list view occupies the left pane, while the detail widget is displayed beside the list view on the same screen&lt;/li&gt;
&lt;li&gt;  On a very large screen size, we will add margins to the left and right sides of the home screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A structure like this makes implementing navigation tricky because the user can resize the window or change the orientation of the device. But you don't want your users to see an ugly wide detail screen after resizing the window from a small to a larger size &lt;em&gt;(if the detail screen is already in view)&lt;/em&gt;. In other words, you want the navigation to be in sync even if the user suddenly resizes the window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation for Flutter Web
&lt;/h3&gt;

&lt;p&gt;While Navigator 1.0 &lt;em&gt;(imperative API)&lt;/em&gt; works fine for most Flutter applications, it is very inefficient for complex routing for web applications and complex deep linking. That is why the Flutter team introduced Navigator 2.0 &lt;em&gt;(declarative API)&lt;/em&gt; for Flutter. Although it is much more efficient, it also introduced a great deal of complexity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A discussion of Navigator 2.0 is out of the scope of this article. To learn more about Navigator 2.0, read &lt;a href="https://blog.codemagic.io/flutter-navigator2/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution#responsiveness-in-flutter" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the positive side, we can use Flutter packages like &lt;a href="https://pub.dev/packages/go_router" rel="noopener noreferrer"&gt;&lt;code&gt;go_router&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pub.dev/packages/beamer" rel="noopener noreferrer"&gt;&lt;code&gt;Beamer&lt;/code&gt;&lt;/a&gt;, which are built upon Navigator 2.0, to simplify things for ourselves.&lt;/p&gt;

&lt;p&gt;We are using the &lt;a href="https://pub.dev/packages/beamer" rel="noopener noreferrer"&gt;&lt;code&gt;Beamer&lt;/code&gt;&lt;/a&gt; package for this project because of its flexibility in handling nested navigation. (It synchronizes the navigation history of both a nested &lt;code&gt;Router&lt;/code&gt; and parent &lt;code&gt;Router&lt;/code&gt; seamlessly.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;You have probably noticed by now that although the majority of the code has been written, there are some tasks left for you to handle to construct our Flutter PWA. This section will walk you through implementing these TODOs.&lt;/p&gt;

&lt;h4&gt;
  
  
  First task: Starting up with Beamer
&lt;/h4&gt;

&lt;p&gt;Our first task is to initialize the &lt;code&gt;BeamerDelegate&lt;/code&gt; variable. Search for &lt;code&gt;TODO: 1&lt;/code&gt; in the &lt;code&gt;lib/main.dart&lt;/code&gt; file, and assign the code below to the &lt;code&gt;routerDelegate&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final routerDelegate = BeamerDelegate(
    locationBuilder: (routeInformation, _) {
        return HomeLocation();
    },
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BeamerDelegate&lt;/code&gt; is Beamer's implementation of Navigation 2.0 &lt;a href="https://api.flutter.dev/flutter/widgets/RouterDelegate-class.html" rel="noopener noreferrer"&gt;&lt;code&gt;RouterDelegate&lt;/code&gt;&lt;/a&gt;, which is used to build and configure a navigation widget based on the pop and push route intent it receives from the engine.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;locationBuilder&lt;/code&gt; helps Beamer determine what screen to show based on the developer's configuration. An instance of &lt;code&gt;HomeLocation&lt;/code&gt; defined for this project is passed as an argument to the &lt;code&gt;locationBuilder&lt;/code&gt;. The &lt;code&gt;HomeLocation&lt;/code&gt; is an implementation of the &lt;code&gt;BeamLocation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By implementing &lt;code&gt;BeamLocation&lt;/code&gt;, we can configure what URI to handle and how to build the stack of pages based on the URI. Take a look through the comments in the code snippet below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HomeLocation extends BeamLocation&amp;lt;BeamState&amp;gt; {
  @override
  List&amp;lt;BeamPage&amp;gt; buildPages(BuildContext context, BeamState state) {
    JobModel? job;

    // Try to get the job with the [id] passed from the route path
    try {
      job = context.provider.jobs.firstWhere(
          (element) =&amp;gt; element.id.toString() == state.pathParameters['id']);
    } catch (e) {
      log(e.toString());
    }

    return [
      /// Always have the [MyHomePage] in the stack of pages
      const BeamPage(
        key: ValueKey('home'),
        title: "Home",
        name: '/',
        child: MyHomePage(
          currentIndex: 0,
        ),
      ),

      /// Only show the detail page over the [MyHomePage] when on a small screen and
      //a job id is passed alongside the URI. Note that [MyHomePage] is configured to be responsive.
      if (!context.isLargeScreen &amp;amp;&amp;amp; state.pathParameters.containsKey('id'))
        BeamPage(
          key: ValueKey('job-${job!.id}'),
          title: "${job.company.name} - ${job.role}",
          /// The URL path to navigate to this page, the [id] is a placeholder for the actual job id to be passed
          name: '/:id',
          child: JobDetailWidget(
            model: job,
          ),
        ),
    ];
  }

  /// This is where you indicate the URI patterns that can be handled by this [BeamLocation]
  @override
  List&amp;lt;Pattern&amp;gt; get pathPatterns =&amp;gt; ['/:id'];
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A somewhat similar implementation of the above is done for the nested navigator. The beam location is named &lt;code&gt;InnerJobLocation&lt;/code&gt;, which determines the current detail screen that is navigated to when the app is displayed on a large screen.&lt;/p&gt;

&lt;h4&gt;
  
  
  Second task: Determining what widget to return for small screens
&lt;/h4&gt;

&lt;p&gt;The goal of this section is to determine what widget to return when the app is running on a small screen.&lt;/p&gt;

&lt;p&gt;Locate &lt;code&gt;TODO: 2&lt;/code&gt; on line 123 and return the &lt;code&gt;JobList&lt;/code&gt; widget;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return const JobList();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Third task: Determining what widget to return for large screens
&lt;/h4&gt;

&lt;p&gt;Replace the &lt;code&gt;TODO&lt;/code&gt; on line 134 with the code below to display a list-detail widget when the app runs on a large screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    return Container(
            color: Colors.white70,
            // Add a horizontal margin for the largest screen size
            margin: EdgeInsets.symmetric(horizontal: horizontalMargin),
            child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
                /// Display the [JobList] on the left pane and constrain its width
                SizedBox(width: listviewMaxWidth, child: const JobList()),
                /// Use a nested Router
                SizedBox(
                width: detailMaxWidth,
                /// By using the [Beamer] widget, we add a nested Router
                child: Beamer(
                    /// Pass in the [childBeamerKey] instantiated in the [AppProvider] class
                    key: context.provider.childBeamerKey,
                    /// Pass a routerDelegate that uses the [InnerJobLocations]
                    routerDelegate: innerRouterDelegate,
                ),
              ),
            ],
          ),
        );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Fourth task: Getting rid of exceptions
&lt;/h4&gt;

&lt;p&gt;Remove the &lt;code&gt;Exception&lt;/code&gt; thrown on &lt;code&gt;line 155&lt;/code&gt; that was added to keep the compiler happy in regard to missing a return statement.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fifth task: making the beaming work
&lt;/h4&gt;

&lt;p&gt;The final task is making the actual "beaming" --- in other words, the navigation when a &lt;em&gt;job item&lt;/em&gt; widget is tapped on.&lt;/p&gt;

&lt;p&gt;Now, we are faced with a minor issue. We have implemented two &lt;a href="https://api.flutter.dev/flutter/widgets/Router-class.html" rel="noopener noreferrer"&gt;&lt;code&gt;Router&lt;/code&gt;s&lt;/a&gt;: a parent &lt;code&gt;Router&lt;/code&gt; and a nested &lt;code&gt;Router&lt;/code&gt; that is added to the widget tree when the app is running on a device with a large screen. So we'll need to use a different instance of &lt;code&gt;Beamer&lt;/code&gt; to navigate based on the screen size.&lt;/p&gt;

&lt;p&gt;Head over to &lt;code&gt;lib/widgets/job_list.dart&lt;/code&gt; and replace the &lt;code&gt;TODO&lt;/code&gt; at line 40 with the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context.isLargeScreen
    // Use the nested Beamer to navigate
    // to the correct detail page for large screen
    ? context.provider.childBeamerKey.currentState?.routerDelegate
        .beamToNamed('/${model.id}')
    : context.beamToNamed('/${model.id}');

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats on finishing these tasks! You can now run your web app by running &lt;code&gt;flutter run -d chrome&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I know you're eager to download your Flutter PWA onto your mobile device, but we need to host our mobile web application. Luckily, Codemagic has simplified a lot of things for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing a Flutter PWA with Codemagic
&lt;/h2&gt;

&lt;p&gt;Codemagic is an excellent continuous integration/delivery (CI/CD) tool that works with Flutter. CI/CD tools automate building and deployment processes so that developers can focus on tasks like meeting product requirements and writing and maintaining high-quality code.&lt;/p&gt;

&lt;p&gt;With Codemagic, you can automate the process of building for all platforms supported by Flutter. Here, we'll focus on setting up the web pipeline.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Start by logging in or creating an account if you don't have one by clicking &lt;a href="https://codemagic.io/login/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution#responsiveness-in-flutter" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add an application by clicking the Other -&amp;gt; Add repository button.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Input the repository URL, check the Public Repository checkbox, and select Flutter App as the project type.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use the repository URL for the completed version: &lt;a href="https://blog.codemagic.io/pwa-in-flutter/git@github.com:JasperEssien2/pwa_demo.git" rel="noopener noreferrer"&gt;git@github.com:JasperEssien2/pwa_demo.git&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fclone_repo_from_url.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fclone_repo_from_url.png" alt="Codemagic clone repository dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Select the platforms you want to build for in the Build for platforms section. Select only Web for now.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fcodemagic_check_web_setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fcodemagic_check_web_setup.png" alt="Codemagic build for platforms setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Scroll down to the Distribution section, enable Codemagic Static Page publishing, and input your desired web page subdomain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fcodemagic_web_distribution_setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fcodemagic_web_distribution_setup.png" alt="Codemagic distribution section setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Save your settings.&lt;/li&gt;
&lt;li&gt; Click Start new build.&lt;/li&gt;
&lt;li&gt; Relax and sip your coffee while Codemagic does the hard work of building and publishing your web app. Codemagic will notify you once it is done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can now access your published web page by visiting &lt;a href="https://blog.codemagic.io/pwa-in-flutter/yourSubdomain/codemagic.app" rel="noopener noreferrer"&gt;yourSubdomain/codemagic.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;yourSubdomain&lt;/em&gt; is the text you input in the Web page subdomain in step 5.)&lt;/p&gt;

&lt;p&gt;Here's the app running on a Chrome browser for desktop.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You probably want your app to be hosted on your own domain. At the time of writing this article, Codemagic supports setting up automatic publishing of your web apps to AWS S3 Bucket. How to set this up is out of the scope of this article, but you can learn more &lt;a href="https://docs.codemagic.io/flutter-publishing/publishing-to-aws/?utm_source=devto&amp;amp;utm_medium=pwa-article-devto&amp;amp;utm_campaign=flutter_distribution#responsiveness-in-flutter" rel="noopener noreferrer"&gt;in the docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Installing a PWA
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;iOS: Visit the web page on your Safari browser, and then tap Share -&amp;gt; Add to Home Screen -&amp;gt; Add.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At the time of writing this article, Chrome does not support the installation of PWA on iOS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fios-install-pwa-safari.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fios-install-pwa-safari.png%23center" alt="iOS PWA installation demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Android: Visit the web page on your Chrome browser, and tap the Add pwa_demo to Home Screen snackbar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fandroid-install-pwa-chrome.png%23center" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F09%2Fandroid-install-pwa-chrome.png%23center" alt="Android PWA installation demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the PWA among your installed apps on your mobile device. Open the app, and you should see a nice splash screen on startup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, PWAs for Android have the splash screen enabled. To add splash screens for an iOS PWA, you have to specify this in the &lt;code&gt;/web/index.html&lt;/code&gt; file --- click &lt;a href="https://blog.expo.dev/enabling-ios-splash-screens-for-progressive-web-apps-34f06f096e5c" rel="noopener noreferrer"&gt;here&lt;/a&gt; to learn how to go about this. Generate splash screen images for specific iOS screen sizes by using this &lt;a href="https://progressier.com/pwa-icons-and-ios-splash-screen-generator" rel="noopener noreferrer"&gt;PWA toolkit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Benefits of PWAs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Provide a legal way to have users install your iOS app without the headache of using app stores&lt;/li&gt;
&lt;li&gt;  Quick installation time&lt;/li&gt;
&lt;li&gt;  Small app size for your users&lt;/li&gt;
&lt;li&gt;  Native app-like features&lt;/li&gt;
&lt;li&gt;  Instant updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You'll probably agree that Flutter is a great tool for PWA and, when combined with Codemagic, makes things a lot easier and smoother.&lt;/p&gt;

&lt;p&gt;The next time you're faced with the problem introduced at the beginning of this article, don't fret --- you can simply utilize Flutter Web so that your users can install a PWA.&lt;/p&gt;

&lt;p&gt;Up for a challenge? Currently, the web app works fine on a browser until a URL that contains an invalid job ID is accessed (e.g., pwa_demo.codemagic.app/60). The challenge is to direct users to an error screen that tells them the job they are trying to access does not exist. Cheers, and good luck.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>pwa</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
