<?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>DEV Community: Codemagic</title>
    <description>The latest articles on DEV Community by Codemagic (@codemagicio).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F443681%2Faefe0e9f-392d-4abf-84ac-cc9d365228b0.jpg</url>
      <title>DEV Community: Codemagic</title>
      <link>https://dev.to/codemagicio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codemagicio"/>
    <language>en</language>
    <item>
      <title>Self-hosted live updates for Capacitor with Codemagic Patch</title>
      <dc:creator>Codemagic</dc:creator>
      <pubDate>Mon, 02 Nov 2026 10:26:14 +0000</pubDate>
      <link>https://dev.to/codemagicio/self-hosted-live-updates-for-capacitor-with-codemagic-patch-3ni5</link>
      <guid>https://dev.to/codemagicio/self-hosted-live-updates-for-capacitor-with-codemagic-patch-3ni5</guid>
      <description>&lt;p&gt;&lt;a href="https://patch.codemagic.io/" rel="noopener noreferrer"&gt;Codemagic Patch&lt;/a&gt; is a new self-hosted tool to ship live updates of HTML, CSS, and JavaScript to Capacitor, Ionic, and Cordova-on-Capacitor apps, without a new store binary.&lt;/p&gt;

&lt;p&gt;Say a checkout button stops working on some Android 15 devices, and the fix turns out to be four lines of TypeScript. In a Capacitor app, those four lines live in the web layer, inside a WebView, nowhere near native code. Without over-the-air (OTA) updates, they still have to go the long way round: new binary, store submission, review, and then a slow trickle of users who eventually update. Some never do.&lt;/p&gt;

&lt;p&gt;That's what OTA updates solve through tools such as Live Updates on Appflow, and what you can do with Patch.&lt;/p&gt;

&lt;p&gt;Patch v0.6.0 ships with two new packages: &lt;code&gt;@codemagic/capacitor-patch&lt;/code&gt;, the SDK that goes into your app, and &lt;code&gt;@codemagic/capacitor-patch-cli&lt;/code&gt;, a command line made for publishing and managing Capacitor releases. They use the same server, dashboard, and delivery protocol as our React Native SDK.&lt;/p&gt;

&lt;p&gt;Below, we’ll go through what you get with Patch, , how you can self-host Patch, and where to find the docs to get your first release out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacing Appflow’s Live Updates Before the Sunset
&lt;/h2&gt;

&lt;p&gt;Ionic has announced that &lt;a href="https://ionic.zendesk.com/hc/en-us/articles/29907381079831-Native-Builds-and-Live-Updates" rel="noopener noreferrer"&gt;all Appflow features and services will be sunset on December 31, 2027&lt;/a&gt;, a little over a year from now. They'll keep Appflow up to date with its dependencies until then. After that, Ionic warns that apps still depending on Appflow may hit failed builds and failed live updates, and users may end up looking at a blank white screen.&lt;/p&gt;

&lt;p&gt;A year can feel like plenty of time, but you'll need to make sure no users are on a native version relying on the Appflow SDK for live updates by the time it's sunset.&lt;/p&gt;

&lt;p&gt;We've been building, signing, and publishing Capacitor apps on Codemagic for years, and Ionic's own continuity page lists us as an alternative for Appflow native builds. Capacitor teams could also already get OTA updates from our CodePush service through the community &lt;code&gt;cap-codepush&lt;/code&gt; plugin. Patch replaces that arrangement with a first-party SDK that we design, test, and support ourselves. For a Capacitor team, that means native builds and live updates from one vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get with Patch
&lt;/h2&gt;

&lt;p&gt;A server you can run yourself, an SDK in the app, a CLI to publish the web build you already produce, and a dashboard for what happened after that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web-layer fixes reach devices that already include the SDK. The check runs on launch, and you can also run it when the app returns to the foreground.&lt;/li&gt;
&lt;li&gt;Downloads are a diff of what changed, with a fallback to the full bundle if the diff fails.&lt;/li&gt;
&lt;li&gt;A bad release rolls itself back if the app never reports the new bundle as healthy.&lt;/li&gt;
&lt;li&gt;You choose when an update takes effect. Mandatory updates can follow a different rule.&lt;/li&gt;
&lt;li&gt;Releases can be signed, and the server can reject unsigned ones.&lt;/li&gt;
&lt;li&gt;Staging and Production stay separate. You can send a release to a percentage of devices, then promote the one that worked without rebuilding it.&lt;/li&gt;
&lt;li&gt;The dashboard shows adoption, failures, and download size for each release.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbgqz0cpklk3z3sjihau.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbgqz0cpklk3z3sjihau.png" alt="The Codemagic Patch dashboard with example release metrics" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your native version number is the compatibility boundary
&lt;/h2&gt;

&lt;p&gt;There's one rule every team should understand before shipping their first Capacitor OTA release, because it's much cheaper to learn it now than in production.&lt;/p&gt;

&lt;p&gt;Your web bundle calls into native code: Capacitor plugins, your own native classes, native configuration. A bundle built for one native build can break on another. If your new web code calls &lt;code&gt;Camera.getPhoto()&lt;/code&gt; and the installed binary shipped before you added &lt;code&gt;@capacitor/camera&lt;/code&gt;, it will fail.&lt;/p&gt;

&lt;p&gt;The Capacitor CLI doesn't try to detect this by fingerprinting your native project. Every release instead targets one exact native app version (&lt;code&gt;CFBundleShortVersionString&lt;/code&gt; on iOS, &lt;code&gt;versionName&lt;/code&gt; on Android) and is only delivered to that version. Ranges and wildcards aren't accepted. In day-to-day terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bump the native version every time anything native changes: a plugin added, removed, or upgraded, native code edited, native configuration changed.&lt;/li&gt;
&lt;li&gt;If one web bundle should go to several binary versions, release it once per version.&lt;/li&gt;
&lt;li&gt;Never ship two different native builds under the same version number. If they differ in ways your web code cares about, no OTA tool can tell them apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team already versions carefully, you'll hardly notice this. If it doesn't, OTA will push you into the habit, and you'll be better off for it. The binary version filter added to the Patch dashboard's deployment and Metrics pages in this release makes it easy to see how each version is doing.&lt;/p&gt;

&lt;p&gt;Put together, every change goes down one of two paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If the change touches…&lt;/th&gt;
&lt;th&gt;Then…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Only the web layer&lt;/td&gt;
&lt;td&gt;Build your web assets, publish them with &lt;code&gt;cmpatch-capacitor release create&lt;/code&gt; against the current binary version, test on Staging, then promote to Production with a staged rollout.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugins, native code, or native config&lt;/td&gt;
&lt;td&gt;Bump the native version, then build, sign, and publish through Codemagic. Once the new binary is through review and in users' hands, future OTA releases target the new version.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What OTA buys you, and what it doesn't
&lt;/h2&gt;

&lt;p&gt;OTA is easy to oversell, so it's worth being specific.&lt;/p&gt;

&lt;p&gt;The obvious win is speed. A web-layer fix stops taking days of review and update lag and starts reaching users within minutes of publishing, as soon as their devices check in. For a consumer app, that can be the difference between a wave of one-star reviews and nobody noticing. For a B2B or internal app, it's the difference between an escalation and a ticket closed the same afternoon.&lt;/p&gt;

&lt;p&gt;You also end up supporting fewer versions. With store-only updates, every version a user hasn't bothered to update is still out there, still generating bug reports. With OTA, devices on the same binary converge on the same web bundle, so "can't reproduce" happens a lot less.&lt;/p&gt;

&lt;p&gt;And your two release cycles can run at their own pace. The native release train can stay as deliberate as your compliance process requires, monthly or quarterly, while the web layer ships whenever your team is ready. Staging, staged rollouts, and promotion give you a controlled route from merge to full rollout.&lt;/p&gt;

&lt;p&gt;What OTA can't do is ship native code, and the app stores are explicit about where that line is.&lt;/p&gt;

&lt;p&gt;Apple's &lt;a href="https://developer.apple.com/support/terms/apple-developer-program-license-agreement/#b331" rel="noopener noreferrer"&gt;Developer Program License Agreement, section 3.3.1(B)&lt;/a&gt; forbids apps from downloading executable code but allows interpreted code, which is what the JavaScript in a Capacitor WebView is. There are conditions. The downloaded code can't change the app's primary purpose by adding features that don't match how the app was described and advertised. It can't get around signing, sandboxing, or other OS security features. And it can't turn the app into a store for other apps.&lt;/p&gt;

&lt;p&gt;Google Play's &lt;a href="https://support.google.com/googleplay/android-developer/answer/16559646" rel="noopener noreferrer"&gt;Device and Network Abuse policy&lt;/a&gt; starts from a stricter position. Apps may not update themselves outside Google Play or download executable code such as dex, JAR, or &lt;code&gt;.so&lt;/code&gt; files from anywhere else. The policy then makes an exception for code that runs in an interpreter or virtual machine with only indirect access to Android APIs, and gives "JavaScript in a webview or browser" as its example. That code still can't be used to break any other Play policy.&lt;/p&gt;

&lt;p&gt;So both stores end up in the same place. Bug fixes, copy changes, UI tweaks, and incremental improvements to the app people already installed are fine. Using OTA to turn the app into something other than what was reviewed is not, and it puts your developer account at risk. Signed releases help with the security side of Apple's conditions, and keeping your native versions disciplined keeps native changes inside reviewed binaries, where they belong.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to self-host live updates for Capacito
&lt;/h2&gt;

&lt;p&gt;The server, release worker, and dashboard run on your infrastructure as a single Docker Compose deployment: Caddy for TLS, the Patch server, PostgreSQL, and S3-compatible object storage, with Cloudflare or CloudFront in front if you want a CDN. Your bundles, metrics, and release history stay on your network. The Capacitor SDK talks to the standard Patch server without any changes, so you set up the server once with the &lt;a href="https://github.com/codemagic-ci-cd/codemagic-patch#part-1--run-the-server-self-host" rel="noopener noreferrer"&gt;self-hosting guide&lt;/a&gt;, and &lt;code&gt;cmpatch-capacitor&lt;/code&gt; handles everything Capacitor-specific from there.&lt;/p&gt;

&lt;p&gt;The SDKs and CLIs are licensed under Apache 2.0. The server uses the Codemagic Server License, which is &lt;strong&gt;free for up to 1,000,000 monthly active devices&lt;/strong&gt; and converts to Apache 2.0 two years after each release. The only thing you can't do is resell Patch as a competing OTA service.&lt;/p&gt;

&lt;p&gt;To try it before provisioning anything, run the &lt;a href="https://github.com/codemagic-ci-cd/codemagic-patch#quickstart--try-it-locally" rel="noopener noreferrer"&gt;local evaluation stack&lt;/a&gt;. It runs the real server, database, storage, and dashboard on your machine in Docker, and the Capacitor CLI can point straight at it:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight bash "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
git clone &lt;a href="https://github.com/codemagic-ci-cd/codemagic-patch.git" rel="noopener noreferrer"&gt;https://github.com/codemagic-ci-cd/codemagic-patch.git&lt;/a&gt;&lt;br&gt;
cd codemagic-patch&lt;br&gt;
./scripts/local-eval/up.sh&lt;/p&gt;

&lt;p&gt;cmpatch-capacitor config set --server-url &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;br&gt;
cmpatch-capacitor login&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;The local server has a prefilled one-click login, so there's no account to create.&lt;/p&gt;

&lt;p&gt;Self-hosting makes the most sense for teams with strict data residency rules, regulated or air-gapped environments, or a preference for owning their infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://patch.codemagic.io/docs/?sdk=capacitor" rel="noopener noreferrer"&gt;Patch documentation&lt;/a&gt; covers setup step by step, so this is an overview of the path from nothing to your first OTA release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Decide where Patch runs.&lt;/strong&gt; Self-host with the &lt;a href="https://github.com/codemagic-ci-cd/codemagic-patch#part-1--run-the-server-self-host" rel="noopener noreferrer"&gt;self-hosting guide&lt;/a&gt;, or &lt;a href="https://codemagic.io/contact-sales/" rel="noopener noreferrer"&gt;talk to our team&lt;/a&gt; about hosted service. For a first look, the local stack above is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install the CLI and create one Patch app per platform.&lt;/strong&gt; Each app comes with &lt;code&gt;Staging&lt;/code&gt; and &lt;code&gt;Production&lt;/code&gt; deployments, each with its own deployment key. Don't let iOS and Android share a deployment. A release's delivery path doesn't include the platform, so the two would overwrite each other.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight bash "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
npm install -g @codemagic/capacitor-patch-cli&lt;/p&gt;

&lt;p&gt;cmpatch-capacitor config set --server-url &lt;a href="https://YOUR_PATCH_SERVER" rel="noopener noreferrer"&gt;https://YOUR_PATCH_SERVER&lt;/a&gt;&lt;br&gt;
cmpatch-capacitor login                                # opens the browser&lt;/p&gt;

&lt;p&gt;cmpatch-capacitor app create --name MyApp-iOS&lt;br&gt;
cmpatch-capacitor app create --name MyApp-Android&lt;br&gt;
cmpatch-capacitor deployment list --app MyApp-iOS      # deployment IDs and keys&lt;br&gt;
cmpatch-capacitor deployment list --app MyApp-Android&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add the SDK to your Capacitor project.&lt;/strong&gt; It supports the two most recent Capacitor major versions (currently 7 and 8) and Android &lt;code&gt;minSdkVersion&lt;/code&gt; 24 or higher.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight bash "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
npm install @codemagic/capacitor-patch&lt;br&gt;
npx cap sync&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;Configuration goes in &lt;code&gt;capacitor.config.ts&lt;/code&gt;, with one block per platform. The API URL and download base URL come from your Patch server: the self-host installer prints both, and we give them to you for hosted Patch.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight typescript "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
// capacitor.config.ts (excerpt)&lt;br&gt;
plugins: {&lt;br&gt;
  CodemagicPatch: {&lt;br&gt;
    ios: {&lt;br&gt;
      deploymentKey: 'YOUR_IOS_DEPLOYMENT_KEY',&lt;br&gt;
      apiUrl: '&lt;a href="https://YOUR_PATCH_API_URL" rel="noopener noreferrer"&gt;https://YOUR_PATCH_API_URL&lt;/a&gt;',&lt;br&gt;
      downloadBaseUrl: '&lt;a href="https://YOUR_PATCH_DOWNLOAD_BASE_URL" rel="noopener noreferrer"&gt;https://YOUR_PATCH_DOWNLOAD_BASE_URL&lt;/a&gt;',&lt;br&gt;
    },&lt;br&gt;
    android: {&lt;br&gt;
      deploymentKey: 'YOUR_ANDROID_DEPLOYMENT_KEY',&lt;br&gt;
      apiUrl: '&lt;a href="https://YOUR_PATCH_API_URL" rel="noopener noreferrer"&gt;https://YOUR_PATCH_API_URL&lt;/a&gt;',&lt;br&gt;
      downloadBaseUrl: '&lt;a href="https://YOUR_PATCH_DOWNLOAD_BASE_URL" rel="noopener noreferrer"&gt;https://YOUR_PATCH_DOWNLOAD_BASE_URL&lt;/a&gt;',&lt;br&gt;
    },&lt;br&gt;
  },&lt;br&gt;
},&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;Then call &lt;code&gt;start()&lt;/code&gt; once the app has rendered its first screen:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight typescript "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
import { CheckFrequency, start } from '@codemagic/capacitor-patch';&lt;/p&gt;

&lt;p&gt;// Checks on launch and on every return to the foreground.&lt;br&gt;
// Calling it after the first render is what marks the bundle as healthy.&lt;br&gt;
start({ checkFrequency: CheckFrequency.ON_APP_RESUME });&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;Where you call it matters. &lt;code&gt;start()&lt;/code&gt; is what tells the SDK the running bundle is healthy, so call it after your UI is actually up, not early in bootstrap. That way an update that crashes during startup still gets rolled back. Two more tips: every config value can also be set as a native resource (&lt;code&gt;Info.plist&lt;/code&gt; or &lt;code&gt;strings.xml&lt;/code&gt;) if you inject per-environment values in CI, and &lt;code&gt;npx codemagic-patch-check-config&lt;/code&gt; is worth adding to your native build workflow to catch a shared deployment key before it ships. The check reads JSON only, so if your config is &lt;code&gt;capacitor.config.ts&lt;/code&gt;, run it after &lt;code&gt;npx cap sync&lt;/code&gt; and point it at the synced copy: &lt;code&gt;npx codemagic-patch-check-config android/app/src/main/assets/capacitor.config.json&lt;/code&gt;. The Patch docs cover &lt;a href="https://patch.codemagic.io/docs/setup/native-setup-capacitor" rel="noopener noreferrer"&gt;native setup for Capacitor&lt;/a&gt;, &lt;a href="https://patch.codemagic.io/docs/setup/checking-for-updates" rel="noopener noreferrer"&gt;checking for updates&lt;/a&gt;, &lt;a href="https://patch.codemagic.io/docs/setup/applying-updates" rel="noopener noreferrer"&gt;install modes&lt;/a&gt;, and the &lt;a href="https://patch.codemagic.io/docs/setup/manual-control" rel="noopener noreferrer"&gt;step-by-step APIs&lt;/a&gt;, and the &lt;a href="https://patch.codemagic.io/docs/reference/sdk-reference" rel="noopener noreferrer"&gt;SDK reference&lt;/a&gt; lists everything the SDK exports. Pick Capacitor in the switcher at the top of the docs menu to see the Capacitor version of each page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Publish a release to Staging from the CLI.&lt;/strong&gt; Build the web assets the way you already do, then publish that folder once per platform. &lt;code&gt;--target-binary-version&lt;/code&gt; has to be the native version you will ship in the next step (&lt;code&gt;CFBundleShortVersionString&lt;/code&gt; on iOS, &lt;code&gt;versionName&lt;/code&gt; on Android). &lt;code&gt;www&lt;/code&gt; below is the &lt;code&gt;webDir&lt;/code&gt; from &lt;code&gt;capacitor.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight bash "style=paraiso-dark"&amp;gt;}}&lt;br&gt;
npm run build&lt;/p&gt;

&lt;p&gt;cmpatch-capacitor release create --bundle-path www \&lt;br&gt;
  --app MyApp-iOS --deployment Staging \&lt;br&gt;
  --target-binary-version 1.4.0&lt;/p&gt;

&lt;p&gt;cmpatch-capacitor release create --bundle-path www \&lt;br&gt;
  --app MyApp-Android --deployment Staging \&lt;br&gt;
  --target-binary-version 1.4.0&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;

&lt;p&gt;Each command publishes one platform. Install a build that includes the SDK and uses that same native version if you want to try the Staging release on a device before it goes to the stores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Ship the native release, then publish Production from CI.&lt;/strong&gt; The SDK has to be in the installed app before a device can receive an update, so this first binary is a normal store release. Build, sign, and publish it through Codemagic as usual. &lt;a href="https://blog.codemagic.io/continuous-integration-and-delivery-for-ionic-apps-codemagic/" rel="noopener noreferrer"&gt;CI/CD for Ionic apps&lt;/a&gt; walks through connecting the repo and a first workflow, and the &lt;a href="https://docs.codemagic.io/yaml-quick-start/building-an-ionic-app/" rel="noopener noreferrer"&gt;Ionic Capacitor build guide&lt;/a&gt; covers code signing, versioning, and publishing for both platforms.&lt;/p&gt;

&lt;p&gt;After that, Production releases belong in CI. Create a token with &lt;code&gt;cmpatch-capacitor token create&lt;/code&gt;, store it with your server URL and the deployment IDs from &lt;code&gt;deployment list&lt;/code&gt;, and publish from the pipeline. The &lt;a href="https://patch.codemagic.io/docs/setup/first-release" rel="noopener noreferrer"&gt;first release guide&lt;/a&gt; covers that workflow, including promotion and staged rollouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from Appflow Live Updates
&lt;/h2&gt;

&lt;p&gt;Most Appflow concepts carry over directly. Appflow channels become Patch deployments, and you get Staging and Production from the start. A few things work differently, and it's better to know about them before you start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The SDK swap takes one native release.&lt;/strong&gt; You can't deliver a new OTA SDK through the old one. Plan a native release that replaces the Appflow Live Updates SDK with &lt;code&gt;@codemagic/capacitor-patch&lt;/code&gt;, and leave enough time for it to reach your users before December 2027.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's no runtime channel switching.&lt;/strong&gt; Appflow's &lt;code&gt;getConfig()&lt;/code&gt; and &lt;code&gt;setConfig()&lt;/code&gt; let the app change its channel at runtime. The Patch SDK doesn't have an equivalent, because the deployment key is part of the binary's configuration. If your beta testers currently opt into a channel from inside the app, you'll need another approach, such as separate beta builds pointing at a &lt;code&gt;Beta&lt;/code&gt; deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your release history stays behind.&lt;/strong&gt; There's no import of Appflow channels, builds, or deployment history. Your first Patch release starts the history fresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each platform gets its own key.&lt;/strong&gt; Appflow used one channel name across platforms. Patch uses a separate app and deployment key for iOS and for Android.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary versions must match exactly.&lt;/strong&gt; If your Appflow setup relied on looser version matching, settle on a clear native versioning scheme before you switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these should stop you, but the first one decides your timeline. The &lt;a href="https://patch.codemagic.io/docs/migration/migrating-from-appflow" rel="noopener noreferrer"&gt;Appflow migration guide&lt;/a&gt; walks through the swap.&lt;/p&gt;

&lt;p&gt;If you're already using our CodePush service with &lt;code&gt;cap-codepush&lt;/code&gt;, nothing changes for you today. When you're ready, the move to Patch follows the same pattern: one native release with the new SDK, then you publish with &lt;code&gt;cmpatch-capacitor&lt;/code&gt; instead of &lt;code&gt;code-push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Patch isn't the right fit
&lt;/h2&gt;

&lt;p&gt;We'd rather you choose the right tool than the wrong one from us, so here are the cases where Patch won't help much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of your changes are native.&lt;/strong&gt; Plugin upgrades, Capacitor version bumps, and native features all need a reviewed binary whichever OTA service you use. For teams like that, a fast and reliable native build-and-publish setup matters more, and Codemagic handles that too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your beta program depends on runtime channel switching.&lt;/strong&gt; The Patch Capacitor SDK doesn't offer it today. Separate beta builds work, but it's a different workflow, so think it through before migrating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your native versioning is a mess and you can't fix it.&lt;/strong&gt; Exact-version targeting will feel strict. We think strict is the safer default, because it makes the compatibility boundary visible instead of guessing at it, but it does ask for some discipline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're happy on Appflow right now.&lt;/strong&gt; You don't have to move tomorrow. You do have to move before the end of 2027, and the native release the swap requires means you should start well before that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Patch today
&lt;/h2&gt;

&lt;p&gt;If Appflow's shutdown is on your roadmap, start before it becomes urgent. Spin up the local evaluation stack this week, and put the SDK in your next native release.&lt;/p&gt;

</description>
      <category>ionic</category>
      <category>capacitor</category>
      <category>mobile</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How Much It Costs to Self-Host OTA Updates for 100 Million Users</title>
      <dc:creator>Codemagic</dc:creator>
      <pubDate>Mon, 26 Oct 2026 23:00:00 +0000</pubDate>
      <link>https://dev.to/codemagicio/how-much-it-costs-to-self-host-ota-updates-for-100-million-users-de6</link>
      <guid>https://dev.to/codemagicio/how-much-it-costs-to-self-host-ota-updates-for-100-million-users-de6</guid>
      <description>&lt;p&gt;How much does it actually cost to self-host over-the-air (OTA) updates and what infrastructure pieces are required? Especially for a React Native or Capacitor app with many millions of users.&lt;br&gt;
It’s a useful number to know if you’ve got a growing Expo Updates bill.&lt;/p&gt;

&lt;p&gt;This article prices an extreme case of 100 million monthly active users (MAU). As a quick answer, here is the monthly cost of hosting &lt;a href="https://patch.codemagic.io/" rel="noopener noreferrer"&gt;Codemagic Patch&lt;/a&gt; on DigitalOcean at a few sizes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Monthly users&lt;/th&gt;
&lt;th&gt;10 thousand&lt;/th&gt;
&lt;th&gt;1 million&lt;/th&gt;
&lt;th&gt;100 million&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DigitalOcean&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;td&gt;$63&lt;/td&gt;
&lt;td&gt;about $843&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$7,250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;td&gt;$63&lt;/td&gt;
&lt;td&gt;about $8,093&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can host Patch on any Linux compatible machine, such as an EC2 on AWS or a machine on Fly.io. We’re just using DigitalOcean as an example.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2gvd5xq5pug7u8a4t5h.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2gvd5xq5pug7u8a4t5h.png" alt="Device requests only go to the CDN, with the server only receiving metric posts" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements for hosting Patch
&lt;/h2&gt;

&lt;p&gt;Patch is designed to be easy to run and adaptable to different requirements. The key pieces it needs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A server for metrics, the dashboard, and publishing&lt;/li&gt;
&lt;li&gt;Object storage for the manifests and the bundles.&lt;/li&gt;
&lt;li&gt;Postgres for the metric rows and the release records.&lt;/li&gt;
&lt;li&gt;A CDN, optionally, in front of the storage hostname for manifests and bundles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small deployment can keep all of this on a single 2 GB machine. &lt;a href="https://blog.codemagic.io/self-hosted-ota-for-an-hour-a-month/" rel="noopener noreferrer"&gt;How to self-host OTA updates for React Native&lt;/a&gt; walks through standing that up. In these cases, the install wizard uses Caddy for HTTPS on the API and storage hostnames, and Silo for object storage. For 100 million users we will split the pieces apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the traffic for Patch
&lt;/h2&gt;

&lt;p&gt;To plan our architecture, let's talk about what requests and posts are made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requests
&lt;/h3&gt;

&lt;p&gt;Devices will make a request for whether an update is available. The answer is provided as &lt;code&gt;meta.json&lt;/code&gt; and &lt;code&gt;manifest.json&lt;/code&gt;, a pair of files created along with each release that will be kept in object storage and served by the CDN. &lt;a href="https://blog.codemagic.io/designing-scalable-ota-update-architecture/" rel="noopener noreferrer"&gt;Designing scalable OTA update architecture for React Native&lt;/a&gt; explains why that check is a file on the CDN rather than a call to the server.&lt;/p&gt;

&lt;p&gt;When an update is available, the bundle will also be fetched from the CDN. So, you won't deal with millions of device requests hitting your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Posts
&lt;/h3&gt;

&lt;p&gt;The posts are the phones reporting back. Receiving and installing an update posts &lt;code&gt;Downloaded&lt;/code&gt;, then &lt;code&gt;Ready&lt;/code&gt; and &lt;code&gt;Applied&lt;/code&gt;. The phone also posts &lt;code&gt;Active&lt;/code&gt;, to say what it is running, at most once per day.&lt;/p&gt;

&lt;p&gt;These are initially stored as an individual row per device, then rolled up into daily totals after eight days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load estimates for 100M users
&lt;/h2&gt;

&lt;p&gt;We will need to make some assumptions to decide the required spec.&lt;/p&gt;

&lt;p&gt;We'll say 1/4 of the monthly active users open the app on a given day, meaning 25 million update checks and &lt;code&gt;Active&lt;/code&gt; posts per day.&lt;/p&gt;

&lt;p&gt;Let's also assume one OTA update per week. On our end, releasing an update only means three extra post events per device.&lt;/p&gt;

&lt;p&gt;The rates are those assumptions spread across a day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;10 thousand&lt;/th&gt;
&lt;th&gt;1 million&lt;/th&gt;
&lt;th&gt;100 million&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opens per day&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;250,000&lt;/td&gt;
&lt;td&gt;25 million&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active posts, averaged over 24 hours&lt;/td&gt;
&lt;td&gt;under 1/s&lt;/td&gt;
&lt;td&gt;about 3/s&lt;/td&gt;
&lt;td&gt;about 300/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active posts, if the day bunches into 8 hours&lt;/td&gt;
&lt;td&gt;under 1/s&lt;/td&gt;
&lt;td&gt;about 10/s&lt;/td&gt;
&lt;td&gt;about 1,000/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sizing the infrastructure
&lt;/h2&gt;

&lt;p&gt;Do not size the droplet from the downloads. Size it from Active, then add the ship-day posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server and throughput
&lt;/h3&gt;

&lt;p&gt;A VM with a dedicated CPU is useful for the incoming metrics. The most basic one on DigitalOcean is 2 vCPUs with 8 GiB of RAM, at $63 a month.&lt;/p&gt;

&lt;p&gt;A quick load test showed that this machine can post about 750 events per second to a basic managed Postgres. Faster than that, and the database couldn't keep up.&lt;/p&gt;

&lt;p&gt;To go beyond 750/s, it's possible to add measures such as saving a batch of posts once a second. That isn't currently included in Patch, but a quick test showed it increasing throughput to 4,000/s on the same setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Postgres
&lt;/h3&gt;

&lt;p&gt;Postgres is best on a separate managed database. You will need roughly 4 GiB per 1M MAU. About half of that is &lt;code&gt;Active&lt;/code&gt;, and about half is the ship-day posts still inside the eight-day window.&lt;/p&gt;

&lt;p&gt;For one million users, the basic managed Postgres is $15 a month. A $48 basic droplet beside it makes the $63 in the table above. At about 10 posts a second, that tier does not need the dedicated CPU.&lt;/p&gt;

&lt;p&gt;For 100M users, we start needing one of the bigger offerings. 400GiB should be enough, but we'll double it to 800GiB to be safe.&lt;/p&gt;

&lt;p&gt;DigitalOcean has a PostgreSQL Advanced plan at $352 a month for 800 GiB, with 16 GiB of RAM and 4 vCPUs. It includes backups and point-in-time recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollup time
&lt;/h3&gt;

&lt;p&gt;On a busy day, rolling 50 million rows into the daily total would take about 25 minutes. Posts that arrive during that time are still stored.&lt;/p&gt;

&lt;p&gt;Approaches such as partitioning the table by day could make that a quick task, but aren't yet supported by Patch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding redundancy
&lt;/h3&gt;

&lt;p&gt;To keep publishing and metric posts going when a machine dies, add three pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A standby database node. It is promoted if the primary dies. Without one, the database is down while a replacement is provisioned. A standby does not take inserts itself.&lt;/li&gt;
&lt;li&gt;A second 2 vCPU droplet, so metric posts and publishing have a machine to land on. The first droplet already covers the busy hour.&lt;/li&gt;
&lt;li&gt;A load balancer in front of those droplets. In front of one droplet it does nothing when that droplet dies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers a dead primary database or a dead droplet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data transfer
&lt;/h3&gt;

&lt;p&gt;The phones download on the order of a petabyte a month. A few megabytes, once a week, across 100 million devices is already about that.&lt;/p&gt;

&lt;p&gt;Cloudflare serves those files, and CDN bandwidth is included in the plan. The objects live in R2, where egress is free. The droplet only sends metric responses, a few hundred gigabytes, which fits in the transfer included with the machine. That means our data transfer costs come to $0.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's not needed
&lt;/h3&gt;

&lt;p&gt;Because requests don't hit the server, pieces to deal with that load aren't needed for Patch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No queue. The post is stored before the server acknowledges it. The phone retries on failure, and &lt;code&gt;event_id&lt;/code&gt; keeps that retry from inserting a row that is already there.&lt;/li&gt;
&lt;li&gt;No HTTP cache in front of the API. The reads worth caching are the manifest and the bundle, which are already on the CDN. Posts are writes, so there is no response to cache.&lt;/li&gt;
&lt;li&gt;No Redis. It is often added for metadata, a cached update check, or metric counters. Metadata and metric rows are in Postgres, and the update check is those manifest files.&lt;/li&gt;
&lt;li&gt;No Kubernetes or API gateway. Two droplets and a load balancer cover a dead machine. Downloads stay on the CDN, so a gateway has no phone traffic to spread.&lt;/li&gt;
&lt;li&gt;No autoscaling. The droplet count does not follow download traffic, because that traffic never reaches the server.&lt;/li&gt;
&lt;li&gt;No multi-region Postgres. One database holds the eight-day window, and update checks do not read it.&lt;/li&gt;
&lt;li&gt;No timeseries database. A timeseries database such as Prometheus could give more granular analysis, but is option.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you pay
&lt;/h2&gt;

&lt;p&gt;At 100 million the month looks like this. Prices are the current &lt;a href="https://www.digitalocean.com/pricing/droplets" rel="noopener noreferrer"&gt;droplet list&lt;/a&gt;, &lt;a href="https://www.digitalocean.com/pricing/managed-databases" rel="noopener noreferrer"&gt;managed Postgres list&lt;/a&gt;, and &lt;a href="https://docs.digitalocean.com/products/networking/load-balancers/details/pricing/" rel="noopener noreferrer"&gt;load balancer price&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two API droplets&lt;/td&gt;
&lt;td&gt;2 vCPU and 8 GiB each&lt;/td&gt;
&lt;td&gt;$126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancer&lt;/td&gt;
&lt;td&gt;Sends metric posts and publishing to the live droplet&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Postgres&lt;/td&gt;
&lt;td&gt;4 vCPUs, 16 GiB, 800 GiB. Backups and point-in-time recovery&lt;/td&gt;
&lt;td&gt;$352&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standby node&lt;/td&gt;
&lt;td&gt;A second copy of that database in case it dies&lt;/td&gt;
&lt;td&gt;$352&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Droplet egress&lt;/td&gt;
&lt;td&gt;Metric responses, a few hundred gigabytes. That sits inside the transfer included with the droplet&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;The release artifacts, tens of gigabytes. Egress is $0&lt;/td&gt;
&lt;td&gt;about $1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare&lt;/td&gt;
&lt;td&gt;Cache in front of the storage hostname&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$843&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;100 million at the published bands&lt;/td&gt;
&lt;td&gt;$7,250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$8,093&lt;br&gt;/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The license is the main item line, which is required above a million users. Pricing is tiered per million users, with details on the &lt;a href="https://patch.codemagic.io/docs/introduction/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to try out Patch?
&lt;/h2&gt;

&lt;p&gt;To find out more about Patch and view the docs, visit &lt;a href="https://patch.codemagic.io/" rel="noopener noreferrer"&gt;patch.codemagic.io&lt;/a&gt;. It includes a local evaluation stack so you can play with it on your machine.&lt;/p&gt;

&lt;p&gt;If you want to use it for over a million users, ask &lt;a href="mailto:sales@codemagic.io"&gt;sales@codemagic.io&lt;/a&gt; for more information.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>mobile</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Java Heap Space error and how Codemagic helps to remediate it</title>
      <dc:creator>Codemagic</dc:creator>
      <pubDate>Thu, 12 Sep 2024 11:15:42 +0000</pubDate>
      <link>https://dev.to/codemagicio/java-heap-space-error-and-how-codemagic-helps-to-remediate-it-24k3</link>
      <guid>https://dev.to/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>Codemagic</dc:creator>
      <pubDate>Wed, 22 Nov 2023 08:17:00 +0000</pubDate>
      <link>https://dev.to/codemagicio/100-flakiness-free-ui-test-automation-with-kaspresso-and-allure-testops-mkb</link>
      <guid>https://dev.to/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/" rel="noopener noreferrer"&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/" rel="noopener noreferrer"&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://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%2Fneh2lzyx6q7kuncq5xpy.jpeg" 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%2Fneh2lzyx6q7kuncq5xpy.jpeg" alt=" " 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://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%2Fy6a5xkhppgp5ry014c83.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%2Fy6a5xkhppgp5ry014c83.png" alt=" " 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/" rel="noopener noreferrer"&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://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%2Ffvm8pptcl4abdt5ul3wh.jpeg" 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%2Ffvm8pptcl4abdt5ul3wh.jpeg" alt=" " 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://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%2Fl06rars1zrfx1yyhkhkh.jpeg" 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%2Fl06rars1zrfx1yyhkhkh.jpeg" alt=" " 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://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%2Fzi5vhxtkvo24udailix0.jpeg" 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%2Fzi5vhxtkvo24udailix0.jpeg" alt=" " 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://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%2Fhg5oely3ei3ol9db4vne.jpeg" 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%2Fhg5oely3ei3ol9db4vne.jpeg" alt=" " 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://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%2Fhploue3uw66z86f34yzx.jpeg" 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%2Fhploue3uw66z86f34yzx.jpeg" alt=" " 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://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%2Fhfcsnb9vfbfwh749l1ai.jpeg" 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%2Fhfcsnb9vfbfwh749l1ai.jpeg" alt=" " 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://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%2Ffqjxl5lztlje6g3fnifc.jpeg" 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%2Ffqjxl5lztlje6g3fnifc.jpeg" alt=" " 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>Codemagic</dc:creator>
      <pubDate>Sun, 05 Nov 2023 13:03:36 +0000</pubDate>
      <link>https://dev.to/codemagicio/how-to-test-native-features-in-flutter-apps-with-patrol-and-codemagic-17ke</link>
      <guid>https://dev.to/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&amp;nbsp;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&amp;nbsp;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,&amp;nbsp;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&amp;nbsp;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&amp;nbsp;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://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%2Fupbhyj1ckp0tgf31h29w.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%2Fupbhyj1ckp0tgf31h29w.png" alt="Codemagic Patrol test app" width="779" height="731"&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://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%2Ftxnjjwn5r9hycooz18tq.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%2Ftxnjjwn5r9hycooz18tq.png" alt="Gradle index.html" width="799" height="601"&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&amp;nbsp;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://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%2Fvt3niybnvvybl92shta3.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%2Fvt3niybnvvybl92shta3.png" alt="A failing Patrol test in the Codemagic build log" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gathering the Patrol test report as a build&amp;nbsp;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>Codemagic</dc:creator>
      <pubDate>Tue, 31 Jan 2023 13:15:23 +0000</pubDate>
      <link>https://dev.to/codemagicio/introducing-the-official-codemagic-integration-for-runway-1ed4</link>
      <guid>https://dev.to/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&amp;nbsp;&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&amp;nbsp;CI/CD solution&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;and&amp;nbsp;&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&amp;nbsp;&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&amp;nbsp;Runway&amp;nbsp;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&amp;nbsp;give your entire mobile org a home. By keeping everyone --- and we mean&amp;nbsp;&lt;em&gt;all&lt;/em&gt;&amp;nbsp;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&amp;nbsp;&lt;a href="https://docs.runway.team/integrations/ci-cd/codemagic" rel="noopener noreferrer"&gt;integrate Codemagic&lt;/a&gt;&amp;nbsp;with Runway. After heading to&amp;nbsp;&lt;a href="https://www.runway.team/" rel="noopener noreferrer"&gt;runway.team&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&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:&amp;nbsp;Codemagic and Runway are both free to start with. So, if you haven't done so already,&amp;nbsp;&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;&amp;nbsp;and head to&amp;nbsp;&lt;a href="https://www.runway.team/" rel="noopener noreferrer"&gt;runway.team&lt;/a&gt;&amp;nbsp;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>Codemagic</dc:creator>
      <pubDate>Mon, 09 Jan 2023 11:32:10 +0000</pubDate>
      <link>https://dev.to/codemagicio/creating-webview-content-blockers-with-flutter-inappwebview-3695</link>
      <guid>https://dev.to/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&amp;nbsp;&lt;a href="https://github.com/pichillilorenzo/flutter_inappwebview" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_inappwebview&lt;/code&gt;&lt;/a&gt;&amp;nbsp;plugin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read&amp;nbsp;&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&amp;nbsp;&lt;code&gt;contentBlockers&lt;/code&gt;&amp;nbsp;property of the&amp;nbsp;&lt;code&gt;InAppWebViewSettings&lt;/code&gt;&amp;nbsp;class, we can define a list of&amp;nbsp;&lt;code&gt;ContentBlocker&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;ContentBlocker&lt;/code&gt;&amp;nbsp;class. Each one contains an&amp;nbsp;action&amp;nbsp;property and a&amp;nbsp;trigger&amp;nbsp;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&amp;nbsp;&lt;code&gt;urlFilter&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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;:&amp;nbsp;&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;:&amp;nbsp;&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;:&amp;nbsp;&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&amp;nbsp;`&lt;/em&gt;&lt;code&gt;&amp;nbsp;in front to match the domain and subdomains. It can't be used with&amp;nbsp;&lt;/code&gt;unlessDomain`.*&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;unlessDomain&lt;/code&gt;:&amp;nbsp;&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&amp;nbsp;`&lt;/em&gt;&lt;code&gt;&amp;nbsp;in front to match the domain and subdomains. It can't be used with&amp;nbsp;&lt;/code&gt;ifDomain`.*&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;loadType&lt;/code&gt;:&amp;nbsp;&lt;em&gt;A list of&amp;nbsp;&lt;code&gt;ContentBlockerTriggerLoadType&lt;/code&gt;&amp;nbsp;that can include one of two mutually exclusive values. If not specified, the rule matches all load types.&amp;nbsp;&lt;code&gt;ContentBlockerTriggerLoadType.FIRST_PARTY&lt;/code&gt;&amp;nbsp;triggers only if the resource has the same scheme, domain, and port as the main page resource.&amp;nbsp;&lt;code&gt;ContentBlockerTriggerLoadType.THIRD_PARTY&lt;/code&gt;&amp;nbsp;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;:&amp;nbsp;&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&amp;nbsp;&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;:&amp;nbsp;&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&amp;nbsp;&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;:&amp;nbsp;&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;:&amp;nbsp;&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:&amp;nbsp;&lt;code&gt;type&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;selector&lt;/code&gt;. An action type is required.&lt;/p&gt;

&lt;p&gt;If the type is&amp;nbsp;&lt;code&gt;ContentBlockerActionType.CSS_DISPLAY_NONE&lt;/code&gt;, a&amp;nbsp;&lt;code&gt;selector&lt;/code&gt;&amp;nbsp;is required as well; otherwise, the&amp;nbsp;&lt;code&gt;selector&lt;/code&gt;&amp;nbsp;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;:&amp;nbsp;&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;:&amp;nbsp;&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&amp;nbsp;&lt;code&gt;BLOCK_COOKIES&lt;/code&gt;&amp;nbsp;with&amp;nbsp;&lt;code&gt;IGNORE_PREVIOUS_RULES&lt;/code&gt;&amp;nbsp;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;:&amp;nbsp;&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;:&amp;nbsp;&lt;em&gt;Changes a URL from&amp;nbsp;&lt;code&gt;http&lt;/code&gt;&amp;nbsp;to&amp;nbsp;&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;:&amp;nbsp;&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&amp;nbsp;ad blocker&amp;nbsp;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&amp;nbsp;&lt;em&gt;Disable/Enable&lt;/em&gt;&amp;nbsp;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/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/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7r2r6qgvcpwi8g6t27tk.gif" alt="Content Blocker example with Flutter InAppWebView" width="296" height="640"&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&amp;nbsp;&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&amp;nbsp;&lt;a href="https://inappwebview.dev/submit-app/" rel="noopener noreferrer"&gt;Submit Application&lt;/a&gt;&amp;nbsp;page and follow the instructions.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;This project follows the&amp;nbsp;&lt;a href="https://github.com/all-contributors/all-contributors" rel="noopener noreferrer"&gt;all-contributors&lt;/a&gt;&amp;nbsp;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>Codemagic</dc:creator>
      <pubDate>Fri, 16 Dec 2022 08:33:53 +0000</pubDate>
      <link>https://dev.to/codemagicio/migrating-a-flutter-app-to-material-3-5fp4</link>
      <guid>https://dev.to/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,&amp;nbsp;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,&amp;nbsp;Flutter apps can have a consistent design across multiple platforms.&amp;nbsp;Material Design 3&amp;nbsp;(the technical name for Material You) is sometimes referred to as&amp;nbsp;Material 3&amp;nbsp;or simply&amp;nbsp;M3&amp;nbsp;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&amp;nbsp;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.&amp;nbsp;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&amp;nbsp;&lt;a href="https://github.com/flutter/flutter/issues/91605" rel="noopener noreferrer"&gt;Bring Material 3 to Flutter&lt;/a&gt;&amp;nbsp;issue.&lt;/p&gt;

&lt;p&gt;Currently,&amp;nbsp;Material 3 changes are only available when opting in, so you'll need to use the&amp;nbsp;&lt;code&gt;useMaterial3&lt;/code&gt;&amp;nbsp;flag on&amp;nbsp;&lt;code&gt;ThemeData&lt;/code&gt;&amp;nbsp;to&amp;nbsp;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&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;&amp;nbsp;(including the ability to generate a full&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;,&amp;nbsp;&lt;code&gt;FloatingActionButton&lt;/code&gt;&amp;nbsp;(FAB),&amp;nbsp;&lt;code&gt;ElevatedButton&lt;/code&gt;,&amp;nbsp;&lt;code&gt;OutlinedButton&lt;/code&gt;,&amp;nbsp;&lt;code&gt;IconButton&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;Card&lt;/code&gt;, have been&amp;nbsp;updated to support Material 3 design. There are also&amp;nbsp;new Material 3 widgets, such as&amp;nbsp;&lt;code&gt;NavigationBar&lt;/code&gt;,&amp;nbsp;&lt;code&gt;NavigationDrawer&lt;/code&gt;, and&lt;code&gt;SegmentedButton&lt;/code&gt;, some new M3-style buttons, such as&amp;nbsp;&lt;code&gt;FilledButton&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&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&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;has no elevation or background color, and the&amp;nbsp;&lt;code&gt;FAB&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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&amp;nbsp;key changes in Material 3&amp;nbsp;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&amp;nbsp;Dynamic color, which enables you to&amp;nbsp;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&amp;nbsp;&lt;a href="https://m3.material.io/theme-builder#/dynamic" rel="noopener noreferrer"&gt;Material Theme Builder web app&lt;/a&gt;&amp;nbsp;or the&amp;nbsp;&lt;a href="https://www.figma.com/community/plugin/1034969338659738588/Material-Theme-Builder" rel="noopener noreferrer"&gt;Figma plugin&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;package that contains algorithms to create a Material Design 3 color system. You can create color schemes for your apps using&amp;nbsp;&lt;a href="https://pub.dev/packages/dynamic_color" rel="noopener noreferrer"&gt;&lt;code&gt;dynamic_color&lt;/code&gt;&lt;/a&gt;&amp;nbsp;based on a platform's implementation of dynamic color.&lt;/p&gt;

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

&lt;p&gt;For instance, add&amp;nbsp;&lt;code&gt;colorSchemeSeed: Colors.green&lt;/code&gt;&amp;nbsp;to the starter app. Notice that the&amp;nbsp;&lt;code&gt;FAB&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;colorSchemeSeed&lt;/code&gt;&amp;nbsp;when&amp;nbsp;&lt;code&gt;primarySwatch&lt;/code&gt;&amp;nbsp;is present will throw&amp;nbsp;&lt;code&gt;assertion&lt;/code&gt;&amp;nbsp;:&amp;nbsp;&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&amp;nbsp;&lt;code&gt;primarySwatch: Colors.blue,&lt;/code&gt;&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5v3ncjfn1qkgum1fns7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj5v3ncjfn1qkgum1fns7.png" width="331" height="701"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, I've created an example that shows all the colors from the M3&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;. On the left, we have the default&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;, which is available when setting the&amp;nbsp;&lt;code&gt;useMaterial&lt;/code&gt;&amp;nbsp;flag to true. And on the right, we're using the custom&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;, generated using the&amp;nbsp;&lt;code&gt;colorSchemeSeed&lt;/code&gt;&amp;nbsp;parameter, which takes a seed color or key color to generate a full Material 3&amp;nbsp;&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&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Custom M3&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6vjs5ip7y2h8ekf796p.png" width="455" height="982"&gt;&lt;/td&gt;
&lt;td&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fedopclrwwsavddq58y3d.png" width="455" height="982"&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&amp;nbsp;&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&amp;nbsp;&lt;code&gt;ColorScheme.fromSeed&lt;/code&gt;, and&amp;nbsp;&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.,&amp;nbsp;&lt;code&gt;BodyLarge&lt;/code&gt;,&amp;nbsp;&lt;code&gt;BodyMedium&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;BodySmall&lt;/code&gt;&amp;nbsp;instead of&amp;nbsp;&lt;code&gt;bodyText1&lt;/code&gt;,&amp;nbsp;&lt;code&gt;bodyText2&lt;/code&gt;, and&amp;nbsp;&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://media2.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://media2.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" width="800" height="400"&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&amp;nbsp;&lt;code&gt;surfaceTintColor&lt;/code&gt;&amp;nbsp;color property. When applied to elevated components, the&amp;nbsp;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://media2.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://media2.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" width="800" height="400"&gt;&lt;/a&gt;&amp;nbsp;Source:&amp;nbsp;&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&amp;nbsp;&lt;code&gt;surfaceTintColor&lt;/code&gt;&amp;nbsp;property is added to all the elevated widgets in Flutter, along with the&amp;nbsp;&lt;code&gt;elevation&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;shadow&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;surfaceTintColor&lt;/code&gt;&amp;nbsp;color, which makes them visible even when no shadow is provided.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;M2&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;M3&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8g4l5hq4s3023al9pz6v.png" width="330" height="116"&gt;&lt;/td&gt;
&lt;td&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwwyb84bgwi0u3s99h29.png" width="330" height="116"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Note: This&amp;nbsp;&lt;code&gt;AppBar.elevation&lt;/code&gt;&amp;nbsp;property is different from the new&amp;nbsp;&lt;code&gt;AppBar.scrolledUnderElevation&lt;/code&gt;&amp;nbsp;property, which is only in effect when the content is scrolled underneath the&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl860ore7pmx6zotq9vn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl860ore7pmx6zotq9vn.png" width="330" height="116"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh96dns2rla9b0n54vmxr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh96dns2rla9b0n54vmxr.png" width="330" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Material 3 offers a&amp;nbsp;wider range of shapes, including squared, rounded, and rounded rectangular shapes. The&amp;nbsp;&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&amp;nbsp;&lt;code&gt;Card&lt;/code&gt;,&amp;nbsp;&lt;code&gt;Dialog&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;BottomSheet&lt;/code&gt;&amp;nbsp;are also more rounded in M3.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbk4yxdw9p3nsotq8rd1j.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbk4yxdw9p3nsotq8rd1j.png" width="536" height="400"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa4rnb706jcfc4sd6tak.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa4rnb706jcfc4sd6tak.png" width="536" height="400"&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,&amp;nbsp;&lt;code&gt;ElevatedButton&lt;/code&gt;&amp;nbsp;can be replaced with the new&amp;nbsp;&lt;code&gt;FilledButton&lt;/code&gt;&amp;nbsp;to preserve the visual design, and&amp;nbsp;&lt;code&gt;BottomNavigationBar&lt;/code&gt;&amp;nbsp;can be replaced with the new M3-style&amp;nbsp;&lt;code&gt;NavigationBar&lt;/code&gt;&amp;nbsp;widget. The demo app also contains some customization needed in an M2 app for some widgets to stack well. For instance,&amp;nbsp;&lt;code&gt;InputDecorationTheme&lt;/code&gt;&amp;nbsp;applies a custom&amp;nbsp;&lt;code&gt;fillColor&lt;/code&gt;&amp;nbsp;so that the&amp;nbsp;&lt;code&gt;TextField&lt;/code&gt;&amp;nbsp;is visible when placed in the&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9pmtokx7pmu9elawdj5.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9pmtokx7pmu9elawdj5.png" width="591" height="1280"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyi0epj0087uw9jb4setp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyi0epj0087uw9jb4setp.png" width="591" height="1280"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Febrpeap786pqfumv813n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Febrpeap786pqfumv813n.png" width="591" height="1280"&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&amp;nbsp;&lt;code&gt;InputDecorationTheme&lt;/code&gt;&amp;nbsp;to make the&amp;nbsp;&lt;code&gt;TextField&lt;/code&gt;&amp;nbsp;visible in the dark green&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;. But when migrating to M3, we can remove this, and the&amp;nbsp;&lt;code&gt;TextField&lt;/code&gt;&amp;nbsp;will be visible without any customization.&lt;/p&gt;

&lt;p&gt;In the demo, remove the existing&amp;nbsp;&lt;code&gt;colorScheme&lt;/code&gt;&amp;nbsp;property,&amp;nbsp;&lt;code&gt;inputDecorationTheme&lt;/code&gt;, and&amp;nbsp;&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&amp;nbsp;&lt;code&gt;ColorScheme&lt;/code&gt;&amp;nbsp;that's applied to all the components in the demo app. Each UI component is using specific tonal palette colors.&amp;nbsp;&lt;code&gt;TextField&lt;/code&gt;&amp;nbsp;uses an appropriate&amp;nbsp;&lt;code&gt;fillColor&lt;/code&gt;, and cards have the default greenish&amp;nbsp;&lt;code&gt;surfaceTintColor&lt;/code&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7xuhcqb07d5wldn28dw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7xuhcqb07d5wldn28dw.png" width="591" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IconButton&lt;/code&gt;&amp;nbsp;now supports the&amp;nbsp;&lt;code&gt;ButtonStyle&lt;/code&gt;&amp;nbsp;property, which you can use to customize the&amp;nbsp;&lt;code&gt;IconButton&lt;/code&gt;&amp;nbsp;to get an M3 visual and a new selected state. Let's update the&amp;nbsp;&lt;code&gt;IconButton&lt;/code&gt;&amp;nbsp;from the&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;in the demo app to an M3 filled-style icon button.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzurueioqvejdtqlt9f25.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzurueioqvejdtqlt9f25.png" width="331" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out&amp;nbsp;&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;&amp;nbsp;and&amp;nbsp;&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;&amp;nbsp;to learn more.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;You can implement&amp;nbsp;&lt;code&gt;AppBar.notificationPredicate&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;is elevated when content is scrolled underneath the&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;. You can adjust this elevation using the new&amp;nbsp;&lt;code&gt;scrolledUnderElevation&lt;/code&gt;&amp;nbsp;property.&lt;/p&gt;

&lt;p&gt;Check out the official&amp;nbsp;&lt;code&gt;scrolledUnderElevation&lt;/code&gt;&amp;nbsp;code sample I've added in the&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;&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&amp;nbsp;&lt;code&gt;flutter create --sample=material.AppBar.2 mysample&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;BottomNavigationBar&lt;/code&gt;, so let's replace it with the new M3-style&amp;nbsp;&lt;code&gt;NavigationBar&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;BottomNavigationBar&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;M3-style&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9voaqy9v4d2g3a8z79b3.png" width="331" height="134"&gt;&lt;/td&gt;
&lt;td&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0l6qvbvei3xgr5z3cerz.png" width="331" height="134"&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&amp;nbsp;&lt;a href="https://api.flutter.dev/flutter/material/NavigationBar-class.html" rel="noopener noreferrer"&gt;NavigationBar class&lt;/a&gt;&amp;nbsp;for more details.&lt;/p&gt;

&lt;p&gt;Tapping on the&amp;nbsp;&lt;code&gt;FAB&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;surfaceTintColor&lt;/code&gt;&amp;nbsp;has been applied and the content padding has been slightly modified, as it has an optional&amp;nbsp;&lt;code&gt;icon&lt;/code&gt;&amp;nbsp;property.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkp5s1pvz42334355n1x0.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkp5s1pvz42334355n1x0.png" width="607" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the documentation for the&amp;nbsp;&lt;a href="https://api.flutter.dev/flutter/material/showDialog.html" rel="noopener noreferrer"&gt;showDialog function&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;SliverAppBar&lt;/code&gt;&amp;nbsp;with&amp;nbsp;&lt;code&gt;expandedHeight&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;flexibleSpace&lt;/code&gt;&amp;nbsp;to make the app bar title large and the&amp;nbsp;&lt;code&gt;SliverAppBar&lt;/code&gt;&amp;nbsp;collapsable. This can be replaced with the new&amp;nbsp;&lt;code&gt;SliverAppBar.large&lt;/code&gt;, which supports large titles and is also collapsable. When doing so, we can remove&amp;nbsp;&lt;code&gt;expandedHeight&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&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&amp;nbsp;&lt;code&gt;AppBar&lt;/code&gt;&amp;nbsp;variants, which support medium and large titles, use the new&amp;nbsp;&lt;code&gt;SliverAppBar.medium&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;SliverAppBar.large&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check out the documentation for the&amp;nbsp;&lt;a href="https://api.flutter.dev/flutter/material/SliverAppBar/SliverAppBar.medium.html" rel="noopener noreferrer"&gt;SilverAppBar.medium constructor&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://api.flutter.dev/flutter/material/SliverAppBar/SliverAppBar.large.html" rel="noopener noreferrer"&gt;SilverAppBar.large constructor&lt;/a&gt;&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71ymcu1dc6711wfj8jwu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71ymcu1dc6711wfj8jwu.png" width="591" height="1280"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5daeg2tj2fcw4i4xbgsp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5daeg2tj2fcw4i4xbgsp.png" width="591" height="1280"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqwexhxksw5mv6nfpum4.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqwexhxksw5mv6nfpum4.png" width="591" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The source code for all the demos is available&amp;nbsp;&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&amp;nbsp;&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>Codemagic</dc:creator>
      <pubDate>Mon, 21 Nov 2022 08:33:46 +0000</pubDate>
      <link>https://dev.to/codemagicio/10-reasons-to-choose-codemagic-cicd-in-2022-2023-4a3f</link>
      <guid>https://dev.to/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" rel="noopener noreferrer"&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&amp;nbsp;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&amp;nbsp;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&amp;nbsp;&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" rel="noopener noreferrer"&gt;"Continuous Integration and Delivery for Mobile Apps"&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below, you'll find&amp;nbsp;ten reasons why you should consider using Codemagic&amp;nbsp;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&amp;nbsp;unique billing model&amp;nbsp;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&amp;nbsp;&lt;a href="https://codemagic.io/pricing/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&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&amp;nbsp;&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" rel="noopener noreferrer"&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&amp;nbsp;&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" rel="noopener noreferrer"&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" rel="noopener noreferrer"&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" rel="noopener noreferrer"&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" rel="noopener noreferrer"&gt;Windows build machine specs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn more about how&amp;nbsp;&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" rel="noopener noreferrer"&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&amp;nbsp;&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" rel="noopener noreferrer"&gt;get started with the Workflow Editor&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&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" rel="noopener noreferrer"&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&amp;nbsp;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&amp;nbsp;&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" rel="noopener noreferrer"&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&amp;nbsp;&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" rel="noopener noreferrer"&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&amp;nbsp;&lt;a href="https://codemagic.io/integrations/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&gt;integrations&lt;/a&gt;&amp;nbsp;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" rel="noopener noreferrer"&gt;&amp;nbsp;Katalon&lt;/a&gt;&amp;nbsp;(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" rel="noopener noreferrer"&gt;&amp;nbsp;Kobiton&lt;/a&gt;&amp;nbsp;(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" rel="noopener noreferrer"&gt;&amp;nbsp;Sofy&lt;/a&gt;&amp;nbsp;(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" rel="noopener noreferrer"&gt;&amp;nbsp;Firebase Test Lab&lt;/a&gt;&amp;nbsp;(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&amp;nbsp;&lt;a href="https://codemagic.io/integrations/?utm_source=devto&amp;amp;utm_medium=cm-reasons-devto&amp;amp;utm_campaign=flutter_distribution" rel="noopener noreferrer"&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/" rel="noopener noreferrer"&gt;&amp;nbsp;GitHub Discussions&lt;/a&gt;&amp;nbsp;(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" rel="noopener noreferrer"&gt;&amp;nbsp;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>Codemagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 14:01:18 +0000</pubDate>
      <link>https://dev.to/codemagicio/how-to-build-a-chrome-extension-with-flutter-web-618</link>
      <guid>https://dev.to/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&amp;nbsp;is the most popular web browser in the world.&amp;nbsp;Chrome extensions&amp;nbsp;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&amp;nbsp;browser extensions&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;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&amp;nbsp;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&amp;nbsp;Flutter application that runs as a Chrome extension. It will look like this.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3l0j4eb45bepzzych761.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3l0j4eb45bepzzych761.png" width="599" height="854"&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&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;web/&lt;/code&gt;&amp;nbsp;folder in a default Flutter project, you will see a&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;file to register an extension. Therefore, to make our extension work, delete the contents inside the&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;icons/&lt;/code&gt;&amp;nbsp;directory.&lt;/p&gt;

&lt;p&gt;In the&amp;nbsp;&lt;code&gt;action&lt;/code&gt;&amp;nbsp;field in the&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;file, set&amp;nbsp;&lt;code&gt;index.html&lt;/code&gt;&amp;nbsp;as the default pop-up page. This way, when you click on the extension icon in the browser's top bar, the&amp;nbsp;&lt;code&gt;index.html&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;file. Here, we define&amp;nbsp;&lt;code&gt;script-src&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;object-src&lt;/code&gt;&amp;nbsp;as&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxmisffnyzjuscllw3ok9.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxmisffnyzjuscllw3ok9.png" width="679" height="845"&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&amp;nbsp;&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&amp;nbsp;&lt;code&gt;index.html&lt;/code&gt;&amp;nbsp;file in the&amp;nbsp;&lt;code&gt;web/&lt;/code&gt;&amp;nbsp;directory. Then link the&amp;nbsp;&lt;code&gt;main.dart.js&lt;/code&gt;&amp;nbsp;file inside the&amp;nbsp;&lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;&amp;nbsp;tag. The&amp;nbsp;&lt;code&gt;main.dart.js&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;index.html&lt;/code&gt;&amp;nbsp;file, and add an explicit width and height to the&amp;nbsp;&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;&amp;nbsp;element. After all these changes, the final version of&amp;nbsp;&lt;code&gt;index.html&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;background.js&lt;/code&gt;&amp;nbsp;inside the&amp;nbsp;&lt;code&gt;web/&lt;/code&gt;&amp;nbsp;directory.&lt;/p&gt;

&lt;p&gt;Then, register the background script in your&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;permissions&lt;/code&gt;&amp;nbsp;field in the&amp;nbsp;&lt;code&gt;manifest.json&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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,&amp;nbsp;HTML renderer, uses a combination of HTML elements, CSS, Canvas elements, and SVG elements. The second,&amp;nbsp;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&amp;nbsp;&lt;code&gt;flutter build web&lt;/code&gt;&amp;nbsp;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,&amp;nbsp;Chrome extensions don't support CanvasKit-rendered applications. Therefore, add the&amp;nbsp;&lt;code&gt;--web-renderer html&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;--csp&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;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&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezrrxviaib61pdxmjnvl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezrrxviaib61pdxmjnvl.png" width="799" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, find your project repository in the dropdown menu, and select&amp;nbsp;&lt;em&gt;"Flutter App (via Workflow Editor)"&lt;/em&gt;&amp;nbsp;as the project type.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyj6puj0dfzsjn0epb8i2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyj6puj0dfzsjn0epb8i2.png" width="800" height="789"&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://media2.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://media2.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" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the build argument, add the&amp;nbsp;&lt;code&gt;--web-renderer&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;--csp&lt;/code&gt;&amp;nbsp;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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyzhliumkgsndlgscr6a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyzhliumkgsndlgscr6a.png" width="800" height="198"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwf8zxxbn1tuemxhidpro.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwf8zxxbn1tuemxhidpro.png" width="800" height="622"&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&amp;nbsp;automatically publish in Chrome Web Store&amp;nbsp;using a&amp;nbsp;&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&amp;nbsp;&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&amp;nbsp;&lt;a href="https://console.developers.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;&amp;nbsp;and create a new project. In the search bar, search for&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspeppict07ran0q89345.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspeppict07ran0q89345.png" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the&amp;nbsp;&lt;em&gt;OAuthConsentScreen&lt;/em&gt;&amp;nbsp;and select the user type&amp;nbsp;&lt;em&gt;"External"&lt;/em&gt;. Fill out all the application details in the respective fields, click&amp;nbsp;&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&amp;nbsp;&lt;em&gt;"Save and continue"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To get the access key, go to&amp;nbsp;&lt;em&gt;"Credentials"&lt;/em&gt;&amp;nbsp;and select the&amp;nbsp;&lt;em&gt;"Create credentials"&lt;/em&gt;&amp;nbsp;option. From the dropdown list, select&amp;nbsp;&lt;em&gt;"OAuth client ID"&lt;/em&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6x6gx8oyf9ksjkzeqr02.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6x6gx8oyf9ksjkzeqr02.png" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the application type, select&amp;nbsp;&lt;em&gt;"Desktop App"&lt;/em&gt;, fill out the name, and click&amp;nbsp;&lt;em&gt;"Create"&lt;/em&gt;. Now, a pop-up containing your&amp;nbsp;&lt;em&gt;client ID&lt;/em&gt;&amp;nbsp;and&amp;nbsp;&lt;em&gt;client secret&lt;/em&gt;&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;in your browser, and replace&amp;nbsp;&lt;code&gt;$client_ID&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzokhqgc07azb46lokfe.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzokhqgc07azb46lokfe.png" width="800" height="615"&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&amp;nbsp;&lt;code&gt;curl&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;$CLIENT_ID&lt;/code&gt;,&amp;nbsp;&lt;code&gt;$CLIENT_SECRET&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;$CODE&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;and create a new item. Fill out the store listing and privacy practices page.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yuobic5kxz7t9famrq1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yuobic5kxz7t9famrq1.png" width="799" height="474"&gt;&lt;/a&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ynefslpf1y12elti0aw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ynefslpf1y12elti0aw.png" width="799" height="474"&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&amp;nbsp;&lt;code&gt;curl&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;$TOKEN&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;$FILE_NAME&lt;/code&gt;&amp;nbsp;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://media2.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://media2.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" width="800" height="400"&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&amp;nbsp;&lt;code&gt;ARTIFACT_URL&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;curl&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;code&gt;-T&lt;/code&gt;&amp;nbsp;flag from the previous curl script with the&amp;nbsp;&lt;code&gt;--url&lt;/code&gt;&amp;nbsp;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&amp;nbsp;&lt;a href="https://developer.chrome.com/docs/webstore/using_webstore_api/" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&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;&amp;nbsp;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>Codemagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 13:50:46 +0000</pubDate>
      <link>https://dev.to/codemagicio/dr-riverpod-how-i-learned-to-stop-worrying-and-love-state-management-4c4f</link>
      <guid>https://dev.to/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" rel="noopener noreferrer"&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&amp;nbsp;&lt;a href="https://twitter.com/MatanLurey/status/1569428738764570624?s=20&amp;amp;t=oo0GhEowBANGVZgmtC7ooQ" rel="noopener noreferrer"&gt;rare enlightened ones&lt;/a&gt;&amp;nbsp;on the other side who recommend&amp;nbsp;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, ©®&amp;nbsp;&lt;a href="https://twitter.com/mkobuolys" rel="noopener noreferrer"&gt;@mkobuolys&lt;/a&gt;) approach, about a third&amp;nbsp;&lt;a href="https://twitter.com/ASalvadorini/status/1572411146501750785?s=20&amp;amp;t=uDcN2vtvMJ4Y9SwoD4mp6A" rel="noopener noreferrer"&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&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=RS36gBEp8OI" rel="noopener noreferrer"&gt;first&lt;/a&gt;&amp;nbsp;shows&amp;nbsp;&lt;a href="https://twitter.com/filiphracek" rel="noopener noreferrer"&gt;Filip Hracek&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/mjohnsullivan" rel="noopener noreferrer"&gt;Matt Sullivan&lt;/a&gt;&amp;nbsp;on the Google I/O stage in May 2018 introducing BLoC (the pattern) to all of us; the&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=d_m5csmrf7I" rel="noopener noreferrer"&gt;second&lt;/a&gt;&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fuploads%2F2022%2F10%2Fdr-riverpod-sm-how-it-started.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%2Fuploads%2F2022%2F10%2Fdr-riverpod-sm-how-it-started.png" title="Google I/O 2018" alt="Filip and Matt at Google I/O 2018" width="800" height="414"&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,&amp;nbsp;&lt;a href="https://twitter.com/felangelov" rel="noopener noreferrer"&gt;Felix Angelov&lt;/a&gt;, started to develop&amp;nbsp;&lt;a href="https://pub.dev/packages/flutter_bloc" rel="noopener noreferrer"&gt;BLoC&lt;/a&gt;&amp;nbsp;(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&amp;nbsp;&lt;a href="https://youtu.be/d_m5csmrf7I?t=1162" rel="noopener noreferrer"&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&amp;nbsp;Provider, a library created by a young and talented French developer,&amp;nbsp;&lt;a href="https://twitter.com/remi_rousselet" rel="noopener noreferrer"&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:&amp;nbsp;Redux&amp;nbsp;(which was already familiar to developers coming from JavaScript),&amp;nbsp;get_it,&amp;nbsp;MobX, the controversial&amp;nbsp;GetX, and many, many more.&lt;/p&gt;

&lt;p&gt;You can find a non-exhaustive list in the&amp;nbsp;&lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/options" rel="noopener noreferrer"&gt;official Flutter docs&lt;/a&gt;.&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%2Fuploads%2F2022%2F10%2Fdr-riverpod-sm-how-it-is-going.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%2Fuploads%2F2022%2F10%2Fdr-riverpod-sm-how-it-is-going.png" title="Google I/O 2019" alt="Filip and Matt at Google I/O 2019" width="800" height="464"&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:&amp;nbsp;&lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/ephemeral-vs-app" rel="noopener noreferrer"&gt;ephemeral&amp;nbsp;or&amp;nbsp;application state&lt;/a&gt;.&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%2Fdr-riverpod-sm-when-to-use-it_14858182863452772886_huf27a411e689d61e3ed97a42a877ace75_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%2Fdr-riverpod-sm-when-to-use-it_14858182863452772886_huf27a411e689d61e3ed97a42a877ace75_0_1280x1800_fit_linear_3.png" title="When to use state management" alt="When to use state management" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of the&amp;nbsp;ephemeral state&amp;nbsp;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&amp;nbsp;application state&amp;nbsp;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&amp;nbsp;&lt;a href="https://twitter.com/RydMike" rel="noopener noreferrer"&gt;Mike Rydstrom&lt;/a&gt;, who created an exhaustive&amp;nbsp;&lt;a href="https://twitter.com/RydMike/status/1578462043593535488?s=20&amp;amp;t=r3QQS6Vd1D5VLGeeY5qbLA" rel="noopener noreferrer"&gt;picture&lt;/a&gt;&amp;nbsp;comparing the top 30 state management packages:&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_hu4a0a44225277f7d1cbe1b0a1831efdec_0_19137d11047704fb55637c1fbae704da.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_hu4a0a44225277f7d1cbe1b0a1831efdec_0_19137d11047704fb55637c1fbae704da.png" title="Mike's state managements comparison" alt="Mike's state managements comparison" width="800" height="400"&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&amp;nbsp;&lt;a href="https://www.theseus.fi/bitstream/handle/10024/355086/Dmitrii_Slepnev.pdf" rel="noopener noreferrer"&gt;"State management approaches in Flutter"&lt;/a&gt;&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2F_hu7cd08fc6850691d36f66e01d29da70da_0_23d2040f0edd22d9e3deb7ebd957ec6e.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_hu7cd08fc6850691d36f66e01d29da70da_0_23d2040f0edd22d9e3deb7ebd957ec6e.png" title="Slepnev's state management comparison" alt="Slepnev's state managements comparison" width="800" height="400"&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.&amp;nbsp;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&amp;nbsp;&lt;a href="https://fluttersamples.com/" rel="noopener noreferrer"&gt;code sample app&lt;/a&gt;&amp;nbsp;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:&amp;nbsp;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&amp;nbsp;&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" rel="noopener noreferrer"&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:&amp;nbsp;&lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple" rel="noopener noreferrer"&gt;Simple state management&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&lt;a href="https://github.com/flutter/samples/tree/main/provider_shopper" rel="noopener noreferrer"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.codemagic.io%2Fdr-riverpod-sm-simple-state-management_13268151069072349849_hu8704f1391c75cdccc6f4f8f531148262_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%2Fdr-riverpod-sm-simple-state-management_13268151069072349849_hu8704f1391c75cdccc6f4f8f531148262_0_1280x1800_fit_linear_3.png" title="Simple state management" alt="Simple state management" width="800" height="400"&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" rel="noopener noreferrer"&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&amp;nbsp;&lt;a href="https://twitter.com/ASalvadorini/status/1567189799202705411?s=20&amp;amp;t=Ydm_1uhBQBVqz3bbGLHG8A" rel="noopener noreferrer"&gt;my talk&lt;/a&gt;&amp;nbsp;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&amp;nbsp;&lt;a href="https://riverpod.dev/" rel="noopener noreferrer"&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" 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>flutter</category>
      <category>dart</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>Flutter widgets cheat sheet</title>
      <dc:creator>Codemagic</dc:creator>
      <pubDate>Tue, 01 Nov 2022 13:43:05 +0000</pubDate>
      <link>https://dev.to/codemagicio/flutter-widgets-cheat-sheet-33an</link>
      <guid>https://dev.to/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&amp;nbsp;&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;&amp;nbsp;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&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9radu44wbh21nzu9u1t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9radu44wbh21nzu9u1t.png" width="667" height="1288"&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://media2.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://media2.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" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The&amp;nbsp;AlertDialog&amp;nbsp;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&amp;nbsp;SnackBar&amp;nbsp;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&amp;nbsp;Dismissable&amp;nbsp;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&amp;nbsp;InteractiveViewer&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8qwcgp9q04gtx7du7be.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8qwcgp9q04gtx7du7be.png" width="657" height="1382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try creating a form using the&amp;nbsp;Forms&amp;nbsp;widget and&amp;nbsp;TextFormFields&amp;nbsp;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&amp;nbsp;&lt;code&gt;optionsBuilder&lt;/code&gt;&amp;nbsp;and rendered by the&amp;nbsp;&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&amp;nbsp;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&amp;nbsp;Expanded&amp;nbsp;widget expands the child of a&amp;nbsp;Row&amp;nbsp;or&amp;nbsp;Column&amp;nbsp;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&amp;nbsp;Align&amp;nbsp;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&amp;nbsp;&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&amp;nbsp;Row&amp;nbsp;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&amp;nbsp;Column&amp;nbsp;widget allows you to arrange a list of widgets vertically, similar to how the&amp;nbsp;Row&amp;nbsp;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&amp;nbsp;SizedBox&amp;nbsp;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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo7tajunsq16jf8cwwvkl.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo7tajunsq16jf8cwwvkl.png" width="675" height="1068"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the&amp;nbsp;BaseLine&amp;nbsp;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&amp;nbsp;Padding&amp;nbsp;widget to provide space around its child. The padding widget adds space around its child using the abstract&amp;nbsp;&lt;code&gt;EdgeInsetsGeometry&lt;/code&gt;&amp;nbsp;class.&lt;/p&gt;

&lt;p&gt;The padding property of the padding widget accepts the&amp;nbsp;&lt;code&gt;EdgeInsets&lt;/code&gt;&amp;nbsp;object, which allows you to add different types of padding, including padding only in certain places using&amp;nbsp;&lt;code&gt;EdgeInsets.only&lt;/code&gt;&amp;nbsp;or from different angles using&amp;nbsp;&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&amp;nbsp;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&amp;nbsp;&lt;code&gt;Listview.builder&lt;/code&gt;&amp;nbsp;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&amp;nbsp;SingleChildScrollView&amp;nbsp;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&amp;nbsp;&lt;code&gt;SingleChildScrollView&lt;/code&gt;&amp;nbsp;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:&amp;nbsp;Scaffold&amp;nbsp;and&amp;nbsp;Loader.&lt;/p&gt;

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

&lt;p&gt;Use&amp;nbsp;Scaffold&amp;nbsp;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&amp;nbsp;Loaders&amp;nbsp;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&amp;nbsp;InkWell&amp;nbsp;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&amp;nbsp;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&amp;nbsp;ClipRect&amp;nbsp;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&amp;nbsp;ClipPath&amp;nbsp;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&amp;nbsp;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&amp;nbsp;FloatingActionButton&amp;nbsp;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&amp;nbsp;Navigator&amp;nbsp;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&amp;nbsp;&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&amp;nbsp;&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;&amp;nbsp;that covers a list of libraries commonly used by Flutter developers, including networking, state management, and more. You can also watch our&amp;nbsp;&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>
  </channel>
</rss>
