<?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: Rody Davis</title>
    <description>The latest articles on DEV Community by Rody Davis (@rodydavis).</description>
    <link>https://dev.to/rodydavis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F432806%2Ffd90e317-0b68-4fd3-a03c-ed0c1a5f8c63.jpg</url>
      <title>DEV Community: Rody Davis</title>
      <link>https://dev.to/rodydavis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rodydavis"/>
    <language>en</language>
    <item>
      <title>Building a Rich Text Editor with Lit</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Wed, 26 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/building-a-rich-text-editor-with-lit-4hi3</link>
      <guid>https://dev.to/rodydavis/building-a-rich-text-editor-with-lit-4hi3</guid>
      <description>&lt;p&gt;In this article I will go over how to set up a &lt;a href="https://lit.dev/"&gt;Lit&lt;/a&gt; web component and use it to create a rich text editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; The final source &lt;a href="https://github.com/rodydavis/lit-html-editor"&gt;here&lt;/a&gt; and an online &lt;a href="https://rodydavis.github.io/lit-html-editor/"&gt;demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vscode&lt;/li&gt;
&lt;li&gt;Node &amp;gt;= 12&lt;/li&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started #
&lt;/h2&gt;

&lt;p&gt;We can start off by navigating in terminal to the location of the project and run 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;npm init @vitejs/app --template lit-element-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enter a project name &lt;code&gt;lit-rich-text-editor&lt;/code&gt; and now open the project in vscode and install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd lit-rich-text-editornpm i @material/mwc-icon-buttonnpm i -D @types/nodecode .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;vite.config.ts&lt;/code&gt; with 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;import { defineConfig } from "vite";import { resolve } from "path";export default defineConfig({ base: '/lit-rich-text-editor/', build: { lib: { entry: "src/lit-rich-text-editor.ts", formats: ["es"], }, rollupOptions: { input: { main: resolve(__dirname, "index.html"), }, }, },});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Template #
&lt;/h2&gt;

&lt;p&gt;Open up the &lt;code&gt;index.html&lt;/code&gt; and update it with 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;&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html lang="en"&amp;gt; &amp;lt;head&amp;gt; &amp;lt;meta charset="UTF-8" /&amp;gt; &amp;lt;link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /&amp;gt; &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt; &amp;lt;link href="https://fonts.googleapis.com/css?family=Material+Icons&amp;amp;display=block" rel="stylesheet" /&amp;gt; &amp;lt;title&amp;gt;Lit Rich Text Editor&amp;lt;/title&amp;gt; &amp;lt;script type="module" src="/src/lit-rich-text-editor.ts"&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;style&amp;gt; body { padding: 0; margin: 0; } lit-rich-text-editor { --editor-width: 100%; --editor-height: 100vh; } &amp;lt;/style&amp;gt; &amp;lt;/head&amp;gt; &amp;lt;body&amp;gt; &amp;lt;lit-rich-text-editor&amp;gt; &amp;lt;template&amp;gt; &amp;lt;h1&amp;gt;Headline 1&amp;lt;/h1&amp;gt; &amp;lt;p&amp;gt;This is a paragraph.&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt; &amp;lt;span style="background-color: rgb(255, 0, 0)" &amp;gt;&amp;lt;font color="#ffffff"&amp;gt;Styled Text&amp;lt;/font&amp;gt;&amp;lt;/span &amp;gt; &amp;lt;/p&amp;gt; &amp;lt;p&amp;gt; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. &amp;lt;/p&amp;gt; &amp;lt;/template&amp;gt; &amp;lt;/lit-rich-text-editor&amp;gt; &amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important things to take away are the styles added to remove the body padding and send size &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*"&gt;CSS Custom Properties&lt;/a&gt; to the editor to take up the full viewport.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;lit-rich-text-editor&lt;/code&gt; tags there is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"&gt;&lt;code&gt;template&lt;/code&gt;&lt;/a&gt; passed as a slot to provide html that will not be rendered but can be accessed.&lt;/p&gt;

&lt;p&gt;There is also an import for the &lt;a href="https://fonts.google.com/icons"&gt;Material Icons&lt;/a&gt; so it can be used in the editor later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor #
&lt;/h2&gt;

&lt;p&gt;The next thing to create is the editor itself. Open up &lt;code&gt;src/lit-rich-text-editor.ts&lt;/code&gt; and update it with 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;import { LitElement, html, customElement, css, state } from "lit-element";import "@material/mwc-icon-button";@customElement("lit-rich-text-editor")export class LitRichTextEditor extends LitElement { @state() content: string = ""; @state() root: Element | null = null; static styles = css` :host { --editor-width: 600px; --editor-height: 600px; --editor-background: #f1f1f1; --editor-toolbar-height: 33px; --editor-toolbar-background: black; --editor-toolbar-on-background: white; --editor-toolbar-on-active-background: #a4a4a4; } main { width: var(--editor-width); height: var(--editor-height); display: grid; grid-template-areas: "toolbar toolbar" "editor editor"; grid-template-rows: var(--editor-toolbar-height) auto; grid-template-columns: auto auto; } #editor-actions { grid-area: toolbar; width: var(--editor-width); height: var(--editor-toolbar-height); background-color: var(--editor-toolbar-background); color: var(--editor-toolbar-on-background); overscroll-behavior: contain; overflow-y: auto; -ms-overflow-style: none; scrollbar-width: none; } #editor-actions::-webkit-scrollbar { display: none; } #editor { width: var(--editor-width); grid-area: editor; background-color: var(--editor-background); } #toolbar { width: 1090px; height: var(--editor-toolbar-height); } [contenteditable] { outline: 0px solid transparent; } #toolbar &amp;gt; mwc-icon-button { color: var(--editor-toolbar-on-background); --mdc-icon-size: 20px; --mdc-icon-button-size: 30px; cursor: pointer; } #toolbar &amp;gt; .active { color: var(--editor-toolbar-on-active-background); } select { margin-top: 5px; height: calc(var(--editor-toolbar-height) - 10px); } input[type="color"] { height: calc(var(--editor-toolbar-height) - 15px); -webkit-appearance: none; border: none; width: 22px; } input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } input[type="color"]::-webkit-color-swatch { border: none; } `; render() { return html`&amp;lt;main&amp;gt; &amp;lt;input id="bg" type="color" style="display:none" /&amp;gt; &amp;lt;input id="fg" type="color" style="display:none" /&amp;gt; &amp;lt;div id="editor-actions"&amp;gt; &amp;lt;div id="toolbar"&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;div id="editor"&amp;gt;${this.root}&amp;lt;/div&amp;gt; &amp;lt;/main&amp;gt; `; } async firstUpdated() { const elem = this.parentElement!.querySelector("lit-rich-text-editor template"); this.content = elem?.innerHTML ?? ""; this.reset(); } reset() { const parser = new DOMParser(); const doc = parser.parseFromString(this.content, "text/html"); document.execCommand("defaultParagraphSeparator", false, "br"); document.addEventListener("selectionchange", () =&amp;gt; { this.requestUpdate(); }); const root = doc.querySelector("body"); root!.setAttribute("contenteditable", "true"); this.root = root; }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With everything updated run &lt;code&gt;npm run dev&lt;/code&gt; and the following should appear in the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IZeTg7qO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/rich-text/editor-start.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IZeTg7qO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/rich-text/editor-start.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing special is happening yet, but the template is being read and passed into the element, parsed and setting the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable"&gt;&lt;code&gt;contenteditable&lt;/code&gt;&lt;/a&gt; attribute to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a way to access the slots and use the nodes to hold data that are not used for rendering. Doing it this way allows for a transformation of the HTML source into a format that can be used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Toolbar #
&lt;/h2&gt;

&lt;p&gt;At the bottom of the class before the last &lt;code&gt;}&lt;/code&gt; add 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;renderToolbar(command: (c: string, val: string | undefined) =&amp;gt; void) { // TODO: Selection does not work on Safari iOS const selection = this.shadowRoot?.getSelection ? this.shadowRoot!.getSelection() : null; const tags: string[] = []; if (selection?.type === "Range") { // @ts-ignore let parentNode = selection?.baseNode; if (parentNode) { const checkNode = () =&amp;gt; { const parentTagName = parentNode?.tagName?.toLowerCase()?.trim(); if (parentTagName) tags.push(parentTagName); }; while (parentNode != null) { checkNode(); parentNode = parentNode?.parentNode; } } } const commands: { icon: string; command: string | (() =&amp;gt; void); active?: boolean; type?: string; values?: { value: string; name: string; font?: boolean }[]; command_value?: string; }[] = [{ icon: "format_clear", command: "removeFormat", }, { icon: "format_bold", command: "bold", active: tags.includes("b"), }, { icon: "format_italic", command: "italic", active: tags.includes("i"), }, { icon: "format_underlined", command: "underline", active: tags.includes("u"), }, { icon: "format_align_left", command: "justifyleft", }, { icon: "format_align_center", command: "justifycenter", }, { icon: "format_align_right", command: "justifyright", }, { icon: "format_list_numbered", command: "insertorderedlist", active: tags.includes("ol"), }, { icon: "format_list_bulleted", command: "insertunorderedlist", active: tags.includes("ul"), }, { icon: "format_quote", command: "formatblock", command_value: "blockquote", }, { icon: "format_indent_decrease", command: "outdent", }, { icon: "format_indent_increase", command: "indent", }, { icon: "add_link", command: () =&amp;gt; { const newLink = prompt("Write the URL here", "http://"); if (newLink &amp;amp;&amp;amp; newLink != "" &amp;amp;&amp;amp; newLink != "http://") { command("createlink", newLink); } }, }, { icon: "link_off", command: "unlink" }, { icon: "format_color_text", command: () =&amp;gt; { const input = this.shadowRoot!.querySelector( "#fg" )! as HTMLInputElement; input.addEventListener("input", (e: any) =&amp;gt; { const val = e.target.value; command("forecolor", val); }); input.click(); }, type: "color", }, { icon: "border_color", command: () =&amp;gt; { const input = this.shadowRoot!.querySelector( "#bg" )! as HTMLInputElement; input.addEventListener("input", (e: any) =&amp;gt; { const val = e.target.value; command("backcolor", val); }); input.click(); }, type: "color", }, { icon: "title", command: "formatblock", values: [ { name: "Normal Text", value: "--" }, { name: "Heading 1", value: "h1" }, { name: "Heading 2", value: "h2" }, { name: "Heading 3", value: "h3" }, { name: "Heading 4", value: "h4" }, { name: "Heading 5", value: "h5" }, { name: "Heading 6", value: "h6" }, { name: "Paragraph", value: "p" }, { name: "Pre-Formatted", value: "pre" },], }, { icon: "text_format", command: "fontname", values: [{ name: "Font Name", value: "--" }, ...[...checkFonts()].map((f) =&amp;gt; ({ name: f, value: f, font: true, })), ], }, { icon: "format_size", command: "fontsize", values: [{ name: "Font Size", value: "--" }, { name: "Very Small", value: "1" }, { name: "Small", value: "2" }, { name: "Normal", value: "3" }, { name: "Medium Large", value: "4" }, { name: "Large", value: "5" }, { name: "Very Large", value: "6" }, { name: "Maximum", value: "7" },], }, { icon: "undo", command: "undo", }, { icon: "redo", command: "redo", }, { icon: "content_cut", command: "cut", }, { icon: "content_copy", command: "copy", }, { icon: "content_paste", command: "paste", }, ]; return html` ${commands.map((n) =&amp;gt; { return html` ${n.values ? html` &amp;lt;select id="${n.icon}" @change=${(e: any) =&amp;gt; { const val = e.target.value; if (val === "--") { command("removeFormat", undefined); } else if (typeof n.command === "string") { command(n.command, val); } }} &amp;gt; ${n.values.map( (v) =&amp;gt; html` &amp;lt;option value=${v.value}&amp;gt;${v.name}&amp;lt;/option&amp;gt;` )} &amp;lt;/select&amp;gt;` : html` &amp;lt;mwc-icon-button icon="${n.icon}" class="${n.active ? "active" : "inactive"}" @click=${() =&amp;gt; { if (n.values) { } else if (typeof n.command === "string") { command(n.command, n.command_value); } else { n.command(); } }} &amp;gt;&amp;lt;/mwc-icon-button&amp;gt;`} `; })} `;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes an array of objects that we can map to &lt;code&gt;mwc-icon-button&lt;/code&gt; or &lt;code&gt;select&lt;/code&gt; depending on the passed values. This will also set up the event listeners and execute the command for the given action.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;&amp;lt;div id="toolbar"&amp;gt;&lt;/code&gt; tag add 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;${this.renderToolbar((command, val) =&amp;gt; { document.execCommand(command, false, val); console.log("command", command, val);})}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will listen for the callback and fire the command on the document and log it to the console.&lt;/p&gt;

&lt;p&gt;And finally at the bottom of the file add 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;export function checkFonts(): string[] { const fontCheck = new Set( [// Windows 10 "Arial", "Arial Black", "Bahnschrift", "Calibri", "Cambria", "Cambria Math", "Candara", "Comic Sans MS", "Consolas", "Constantia", "Corbel", "Courier New", "Ebrima", "Franklin Gothic Medium", "Gabriola", "Gadugi", "Georgia", "HoloLens MDL2 Assets", "Impact", "Ink Free", "Javanese Text", "Leelawadee UI", "Lucida Console", "Lucida Sans Unicode", "Malgun Gothic", "Marlett", "Microsoft Himalaya", "Microsoft JhengHei", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Sans Serif", "Microsoft Tai Le", "Microsoft YaHei", "Microsoft Yi Baiti", "MingLiU-ExtB", "Mongolian Baiti", "MS Gothic", "MV Boli", "Myanmar Text", "Nirmala UI", "Palatino Linotype", "Segoe MDL2 Assets", "Segoe Print", "Segoe Script", "Segoe UI", "Segoe UI Historic", "Segoe UI Emoji", "Segoe UI Symbol", "SimSun", "Sitka", "Sylfaen", "Symbol", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana", "Webdings", "Wingdings", "Yu Gothic", // macOS "American Typewriter", "Andale Mono", "Arial", "Arial Black", "Arial Narrow", "Arial Rounded MT Bold", "Arial Unicode MS", "Avenir", "Avenir Next", "Avenir Next Condensed", "Baskerville", "Big Caslon", "Bodoni 72", "Bodoni 72 Oldstyle", "Bodoni 72 Smallcaps", "Bradley Hand", "Brush Script MT", "Chalkboard", "Chalkboard SE", "Chalkduster", "Charter", "Cochin", "Comic Sans MS", "Copperplate", "Courier", "Courier New", "Didot", "DIN Alternate", "DIN Condensed", "Futura", "Geneva", "Georgia", "Gill Sans", "Helvetica", "Helvetica Neue", "Herculanum", "Hoefler Text", "Impact", "Lucida Grande", "Luminari", "Marker Felt", "Menlo", "Microsoft Sans Serif", "Monaco", "Noteworthy", "Optima", "Palatino", "Papyrus", "Phosphate", "Rockwell", "Savoye LET", "SignPainter", "Skia", "Snell Roundhand", "Tahoma", "Times", "Times New Roman", "Trattatello", "Trebuchet MS", "Verdana", "Zapfino",].sort() ); const fontAvailable = new Set&amp;lt;string&amp;gt;(); // @ts-ignore for (const font of fontCheck.values()) { // @ts-ignore if (document.fonts.check(`12px "${font}"`)) { fontAvailable.add(font); } } // @ts-ignore return fontAvailable.values();}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following this great suggestion &lt;a href="https://stackoverflow.com/a/62755574/7303311"&gt;here&lt;/a&gt; the document check check to see all the avaliable fonts for the browser and given document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running #
&lt;/h2&gt;

&lt;p&gt;If everything went well when the command &lt;code&gt;npm run dev&lt;/code&gt; is run the following should appear in the viewport:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---4bOO-IN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/rich-text/editor-finish.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---4bOO-IN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/rich-text/editor-finish.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;If you want to learn more about building with Lit you can read the docs &lt;a href="https://lit.dev/"&gt;here&lt;/a&gt;. There is also an example on the Lit playground &lt;a href="https://lit.dev/playground/#project=W3sibmFtZSI6InJpY2gtdGV4dC1lZGl0b3IudHMiLCJjb250ZW50IjoiaW1wb3J0IHsgTGl0RWxlbWVudCwgaHRtbCwgY3VzdG9tRWxlbWVudCwgY3NzLCBzdGF0ZSB9IGZyb20gXCJsaXQtZWxlbWVudFwiO1xuXG5pbXBvcnQgXCJAbWF0ZXJpYWwvbXdjLWljb24tYnV0dG9uXCI7XG5cbkBjdXN0b21FbGVtZW50KFwicmljaC10ZXh0LWVkaXRvclwiKVxuZXhwb3J0IGNsYXNzIFJpY2hUZXh0RWRpdG9yIGV4dGVuZHMgTGl0RWxlbWVudCB7XG4gIEBzdGF0ZSgpIGNvbnRlbnQ6IHN0cmluZyA9IFwiXCI7XG4gIEBzdGF0ZSgpIHJvb3Q6IEVsZW1lbnQgfCBudWxsID0gbnVsbDtcblxuICBzdGF0aWMgc3R5bGVzID0gY3NzYFxuICAgIDpob3N0IHtcbiAgICAgIC0tZWRpdG9yLXdpZHRoOiA2MDBweDtcbiAgICAgIC0tZWRpdG9yLWhlaWdodDogNjAwcHg7XG4gICAgICAtLWVkaXRvci1iYWNrZ3JvdW5kOiAjZjFmMWYxO1xuICAgICAgLS1lZGl0b3ItdG9vbGJhci1oZWlnaHQ6IDMzcHg7XG4gICAgICAtLWVkaXRvci10b29sYmFyLWJhY2tncm91bmQ6IGJsYWNrO1xuICAgICAgLS1lZGl0b3ItdG9vbGJhci1vbi1iYWNrZ3JvdW5kOiB3aGl0ZTtcbiAgICAgIC0tZWRpdG9yLXRvb2xiYXItb24tYWN0aXZlLWJhY2tncm91bmQ6ICNhNGE0YTQ7XG4gICAgfVxuICAgIG1haW4ge1xuICAgICAgd2lkdGg6IHZhcigtLWVkaXRvci13aWR0aCk7XG4gICAgICBoZWlnaHQ6IHZhcigtLWVkaXRvci1oZWlnaHQpO1xuICAgICAgZGlzcGxheTogZ3JpZDtcbiAgICAgIGdyaWQtdGVtcGxhdGUtYXJlYXM6XG4gICAgICAgIFwidG9vbGJhciB0b29sYmFyXCJcbiAgICAgICAgXCJlZGl0b3IgZWRpdG9yXCI7XG4gICAgICBncmlkLXRlbXBsYXRlLXJvd3M6IHZhcigtLWVkaXRvci10b29sYmFyLWhlaWdodCkgYXV0bztcbiAgICAgIGdyaWQtdGVtcGxhdGUtY29sdW1uczogYXV0byBhdXRvO1xuICAgIH1cbiAgICAjZWRpdG9yLWFjdGlvbnMge1xuICAgICAgZ3JpZC1hcmVhOiB0b29sYmFyO1xuICAgICAgd2lkdGg6IHZhcigtLWVkaXRvci13aWR0aCk7XG4gICAgICBoZWlnaHQ6IHZhcigtLWVkaXRvci10b29sYmFyLWhlaWdodCk7XG4gICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1lZGl0b3ItdG9vbGJhci1iYWNrZ3JvdW5kKTtcbiAgICAgIGNvbG9yOiB2YXIoLS1lZGl0b3ItdG9vbGJhci1vbi1iYWNrZ3JvdW5kKTtcbiAgICAgIG92ZXJzY3JvbGwtYmVoYXZpb3I6IGNvbnRhaW47XG4gICAgICBvdmVyZmxvdy15OiBhdXRvO1xuICAgICAgLW1zLW92ZXJmbG93LXN0eWxlOiBub25lO1xuICAgICAgc2Nyb2xsYmFyLXdpZHRoOiBub25lO1xuICAgIH1cbiAgICAjZWRpdG9yLWFjdGlvbnM6Oi13ZWJraXQtc2Nyb2xsYmFyIHtcbiAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgfVxuICAgICNlZGl0b3Ige1xuICAgICAgd2lkdGg6IHZhcigtLWVkaXRvci13aWR0aCk7XG4gICAgICBncmlkLWFyZWE6IGVkaXRvcjtcbiAgICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWVkaXRvci1iYWNrZ3JvdW5kKTtcbiAgICB9XG4gICAgI3Rvb2xiYXIge1xuICAgICAgd2lkdGg6IDEwODBweDtcbiAgICAgIGhlaWdodDogdmFyKC0tZWRpdG9yLXRvb2xiYXItaGVpZ2h0KTtcbiAgICB9XG4gICAgW2NvbnRlbnRlZGl0YWJsZV0ge1xuICAgICAgb3V0bGluZTogMHB4IHNvbGlkIHRyYW5zcGFyZW50O1xuICAgIH1cbiAgICAjdG9vbGJhciA-IG13Yy1pY29uLWJ1dHRvbiB7XG4gICAgICBjb2xvcjogdmFyKC0tZWRpdG9yLXRvb2xiYXItb24tYmFja2dyb3VuZCk7XG4gICAgICAtLW1kYy1pY29uLXNpemU6IDIwcHg7XG4gICAgICAtLW1kYy1pY29uLWJ1dHRvbi1zaXplOiAzMHB4O1xuICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgIH1cbiAgICAjdG9vbGJhciA-IC5hY3RpdmUge1xuICAgICAgY29sb3I6IHZhcigtLWVkaXRvci10b29sYmFyLW9uLWFjdGl2ZS1iYWNrZ3JvdW5kKTtcbiAgICB9XG4gICAgc2VsZWN0IHtcbiAgICAgIG1hcmdpbi10b3A6IDVweDtcbiAgICAgIGhlaWdodDogY2FsYyh2YXIoLS1lZGl0b3ItdG9vbGJhci1oZWlnaHQpIC0gMTBweCk7XG4gICAgfVxuICAgIGlucHV0W3R5cGU9XCJjb2xvclwiXSB7XG4gICAgICBoZWlnaHQ6IGNhbGModmFyKC0tZWRpdG9yLXRvb2xiYXItaGVpZ2h0KSAtIDE1cHgpO1xuICAgICAgLXdlYmtpdC1hcHBlYXJhbmNlOiBub25lO1xuICAgICAgYm9yZGVyOiBub25lO1xuICAgICAgd2lkdGg6IDIycHg7XG4gICAgfVxuICAgIGlucHV0W3R5cGU9XCJjb2xvclwiXTo6LXdlYmtpdC1jb2xvci1zd2F0Y2gtd3JhcHBlciB7XG4gICAgICBwYWRkaW5nOiAwO1xuICAgIH1cbiAgICBpbnB1dFt0eXBlPVwiY29sb3JcIl06Oi13ZWJraXQtY29sb3Itc3dhdGNoIHtcbiAgICAgIGJvcmRlcjogbm9uZTtcbiAgICB9XG4gIGA7XG5cbiAgcmVuZGVyKCkge1xuICAgIHJldHVybiBodG1sYDxtYWluPlxuICAgICAgPGlucHV0IGlkPVwiYmdcIiB0eXBlPVwiY29sb3JcIiBzdHlsZT0nZGlzcGxheTpub25lJyAvPlxuICAgICAgPGlucHV0IGlkPVwiZmdcIiB0eXBlPVwiY29sb3JcIiBzdHlsZT0nZGlzcGxheTpub25lJyAvPlxuICAgICAgPGRpdiBpZD1cImVkaXRvci1hY3Rpb25zXCI-XG4gICAgICAgIDxkaXYgaWQ9XCJ0b29sYmFyXCI-XG4gICAgICAgICAgJHt0b29sYmFyKHRoaXMuc2hhZG93Um9vdCEsIHRoaXMuZ2V0U2VsZWN0aW9uKCksIChjb21tYW5kLCB2YWwpID0-IHtcbiAgICAgICAgICAgIGRvY3VtZW50LmV4ZWNDb21tYW5kKGNvbW1hbmQsIGZhbHNlLCB2YWwpO1xuICAgICAgICAgICAgY29uc29sZS5sb2coXCJjb21tYW5kXCIsIGNvbW1hbmQsIHZhbCk7XG4gICAgICAgICAgfSl9XG4gICAgICAgIDwvZGl2PlxuICAgICAgPC9kaXY-XG4gICAgICA8ZGl2IGlkPVwiZWRpdG9yXCI-JHt0aGlzLnJvb3R9PC9kaXY-XG4gICAgPC9tYWluPiBgO1xuICB9XG5cbiAgYXN5bmMgZmlyc3RVcGRhdGVkKCkge1xuICAgIGNvbnN0IGVsZW0gPSB0aGlzLnBhcmVudEVsZW1lbnQhLnF1ZXJ5U2VsZWN0b3IoXCJyaWNoLXRleHQtZWRpdG9yIHRlbXBsYXRlXCIpO1xuICAgIHRoaXMuY29udGVudCA9IGVsZW0_LmlubmVySFRNTCA_PyBcIlwiO1xuICAgIHRoaXMucmVzZXQoKTtcbiAgfVxuXG4gIHJlc2V0KCkge1xuICAgIGNvbnN0IHBhcnNlciA9IG5ldyBET01QYXJzZXIoKTtcbiAgICBjb25zdCBkb2MgPSBwYXJzZXIucGFyc2VGcm9tU3RyaW5nKHRoaXMuY29udGVudCwgXCJ0ZXh0L2h0bWxcIik7XG4gICAgZG9jdW1lbnQuZXhlY0NvbW1hbmQoXCJkZWZhdWx0UGFyYWdyYXBoU2VwYXJhdG9yXCIsIGZhbHNlLCBcImJyXCIpO1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoXCJzZWxlY3Rpb25jaGFuZ2VcIiwgKCkgPT4ge1xuICAgICAgdGhpcy5yZXF1ZXN0VXBkYXRlKCk7XG4gICAgfSk7XG4gICAgY29uc3Qgcm9vdCA9IGRvYy5xdWVyeVNlbGVjdG9yKFwiYm9keVwiKTtcbiAgICByb290IS5zZXRBdHRyaWJ1dGUoXCJjb250ZW50ZWRpdGFibGVcIiwgXCJ0cnVlXCIpO1xuICAgIHRoaXMucm9vdCA9IHJvb3Q7XG4gIH1cblxuICBnZXRTZWxlY3Rpb24oKTogU2VsZWN0aW9uIHwgbnVsbCB7XG4gICAgaWYgKHRoaXMuc2hhZG93Um9vdD8uZ2V0U2VsZWN0aW9uKSByZXR1cm4gdGhpcy5zaGFkb3dSb290Py5nZXRTZWxlY3Rpb24oKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufVxuXG5mdW5jdGlvbiB0b29sYmFyKFxuICBzaGFkb3dSb290OiBTaGFkb3dSb290LFxuICBzZWxlY3Rpb246IFNlbGVjdGlvbiB8IG51bGwsXG4gIGNvbW1hbmQ6IChjOiBzdHJpbmcsIHZhbDogc3RyaW5nIHwgdW5kZWZpbmVkKSA9PiB2b2lkXG4pIHtcbiAgY29uc3QgdGFnczogc3RyaW5nW10gPSBbXTtcbiAgaWYgKHNlbGVjdGlvbj8udHlwZSA9PT0gXCJSYW5nZVwiKSB7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIGxldCBwYXJlbnROb2RlID0gc2VsZWN0aW9uPy5iYXNlTm9kZTtcbiAgICBpZiAocGFyZW50Tm9kZSkge1xuICAgICAgY29uc3QgY2hlY2tOb2RlID0gKCkgPT4ge1xuICAgICAgICBjb25zdCBwYXJlbnRUYWdOYW1lID0gcGFyZW50Tm9kZT8udGFnTmFtZT8udG9Mb3dlckNhc2UoKT8udHJpbSgpO1xuICAgICAgICBpZiAocGFyZW50VGFnTmFtZSkgdGFncy5wdXNoKHBhcmVudFRhZ05hbWUpO1xuICAgICAgfTtcbiAgICAgIHdoaWxlIChwYXJlbnROb2RlICE9IG51bGwpIHtcbiAgICAgICAgY2hlY2tOb2RlKCk7XG4gICAgICAgIHBhcmVudE5vZGUgPSBwYXJlbnROb2RlPy5wYXJlbnROb2RlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGNvbW1hbmRzOiB7XG4gICAgaWNvbjogc3RyaW5nO1xuICAgIGNvbW1hbmQ6IHN0cmluZyB8ICgoKSA9PiB2b2lkKTtcbiAgICBhY3RpdmU_OiBib29sZWFuO1xuICAgIHR5cGU_OiBzdHJpbmc7XG4gICAgdmFsdWVzPzogeyB2YWx1ZTogc3RyaW5nOyBuYW1lOiBzdHJpbmc7IGZvbnQ_OiBib29sZWFuIH1bXTtcbiAgICBjb21tYW5kX3ZhbHVlPzogc3RyaW5nO1xuICB9W10gPSBbXG4gICAge1xuICAgICAgaWNvbjogXCJmb3JtYXRfY2xlYXJcIixcbiAgICAgIGNvbW1hbmQ6IFwicmVtb3ZlRm9ybWF0XCIsXG4gICAgfSxcblxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2JvbGRcIixcbiAgICAgIGNvbW1hbmQ6IFwiYm9sZFwiLFxuICAgICAgYWN0aXZlOiB0YWdzLmluY2x1ZGVzKFwiYlwiKSxcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2l0YWxpY1wiLFxuICAgICAgY29tbWFuZDogXCJpdGFsaWNcIixcbiAgICAgIGFjdGl2ZTogdGFncy5pbmNsdWRlcyhcImlcIiksXG4gICAgfSxcbiAgICB7XG4gICAgICBpY29uOiBcImZvcm1hdF91bmRlcmxpbmVkXCIsXG4gICAgICBjb21tYW5kOiBcInVuZGVybGluZVwiLFxuICAgICAgYWN0aXZlOiB0YWdzLmluY2x1ZGVzKFwidVwiKSxcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2FsaWduX2xlZnRcIixcbiAgICAgIGNvbW1hbmQ6IFwianVzdGlmeWxlZnRcIixcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2FsaWduX2NlbnRlclwiLFxuICAgICAgY29tbWFuZDogXCJqdXN0aWZ5Y2VudGVyXCIsXG4gICAgfSxcbiAgICB7XG4gICAgICBpY29uOiBcImZvcm1hdF9hbGlnbl9yaWdodFwiLFxuICAgICAgY29tbWFuZDogXCJqdXN0aWZ5cmlnaHRcIixcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2xpc3RfbnVtYmVyZWRcIixcbiAgICAgIGNvbW1hbmQ6IFwiaW5zZXJ0b3JkZXJlZGxpc3RcIixcbiAgICAgIGFjdGl2ZTogdGFncy5pbmNsdWRlcyhcIm9sXCIpLFxuICAgIH0sXG4gICAge1xuICAgICAgaWNvbjogXCJmb3JtYXRfbGlzdF9idWxsZXRlZFwiLFxuICAgICAgY29tbWFuZDogXCJpbnNlcnR1bm9yZGVyZWRsaXN0XCIsXG4gICAgICBhY3RpdmU6IHRhZ3MuaW5jbHVkZXMoXCJ1bFwiKSxcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X3F1b3RlXCIsXG4gICAgICBjb21tYW5kOiBcImZvcm1hdGJsb2NrXCIsXG4gICAgICBjb21tYW5kX3ZhbHVlOiBcImJsb2NrcXVvdGVcIixcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2luZGVudF9kZWNyZWFzZVwiLFxuICAgICAgY29tbWFuZDogXCJvdXRkZW50XCIsXG4gICAgfSxcbiAgICB7XG4gICAgICBpY29uOiBcImZvcm1hdF9pbmRlbnRfaW5jcmVhc2VcIixcbiAgICAgIGNvbW1hbmQ6IFwiaW5kZW50XCIsXG4gICAgfSxcbiAgICB0YWdzLmluY2x1ZGVzKFwiYVwiKVxuICAgICAgPyB7IGljb246IFwibGlua19vZmZcIiwgY29tbWFuZDogXCJ1bmxpbmtcIiB9XG4gICAgICA6IHtcbiAgICAgICAgICBpY29uOiBcImFkZF9saW5rXCIsXG4gICAgICAgICAgY29tbWFuZDogKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgbmV3TGluayA9IHByb21wdChcIldyaXRlIHRoZSBVUkwgaGVyZVwiLCBcImh0dHA6Ly9cIik7XG4gICAgICAgICAgICBpZiAobmV3TGluayAmJiBuZXdMaW5rICE9IFwiXCIgJiYgbmV3TGluayAhPSBcImh0dHA6Ly9cIikge1xuICAgICAgICAgICAgICBjb21tYW5kKFwiY3JlYXRlbGlua1wiLCBuZXdMaW5rKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X2NvbG9yX3RleHRcIixcbiAgICAgIGNvbW1hbmQ6ICgpID0-IHtcbiAgICAgICAgY29uc3QgaW5wdXQgPSBzaGFkb3dSb290LnF1ZXJ5U2VsZWN0b3IoXCIjZmdcIikhIGFzIEhUTUxJbnB1dEVsZW1lbnQ7XG4gICAgICAgIGlucHV0LmFkZEV2ZW50TGlzdGVuZXIoXCJpbnB1dFwiLCAoZTogYW55KSA9PiB7XG4gICAgICAgICAgY29uc3QgdmFsID0gZS50YXJnZXQudmFsdWU7XG4gICAgICAgICAgY29tbWFuZChcImZvcmVjb2xvclwiLCB2YWwpO1xuICAgICAgICB9KTtcbiAgICAgICAgaW5wdXQuY2xpY2soKTtcbiAgICAgIH0sXG4gICAgICB0eXBlOiBcImNvbG9yXCIsXG4gICAgfSxcbiAgICB7XG4gICAgICBpY29uOiBcImJvcmRlcl9jb2xvclwiLFxuICAgICAgY29tbWFuZDogKCkgPT4ge1xuICAgICAgICBjb25zdCBpbnB1dCA9IHNoYWRvd1Jvb3QucXVlcnlTZWxlY3RvcihcIiNiZ1wiKSEgYXMgSFRNTElucHV0RWxlbWVudDtcbiAgICAgICAgaW5wdXQuYWRkRXZlbnRMaXN0ZW5lcihcImlucHV0XCIsIChlOiBhbnkpID0-IHtcbiAgICAgICAgICBjb25zdCB2YWwgPSBlLnRhcmdldC52YWx1ZTtcbiAgICAgICAgICBjb21tYW5kKFwiYmFja2NvbG9yXCIsIHZhbCk7XG4gICAgICAgIH0pO1xuICAgICAgICBpbnB1dC5jbGljaygpO1xuICAgICAgfSxcbiAgICAgIHR5cGU6IFwiY29sb3JcIixcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwidGl0bGVcIixcbiAgICAgIGNvbW1hbmQ6IFwiZm9ybWF0YmxvY2tcIixcbiAgICAgIHZhbHVlczogW1xuICAgICAgICB7IG5hbWU6IFwiTm9ybWFsIFRleHRcIiwgdmFsdWU6IFwiLS1cIiB9LFxuICAgICAgICB7IG5hbWU6IFwiSGVhZGluZyAxXCIsIHZhbHVlOiBcImgxXCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIkhlYWRpbmcgMlwiLCB2YWx1ZTogXCJoMlwiIH0sXG4gICAgICAgIHsgbmFtZTogXCJIZWFkaW5nIDNcIiwgdmFsdWU6IFwiaDNcIiB9LFxuICAgICAgICB7IG5hbWU6IFwiSGVhZGluZyA0XCIsIHZhbHVlOiBcImg0XCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIkhlYWRpbmcgNVwiLCB2YWx1ZTogXCJoNVwiIH0sXG4gICAgICAgIHsgbmFtZTogXCJIZWFkaW5nIDZcIiwgdmFsdWU6IFwiaDZcIiB9LFxuICAgICAgICB7IG5hbWU6IFwiUGFyYWdyYXBoXCIsIHZhbHVlOiBcInBcIiB9LFxuICAgICAgICB7IG5hbWU6IFwiUHJlLUZvcm1hdHRlZFwiLCB2YWx1ZTogXCJwcmVcIiB9LFxuICAgICAgXSxcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwidGV4dF9mb3JtYXRcIixcbiAgICAgIGNvbW1hbmQ6IFwiZm9udG5hbWVcIixcbiAgICAgIHZhbHVlczogW1xuICAgICAgICB7IG5hbWU6IFwiRm9udCBOYW1lXCIsIHZhbHVlOiBcIi0tXCIgfSxcbiAgICAgICAgLi4uWy4uLmNoZWNrRm9udHMoKV0ubWFwKChmKSA9PiAoe1xuICAgICAgICAgIG5hbWU6IGYsXG4gICAgICAgICAgdmFsdWU6IGYsXG4gICAgICAgICAgZm9udDogdHJ1ZSxcbiAgICAgICAgfSkpLFxuICAgICAgXSxcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwiZm9ybWF0X3NpemVcIixcbiAgICAgIGNvbW1hbmQ6IFwiZm9udHNpemVcIixcbiAgICAgIHZhbHVlczogW1xuICAgICAgICB7IG5hbWU6IFwiRm9udCBTaXplXCIsIHZhbHVlOiBcIi0tXCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIlZlcnkgU21hbGxcIiwgdmFsdWU6IFwiMVwiIH0sXG4gICAgICAgIHsgbmFtZTogXCJTbWFsbFwiLCB2YWx1ZTogXCIyXCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIk5vcm1hbFwiLCB2YWx1ZTogXCIzXCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIk1lZGl1bSBMYXJnZVwiLCB2YWx1ZTogXCI0XCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIkxhcmdlXCIsIHZhbHVlOiBcIjVcIiB9LFxuICAgICAgICB7IG5hbWU6IFwiVmVyeSBMYXJnZVwiLCB2YWx1ZTogXCI2XCIgfSxcbiAgICAgICAgeyBuYW1lOiBcIk1heGltdW1cIiwgdmFsdWU6IFwiN1wiIH0sXG4gICAgICBdLFxuICAgIH0sXG4gICAge1xuICAgICAgaWNvbjogXCJ1bmRvXCIsXG4gICAgICBjb21tYW5kOiBcInVuZG9cIixcbiAgICB9LFxuICAgIHtcbiAgICAgIGljb246IFwicmVkb1wiLFxuICAgICAgY29tbWFuZDogXCJyZWRvXCIsXG4gICAgfSxcbiAgICB7XG4gICAgICBpY29uOiBcImNvbnRlbnRfY3V0XCIsXG4gICAgICBjb21tYW5kOiBcImN1dFwiLFxuICAgIH0sXG4gICAge1xuICAgICAgaWNvbjogXCJjb250ZW50X2NvcHlcIixcbiAgICAgIGNvbW1hbmQ6IFwiY29weVwiLFxuICAgIH0sXG4gICAge1xuICAgICAgaWNvbjogXCJjb250ZW50X3Bhc3RlXCIsXG4gICAgICBjb21tYW5kOiBcInBhc3RlXCIsXG4gICAgfSxcbiAgXTtcblxuICByZXR1cm4gaHRtbGBcbiAgICAke2NvbW1hbmRzLm1hcCgobikgPT4ge1xuICAgICAgY29uc3QgZWxlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJtd2MtaWNvbi1idXR0b25cIik7XG4gICAgICBlbGVtLnNldEF0dHJpYnV0ZShcImljb25cIiwgbi5pY29uKTtcbiAgICAgIGVsZW0uY2xhc3NOYW1lID0gbi5hY3RpdmUgPyBcImFjdGl2ZVwiIDogXCJpbmFjdGl2ZVwiO1xuICAgICAgZWxlbS5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgKCkgPT4ge1xuICAgICAgICBpZiAobi52YWx1ZXMpIHtcbiAgICAgICAgfSBlbHNlIGlmICh0eXBlb2Ygbi5jb21tYW5kID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgICAgY29tbWFuZChuLmNvbW1hbmQsIG4uY29tbWFuZF92YWx1ZSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgbi5jb21tYW5kKCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGh0bWxgXG4gICAgICAgICR7bi52YWx1ZXNcbiAgICAgICAgICA_IGh0bWxgIDxzZWxlY3RcbiAgICAgICAgICAgICAgaWQ9XCIke24uaWNvbn1cIlxuICAgICAgICAgICAgICBAY2hhbmdlPSR7KGU6IGFueSkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IHZhbCA9IGUudGFyZ2V0LnZhbHVlO1xuICAgICAgICAgICAgICAgIGlmICh2YWwgPT09IFwiLS1cIikge1xuICAgICAgICAgICAgICAgICAgY29tbWFuZChcInJlbW92ZUZvcm1hdFwiLCB1bmRlZmluZWQpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAodHlwZW9mIG4uY29tbWFuZCA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgICAgICAgICAgICAgY29tbWFuZChuLmNvbW1hbmQsIHZhbCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9fVxuICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAke24udmFsdWVzLm1hcChcbiAgICAgICAgICAgICAgICAodikgPT4gaHRtbGAgPG9wdGlvbiB2YWx1ZT0ke3YudmFsdWV9PiR7di5uYW1lfTwvb3B0aW9uPmBcbiAgICAgICAgICAgICAgKX1cbiAgICAgICAgICAgIDwvc2VsZWN0PmBcbiAgICAgICAgICA6IGh0bWxgICR7ZWxlbX1gfVxuICAgICAgYDtcbiAgICB9KX1cbiAgYDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrRm9udHMoKTogc3RyaW5nW10ge1xuICBjb25zdCBmb250Q2hlY2sgPSBuZXcgU2V0KFxuICAgIFtcbiAgICAgIC8vIFdpbmRvd3MgMTBcbiAgICAgIFwiQXJpYWxcIixcbiAgICAgIFwiQXJpYWwgQmxhY2tcIixcbiAgICAgIFwiQmFobnNjaHJpZnRcIixcbiAgICAgIFwiQ2FsaWJyaVwiLFxuICAgICAgXCJDYW1icmlhXCIsXG4gICAgICBcIkNhbWJyaWEgTWF0aFwiLFxuICAgICAgXCJDYW5kYXJhXCIsXG4gICAgICBcIkNvbWljIFNhbnMgTVNcIixcbiAgICAgIFwiQ29uc29sYXNcIixcbiAgICAgIFwiQ29uc3RhbnRpYVwiLFxuICAgICAgXCJDb3JiZWxcIixcbiAgICAgIFwiQ291cmllciBOZXdcIixcbiAgICAgIFwiRWJyaW1hXCIsXG4gICAgICBcIkZyYW5rbGluIEdvdGhpYyBNZWRpdW1cIixcbiAgICAgIFwiR2FicmlvbGFcIixcbiAgICAgIFwiR2FkdWdpXCIsXG4gICAgICBcIkdlb3JnaWFcIixcbiAgICAgIFwiSG9sb0xlbnMgTURMMiBBc3NldHNcIixcbiAgICAgIFwiSW1wYWN0XCIsXG4gICAgICBcIkluayBGcmVlXCIsXG4gICAgICBcIkphdmFuZXNlIFRleHRcIixcbiAgICAgIFwiTGVlbGF3YWRlZSBVSVwiLFxuICAgICAgXCJMdWNpZGEgQ29uc29sZVwiLFxuICAgICAgXCJMdWNpZGEgU2FucyBVbmljb2RlXCIsXG4gICAgICBcIk1hbGd1biBHb3RoaWNcIixcbiAgICAgIFwiTWFybGV0dFwiLFxuICAgICAgXCJNaWNyb3NvZnQgSGltYWxheWFcIixcbiAgICAgIFwiTWljcm9zb2Z0IEpoZW5nSGVpXCIsXG4gICAgICBcIk1pY3Jvc29mdCBOZXcgVGFpIEx1ZVwiLFxuICAgICAgXCJNaWNyb3NvZnQgUGhhZ3NQYVwiLFxuICAgICAgXCJNaWNyb3NvZnQgU2FucyBTZXJpZlwiLFxuICAgICAgXCJNaWNyb3NvZnQgVGFpIExlXCIsXG4gICAgICBcIk1pY3Jvc29mdCBZYUhlaVwiLFxuICAgICAgXCJNaWNyb3NvZnQgWWkgQmFpdGlcIixcbiAgICAgIFwiTWluZ0xpVS1FeHRCXCIsXG4gICAgICBcIk1vbmdvbGlhbiBCYWl0aVwiLFxuICAgICAgXCJNUyBHb3RoaWNcIixcbiAgICAgIFwiTVYgQm9saVwiLFxuICAgICAgXCJNeWFubWFyIFRleHRcIixcbiAgICAgIFwiTmlybWFsYSBVSVwiLFxuICAgICAgXCJQYWxhdGlubyBMaW5vdHlwZVwiLFxuICAgICAgXCJTZWdvZSBNREwyIEFzc2V0c1wiLFxuICAgICAgXCJTZWdvZSBQcmludFwiLFxuICAgICAgXCJTZWdvZSBTY3JpcHRcIixcbiAgICAgIFwiU2Vnb2UgVUlcIixcbiAgICAgIFwiU2Vnb2UgVUkgSGlzdG9yaWNcIixcbiAgICAgIFwiU2Vnb2UgVUkgRW1vamlcIixcbiAgICAgIFwiU2Vnb2UgVUkgU3ltYm9sXCIsXG4gICAgICBcIlNpbVN1blwiLFxuICAgICAgXCJTaXRrYVwiLFxuICAgICAgXCJTeWxmYWVuXCIsXG4gICAgICBcIlN5bWJvbFwiLFxuICAgICAgXCJUYWhvbWFcIixcbiAgICAgIFwiVGltZXMgTmV3IFJvbWFuXCIsXG4gICAgICBcIlRyZWJ1Y2hldCBNU1wiLFxuICAgICAgXCJWZXJkYW5hXCIsXG4gICAgICBcIldlYmRpbmdzXCIsXG4gICAgICBcIldpbmdkaW5nc1wiLFxuICAgICAgXCJZdSBHb3RoaWNcIixcbiAgICAgIC8vIG1hY09TXG4gICAgICBcIkFtZXJpY2FuIFR5cGV3cml0ZXJcIixcbiAgICAgIFwiQW5kYWxlIE1vbm9cIixcbiAgICAgIFwiQXJpYWxcIixcbiAgICAgIFwiQXJpYWwgQmxhY2tcIixcbiAgICAgIFwiQXJpYWwgTmFycm93XCIsXG4gICAgICBcIkFyaWFsIFJvdW5kZWQgTVQgQm9sZFwiLFxuICAgICAgXCJBcmlhbCBVbmljb2RlIE1TXCIsXG4gICAgICBcIkF2ZW5pclwiLFxuICAgICAgXCJBdmVuaXIgTmV4dFwiLFxuICAgICAgXCJBdmVuaXIgTmV4dCBDb25kZW5zZWRcIixcbiAgICAgIFwiQmFza2VydmlsbGVcIixcbiAgICAgIFwiQmlnIENhc2xvblwiLFxuICAgICAgXCJCb2RvbmkgNzJcIixcbiAgICAgIFwiQm9kb25pIDcyIE9sZHN0eWxlXCIsXG4gICAgICBcIkJvZG9uaSA3MiBTbWFsbGNhcHNcIixcbiAgICAgIFwiQnJhZGxleSBIYW5kXCIsXG4gICAgICBcIkJydXNoIFNjcmlwdCBNVFwiLFxuICAgICAgXCJDaGFsa2JvYXJkXCIsXG4gICAgICBcIkNoYWxrYm9hcmQgU0VcIixcbiAgICAgIFwiQ2hhbGtkdXN0ZXJcIixcbiAgICAgIFwiQ2hhcnRlclwiLFxuICAgICAgXCJDb2NoaW5cIixcbiAgICAgIFwiQ29taWMgU2FucyBNU1wiLFxuICAgICAgXCJDb3BwZXJwbGF0ZVwiLFxuICAgICAgXCJDb3VyaWVyXCIsXG4gICAgICBcIkNvdXJpZXIgTmV3XCIsXG4gICAgICBcIkRpZG90XCIsXG4gICAgICBcIkRJTiBBbHRlcm5hdGVcIixcbiAgICAgIFwiRElOIENvbmRlbnNlZFwiLFxuICAgICAgXCJGdXR1cmFcIixcbiAgICAgIFwiR2VuZXZhXCIsXG4gICAgICBcIkdlb3JnaWFcIixcbiAgICAgIFwiR2lsbCBTYW5zXCIsXG4gICAgICBcIkhlbHZldGljYVwiLFxuICAgICAgXCJIZWx2ZXRpY2EgTmV1ZVwiLFxuICAgICAgXCJIZXJjdWxhbnVtXCIsXG4gICAgICBcIkhvZWZsZXIgVGV4dFwiLFxuICAgICAgXCJJbXBhY3RcIixcbiAgICAgIFwiTHVjaWRhIEdyYW5kZVwiLFxuICAgICAgXCJMdW1pbmFyaVwiLFxuICAgICAgXCJNYXJrZXIgRmVsdFwiLFxuICAgICAgXCJNZW5sb1wiLFxuICAgICAgXCJNaWNyb3NvZnQgU2FucyBTZXJpZlwiLFxuICAgICAgXCJNb25hY29cIixcbiAgICAgIFwiTm90ZXdvcnRoeVwiLFxuICAgICAgXCJPcHRpbWFcIixcbiAgICAgIFwiUGFsYXRpbm9cIixcbiAgICAgIFwiUGFweXJ1c1wiLFxuICAgICAgXCJQaG9zcGhhdGVcIixcbiAgICAgIFwiUm9ja3dlbGxcIixcbiAgICAgIFwiU2F2b3llIExFVFwiLFxuICAgICAgXCJTaWduUGFpbnRlclwiLFxuICAgICAgXCJTa2lhXCIsXG4gICAgICBcIlNuZWxsIFJvdW5kaGFuZFwiLFxuICAgICAgXCJUYWhvbWFcIixcbiAgICAgIFwiVGltZXNcIixcbiAgICAgIFwiVGltZXMgTmV3IFJvbWFuXCIsXG4gICAgICBcIlRyYXR0YXRlbGxvXCIsXG4gICAgICBcIlRyZWJ1Y2hldCBNU1wiLFxuICAgICAgXCJWZXJkYW5hXCIsXG4gICAgICBcIlphcGZpbm9cIixcbiAgICBdLnNvcnQoKVxuICApO1xuICBjb25zdCBmb250QXZhaWxhYmxlID0gbmV3IFNldDxzdHJpbmc-KCk7XG4gIC8vIEB0cy1pZ25vcmVcbiAgZm9yIChjb25zdCBmb250IG9mIGZvbnRDaGVjay52YWx1ZXMoKSkge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBpZiAoZG9jdW1lbnQuZm9udHMuY2hlY2soYDEycHggXCIke2ZvbnR9XCJgKSkge1xuICAgICAgZm9udEF2YWlsYWJsZS5hZGQoZm9udCk7XG4gICAgfVxuICB9XG4gIC8vIEB0cy1pZ25vcmVcbiAgcmV0dXJuIGZvbnRBdmFpbGFibGUudmFsdWVzKCk7XG59XG4ifSx7Im5hbWUiOiJpbmRleC5odG1sIiwiY29udGVudCI6IjwhRE9DVFlQRSBodG1sPlxuPGh0bWwgbGFuZz1cImVuXCI-XG4gIDxoZWFkPlxuICAgIDxtZXRhIGNoYXJzZXQ9XCJVVEYtOFwiIC8-XG4gICAgPGxpbmsgcmVsPVwiaWNvblwiIHR5cGU9XCJpbWFnZS9zdmcreG1sXCIgaHJlZj1cIi9zcmMvZmF2aWNvbi5zdmdcIiAvPlxuICAgIDxtZXRhIG5hbWU9XCJ2aWV3cG9ydFwiIGNvbnRlbnQ9XCJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wXCIgLz5cbiAgICA8bGlua1xuICAgICAgaHJlZj1cImh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1NYXRlcmlhbCtJY29ucyZkaXNwbGF5PWJsb2NrXCJcbiAgICAgIHJlbD1cInN0eWxlc2hlZXRcIlxuICAgIC8-XG4gICAgPHRpdGxlPkxpdCBIVE1MIEVkaXRvcjwvdGl0bGU-XG4gICAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiLi9yaWNoLXRleHQtZWRpdG9yLmpzXCI-PC9zY3JpcHQ-XG4gICAgPHN0eWxlPlxuICAgICAgYm9keSB7XG4gICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICBoZWlnaHQ6IDEwMHZoO1xuICAgICAgICBwYWRkaW5nOiAwO1xuICAgICAgICBtYXJnaW46IDA7XG4gICAgICB9XG4gICAgICByaWNoLXRleHQtZWRpdG9yIHtcbiAgICAgICAgLS1lZGl0b3Itd2lkdGg6IDEwMCU7XG4gICAgICAgIC0tZWRpdG9yLWhlaWdodDogMTAwdmg7XG4gICAgICB9XG4gICAgPC9zdHlsZT5cbiAgPC9oZWFkPlxuICA8Ym9keT5cbiAgICA8cmljaC10ZXh0LWVkaXRvcj5cbiAgICAgIDx0ZW1wbGF0ZT5cbiAgICAgICAgPGgxPkhlYWRsaW5lIDE8L2gxPlxuICAgICAgICA8cD5UaGlzIGlzIGEgcGFyYWdyYXBoLjwvcD5cbiAgICAgICAgPHA-XG4gICAgICAgICAgPHNwYW4gc3R5bGU9XCJiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAwLCAwKVwiXG4gICAgICAgICAgICA-PGZvbnQgY29sb3I9XCIjZmZmZmZmXCI-U3R5bGVkIFRleHQ8L2ZvbnQ-PC9zcGFuXG4gICAgICAgICAgPlxuICAgICAgICA8L3A-XG4gICAgICA8L3RlbXBsYXRlPlxuICAgIDwvcmljaC10ZXh0LWVkaXRvcj5cbiAgPC9ib2R5PlxuPC9odG1sPlxuIn1d"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The source for this example can be found &lt;a href="https://github.com/rodydavis/lit-html-editor"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YttBW1KW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Flit-rich-text-editor%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DBuilding%2520a%2520Rich%2520Text%2520Editor%2520with%2520Lit%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YttBW1KW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Flit-rich-text-editor%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DBuilding%2520a%2520Rich%2520Text%2520Editor%2520with%2520Lit%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lit and Figma</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Mon, 10 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/lit-and-figma-444m</link>
      <guid>https://dev.to/rodydavis/lit-and-figma-444m</guid>
      <description>&lt;p&gt;In this article I will go over how to set up a &lt;a href="https://lit.dev/"&gt;Lit&lt;/a&gt; web component and use it to create a figma plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; You can find the final source &lt;a href="https://github.com/rodydavis/figma_lit_example"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vscode&lt;/li&gt;
&lt;li&gt;Figma Desktop&lt;/li&gt;
&lt;li&gt;Node&lt;/li&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started #
&lt;/h2&gt;

&lt;p&gt;We can start off by creating a empty directory and naming it with &lt;code&gt;snake_case&lt;/code&gt; whatever we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir figma_lit_examplecd figma_lit_example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Web Setup #
&lt;/h3&gt;

&lt;p&gt;Now we are in the &lt;code&gt;figma_lit_example&lt;/code&gt; directory and can setup Figma and Lit. Let's start with node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will setup the basics for a node project and install the packages we need. Now lets add some config files. Now open the &lt;code&gt;package.json&lt;/code&gt; and replace it with 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;{ "name": "figma_lit_example", "version": "1.0.0", "description": "Lit Figma Plugin", "dependencies": { "lit": "^2.0.0-rc.1" }, "devDependencies": { "@figma/plugin-typings": "^1.23.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^4.3.0", "css-loader": "^5.2.4", "ts-loader": "^8.0.0", "typescript": "^4.2.4", "url-loader": "^4.1.1", "webpack": "^4.44.1", "webpack-cli": "^4.6.0" }, "scripts": { "dev": "npx webpack --mode=development --watch", "copy": "mkdir -p lit-plugin &amp;amp;&amp;amp; cp ./manifest.json ./lit-plugin/manifest.json &amp;amp;&amp;amp; cp ./dist/ui.html ./lit-plugin/ui.html &amp;amp;&amp;amp; cp ./dist/code.js ./lit-plugin/code.js", "build": "npx webpack --mode=production &amp;amp;&amp;amp; npm run copy", "zip": "npm run build &amp;amp;&amp;amp; zip -r lit-plugin.zip lit-plugin" }, "browserslist": ["last 1 Chrome versions"], "keywords": [], "author": "", "license": "ISC"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add everything we need and add the scripts we need for development and production. Then run 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;npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install everything we need to get started. Now we need to setup some config files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch tsconfig.jsontouch webpack.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create 2 files. Now open up &lt;code&gt;tsconfig.json&lt;/code&gt; and paste 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;{ "compilerOptions": { "target": "es2017", "module": "esNext", "moduleResolution": "node", "lib": ["es2017", "dom", "dom.iterable"], "typeRoots": ["./node_modules/@types", "./node_modules/@figma"], "declaration": true, "sourceMap": true, "inlineSources": true, "noUnusedLocals": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "experimentalDecorators": true, "skipLibCheck": true, "strict": true, "noImplicitAny": false, "outDir": "./lib", "baseUrl": "./packages", "importHelpers": true, "plugins": [{ "name": "ts-lit-plugin", "rules": { "no-unknown-tag-name": "error", "no-unclosed-tag": "error", "no-unknown-property": "error", "no-unintended-mixed-binding": "error", "no-invalid-boolean-binding": "error", "no-expressionless-property-binding": "error", "no-noncallable-event-binding": "error", "no-boolean-in-attribute-binding": "error", "no-complex-attribute-binding": "error", "no-nullable-attribute-binding": "error", "no-incompatible-type-binding": "error", "no-invalid-directive-binding": "error", "no-incompatible-property-type": "error", "no-unknown-property-converter": "error", "no-invalid-attribute-name": "error", "no-invalid-tag-name": "error", "no-unknown-attribute": "off", "no-unknown-event": "off", "no-unknown-slot": "off", "no-invalid-css": "off" } }] }, "include": ["src/**/*.ts"], "references": []}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic typescript config. Now open up &lt;code&gt;webpack.config.ts&lt;/code&gt; and paste 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;const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const path = require("path");module.exports = (env, argv) =&amp;gt; ({ mode: argv.mode === "production" ? "production" : "development", devtool: argv.mode === "production" ? false : "inline-source-map", entry: { ui: "./src/ui.ts", code: "./src/code.ts", app: "./src/my-app.ts", }, module: { rules: [{ test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ }, { test: /\.css$/, use: ["style-loader", { loader: "css-loader" }] }, { test: /\.(png|jpg|gif|webp|svg)$/, loader: "url-loader" }, ], }, resolve: { extensions: [".ts", ".js"] }, output: { filename: "[name].js", path: path.resolve(__dirname, "dist"), }, plugins: [new HtmlWebpackPlugin({ template: path.resolve(__dirname, "ui.html"), filename: "ui.html", inject: true, inlineSource: ".(js|css)$", chunks: ["ui"], }), new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin), ],});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to create the ui for the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch ui.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open up &lt;code&gt;ui.html&lt;/code&gt; and add 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;&amp;lt;my-app&amp;gt;&amp;lt;/my-app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need a manifest file for the figma plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch manifest.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;manifest.json&lt;/code&gt; and add 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;{ "name": "figma_lit_example", "id": "973668777853442323", "api": "1.0.0", "main": "code.js", "ui": "ui.html"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to create our web component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir srccd srctouch my-app.tstouch code.tstouch ui.tscd ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;ui.ts&lt;/code&gt; and paste 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;import "./my-app";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;my-app.ts&lt;/code&gt; and paste 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;import { html, LitElement } from "lit";import { customElement, query } from "lit/decorators.js";@customElement("my-app")export class MyApp extends LitElement { @query("#count") countInput!: HTMLInputElement; render() { return html` &amp;lt;div&amp;gt; &amp;lt;h2&amp;gt;Rectangle Creator&amp;lt;/h2&amp;gt; &amp;lt;p&amp;gt;Count: &amp;lt;input id="count" value="5" /&amp;gt;&amp;lt;/p&amp;gt; &amp;lt;button id="create" @click=${this.create}&amp;gt;Create&amp;lt;/button&amp;gt; &amp;lt;button id="cancel" @click=${this.cancel}&amp;gt;Cancel&amp;lt;/button&amp;gt; &amp;lt;/div&amp;gt; `; } create() { const count = parseInt(this.countInput.value, 10); this.sendMessage("create-rectangles", { count }); } cancel() { this.sendMessage("cancel"); } private sendMessage(type: string, content: Object = {}) { const message = { pluginMessage: { type: type, ...content } }; parent.postMessage(message, "*"); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;code.ts&lt;/code&gt; and paste 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;const options: ShowUIOptions = { width: 250, height: 200,};figma.showUI( __html__ , options);figma.ui.onmessage = msg =&amp;gt; { switch (msg.type) { case 'create-rectangles': const nodes: SceneNode[] = []; for (let i = 0; i &amp;lt; msg.count; i++) { const rect = figma.createRectangle(); rect.x = i * 150; rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 } }]; figma.currentPage.appendChild(rect); nodes.push(rect); } figma.currentPage.selection = nodes; figma.viewport.scrollAndZoomIntoView(nodes); break; default: break; } figma.closePlugin();};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building the Plugin #
&lt;/h2&gt;

&lt;p&gt;Now that we have all the code in place we can build the plugin and test it in Figma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 1 #
&lt;/h4&gt;

&lt;p&gt;Download and open the desktop version of Figma.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.figma.com/downloads/"&gt;https://www.figma.com/downloads/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2 #
&lt;/h4&gt;

&lt;p&gt;Open the menu and navigate to “Plugins &amp;gt; Manage plugins”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uKDwALfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/manage-plugin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uKDwALfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/manage-plugin.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3 #
&lt;/h4&gt;

&lt;p&gt;Click on the plus icon to add a local plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C8aG27aN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/add-plugin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C8aG27aN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/add-plugin.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the box to link to an existing plugin to navigate to the &lt;code&gt;lit-plugin&lt;/code&gt; folder that was created after the build process in your source code and select &lt;code&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h4-fWvit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/create-plugin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h4-fWvit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/create-plugin.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 4 #
&lt;/h4&gt;

&lt;p&gt;To run the plugin navigate to “Plugins &amp;gt; Development &amp;gt; figma_lit_example” to launch your plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dTbTxHh2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/run-lit-plugin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dTbTxHh2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/run-lit-plugin.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5 #
&lt;/h4&gt;

&lt;p&gt;Now your plugin should launch and you can create 5 rectangles on the canvas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--trouazbk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/plugin-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--trouazbk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/plugin-overview.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything worked you will have 5 new rectangles on the canvas focused by figma.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7imCSb4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/rectangles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7imCSb4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/img/figma/rectangles.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;If you want to learn more about building a plugin in Figma you can read more &lt;a href="https://www.figma.com/plugin-docs/intro/"&gt;here&lt;/a&gt; and for Lit you can read the docs &lt;a href="https://lit.dev/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GQMm-G0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Ffigma-and-lit%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DLit%2520and%2520Figma%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GQMm-G0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Ffigma-and-lit%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DLit%2520and%2520Figma%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lit and Flutter</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Wed, 05 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/lit-and-flutter-3l26</link>
      <guid>https://dev.to/rodydavis/lit-and-flutter-3l26</guid>
      <description>&lt;p&gt;In this article I will go over how to set up a &lt;a href="https://lit.dev"&gt;Lit&lt;/a&gt; web component and use it inline in the Flutter widget tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; You can find the final source &lt;a href="https://github.com/rodydavis/flutter_hybrid_template"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reason you would want this integration is so you can take an existing web app, or just a single part of it and embed it in the widget tree.&lt;/p&gt;

&lt;p&gt;With it wrapped in Flutter you can call device APIs from event listeners on your web component. &lt;/p&gt;

&lt;p&gt;For example you may have an app that handles purchases, and now you can call the in app purchase API or other device specific features not available on the web.&lt;/p&gt;

&lt;p&gt;You also get a cross platform app that can be delivered to both Google Play and the App Store.&lt;/p&gt;

&lt;p&gt;The web component will receive new code each time you update your site, so you do not have to ship an update each time the web component changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Flutter SDK&lt;/li&gt;
&lt;li&gt;Xcode and Command Line Tools&lt;/li&gt;
&lt;li&gt;Android SDK&lt;/li&gt;
&lt;li&gt;Vscode&lt;/li&gt;
&lt;li&gt;Node&lt;/li&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;We can start off by creating a empty directory and naming it with &lt;code&gt;snake_case&lt;/code&gt; whatever we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;flutter_lit_example
&lt;span class="nb"&gt;cd &lt;/span&gt;flutter_lit_example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Web Setup
&lt;/h3&gt;

&lt;p&gt;Now we are in the &lt;code&gt;flutter_lit_example&lt;/code&gt; directory and can setup Flutter and Lit. Let's start with node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm i lit
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; typescript vite @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will setup the basics for a node project and install the packages we need. Now lets add some config files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;tsconfig.json
&lt;span class="nb"&gt;touch &lt;/span&gt;vite.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create 2 files. Now open up &lt;code&gt;tsconfig.json&lt;/code&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"es2017"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"dom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"dom.iterable"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"vite/client"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"emitDeclarationOnly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./types"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noUnusedLocals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noUnusedParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noImplicitReturns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noFallthroughCasesInSwitch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowSyntheticDefaultImports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"experimentalDecorators"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic typescript config. Now open up &lt;code&gt;vite.config.ts&lt;/code&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/flutter_lit_example/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// TODO: Name of your github repo&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build/web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// external: /^lit-element/,&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO: Create a new module for each component you want to embed&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;Now we need to create our web component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src
&lt;span class="nb"&gt;cd &lt;/span&gt;src
&lt;span class="nb"&gt;touch &lt;/span&gt;my-app.ts
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;my-app.ts&lt;/code&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit/decorators.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
    p {
      color: blue;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Somebody&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&amp;lt;/p&amp;gt;
      &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
    &amp;lt;/div&amp;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;We need to create a &lt;code&gt;index.html&lt;/code&gt; for our web app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;index.html&lt;/code&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/my-app.ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;my-app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;my-app&amp;gt;&amp;lt;/my-app&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flutter Setup
&lt;/h3&gt;

&lt;p&gt;Now that we have the basics setup for web we can move on to flutter. Let's create the project with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create &lt;span class="nt"&gt;--platforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ios,android &lt;span class="nb"&gt;.&lt;/span&gt;
flutter packages get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open up &lt;code&gt;pubspec.yaml&lt;/code&gt; and update it with the following:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter_lit_example&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A hybrid Flutter app.&lt;/span&gt;
&lt;span class="na"&gt;publish_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0+1&lt;/span&gt;

&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=2.7.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;3.0.0"&lt;/span&gt;

&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;
  &lt;span class="na"&gt;flutter_inappwebview&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^5.3.2&lt;/span&gt;

&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;

&lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;uses-material-design&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to get the packages again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter packages get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to create the file that will wrap the web component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;lib
&lt;span class="nb"&gt;touch &lt;/span&gt;web_component.dart
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;web_component.dart&lt;/code&gt; and paste the following:&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:flutter/material.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:flutter_inappwebview/flutter_inappwebview.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WebComponent&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="o"&gt;{},&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="o"&gt;[],&lt;/span&gt;
  &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventCallback&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;_WebComponentState&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_WebComponentState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_WebComponentState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;InAppWebViewController&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventCallback&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;

  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'''&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta http-equiv="X-UA-Compatible" content="IE=edge" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;style&amp;gt;
      body {
        padding: 0;
        margin: 0;
      }
      &lt;/span&gt;&lt;span class="si"&gt;${widget.name}&lt;/span&gt;&lt;span class="s"&gt; {
        width: 100%;
        height: 100vh;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;script type="module" crossorigin src="&lt;/span&gt;&lt;span class="si"&gt;${widget.bundle}&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span class="si"&gt;${widget.name}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;${widget.attributes.entries.map((e) =&amp;gt; '${e.key}&lt;/span&gt;&lt;span class="s"&gt;="&lt;/span&gt;&lt;span class="si"&gt;${e.value}&lt;/span&gt;&lt;span class="s"&gt;"'&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;' '&lt;/span&gt;&lt;span class="o"&gt;)}&amp;gt;&lt;/span&gt;
      &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;}&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEventListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flutterInAppWebViewPlatformReady"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="s"&gt;''';
  }

  void _setup(InAppWebViewController controller) {
    this.controller = controller;
    this._setupEvents();
  }

  void _setupEvents() {
    for (final event in _events.keys) {
      controller.removeJavaScriptHandler(handlerName: event);
    }
    for (final event in widget.events) {
      _addEvent(event);
    }
  }

  void _addEvent(EventCallback event) {
    controller.addJavaScriptHandler(
      handlerName: event.query,
      callback: event.onPressed,
    );
    _events[event.event] ??= [];
    _events[event.event].add(event);
  }

  @override
  void didUpdateWidget(covariant WebComponent oldWidget) {
    if (oldWidget.events != widget.events) {
      _setupEvents();
    }
    if (oldWidget.slot != widget.slot ||
        oldWidget.bundle != widget.bundle ||
        oldWidget.name != widget.name) {
      controller.loadData(data: source);
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return InAppWebView(
      initialData: InAppWebViewInitialData(data: source),
      onWebViewCreated: _setup,
    );
  }
}

class EventCallback {
  EventCallback({
    @required this.onPressed,
    @required this.event,
    this.query,
  });
  final String query, event;
  final dynamic Function(List&amp;lt;dynamic&amp;gt; args) onPressed;

  @override
  String toString() =&amp;gt; _source;

  String get _prefix =&amp;gt; query != null &amp;amp;&amp;amp; query.isNotEmpty
      ? '&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$query&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s"&gt;'
      : '&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="s"&gt;';

  String get _source =&amp;gt; [
        '&lt;/span&gt;&lt;span class="n"&gt;$_prefix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEventListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$event&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;',
        '&lt;/span&gt;  &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flutter_inappwebview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;callHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$query&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;&lt;span class="s"&gt;',
        '&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;&lt;span class="s"&gt;',
      ].join('&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="s"&gt;');
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;main.dart&lt;/code&gt; and paste it with th following:&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:flutter/material.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;'web_component.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WEBSITE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://rodydavis.github.io/flutter_lit_example/'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BUNDLE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'assets/main.js'&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="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;WidgetsFlutterBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Flutter Hybrid App'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;debugShowCheckedModeBanner:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;primarySwatch:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MyHomePageState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WebComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'my-app'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;bundle:&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$WEBSITE_URL&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$BUNDLE_PATH&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;attributes:&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="o"&gt;},&lt;/span&gt;
          &lt;span class="nl"&gt;slot:&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;button id="my-button"&amp;gt;Talk back!&amp;lt;/button&amp;gt;'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;events:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;EventCallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;event:&lt;/span&gt; &lt;span class="s"&gt;'click'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;query:&lt;/span&gt; &lt;span class="s"&gt;'#my-button'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Clicked!'&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
              &lt;span class="o"&gt;},&lt;/span&gt;
            &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;You will need to update &lt;code&gt;WEBSITE_URL&lt;/code&gt; to have the url of the website where you will be deploying and &lt;code&gt;BUNDLE_URL&lt;/code&gt; to the relative path to the js bundle. &lt;/p&gt;

&lt;p&gt;This will ensure auto updates with a new version rolls out and the cache it stale. This will also allow for offline support after the first time it is downloaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running
&lt;/h2&gt;

&lt;p&gt;Now we can run our application but it requires a few steps to get it all setup.&lt;/p&gt;

&lt;p&gt;To test and build our web app locally we will use &lt;a href="https://github.com/vitejs/vite"&gt;vite&lt;/a&gt; and render the &lt;code&gt;index.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;vite v2.2.3 dev server running at:

Local:    http://localhost:3000/flutter_lit_example/
Network:  http://192.168.1.143:3000/flutter_lit_example/

ready in 311ms.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can open the link &lt;code&gt;http://localhost:3000/flutter_lit_example/&lt;/code&gt; to see our running web app and hot reload changes from &lt;code&gt;my-app.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to learn more about Lit you can read the docs &lt;a href="https://lit.dev"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you are happy with how it looks we can move on to Flutter to wrap it in a native app. This will give us access to native code if we wanted to use the in app purchase api or push notifications.&lt;/p&gt;

&lt;p&gt;Kill the terminal and run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter packages get
flutter build ios
flutter build appbundle
flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should select a running device or prompt you to select one. Now that it is running on the device you can see we have two way communication with the Flutter app and the web component.&lt;/p&gt;

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

&lt;p&gt;If you want to find the source code you can check it out &lt;a href="https://github.com/rodydavis/flutter_hybrid_template"&gt;here&lt;/a&gt; otherwise thanks for reading and let me know if you have any questions!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--30Q42bUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-and-lit%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DLit%2520and%2520Flutter%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--30Q42bUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-and-lit%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DLit%2520and%2520Flutter%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Multi-touch Canvas with Flutter</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Wed, 10 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/multi-touch-canvas-with-flutter-4i00</link>
      <guid>https://dev.to/rodydavis/multi-touch-canvas-with-flutter-4i00</guid>
      <description>&lt;p&gt;If you ever wanted to create a canvas in &lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt; that needs to be panned in any direction and allow zoom then you also probably tried to create a &lt;a href="https://api.flutter.dev/flutter/gestures/MultiDragGestureRecognizer-class.html"&gt;MultiGestureRecognizer&lt;/a&gt; or under a &lt;a href="https://api.flutter.dev/flutter/widgets/GestureDetector-class.html"&gt;GestureDetector&lt;/a&gt; added onPanUpdate and onScaleUpdate and received an error because both can not work at the same time. Even if you have to GestureDetectors then you will still find it does not work how you want and one will always win.&lt;/p&gt;

&lt;p&gt;Online demo: &lt;a href="https://rodydavis.github.io/flutter_multi_touch_canvas/"&gt;https://rodydavis.github.io/flutter_multi_touch_canvas/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the canvas rendering logic used in &lt;a href="https://widget.studio/"&gt;https://widget.studio&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi Touch Goal #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pan the canvas with two or more fingers&lt;/li&gt;
&lt;li&gt;Zoom the canvas with two fingers only (Pinch/Zoom)&lt;/li&gt;
&lt;li&gt;Single finger will interact with canvas object and detect selection&lt;/li&gt;
&lt;li&gt;Bonus trackpad support with similar results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to achieve this we need to use a Listener for the trackpad events and raw touch interactions and &lt;a href="https://api.flutter.dev/flutter/widgets/RawKeyboardListener-class.html"&gt;RawKeyboardListener&lt;/a&gt; for keyboard shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 - Project Setup #
&lt;/h2&gt;

&lt;p&gt;Open your terminal and type 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;mkdir flutter_multi_touchcd flutter_multi_touchflutter create .code .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line is optional and if you have VSCode installed. The command will open the directory inside VSCode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2 - Boilerplate #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remove all comments&lt;/li&gt;
&lt;li&gt;Remove extra empty lines&lt;/li&gt;
&lt;li&gt;Update UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now when you run the project you will have this UI.&lt;/p&gt;

&lt;p&gt;Create a new file located at &lt;code&gt;ui/home/screen.dart&lt;/code&gt; and add 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;import 'package:flutter/material.dart';class HomeScreen extends StatelessWidget { const HomeScreen({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Container(); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;main.dart&lt;/code&gt; with 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;import 'package:flutter/material.dart';import 'ui/home/screen.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), darkTheme: ThemeData.dark().copyWith( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomeScreen(), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will now have a black screen when you run the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3 - Creating the Controller #
&lt;/h2&gt;

&lt;p&gt;Now we want to create a class that will act as our controller on the canvas.&lt;br&gt;&lt;br&gt;
Create a new file at &lt;code&gt;src/controllers/canvas.dart&lt;/code&gt; and add the following to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';/// Control the canvas and the objects on itclass CanvasController { // Controller for the stream output final _controller = StreamController&amp;lt;CanvasController&amp;gt;(); // Reference to the stream to update the UI Stream&amp;lt;CanvasController&amp;gt; get stream =&amp;gt; _controller.stream; // Emit a new event to rebuild the UI void add([CanvasController val]) =&amp;gt; _controller.add(val ?? this); // Stop the stream and finish void close() =&amp;gt; _controller.close();    // Start the stream void init() =&amp;gt; add();}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the home screen with 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;import 'package:flutter/material.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); super.initState(); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar(), body: Stack( children: [Positioned( top: 20, left: 20, width: 100, height: 100, child: Container(color: Colors.red), )], ), ); }); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are just adding the basics to rebuild when the controller changes or the screen is finished. We are using a stateful widget here because we want to dispose of the controller and load it only once. We are also using a stack because thats all we need under the hood. After a quick hot restart you should have the following view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4 - Adding Canvas Objects #
&lt;/h2&gt;

&lt;p&gt;Now we need to create the class for the objects that will be stored on the canvas. Create a new file at &lt;code&gt;src/classes/canvas_object.dart&lt;/code&gt; and add 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;import 'dart:ui';class CanvasObject&amp;lt;T&amp;gt; { final double dx; final double dy; final double width; final double height; final T child; CanvasObject({ this.dx = 0, this.dy = 0, this.width = 100, this.height = 100, this.child, }); CanvasObject&amp;lt;T&amp;gt; copyWith({ double dx, double dy, double width, double height, T child, }) { return CanvasObject&amp;lt;T&amp;gt;( dx: dx ?? this.dx, dy: dy ?? this.dy, width: width ?? this.width, height: height ?? this.height, child: child ?? this.child, ); } Size get size =&amp;gt; Size(width, height); Offset get offset =&amp;gt; Offset(dx, dy); Rect get rect =&amp;gt; offset &amp;amp; size;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using a generic here to not depend on flutter or material in the class. Update the controller with 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;import 'dart:async';import 'package:flutter/material.dart';import '../classes/canvas_object.dart';/// Control the canvas and the objects on itclass CanvasController { /// Controller for the stream output final _controller = StreamController&amp;lt;CanvasController&amp;gt;(); /// Reference to the stream to update the UI Stream&amp;lt;CanvasController&amp;gt; get stream =&amp;gt; _controller.stream; /// Emit a new event to rebuild the UI void add([CanvasController val]) =&amp;gt; _controller.add(val ?? this); /// Stop the stream and finish void close() =&amp;gt; _controller.close(); /// Start the stream void init() =&amp;gt; add(); // -- Canvas Objects -- final List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; _objects = []; /// Current Objects on the canvas List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; get objects =&amp;gt; _objects; /// Add an object to the canvas void addObject(CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects.add(value); }); /// Add an object to the canvas void updateObject(int i, CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects[i] = value; }); /// Remove an object from the canvas void removeObject(int i) =&amp;gt; _update(() { _objects.removeAt(i); }); void _update(void Function() action) { action(); add(this); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are just adding the objects to the canvas and removing them if needed. Update the home screen with the following to use these new objects:&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/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar(), body: Stack( children: [for (final object in instance.objects) Positioned( top: object.dy, left: object.dx, width: object.width, height: object.height, child: object.child, )], ), ); }); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI is thee same as before but now is dynamic and we have access to the Stack children and position of each child.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5 - Capture the Input #
&lt;/h2&gt;

&lt;p&gt;We need to capture the input of the MultiGestureRecognizer, GestureDetector and RawKeyboardListener. Update the canvas controller with 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;import 'dart:async';import 'package:flutter/material.dart';import '../classes/canvas_object.dart';/// Control the canvas and the objects on itclass CanvasController { /// Controller for the stream output final _controller = StreamController&amp;lt;CanvasController&amp;gt;(); /// Reference to the stream to update the UI Stream&amp;lt;CanvasController&amp;gt; get stream =&amp;gt; _controller.stream; /// Emit a new event to rebuild the UI void add([CanvasController val]) =&amp;gt; _controller.add(val ?? this); /// Stop the stream and finish void close() { _controller.close(); focusNode.dispose(); } /// Start the stream void init() =&amp;gt; add(); // -- Canvas Objects -- final List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; _objects = []; /// Current Objects on the canvas List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; get objects =&amp;gt; _objects; /// Add an object to the canvas void addObject(CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects.add(value); }); /// Add an object to the canvas void updateObject(int i, CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects[i] = value; }); /// Remove an object from the canvas void removeObject(int i) =&amp;gt; _update(() { _objects.removeAt(i); }); /// Focus node for listening for keyboard shortcuts final focusNode = FocusNode(); /// Raw events from keys pressed void rawKeyEvent(BuildContext context, RawKeyEvent key) {} /// Called every time a new finger touches the screen void addTouch(int pointer, Offset offsetVal, Offset globalVal) {} /// Called when any of the fingers update position void updateTouch(int pointer, Offset offsetVal, Offset globalVal) {} /// Called when a finger is removed from the screen void removeTouch(int pointer) {} /// Checks if the shift key on the keyboard is pressed bool shiftPressed = false; /// Scale of the canvas double get scale =&amp;gt; _scale; double _scale = 1; set scale(double value) =&amp;gt; _update(() { _scale = value; }); /// Max possible scale static const double maxScale = 3.0; /// Min possible scale static const double minScale = 0.2; /// How much to scale the canvas in increments static const double scaleAdjust = 0.05; /// Current offset of the canvas Offset get offset =&amp;gt; _offset; Offset _offset = Offset.zero; set offset(Offset value) =&amp;gt; _update(() { _offset = value; }); void _update(void Function() action) { action(); add(this); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the home screen with 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;import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar(), body: Listener( behavior: HitTestBehavior.opaque, onPointerSignal: (details) { if (details is PointerScrollEvent) { GestureBinding.instance.pointerSignalResolver .register(details, (event) { if (event is PointerScrollEvent) { if (_controller.shiftPressed) { double zoomDelta = (-event.scrollDelta.dy / 300); _controller.scale = _controller.scale + zoomDelta; } else { _controller.offset = _controller.offset - event.scrollDelta; } } }); } }, onPointerMove: (details) { _controller.updateTouch( details.pointer, details.localPosition, details.position, ); }, onPointerDown: (details) { _controller.addTouch( details.pointer, details.localPosition, details.position, ); }, onPointerUp: (details) { _controller.removeTouch(details.pointer); }, onPointerCancel: (details) { _controller.removeTouch(details.pointer); }, child: RawKeyboardListener( autofocus: true, focusNode: _controller.focusNode, onKey: (key) =&amp;gt; _controller.rawKeyEvent(context, key), child: Stack( children: [for (final object in instance.objects) Positioned( top: object.dy, left: object.dx, width: object.width, height: object.height, child: object.child, )], ), ), ), ); }); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we are doing now is just mapping the inputs of the UI to the actions in the controller. Feel free to look through the comments if you are curious how each one works. Running the application should still just show the red square.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5 - Canvas Offset and Scale #
&lt;/h2&gt;

&lt;p&gt;Now we want to start moving the canvas. Let’s first tackle the offset as scale will take a different approach. Update the home screen with 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;import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar(), body: Listener( behavior: HitTestBehavior.opaque, onPointerSignal: (details) { if (details is PointerScrollEvent) { GestureBinding.instance.pointerSignalResolver .register(details, (event) { if (event is PointerScrollEvent) { if (_controller.shiftPressed) { double zoomDelta = (-event.scrollDelta.dy / 300); _controller.scale = _controller.scale + zoomDelta; } else { _controller.offset = _controller.offset - event.scrollDelta; } } }); } }, onPointerMove: (details) { _controller.updateTouch( details.pointer, details.localPosition, details.position, ); }, onPointerDown: (details) { _controller.addTouch( details.pointer, details.localPosition, details.position, ); }, onPointerUp: (details) { _controller.removeTouch(details.pointer); }, onPointerCancel: (details) { _controller.removeTouch(details.pointer); }, child: RawKeyboardListener( autofocus: true, focusNode: _controller.focusNode, onKey: (key) =&amp;gt; _controller.rawKeyEvent(context, key), child: SizedBox.expand( child: Stack( children: [for (final object in instance.objects) AnimatedPositioned.fromRect( duration: const Duration(milliseconds: 50), rect: object.rect.adjusted( _controller.offset, _controller.scale, ), child: FittedBox( fit: BoxFit.fill, child: SizedBox.fromSize( size: object.size, child: object.child, ), ), )], ), ), ), ), ); }); }}extension RectUtils on Rect { Rect adjusted(Offset offset, double scale) { final left = (this.left + offset.dx) * scale; final top = (this.top + offset.dy) * scale; final width = this.width * scale; final height = this.height * scale; return Rect.fromLTWH(left, top, width, height); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you use your trackpad to pan with two fingers you will see the red square move. We now need to add finger support too. You may notice the FittedBox and that will come in as soon as we add scaling.&lt;/p&gt;

&lt;p&gt;Now if we move the square off the screen we may need to bring it back. We can add a reset button to the AppBar. Add the following to the canvas controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  static const double _scaleDefault = 1; static const Offset _offsetDefault = Offset.zero; void reset() { scale = _scaleDefault; offset = _offsetDefault; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the home screen with 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;import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar( actions: [IconButton( tooltip: 'Reset the Scale and Offset', icon: Icon(Icons.restore), onPressed: _controller.reset, ),], ), body: Listener( behavior: HitTestBehavior.opaque, onPointerSignal: (details) { if (details is PointerScrollEvent) { GestureBinding.instance.pointerSignalResolver .register(details, (event) { if (event is PointerScrollEvent) { if (_controller.shiftPressed) { double zoomDelta = (-event.scrollDelta.dy / 300); _controller.scale = _controller.scale + zoomDelta; } else { _controller.offset = _controller.offset - event.scrollDelta; } } }); } }, onPointerMove: (details) { _controller.updateTouch( details.pointer, details.localPosition, details.position, ); }, onPointerDown: (details) { _controller.addTouch( details.pointer, details.localPosition, details.position, ); }, onPointerUp: (details) { _controller.removeTouch(details.pointer); }, onPointerCancel: (details) { _controller.removeTouch(details.pointer); }, child: RawKeyboardListener( autofocus: true, focusNode: _controller.focusNode, onKey: (key) =&amp;gt; _controller.rawKeyEvent(context, key), child: SizedBox.expand( child: Stack( children: [for (final object in instance.objects) AnimatedPositioned.fromRect( duration: const Duration(milliseconds: 50), rect: object.rect.adjusted( _controller.offset, _controller.scale, ), child: FittedBox( fit: BoxFit.fill, child: SizedBox.fromSize( size: object.size, child: object.child, ), ), )], ), ), ), ), ); }); }}extension RectUtils on Rect { Rect adjusted(Offset offset, double scale) { final left = (this.left + offset.dx) * scale; final top = (this.top + offset.dy) * scale; final width = this.width * scale; final height = this.height * scale; return Rect.fromLTWH(left, top, width, height); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you press the reset button the canvas animates back to the default offset and scale.&lt;/p&gt;

&lt;p&gt;While we are here we can add actions for zoom in/out and connect them to the controller. Add the following to the canvas controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  void zoomIn() { scale += scaleAdjust; } void zoomOut() { scale -= scaleAdjust; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the AppBar actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  IconButton( tooltip: 'Zoom In', icon: Icon(Icons.zoom_in), onPressed: _controller.zoomIn, ), IconButton( tooltip: 'Zoom Out', icon: Icon(Icons.zoom_out), onPressed: _controller.zoomOut, ),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you run the application you can easily zoom in/out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 6 - Keyboard Shortcuts #
&lt;/h2&gt;

&lt;p&gt;Now we need to capture the keyboard events so we can move the canvas with the arrow keys and scale with +/- keys. Update the controller with 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;import 'dart:async';import 'package:flutter/material.dart';import 'package:flutter/services.dart';import '../classes/canvas_object.dart';/// Control the canvas and the objects on itclass CanvasController { /// Controller for the stream output final _controller = StreamController&amp;lt;CanvasController&amp;gt;(); /// Reference to the stream to update the UI Stream&amp;lt;CanvasController&amp;gt; get stream =&amp;gt; _controller.stream; /// Emit a new event to rebuild the UI void add([CanvasController val]) =&amp;gt; _controller.add(val ?? this); /// Stop the stream and finish void close() { _controller.close(); focusNode.dispose(); } /// Start the stream void init() =&amp;gt; add(); // -- Canvas Objects -- final List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; _objects = []; /// Current Objects on the canvas List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; get objects =&amp;gt; _objects; /// Add an object to the canvas void addObject(CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects.add(value); }); /// Add an object to the canvas void updateObject(int i, CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects[i] = value; }); /// Remove an object from the canvas void removeObject(int i) =&amp;gt; _update(() { _objects.removeAt(i); }); /// Focus node for listening for keyboard shortcuts final focusNode = FocusNode(); /// Raw events from keys pressed void rawKeyEvent(BuildContext context, RawKeyEvent key) { // Scale keys if (key.isKeyPressed(LogicalKeyboardKey.minus)) { zoomOut(); } if (key.isKeyPressed(LogicalKeyboardKey.equal)) { zoomIn(); } // Directional Keys if (key.isKeyPressed(LogicalKeyboardKey.arrowLeft)) { offset = offset + Offset(offsetAdjust, 0.0); } if (key.isKeyPressed(LogicalKeyboardKey.arrowRight)) { offset = offset + Offset(-offsetAdjust, 0.0); } if (key.isKeyPressed(LogicalKeyboardKey.arrowUp)) { offset = offset + Offset(0.0, offsetAdjust); } if (key.isKeyPressed(LogicalKeyboardKey.arrowDown)) { offset = offset + Offset(0.0, -offsetAdjust); } _shiftPressed = key.isShiftPressed; /// Update Controller Instance add(this); } /// Called every time a new finger touches the screen void addTouch(int pointer, Offset offsetVal, Offset globalVal) {} /// Called when any of the fingers update position void updateTouch(int pointer, Offset offsetVal, Offset globalVal) {} /// Called when a finger is removed from the screen void removeTouch(int pointer) {} /// Checks if the shift key on the keyboard is pressed bool get shiftPressed =&amp;gt; _shiftPressed; bool _shiftPressed = false; /// Scale of the canvas double get scale =&amp;gt; _scale; double _scale = 1; set scale(double value) =&amp;gt; _update(() { _scale = value; }); /// Max possible scale static const double maxScale = 3.0; /// Min possible scale static const double minScale = 0.2; /// How much to scale the canvas in increments static const double scaleAdjust = 0.05; /// How much to shift the canvas in increments static const double offsetAdjust = 15; /// Current offset of the canvas Offset get offset =&amp;gt; _offset; Offset _offset = Offset.zero; set offset(Offset value) =&amp;gt; _update(() { _offset = value; }); static const double _scaleDefault = 1; static const Offset _offsetDefault = Offset.zero; /// Reset the canvas zoom and offset void reset() { scale = _scaleDefault; offset = _offsetDefault; } /// Zoom in the canvas void zoomIn() { scale += scaleAdjust; } /// Zoom out the canvas void zoomOut() { scale -= scaleAdjust; } void _update(void Function() action) { action(); add(this); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you run the application you can control the zoom and pan with just a keyboard. This could be useful for a fallback input that would work on a TV for example…&lt;/p&gt;

&lt;p&gt;If you want to see if it is actually scaling proportionally then add the following the home screen:&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/gestures.dart';import 'package:flutter/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); _controller.addObject( CanvasObject( dx: 80, dy: 60, width: 100, height: 200, child: Container(color: Colors.green), ), ); _controller.addObject( CanvasObject( dx: 100, dy: 40, width: 100, height: 50, child: Container(color: Colors.blue), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar( actions: [FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Zoom In', icon: Icon(Icons.zoom_in), onPressed: _controller.zoomIn, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Zoom Out', icon: Icon(Icons.zoom_out), onPressed: _controller.zoomOut, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Reset the Scale and Offset', icon: Icon(Icons.restore), onPressed: _controller.reset, ), ),], ), body: Listener( behavior: HitTestBehavior.opaque, onPointerSignal: (details) { if (details is PointerScrollEvent) { GestureBinding.instance.pointerSignalResolver .register(details, (event) { if (event is PointerScrollEvent) { if (_controller.shiftPressed) { double zoomDelta = (-event.scrollDelta.dy / 300); _controller.scale = _controller.scale + zoomDelta; } else { _controller.offset = _controller.offset - event.scrollDelta; } } }); } }, onPointerMove: (details) { _controller.updateTouch( details.pointer, details.localPosition, details.position, ); }, onPointerDown: (details) { _controller.addTouch( details.pointer, details.localPosition, details.position, ); }, onPointerUp: (details) { _controller.removeTouch(details.pointer); }, onPointerCancel: (details) { _controller.removeTouch(details.pointer); }, child: RawKeyboardListener( autofocus: true, focusNode: _controller.focusNode, onKey: (key) =&amp;gt; _controller.rawKeyEvent(context, key), child: SizedBox.expand( child: Stack( children: [for (final object in instance.objects) AnimatedPositioned.fromRect( duration: const Duration(milliseconds: 50), rect: object.rect.adjusted( _controller.offset, _controller.scale, ), child: FittedBox( fit: BoxFit.fill, child: SizedBox.fromSize( size: object.size, child: object.child, ), ), )], ), ), ), ), ); }); }}extension RectUtils on Rect { Rect adjusted(Offset offset, double scale) { final left = (this.left + offset.dx) * scale; final top = (this.top + offset.dy) * scale; final width = this.width * scale; final height = this.height * scale; return Rect.fromLTWH(left, top, width, height); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can zoom and the blocks all scale correctly and pan around.&lt;/p&gt;

&lt;p&gt;Just press the reset button to start over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 7 - Multi Touch Input #
&lt;/h2&gt;

&lt;p&gt;Now time for the fingers. For this you will need a touchscreen device to test. You can plug in your phone or if you have a touch screen computer you can run the web version. Update the controller with following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';import 'dart:math' as math;import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import 'package:flutter/services.dart';import '../classes/canvas_object.dart';import '../classes/rect_points.dart';/// Control the canvas and the objects on itclass CanvasController { /// Controller for the stream output final _controller = StreamController&amp;lt;CanvasController&amp;gt;(); /// Reference to the stream to update the UI Stream&amp;lt;CanvasController&amp;gt; get stream =&amp;gt; _controller.stream; /// Emit a new event to rebuild the UI void add([CanvasController val]) =&amp;gt; _controller.add(val ?? this); /// Stop the stream and finish void close() { _controller.close(); focusNode.dispose(); } /// Start the stream void init() =&amp;gt; add(); // -- Canvas Objects -- final List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; _objects = []; /// Current Objects on the canvas List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; get objects =&amp;gt; _objects; /// Add an object to the canvas void addObject(CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects.add(value); }); /// Add an object to the canvas void updateObject(int i, CanvasObject&amp;lt;Widget&amp;gt; value) =&amp;gt; _update(() { _objects[i] = value; }); /// Remove an object from the canvas void removeObject(int i) =&amp;gt; _update(() { _objects.removeAt(i); }); /// Focus node for listening for keyboard shortcuts final focusNode = FocusNode(); /// Raw events from keys pressed void rawKeyEvent(BuildContext context, RawKeyEvent key) { // Scale keys if (key.isKeyPressed(LogicalKeyboardKey.minus)) { zoomOut(); } if (key.isKeyPressed(LogicalKeyboardKey.equal)) { zoomIn(); } // Directional Keys if (key.isKeyPressed(LogicalKeyboardKey.arrowLeft)) { offset = offset + Offset(offsetAdjust, 0.0); } if (key.isKeyPressed(LogicalKeyboardKey.arrowRight)) { offset = offset + Offset(-offsetAdjust, 0.0); } if (key.isKeyPressed(LogicalKeyboardKey.arrowUp)) { offset = offset + Offset(0.0, offsetAdjust); } if (key.isKeyPressed(LogicalKeyboardKey.arrowDown)) { offset = offset + Offset(0.0, -offsetAdjust); } _shiftPressed = key.isShiftPressed; _metaPressed = key.isMetaPressed; /// Update Controller Instance add(this); } /// Trigger Shift Press void shiftSelect() { _shiftPressed = true; } /// Trigger Meta Press void metaSelect() { _metaPressed = true; } final Map&amp;lt;int, Offset&amp;gt; _pointerMap = {}; /// Number of inputs currently on the screen int get touchCount =&amp;gt; _pointerMap.values.length; /// Marquee selection on the canvas RectPoints get marquee =&amp;gt; _marquee; RectPoints _marquee; /// Dragging a canvas object bool get isMovingCanvasObject =&amp;gt; _isMovingCanvasObject; bool _isMovingCanvasObject = false; final List&amp;lt;int&amp;gt; _selectedObjects = []; List&amp;lt;int&amp;gt; get selectedObjectsIndices =&amp;gt; _selectedObjects; List&amp;lt;CanvasObject&amp;lt;Widget&amp;gt;&amp;gt; get selectedObjects =&amp;gt; _selectedObjects.map((i) =&amp;gt; _objects[i]).toList(); bool isObjectSelected(int i) =&amp;gt; _selectedObjects.contains(i); /// Called every time a new input touches the screen void addTouch(int pointer, Offset offsetVal, Offset globalVal) { _pointerMap[pointer] = offsetVal; if (shiftPressed) { final pt = (offsetVal / scale) - (offset); _marquee = RectPoints(pt, pt); } /// Update Controller Instance add(this); } /// Called when any of the inputs update position void updateTouch(int pointer, Offset offsetVal, Offset globalVal) { if (_marquee != null) { // Update New Widget Rect final _pts = _marquee; final a = _pointerMap.values.first; _pointerMap[pointer] = offsetVal; final b = _pointerMap.values.first; final delta = (b - a) / scale; _pts.end = _pts.end + delta; _marquee = _pts; final _rect = Rect.fromPoints(_pts.start, _pts.end); _selectedObjects.clear(); for (var i = 0; i &amp;lt; _objects.length; i++) { if (_rect.overlaps(_objects[i].rect)) { _selectedObjects.add(i); } } } else if (touchCount == 1) { // Widget Move _isMovingCanvasObject = true; final a = _pointerMap.values.first; _pointerMap[pointer] = offsetVal; final b = _pointerMap.values.first; if (_selectedObjects.isEmpty) return; for (final idx in _selectedObjects) { final widget = _objects[idx]; final delta = (b - a) / scale; final _newOffset = widget.offset + delta; _objects[idx] = widget.copyWith(dx: _newOffset.dx, dy: _newOffset.dy); } } else if (touchCount == 2) { // Scale and Rotate Update _isMovingCanvasObject = false; final _rectA = _getRectFromPoints(_pointerMap.values.toList()); _pointerMap[pointer] = offsetVal; final _rectB = _getRectFromPoints(_pointerMap.values.toList()); final _delta = _rectB.center - _rectA.center; final _newOffset = offset + (_delta / scale); offset = _newOffset; final aDistance = (_rectA.topLeft - _rectA.bottomRight).distance; final bDistance = (_rectB.topLeft - _rectB.bottomRight).distance; final change = (bDistance / aDistance); scale = scale * change; } else { // Pan Update _isMovingCanvasObject = false; final _rectA = _getRectFromPoints(_pointerMap.values.toList()); _pointerMap[pointer] = offsetVal; final _rectB = _getRectFromPoints(_pointerMap.values.toList()); final _delta = _rectB.center - _rectA.center; offset = offset + (_delta / scale); } _pointerMap[pointer] = offsetVal; /// Update Controller Instance add(this); } /// Called when a input is removed from the screen void removeTouch(int pointer) { _pointerMap.remove(pointer); if (touchCount &amp;lt; 1) { _isMovingCanvasObject = false; } if (_marquee != null) { _marquee = null; _shiftPressed = false; } /// Update Controller Instance add(this); } void selectObject(int i) =&amp;gt; _update(() { if (!_metaPressed) { _selectedObjects.clear(); } _selectedObjects.add(0); final item = _objects.removeAt(i); _objects.insert(0, item); }); /// Checks if the shift key on the keyboard is pressed bool get shiftPressed =&amp;gt; _shiftPressed; bool _shiftPressed = false; /// Checks if the meta key on the keyboard is pressed bool get metaPressed =&amp;gt; _metaPressed; bool _metaPressed = false; /// Scale of the canvas double get scale =&amp;gt; _scale; double _scale = 1; set scale(double value) =&amp;gt; _update(() { if (value &amp;lt;= minScale) { value = minScale; } else if (value &amp;gt;= maxScale) { value = maxScale; } _scale = value; }); /// Max possible scale static const double maxScale = 3.0; /// Min possible scale static const double minScale = 0.2; /// How much to scale the canvas in increments static const double scaleAdjust = 0.05; /// How much to shift the canvas in increments static const double offsetAdjust = 15; /// Current offset of the canvas Offset get offset =&amp;gt; _offset; Offset _offset = Offset.zero; set offset(Offset value) =&amp;gt; _update(() { _offset = value; }); static const double _scaleDefault = 1; static const Offset _offsetDefault = Offset.zero; /// Reset the canvas zoom and offset void reset() { scale = _scaleDefault; offset = _offsetDefault; } /// Zoom in the canvas void zoomIn() { scale += scaleAdjust; } /// Zoom out the canvas void zoomOut() { scale -= scaleAdjust; } void _update(void Function() action) { action(); add(this); } Rect _getRectFromPoints(List&amp;lt;Offset&amp;gt; offsets) { if (offsets.length == 2) { return Rect.fromPoints(offsets.first, offsets.last); } final dxs = offsets.map((e) =&amp;gt; e.dx).toList(); final dys = offsets.map((e) =&amp;gt; e.dy).toList(); double left = _minFromList(dxs); double top = _minFromList(dys); double bottom = _maxFromList(dys); double right = _maxFromList(dxs); return Rect.fromLTRB(left, top, right, bottom); } double _minFromList(List&amp;lt;double&amp;gt; values) { double value = double.infinity; for (final item in values) { value = math.min(item, value); } return value; } double _maxFromList(List&amp;lt;double&amp;gt; values) { double value = -double.infinity; for (final item in values) { value = math.max(item, value); } return value; }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a new file &lt;code&gt;src/classes/rect_points.dart&lt;/code&gt; and add 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;import 'dart:ui';class RectPoints { RectPoints(this.start, this.end); Offset start, end; Rect get rect =&amp;gt; Rect.fromPoints(start, end);}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;main.dart&lt;/code&gt; with 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;import 'package:flutter/material.dart';import 'ui/home/screen.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.red, visualDensity: VisualDensity.adaptivePlatformDensity, ), darkTheme: ThemeData.dark().copyWith( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomeScreen(), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the home screen with 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;import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import '../../src/classes/canvas_object.dart';import '../../src/controllers/canvas.dart';class HomeScreen extends StatefulWidget { const HomeScreen({Key key}) : super(key: key); @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { final _controller = CanvasController(); @override void initState() { _controller.init(); _dummyData(); super.initState(); } void _dummyData() { _controller.addObject( CanvasObject( dx: 20, dy: 20, width: 100, height: 100, child: Container(color: Colors.red), ), ); _controller.addObject( CanvasObject( dx: 80, dy: 60, width: 100, height: 200, child: Container(color: Colors.green), ), ); _controller.addObject( CanvasObject( dx: 100, dy: 40, width: 100, height: 50, child: Container(color: Colors.blue), ), ); } @override void dispose() { _controller.close(); super.dispose(); } @override Widget build(BuildContext context) { return StreamBuilder&amp;lt;CanvasController&amp;gt;( stream: _controller.stream, builder: (context, snapshot) { if (!snapshot.hasData) { return Scaffold( appBar: AppBar(), body: Center(child: CircularProgressIndicator()), ); } final instance = snapshot.data; return Scaffold( appBar: AppBar( actions: [FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Selection', icon: Icon(Icons.select_all), color: instance.shiftPressed ? Theme.of(context).accentColor : null, onPressed: _controller.shiftSelect, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Meta Key', color: instance.metaPressed ? Theme.of(context).accentColor : null, icon: Icon(Icons.category), onPressed: _controller.metaSelect, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Zoom In', icon: Icon(Icons.zoom_in), onPressed: _controller.zoomIn, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Zoom Out', icon: Icon(Icons.zoom_out), onPressed: _controller.zoomOut, ), ), FocusScope( canRequestFocus: false, child: IconButton( tooltip: 'Reset the Scale and Offset', icon: Icon(Icons.restore), onPressed: _controller.reset, ), ),], ), body: Listener( behavior: HitTestBehavior.opaque, onPointerSignal: (details) { if (details is PointerScrollEvent) { GestureBinding.instance.pointerSignalResolver .register(details, (event) { if (event is PointerScrollEvent) { if (_controller.shiftPressed) { double zoomDelta = (-event.scrollDelta.dy / 300); _controller.scale = _controller.scale + zoomDelta; } else { _controller.offset = _controller.offset - event.scrollDelta; } } }); } }, onPointerMove: (details) { _controller.updateTouch( details.pointer, details.localPosition, details.position, ); }, onPointerDown: (details) { _controller.addTouch( details.pointer, details.localPosition, details.position, ); }, onPointerUp: (details) { _controller.removeTouch(details.pointer); }, onPointerCancel: (details) { _controller.removeTouch(details.pointer); }, child: RawKeyboardListener( autofocus: true, focusNode: _controller.focusNode, onKey: (key) =&amp;gt; _controller.rawKeyEvent(context, key), child: SizedBox.expand( child: Stack( children: [for (var i = 0; i &amp;lt; instance.objects.length; i++) Positioned.fromRect( rect: instance.objects[i].rect.adjusted( _controller.offset, _controller.scale, ), child: Container( decoration: BoxDecoration( border: Border.all( color: instance.isObjectSelected(i) ? Colors.grey : Colors.transparent, )), child: GestureDetector( onTapDown: (_) =&amp;gt; _controller.selectObject(i), child: FittedBox( fit: BoxFit.fill, child: SizedBox.fromSize( size: instance.objects[i].size, child: instance.objects[i].child, ), ), ), ), ), if (instance?.marquee != null) Positioned.fromRect( rect: instance.marquee.rect .adjusted(instance.offset, instance.scale), child: Container( color: Colors.blueAccent.withOpacity(0.3), ), ), ], ), ), ), ), ); }); }}extension RectUtils on Rect { Rect adjusted(Offset offset, double scale) { final left = (this.left + offset.dx) * scale; final top = (this.top + offset.dy) * scale; final width = this.width * scale; final height = this.height * scale; return Rect.fromLTWH(left, top, width, height); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can move any object on the canvas just by clicking and dragging. You can zoom with 2 fingers and pan with 2 or 3 fingers. If you hold down the shift key then you can use a marquee to select multiple and if you hold down the meta/command key then you can select multiple by tapping each.&lt;/p&gt;

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

&lt;p&gt;If you are on a device without a keyboard you can tap the new icons to turn on the keyboard key actions. When the object is selected there is a grey border.&lt;/p&gt;

&lt;p&gt;Now you can add any widget to the canvas and pan and zoom!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---InzYjDu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-multi-touch-canvas%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DMulti-touch%2520Canvas%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---InzYjDu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-multi-touch-canvas%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DMulti-touch%2520Canvas%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How To Send Push Notifications on Flutter Web (FCM)</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/how-to-send-push-notifications-on-flutter-web-fcm-3g8j</link>
      <guid>https://dev.to/rodydavis/how-to-send-push-notifications-on-flutter-web-fcm-3g8j</guid>
      <description>&lt;p&gt;If you are using Firebase then you are probably familiar with Firebase Cloud Messaging. The setup on Flutter web is very different than mobile and other plugins you are probably used to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u_X4gKZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2AB0Av_NnFrCKsyC7gN94ytg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u_X4gKZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3200/1%2AB0Av_NnFrCKsyC7gN94ytg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up #
&lt;/h2&gt;

&lt;p&gt;Open your web/index.html and look for the following script. If you do not have one you can add it now in the&lt;/p&gt;

&lt;p&gt;tag.&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;script&amp;gt;if ("serviceWorker" in navigator) { window.addEventListener("load", function () { navigator.serviceWorker.register("/flutter_service_worker.js"); });}&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to modify it to support the FCM service worker. The important thing we need to do is comment out the flutter_service_worker.js so that we will not get 404 errors when registering the FCM service worker.&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;script&amp;gt;if ("serviceWorker" in navigator) { window.addEventListener("load", function () { // navigator.serviceWorker.register("/flutter_service_worker.js"); navigator.serviceWorker.register("/firebase-messaging-sw.js"); });}&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new file called firebase-messaging-sw.js in the web folder with the following contents:&lt;/p&gt;

&lt;p&gt;Make sure to replace the config keys with your firebase keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helper Methods #
&lt;/h2&gt;

&lt;p&gt;Create a new dart file wherever you like named firebase_messaging.dart with the following:&lt;/p&gt;

&lt;p&gt;Create a button in the app that will be used to request permissions. While it is possible to request for permission when the app launches this is usually bad practice as the user is unlikely to accept and there is no trust built yet. You can request permissions with 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;final _messaging = FBMessaging.instance;_messaging.requestPermission().then((_) async { final _token = await _messaging.getToken(); print('Token: $_token');});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can listen to messages with 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;final _messaging = FBMessaging.instance;_messaging.stream.listen((event) { print('New Message: ${event}');});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing #
&lt;/h2&gt;

&lt;p&gt;Now when you run your application and request permissions you will get a token back. With this token you can open the firebase console and sent a test message to the token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7NsaOc4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AZzoPP7kRHOR-yDN6Sv9XjQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7NsaOc4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AZzoPP7kRHOR-yDN6Sv9XjQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Now you can send push notifications to Flutter apps! You still need to use conditional imports to support the mobile side as well but stay tuned for an example with that. Let me know your questions and any feedback you may have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KMJXD5Hr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fpush-notifications-flutter-web%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHow%2520To%2520Send%2520Push%2520Notifications%2520on%2520Flutter%2520Web%2520%28FCM%29%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KMJXD5Hr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fpush-notifications-flutter-web%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHow%2520To%2520Send%2520Push%2520Notifications%2520on%2520Flutter%2520Web%2520%28FCM%29%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deep Linking for Flutter Web</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Wed, 11 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/deep-linking-for-flutter-web-3279</link>
      <guid>https://dev.to/rodydavis/deep-linking-for-flutter-web-3279</guid>
      <description>&lt;p&gt;In this article I will show you how to have proper URL navigation for your application. Open links to specific pages, protected routes and custom transitions.&lt;/p&gt;

&lt;p&gt;There is a online demo here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rodydavis.github.io/flutter_deep_linking/"&gt;https://rodydavis.github.io/flutter_deep_linking/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the source code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rodydavis/flutter_deep_linking"&gt;https://github.com/rodydavis/flutter_deep_linking&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a new flutter project called “flutter_deep_linking”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open that folder up in VSCode&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update your “pubspec.yaml” with the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vUM98cRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AT8p4RgLHWHHaAo1JbhUB-g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vUM98cRi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AT8p4RgLHWHHaAo1JbhUB-g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a file at “lib/ui/home/screen.dart” and add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QeVcnCJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1qTWt0lGfSQeUwoTjzbFuA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QeVcnCJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1qTWt0lGfSQeUwoTjzbFuA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update your “lib/main.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U6ZRsS-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AVo3FEkRX_to3SbYsUQ8a5Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U6ZRsS-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AVo3FEkRX_to3SbYsUQ8a5Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run your application and you should see the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--svY8vl3O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A1O9oKvIOSCpgwJ6GekFG4Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--svY8vl3O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A1O9oKvIOSCpgwJ6GekFG4Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 #
&lt;/h2&gt;

&lt;p&gt;Now we need to grab the url the user enters into the address bar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a folder at this location “lib/plugins/navigator”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a file inside named: “web.dart” with the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ImLmriwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2072/1%2AhkuI9pfODs-yewfNUPMbTw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ImLmriwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2072/1%2AhkuI9pfODs-yewfNUPMbTw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file inside named: “unsupported.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pUJjDaWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Au2BPO9L2w9ci-RI9dOi43Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pUJjDaWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Au2BPO9L2w9ci-RI9dOi43Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file inside named: “navigator.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dbVwvkyh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2032/1%2A-QnmWVuypD4z58ZXLircaA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dbVwvkyh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2032/1%2A-QnmWVuypD4z58ZXLircaA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now go back to your “lib/main.dart” file and add the navigator:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p-X206Dj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2080/1%2AUN0nQy8wcXELNBzfIMqNZA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p-X206Dj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2080/1%2AUN0nQy8wcXELNBzfIMqNZA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s important to import the navigator as shown as this will have the conditional import for web compiling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;If you run the app now nothing should change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3 #
&lt;/h2&gt;

&lt;p&gt;Now let’s add the proper routing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new file “lib/ui/router.dart” and add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MyqaIdq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2404/1%2AJpwIDmcnQMzxTdSr0OQWfQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MyqaIdq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2404/1%2AJpwIDmcnQMzxTdSr0OQWfQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Also update “lib/main.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2F0LBbzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2120/1%2A4e_oz-ENDkaBS35ZoEZQMA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2F0LBbzu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2120/1%2A4e_oz-ENDkaBS35ZoEZQMA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice how we removed the “home” field for MaterialApp. This is because the router will handle everything. By default we will go home on “/”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4 #
&lt;/h2&gt;

&lt;p&gt;Now let’s add multiple screens to put this to the test! Add the following folders and files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file “lib/ui/account/screen.dart” and add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N_ZpwLbi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2056/1%2AvRXbGfLK-D1827s2C57aEQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N_ZpwLbi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2056/1%2AvRXbGfLK-D1827s2C57aEQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file “lib/ui/settings/screen.dart” and add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QHXc5Nkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AFJK7a8eeOyVFbAcULxo_Gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QHXc5Nkk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AFJK7a8eeOyVFbAcULxo_Gw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a file “lib/ui/about/screen.dart” and add the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qXmcA_J9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AA9CJ8-bGCv2Np6ZLS9l5oA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qXmcA_J9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AA9CJ8-bGCv2Np6ZLS9l5oA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the following to “lib/ui/router.dart”:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gv3zy-gL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2332/1%2AkKTHn14JH6AXhkbuLUCnFA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gv3zy-gL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2332/1%2AkKTHn14JH6AXhkbuLUCnFA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now when you navigate to /about, /account and /settings you will go to the new pages!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1Ux0xAY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A5Qtymz13vrEZmCa9zRikWw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1Ux0xAY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A5Qtymz13vrEZmCa9zRikWw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 #
&lt;/h2&gt;

&lt;p&gt;Now let’s tie into the browser navigation buttons! Update “lib/ui/home/screen.dart” with the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CWSr5prj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2516/1%2A6JTelDsDG6njWK8watZdlQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CWSr5prj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2516/1%2A6JTelDsDG6njWK8watZdlQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now when you run the application and click on the settings icon it will launch the new screen as expected. But if you click your browsers back button it will go back to the home screen!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j6C4Hf0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2AFji7xHupcpduXITJ9JmzYw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j6C4Hf0k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2AFji7xHupcpduXITJ9JmzYw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--knHlrRZo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A8wQrI1MJPi6MGr65jaqjTQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--knHlrRZo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2A8wQrI1MJPi6MGr65jaqjTQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 #
&lt;/h2&gt;

&lt;p&gt;These urls are great but what if you want to pass data such as an ID that is not known ahead of time? No worries!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update “lib/ui/account/screen.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LoUrYmUr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2184/1%2AyOpcN7lpYP9cc5Mo2dJQwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LoUrYmUr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2184/1%2AyOpcN7lpYP9cc5Mo2dJQwg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let’s update our “lib/ui/router.dart” with the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B8E0mQuK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2064/1%2AizyMARAi5g8GrV3q-qwUcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8E0mQuK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2064/1%2AizyMARAi5g8GrV3q-qwUcw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now when you run your application and navigate to “/account/40” you will see the following:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W5Cj5ptj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2AwKMr8wDsEWKxvrkTVnfKLQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W5Cj5ptj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3468/1%2AwKMr8wDsEWKxvrkTVnfKLQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Dynamic routes work great for Flutter web, you just need to know what to tweak! This package uses a forked version of fluro for some fixes I added but once the PRs is merged you can just use the regular package. Let me know what you think below and if there is a better way I am not seeing!&lt;/p&gt;

&lt;p&gt;Here is the final code: &lt;a href="https://github.com/rodydavis/flutter_deep_linking"&gt;https://github.com/rodydavis/flutter_deep_linking&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jXO0xbnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fdeep-linking-flutter-web%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DDeep%2520Linking%2520for%2520Flutter%2520Web%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jXO0xbnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fdeep-linking-flutter-web%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DDeep%2520Linking%2520for%2520Flutter%2520Web%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Flutter Cheat Sheet — Terminal</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Thu, 05 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/flutter-cheat-sheet-terminal-1362</link>
      <guid>https://dev.to/rodydavis/flutter-cheat-sheet-terminal-1362</guid>
      <description>&lt;p&gt;Run Flutter web with SKIA&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter run -d web --release --dart-define=FLUTTER_WEB_USE_SKIA=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run Flutter web with Canvas Kit
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter run -d chrome --release --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Build your Flutter web app to Github Pages to the docs folder
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter build web &amp;amp;&amp;amp; rm -rf ./docs &amp;amp;&amp;amp; mkdir ./docs &amp;amp;&amp;amp; cp -a ./build/web/. ./docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Clean rebuild CocoaPods
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ios &amp;amp;&amp;amp; pod deintegrate &amp;amp;&amp;amp; pod cache clean —all &amp;amp;&amp;amp; pod install &amp;amp;&amp;amp; cd ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Sometimes with firebase you need to run: &lt;code&gt;pod update Firebase&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Create Dart package with Example
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter create -t plugin . &amp;amp;&amp;amp; flutter create -i swift -a kotlin --androidx example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Watch Build Files
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter packages pub run build_runner watch -—delete-conflicting-outputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate Build Files
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter packages pub run build_runner build -—delete-conflicting-outputs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Build Bug Report
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter run —bug-report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Flutter generate test coverage
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter test --coverage &amp;amp;&amp;amp; genhtml -o coverage coverage/lcov.info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Rebuild Flutter Cache
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter pub pub cache repair
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Conditional Export/Import
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export 'unsupported.dart'if (dart.library.html) 'web.dart'if (dart.library.io) 'mobile.dart';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Kill Dart Running
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;killall -9 dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fY7cyvDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-cheat-sheet%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DFlutter%2520Cheat%2520Sheet%25E2%2580%258A%25E2%2580%2594%25E2%2580%258ATerminal%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fY7cyvDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fflutter-cheat-sheet%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DFlutter%2520Cheat%2520Sheet%25E2%2580%258A%25E2%2580%2594%25E2%2580%258ATerminal%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Host your Flutter Project as a REST API</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Fri, 18 Oct 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/host-your-flutter-project-as-a-rest-api-48mc</link>
      <guid>https://dev.to/rodydavis/host-your-flutter-project-as-a-rest-api-48mc</guid>
      <description>&lt;p&gt;After you build your flutter project you may want to reuse the models and business logic from your lib folder. I will show you how to go about setting up the project to have iOS, Android, Web, Windows, MacOS, Linux and a REST API interface with one project. The REST API can also be deploy to Google Cloud Run for Dart everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pazlJX39--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://rodydavis.com/img/gifs/mind_blown.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pazlJX39--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://rodydavis.com/img/gifs/mind_blown.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One Codebase for Client and Sever.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This will allow you to expose your Dart models as a REST API and run your business logic from your lib folder while the application runs the models as they are. &lt;a href="https://github.com/AppleEducate/shared_dart"&gt;Here&lt;/a&gt; is the final project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up #
&lt;/h2&gt;

&lt;p&gt;As with any Flutter project I am going to assume that you already have &lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt; installed on your machine and that you can create a project. This is a intermediate level difficulty so read on if you are up to the challenge. You will also need to know the basics of &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one project? #
&lt;/h2&gt;

&lt;p&gt;It may not be obvious but when building complex applications you will at some point have a server and an application that calls that server. &lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt; is an excellent option for doing this and I use it in almost all my projects. &lt;a href="https://firebase.google.com/products/functions/"&gt;Firebase Functions&lt;/a&gt; are really powerful but you are limited by Javascript or Typescript. What if you could use the same packages that you are using in the Flutter project, or better yet what if they both used the same?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1kUO7Axm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1xVIzjzgJnmuWTJoaG3kAQ.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1kUO7Axm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1xVIzjzgJnmuWTJoaG3kAQ.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have a server project and a client project that communicate over a rest api or client sdk like Firebase then you will run into the problem that the server has models of objects stored and the client has models of the objects that are stored. This can lead to a serious mismatch when it changed without you knowing. GraphQL helps a lot with this since you define the model that you recieve. This approach allows your business logic to be always up to date for both the client and server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client Setup #
&lt;/h2&gt;

&lt;p&gt;The first step is to just build your application. The only difference that we will make is keeping the UI and business logic separate. When starting out with Flutter it can be very easy to throw all the logic into the screen and calling setState when the data changes. Even the application when creating a new Flutter project does this. That's why &lt;a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/options"&gt;choosing a state management solution&lt;/a&gt;is so important.&lt;/p&gt;

&lt;p&gt;To make things clean and concise we will make 2 folders in our lib folder.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ui for all Flutter Widgets and Screens&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;src for all business logic, classes, models and utility functions&lt;/p&gt;

&lt;p&gt;This will leave us with main.dart being only the entry point into our client application.&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/material.dart';import 'plugins/desktop/desktop.dart';import 'ui/home/screen.dart';void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.light(), darkTheme: ThemeData.dark(), home: HomeScreen(), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s Start by making a tab bar for the 2 screens. Create a file in the folder ui/home/screen.dart and add 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;import 'package:flutter/material.dart';import '../counter/screen.dart';import '../todo/screen.dart';class HomeScreen extends StatefulWidget { @override _HomeScreenState createState() =&amp;gt; _HomeScreenState();}class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; { int _currentIndex = 0;@override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _currentIndex, children: &amp;lt;Widget&amp;gt;[CounterScreen(), TodosScreen(),], ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: (val) { if (mounted) setState(() { _currentIndex = val; }); }, type: BottomNavigationBarType.fixed, items: [BottomNavigationBarItem( icon: Icon(Icons.add), title: Text('Counter'), ), BottomNavigationBarItem( icon: Icon(Icons.list), title: Text('Todos'), ),], ), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just a basic screen and should look very normal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counter Example #
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3-FbrE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2336/1%2AqFZepZBtk0RhEojjGsI85g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3-FbrE5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2336/1%2AqFZepZBtk0RhEojjGsI85g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now create a file ui/counter/screen.dart and add 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;import 'package:flutter/material.dart';import 'package:shared_dart/src/models/counter.dart';class CounterScreen extends StatefulWidget { @override _CounterScreenState createState() =&amp;gt; _CounterScreenState();}class _CounterScreenState extends State&amp;lt;CounterScreen&amp;gt; { CounterModel _counterModel = CounterModel();void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counterModel.add(); }); }@override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyCounterPage object that was created by // the App.build method, and use it to set our appbar title. title: Text('Counter Screen'), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke "debug painting" (press "p" in the console, choose the // "Toggle Debug Paint" action from the Flutter Inspector in Android // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: &amp;lt;Widget&amp;gt;[Text( 'You have pushed the button this many times:', ), Text( '${_counterModel.count}', style: Theme.of(context).textTheme.display1, ),], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the default counter app you get when you create a Flutter application but with one change, it uses &lt;code&gt;CounterModel&lt;/code&gt; to hold the logic.&lt;/p&gt;

&lt;p&gt;Create the counter model at src/models/counter.dart and add 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;class CounterModel { CounterModel(); int _count = 0; int get count =&amp;gt; _count; void add() =&amp;gt; _count++; void subtract() =&amp;gt; _count--; void set(int val) =&amp;gt; _count = val;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see it is really easy to expose only what we want to while still having complete flexibility. You could use provider here if you choose, or even bloc and/or streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Todo Example #
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---mTF5Z19--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2336/1%2A47x4TDkyWCo8-qMCfYaeng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---mTF5Z19--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2336/1%2A47x4TDkyWCo8-qMCfYaeng.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets create a file at ui/todos/screen.dart and add 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;import 'package:flutter/material.dart';import '../../src/classes/todo.dart';import '../../src/models/todos.dart';class TodosScreen extends StatefulWidget { @override _TodosScreenState createState() =&amp;gt; _TodosScreenState();}class _TodosScreenState extends State&amp;lt;TodosScreen&amp;gt; { final _model = TodosModel(); List&amp;lt;ToDo&amp;gt; _todos;@override void initState() { _model.getList().then((val) { if (mounted) setState(() { _todos = val; }); }); super.initState(); }@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Todos Screen'), ), body: Builder( builder: (_) { if (_todos != null) { return ListView.builder( itemCount: _todos.length, itemBuilder: (context, index) { final _item = _todos[index]; return ListTile( title: Text(_item.title), subtitle: Text(_item.completed ? 'Completed' : 'Pending'), ); }, ); } return Center( child: CircularProgressIndicator(), ); }, ), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see that we have the logic in TodosModel and uses the class ToDo for toJson and fromJson.&lt;/p&gt;

&lt;p&gt;Create a file at the location src/classes/todo.dart and add 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;// To parse this JSON data, do//// final toDo = toDoFromJson(jsonString);import 'dart:convert';List&amp;lt;ToDo&amp;gt; toDoFromJson(String str) =&amp;gt; List&amp;lt;ToDo&amp;gt;.from(json.decode(str).map((x) =&amp;gt; ToDo.fromJson(x)));String toDoToJson(List&amp;lt;ToDo&amp;gt; data) =&amp;gt; json.encode(List&amp;lt;dynamic&amp;gt;.from(data.map((x) =&amp;gt; x.toJson())));class ToDo { int userId; int id; String title; bool completed;ToDo({ this.userId, this.id, this.title, this.completed, });factory ToDo.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt; ToDo( userId: json["userId"], id: json["id"], title: json["title"], completed: json["completed"], );Map&amp;lt;String, dynamic&amp;gt; toJson() =&amp;gt; { "userId": userId, "id": id, "title": title, "completed": completed, };}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and create the model src/models/todo.dart and add 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;import 'dart:convert';import 'package:http/http.dart' as http;import 'package:shared_dart/src/classes/todo.dart' as t;class TodosModel { final kTodosUrl = '[https://jsonplaceholder.typicode.com/todos'](https://jsonplaceholder.typicode.com/todos');Future&amp;lt;List&amp;lt;t.ToDo&amp;gt;&amp;gt; getList() async { final _response = await http.get(kTodosUrl); if (_response != null) { final _todos = t.toDoFromJson(_response.body); if (_todos != null) { return _todos; } } return []; }Future&amp;lt;t.ToDo&amp;gt; getItem(int id) async { final _response = await http.get('$kTodosUrl/$id'); if (_response != null) { final _todo = t.ToDo.fromJson(json.decode(_response.body)); if (_todo != null) { return _todo; } } return null; }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we just get dummy data from a url that emits json and convert them to our classes. This is an example I want to show with networking. There is only one place that fetches the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Project (Web) #
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7gS2kYbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Aet2kG6_skauXJ6rogFy_ZQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7gS2kYbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Aet2kG6_skauXJ6rogFy_ZQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ibS596yu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Aza20ru3G18DUFob07Cjv2A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ibS596yu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Aza20ru3G18DUFob07Cjv2A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see when you run your project on chrome you will get the same application that you got on mobile. Even the networking is working in the web. You can call the model and retrieve the list just like you would expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Setup #
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Now time for the magic..&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the root of the project folder create a file Dockerfile and add 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;# Use Google's official Dart image.# [https://hub.docker.com/r/google/dart-runtime/](https://hub.docker.com/r/google/dart-runtime/)FROM google/dart-runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create another file at the root called service.yaml and add 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;apiVersion: serving.knative.dev/v1 kind: Service metadata: name: PROJECT_NAME namespace: default spec: template: spec: containers: - image: docker.io/YOUR_DOCKER_NAME/PROJECT_NAME env: - name: TARGET value: "PROJECT_NAME v1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace PROJECT_NAME with your project name, mine is shared-dart for this example.&lt;/p&gt;

&lt;p&gt;You will also need to replace YOUR_DOCKER_NAME with your docker username so the container can be deployed correctly.&lt;/p&gt;

&lt;p&gt;Update your pubspec.yaml with 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;name: shared_dartdescription: A new Flutter project.publish_to: noneversion: 1.0.0+1environment: sdk: "&amp;gt;=2.1.0 &amp;lt;3.0.0"dependencies: flutter: sdk: flutter shelf: ^0.7.3 cupertino_icons: ^0.1.2 http: ^0.12.0+2dev_dependencies: flutter_test: sdk: flutterflutter: uses-material-design: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important package here is shelf as it allows us to run a http server with dart.&lt;/p&gt;

&lt;p&gt;Create a folder in the root of the project called bin then add a file server.dart and replace it with 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;import 'dart:io';import 'package:shelf/shelf.dart' as shelf;import 'package:shelf/shelf_io.dart' as io;import 'src/routing.dart';void main() { final handler = const shelf.Pipeline() .addMiddleware(shelf.logRequests()) .addHandler(RouteUtils.handler);final port = int.tryParse(Platform.environment['PORT'] ?? '8080'); final address = InternetAddress.anyIPv4;io.serve(handler, address, port).then((server) { server.autoCompress = true; print('Serving at [http://${server.address.host}:${server.port}'](http://${server.address.host}:${server.port}')); });}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will tell the container what port to listen for and how to handle the requests.&lt;/p&gt;

&lt;p&gt;Create a folder src in the bin folder and add a file routing.dart and replace the contents with 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;import 'dart:async';import 'package:shelf/shelf.dart' as shelf;import 'controllers/index.dart';import 'result.dart';class RouteUtils { static FutureOr&amp;lt;shelf.Response&amp;gt; handler(shelf.Request request) { var component = request.url.pathSegments.first; var handler = _handlers(request)[component]; if (handler == null) return shelf.Response.notFound(null); return handler; }static Map&amp;lt;String, FutureOr&amp;lt;shelf.Response&amp;gt;&amp;gt; _handlers( shelf.Request request) { return { 'info': ServerResponse('Info', body: { "version": 'v1.0.0', "status": "ok", }).ok(), 'counter': CounterController().result(request), 'todos': TodoController().result(request), }; }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is still nothing imported from our main project but you will start to see some similarities. Here we specify controllers for todos and counter url paths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'counter': CounterController().result(request),'todos': TodoController().result(request),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that means any url with the following:&lt;a href="https://mydomain.com/todos"&gt;https://mydomain.com/todos&lt;/a&gt; , &lt;a href="https://mydomain.com/todos"&gt;https://mydomain.com/todos&lt;/a&gt;/1&lt;/p&gt;

&lt;p&gt;will get routed to the TodoController to handle the request.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is also the first time I found out about FutureOr. It allows you to return a sync or async function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And important part about build a REST API is having a consistent response body, so here we can create a wrapper that adds fields we always want to return, like the status of the call, a message and the body.&lt;/p&gt;

&lt;p&gt;Create a file at src/result.dart and add 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;import 'dart:convert';import 'package:shelf/shelf.dart' as shelf;class ServerResponse { final String message; final dynamic body; final StatusType type;ServerResponse( this.message, { this.type = StatusType.success, this.body, });Map&amp;lt;String, dynamic&amp;gt; toJson() { return { "status": type.toString().replaceAll('StatusType.', ''), "message": message, "body": body ?? '', }; }String toJsonString() { return json.encode(toJson()); }shelf.Response ok() { return shelf.Response.ok( toJsonString(), headers: { 'Content-Type': 'application/json', }, ); }}enum StatusType { success, error }abstract class ResponseImpl { Future&amp;lt;shelf.Response&amp;gt; result(shelf.Request request);}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will always return json and the fields that we want to show. You could also include your paging meta data here.&lt;/p&gt;

&lt;p&gt;Create a file in at the location src/controllers/counter.dart and add 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;import 'package:shared_dart/src/models/counter.dart';import 'package:shelf/shelf.dart' as shelf;import '../result.dart';class CounterController implements ResponseImpl { const CounterController();@override Future&amp;lt;shelf.Response&amp;gt; result(shelf.Request request) async { final _model = CounterModel(); final _params = request.url.queryParameters; if (_params != null) { final _val = int.tryParse(_params['count'] ?? '0'); _model.set(_val); } else { _model.add(); } return ServerResponse('Info', body: { "counter": _model.count, }).ok(); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see the import to the lib folder of the root project. Since it shares the pubspec.yaml all the packages can be shared. You can import the CounterModel that we created earlier.&lt;/p&gt;

&lt;p&gt;Create a file in at the location src/controllers/todos.dart and add 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;import 'package:shared_dart/src/models/todos.dart';import 'package:shelf/src/request.dart';import 'package:shelf/src/response.dart';import '../result.dart';class TodoController implements ResponseImpl { @override Future&amp;lt;Response&amp;gt; result(Request request) async { final _model = TodosModel(); if (request.url.pathSegments.length &amp;gt; 1) { final _id = int.tryParse(request.url.pathSegments[1] ?? '1'); final _todo = await _model.getItem(_id); return ServerResponse('Todo Item', body: _todo).ok(); } final _todos = await _model.getList(); return ServerResponse( 'List Todos', body: _todos.map((t) =&amp;gt; t.toJson()).toList(), ).ok(); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like before we are importing the TodosModel model from the lib folder.&lt;/p&gt;

&lt;p&gt;For convenience add a file at the location src/controllers/index.dart and add 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;export 'counter.dart';export 'todo.dart';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make it easier to import all the controllers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the Project (Server) #
&lt;/h2&gt;

&lt;p&gt;If you are using &lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt; then you will need to update your launch.json with 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;{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: [https://go.microsoft.com/fwlink/?linkid=830387](https://go.microsoft.com/fwlink/?linkid=830387) "version": "0.2.0", "configurations": [{ "name": "Client", "request": "launch", "type": "dart", "program": "lib/main.dart" }, { "name": "Server", "request": "launch", "type": "dart", "program": "bin/server.dart" }]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you hit run with Server selected you will see the output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EjUO5pCM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AakfcrkLuxit4vZdtHJty_Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EjUO5pCM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AakfcrkLuxit4vZdtHJty_Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can navigate to this in a browser but you can also work with this in &lt;a href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vh-za-u1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4796/1%2AzJR2ZNZfCmvLg3y5wByLNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vh-za-u1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4796/1%2AzJR2ZNZfCmvLg3y5wByLNA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NupS4RxI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4788/1%2AZ2vnHjHEfYe8yWajGWVLOw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NupS4RxI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4788/1%2AZ2vnHjHEfYe8yWajGWVLOw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just by adding to the url todos and todos/1 it will return different responses.&lt;/p&gt;

&lt;p&gt;For the counter model we can use query parameters too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FEZu-W95--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4768/1%2AYMwQEOoaCjngYpidKBbADg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FEZu-W95--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4768/1%2AYMwQEOoaCjngYpidKBbADg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HvI89kN5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4760/1%2AvPYs9780bcVrlIPBMjF4eQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HvI89kN5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4760/1%2AvPYs9780bcVrlIPBMjF4eQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just by adding ?count=22 it will update the model with the input.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind this is running your Dart code from you lib folder in your Flutter project without needing the Flutter widgets!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a side benefit we can also run this project on Desktop. Check out the final project for the desktop folders needed from &lt;a href="https://github.com/google/flutter-desktop-embedding"&gt;Flutter Desktop Embedding&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bEANwzo9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3648/1%2AMk_6Rlq2qMbpk79OPo_QBw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bEANwzo9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3648/1%2AMk_6Rlq2qMbpk79OPo_QBw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kQ9XIulK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3648/1%2AhWirIWGRQjN8hVvUZsB2Nw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kQ9XIulK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3648/1%2AhWirIWGRQjN8hVvUZsB2Nw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Now if you wanted to deploy the container to Cloud Run you could with the following command:&lt;/p&gt;

&lt;p&gt;gcloud builds submit — tag &lt;a href="http://gcr.io/YOUR_GOOGLE_PROJECT_ID/PROJECT_NAME"&gt;gcr.io/YOUR_GOOGLE_PROJECT_ID/PROJECT_NAME&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Replace PROJECT_NAME with your project name, mine is shared-dart for this example.&lt;/p&gt;

&lt;p&gt;You will also need to replace YOUR_GOOGLE_PROJECT_ID with your Google Cloud Project ID. You can create one &lt;a href="https://cloud.google.com/cloud-build/docs/quickstart-docker"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Again the final project source code is &lt;a href="https://github.com/AppleEducate/shared_dart"&gt;here&lt;/a&gt;. Let me know your thoughts!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6NrdSDcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fhost-flutter-rest-api%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHost%2520your%2520Flutter%2520Project%2520as%2520a%2520REST%2520API%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6NrdSDcV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fhost-flutter-rest-api%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHost%2520your%2520Flutter%2520Project%2520as%2520a%2520REST%2520API%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to build a native cross platform project with Flutter</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Fri, 06 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/how-to-build-a-native-cross-platform-project-with-flutter-ojg</link>
      <guid>https://dev.to/rodydavis/how-to-build-a-native-cross-platform-project-with-flutter-ojg</guid>
      <description>&lt;p&gt;Import dart:html and dart:io in the same project!&lt;/p&gt;

&lt;p&gt;You can find the final project &lt;a href="https://github.com/AppleEducate/flutter_x/tree/finish"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Up to now you have been able to create projects with Flutter that run on iOS/Android, Web and Desktop but only sharing pure dart plugins. Flutter launched Flutter for web at Google I/O and was a temporary fork that required you to change imports from import 'package:flutter/material.dart'; to import 'package:flutter_web/material.dart';. As you can image this was really difficult for a code base as you had to create a fork and change the imports. This also meant that you could not import any package that needed on a path or depended on flutter. The time as come and the merge is complete. Now you no longer need to change the imports!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wtuZE4ex--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AepSHkw0msNuaisyHy9yYAA.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wtuZE4ex--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AepSHkw0msNuaisyHy9yYAA.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use any plugin now, have a debugger, create new flutter projects with the web folder added, web plugins, and so much more..&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer #
&lt;/h2&gt;

&lt;p&gt;You will need to be on the latest flutter for this to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flutter.io/get-started/install/"&gt;Download Flutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NfOLlPM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1PVcOkDgx2p_G3Bou3IFsg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NfOLlPM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A1PVcOkDgx2p_G3Bou3IFsg.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are pretty new to Flutter you can check out &lt;a href="https://flutter.io/get-started/codelab/"&gt;this useful guide&lt;/a&gt; on how to create a new project step by step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4RrI85fC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad6qN8hoGMwldMtIsQYIqrg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4RrI85fC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ad6qN8hoGMwldMtIsQYIqrg.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new project named flutter_x and it should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bykuwGZh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2628/1%2Axe6ubLj5psVH4JQd-VqSAQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bykuwGZh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2628/1%2Axe6ubLj5psVH4JQd-VqSAQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also down the starter project &lt;a href="https://github.com/AppleEducate/flutter_x/tree/starter"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your code should look like this:&lt;/p&gt;

&lt;p&gt;Just to make sure everything is working go ahead and run the project on iOS/Android.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dJuE38kM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5464/1%2AteDs_OqHkRdhwyuzyb2rVA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dJuE38kM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5464/1%2AteDs_OqHkRdhwyuzyb2rVA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should have the counter application running and working correctly. Now quit and run on Chrome. It should be listed as a device. You can also run from the command line flutter run -d chrome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UdwYok9n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2ACzZ28crDYxBEMaNVq9CtvQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UdwYok9n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2ACzZ28crDYxBEMaNVq9CtvQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You do not get hot reload yet on web so be aware of that.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;This is optional but I use this structure in all my apps&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7MSqTop4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Azr22DdSlEXlluwcki-ahEQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7MSqTop4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Azr22DdSlEXlluwcki-ahEQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your project should now look like this.&lt;/p&gt;

&lt;p&gt;Open your pubspec.yaml and import the following packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies: universal_html: url_launcher:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You can also remove the comments generated in the pubspec.yaml&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your pubspec.yaml will now read 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;name: flutter_xdescription: A new Flutter project.version: 1.0.0+1environment: sdk: "&amp;gt;=2.1.0 &amp;lt;3.0.0"dependencies: flutter: sdk: fluttercupertino_icons: ^0.1.2 universal_html: ^1.1.0 url_launcher: ^5.1.2dev_dependencies: flutter_test: sdk: flutterflutter:uses-material-design: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default if you were to check if the device was mobile or web you will get an error at compile time when trying to import a plugin that is not meant for the platform. To get around this we will use dynamic imports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6dd8Zgzv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AYCLt5ItFmyOmrd-CKgOQFQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6dd8Zgzv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AYCLt5ItFmyOmrd-CKgOQFQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a url_launcher folder and file url_launcher.dart, mobile.dart, web.dart, unsupported.dart inside the plugins folder.&lt;/p&gt;

&lt;p&gt;In the file url_launcher.dart add 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;export 'unsupported.dart' if (dart.library.html) 'web.dart' if (dart.library.io) 'mobile.dart';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will pick the correct file at runtime and give a fallback if it is not supported.&lt;/p&gt;

&lt;p&gt;To protect against edge cases you will need to set up a fallback for the import. In unsupported.dart add 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;class UrlUtils { UrlUtils._();static void open(String url, {String name}) { throw 'Platform Not Supported'; }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The class UrlUtils and the public methods have to match all three files for this to work correctly. Always set up the unsupported first then copy the file into mobile.dart and web.dart to ensure no typos.&lt;/p&gt;

&lt;p&gt;You should now have 3 files with the above code in each class.&lt;/p&gt;

&lt;p&gt;In mobile.dart add 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;import 'package:url_launcher/url_launcher.dart';class UrlUtils { UrlUtils._();static void open(String url, {String name}) async { if (await canLaunch(url)) { await launch(url); } }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open the link in safari view controller or android’s default browser respectively.&lt;/p&gt;

&lt;p&gt;In web.dart add 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;import 'package:universal_html/prefer_universal/html.dart' as html;class UrlUtils { UrlUtils._();static void open(String url, {String name}) { html.window.open(url, name); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open up a new window in the browser with the specified link.&lt;/p&gt;

&lt;p&gt;Add a button to the center of the screen. The ui/home/screen.dart should read 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;import 'package:flutter/material.dart';class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key);final String title;@override _MyHomePageState createState() =&amp;gt; _MyHomePageState();}class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: RaisedButton( child: Text('Open Flutter.dev'), onPressed: () {}, )), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the onPressed to 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;onPressed: () { try { UrlUtils.open('[https://flutter.dev'](https://flutter.dev')); } catch (e) { print('Error -&amp;gt; $e'); }},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you go to import the UrlUtils it is important to import the correct URI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mTeuSiGU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2140/1%2A7OyxZG6557DYE1XiBscCtA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mTeuSiGU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2140/1%2A7OyxZG6557DYE1XiBscCtA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to import import 'package:flutter_x/plugins/url_launcher/url_launcher.dart'; only.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You can use the relative import if you wish.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You UI code will now read 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;import 'package:flutter/material.dart';import '../../plugins/url_launcher/url_launcher.dart';class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key);final String title;@override _MyHomePageState createState() =&amp;gt; _MyHomePageState();}class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: RaisedButton( child: Text('Open Flutter.dev'), onPressed: () { try { UrlUtils.open('[https://flutter.dev'](https://flutter.dev')); } catch (e) { print('Error -&amp;gt; $e'); } }, )), ); }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app on the web should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lcw8gJJR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2A6ni24NpLIULqi_Cd5NHh3Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lcw8gJJR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2A6ni24NpLIULqi_Cd5NHh3Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when you tap the button..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t9pxVFaO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Atgcfnrgu9O4joiPeKFmqKw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t9pxVFaO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5248/1%2Atgcfnrgu9O4joiPeKFmqKw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when you run it on iOS/Android it should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UWfccR9k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5036/1%2AQEFw3xDevKMsjke4Dd06VA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UWfccR9k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5036/1%2AQEFw3xDevKMsjke4Dd06VA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when you tap the button..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---5WKmcwR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5036/1%2AlCvTvAvu0nI_dhgUUFHkWA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---5WKmcwR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/5036/1%2AlCvTvAvu0nI_dhgUUFHkWA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! You made it :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--opTo_bpc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AKowWujusMfDjlkjeeOFEBg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--opTo_bpc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AKowWujusMfDjlkjeeOFEBg.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the final project located &lt;a href="https://github.com/AppleEducate/flutter_x/tree/finish"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please reach out if you have any questions!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://gist.github.com/a2c38b2020d09e718c8d894d048e9c7e"&gt;http://github.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ti2tD55V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fnative-cross-platform-flutter%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHow%2520to%2520build%2520a%2520native%2520cross%2520platform%2520project%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ti2tD55V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fnative-cross-platform-flutter%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DHow%2520to%2520build%2520a%2520native%2520cross%2520platform%2520project%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating Your First Flutter Project</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Sun, 28 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/creating-your-first-flutter-project-3a4p</link>
      <guid>https://dev.to/rodydavis/creating-your-first-flutter-project-3a4p</guid>
      <description>&lt;p&gt;Flutter is a UI Toolkit from Google allowing you to create expressive and unique experiences unmatched on any platform. You can write your UI once and run it everywhere. Yes everywhere! Web, iOS, Android, Windows, Linux, MacOS, Raspberry PI and much more…&lt;/p&gt;

&lt;p&gt;If you prefer a video you can follow the YouTube series I am doing called “Flutter Take 5” where I explore topics that you encounter when building a Flutter application. I will also give you tips and tricks as I go through the series.&lt;/p&gt;


&lt;center&gt;&lt;/center&gt;
&lt;h2&gt;
  
  
  What is Flutter #
&lt;/h2&gt;

&lt;p&gt;Flutter recently crossed React Native on Github and now has more than 2 million developers using Flutter to create applications. There are more than 50,000 apps on Google Play alone published with Flutter.&lt;br&gt;&lt;br&gt;
&lt;a href="https://flutter.dev/"&gt; &lt;strong&gt;Flutter - Beautiful native apps in record time&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
*Flutter is Google's UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from…*flutter.dev&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started #
&lt;/h2&gt;

&lt;p&gt;Getting started is very easy once you get the SDK installed. After it is installed creating new applications, plugins and packages is lighting fast. Follow this guide to install Flutter:&lt;br&gt;&lt;br&gt;
&lt;a href="https://flutter.dev/docs/get-started/install"&gt; &lt;strong&gt;Install&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
*How to set up your code editor.*flutter.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One nice thing about Flutter is that it is developed in the open as an open source project that anyone can contribute to. If there is something missing you can easily fork the repo and make a PR for the missing functionality.&lt;/p&gt;


&lt;h2&gt;
  
  
  Create the Project #
&lt;/h2&gt;

&lt;p&gt;Now that you have Flutter installed it is time to create your first (Of Many 😉) Flutter project! Open up your terminal and navigate to wherever you want the application folder to be created. Once you “cd” into the directory you can type 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;$ flutter create my_awesome_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can replace “my_awesome_project” with whatever you want the project to be called. It is important to use snake_case as it is the valid syntax for project names in dart.&lt;/p&gt;

&lt;p&gt;Congratulations you just created your first project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Open the Project #
&lt;/h2&gt;

&lt;p&gt;So you may be wondering what we just created so let us dive in to the details. You can open up you project in VSCode if you have it installed by typing the following into terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd my_awesome_project &amp;amp;&amp;amp; code .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can open up the folder in your favorite IDE if you prefer. Two important files to notice are the pubspec.yaml and lib/main.dart&lt;/p&gt;

&lt;p&gt;Your UI and Logic is located at “lib/main.dart” and you should see the following:&lt;/p&gt;

&lt;p&gt;You can define any dependencies and plugins needed for the application at “pubspec.yaml” and you should see the following:&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Project #
&lt;/h2&gt;

&lt;p&gt;Running the application is very easy too. While there are buttons in all the IDEs you can also run your project from the command line for quick testing. You can also configure &lt;a href="https://flutter.dev/desktop"&gt;Flutter for Desktop&lt;/a&gt; and no need to wait for an emulator to warm up. Open your project and enter the following into terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ flutter run -d macos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the “-d macos” as you can customize what device you want to run on. You should see the following in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Building macOS application... Syncing files to device macOS... 141msFlutter run key commands.r Hot reload. 🔥🔥🔥R Hot restart.h Repeat this help message.d Detach (terminate "flutter run" but leave application running).c Clear the screenq Quit (terminate the application on the device).An Observatory debugger and profiler on macOS is available at: [http://127.0.0.1:58932/f1Mspofty_k=/](http://127.0.0.1:58932/f1Mspofty_k=/)Application finished.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also run multiple devices at the same time. You can find more info on the &lt;a href="https://github.com/flutter/flutter/wiki/Multi-device-debugging-in-VS-Code"&gt;Flutter Octopus here&lt;/a&gt;. If everything went well you should see the following application launch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sI0WqvTT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3560/1%2AO3W-7Ge-qfMk7i4CgqN-oQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sI0WqvTT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3560/1%2AO3W-7Ge-qfMk7i4CgqN-oQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a pretty basic application at this point but it is important to show how easy it is to change the state in the application. You can rebuild the UI just by calling “setState()”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Project #
&lt;/h2&gt;

&lt;p&gt;Testing is one of the reasons I love Flutter so much and it is dead simple to run and write tests for the project. If you look at the file “test/widget_test.dart” you should see the following:&lt;/p&gt;

&lt;p&gt;You can run these tests very easily. Open your project and type the following into the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ flutter test00:07 +1: All tests passed!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that all your tests will run and you can catch any bugs you missed.&lt;/p&gt;

&lt;p&gt;You can also generate code coverage for your applications easily by typing 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;$ flutter test --coverage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a new file at “coverage/lcov.info” and will read the following:&lt;/p&gt;

&lt;p&gt;You can now easily create badges and graphs with the LCOV data. Here is a package that will make that easier:&lt;br&gt;&lt;br&gt;
&lt;a href="https://pub.dev/packages/test_coverage"&gt; &lt;strong&gt;test_coverage | Dart Package&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
*Command line utility to run tests in Dart VM and collect coverage data.*pub.dev&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Flutter makes it possible to build applications very quickly that do not depend on web or mobile technologies. It can familiar to writing a game as you have to design all your own UI. You can find the final source code here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/rodydavis/flutter_take_5/tree/master/01_your_first_project"&gt; &lt;strong&gt;rodydavis/flutter_take_5&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
*A new Flutter project. This project is a starting point for a Flutter application. A few resources to get you started…*github.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also find the Flutter source code here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/flutter/flutter"&gt; &lt;strong&gt;flutter/flutter&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
_Flutter makes it easy and fast to build beautiful apps for mobile and beyond. - flutter/flutter_github.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dNhedDVM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Ffirst-flutter-project%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DCreating%2520Your%2520First%2520Flutter%2520Project%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dNhedDVM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Ffirst-flutter-project%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DCreating%2520Your%2520First%2520Flutter%2520Project%26tid%3DG-JQNPVBL9DR" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automate Flutter App Releases</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Fri, 15 Mar 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/automate-flutter-app-releases-4lk6</link>
      <guid>https://dev.to/rodydavis/automate-flutter-app-releases-4lk6</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; You can find the script &lt;a href="https://gist.github.com/rodydavis/774b36e32d7efa882cca8dd16da6e74c"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Make your life easier and automate your builds to beta and production!&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fastlane.tools/"&gt;Fastlane&lt;/a&gt; setup in each directory&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pub.dartlang.org/packages/build_runner"&gt;build_runner&lt;/a&gt; as a dependency&lt;/li&gt;
&lt;li&gt;Git Project in VCS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Initial Setup #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Download &lt;a href="https://gist.github.com/rodydavis/774b36e32d7efa882cca8dd16da6e74c"&gt;this file&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Put it at the root level of your flutter project&lt;/li&gt;
&lt;li&gt;Open the terminal and navigate to your project location&lt;/li&gt;
&lt;li&gt;Enter this command: chmod +x &lt;a href="http://release.sh/"&gt;release.sh&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage #
&lt;/h2&gt;

&lt;p&gt;Now you can call this script!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For beta: &lt;code&gt;./release.sh beta&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For production: &lt;code&gt;./release.sh release&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Bump the version numbers if you are using the version in the pubspec.yaml&lt;/li&gt;
&lt;li&gt;Release the apps with Fastlane&lt;/li&gt;
&lt;li&gt;Format all Dart Files&lt;/li&gt;
&lt;li&gt;Clean Project&lt;/li&gt;
&lt;li&gt;Rebuild classes&lt;/li&gt;
&lt;li&gt;Add commit messages&lt;/li&gt;
&lt;li&gt;Updates Cocoa Pods
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qBhKqBOr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://rodydavis.com/.netlify/functions/ga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fautomate-flutter-apps%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DAutomate%2520Flutter%2520App%2520Releases%26tid%3DG-JQNPVBL9DR" alt=""&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Building A Piano with Flutter</title>
      <dc:creator>Rody Davis</dc:creator>
      <pubDate>Tue, 12 Mar 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/rodydavis/building-a-piano-with-flutter-192g</link>
      <guid>https://dev.to/rodydavis/building-a-piano-with-flutter-192g</guid>
      <description>&lt;h2&gt;
  
  
  TLDR #
&lt;/h2&gt;

&lt;p&gt;You can find the final source code &lt;a href="https://github.com/rodydavis/flutter_piano/tree/5k" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This piano uses only &lt;code&gt;5032&lt;/code&gt; bytes of Dart Code!&lt;/p&gt;

&lt;p&gt;Winner of the &lt;a href="https://flutter.dev/create" rel="noopener noreferrer"&gt;Flutter Create Contest&lt;/a&gt; and you can see the certificate &lt;a href="https://www.credential.net/exbvca0q?key=8be94f32ad2f56882045e013e960fa888afa4edd52edb963c48df351c7d1e443" rel="noopener noreferrer"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will learn #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Working with Dark Mode&lt;/li&gt;
&lt;li&gt;Forcing app to be in landscape&lt;/li&gt;
&lt;li&gt;Working with custom files bundled with the app&lt;/li&gt;
&lt;li&gt;Working with midi and sounds in flutter&lt;/li&gt;
&lt;li&gt;Working with &lt;code&gt;StatefulWidget&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;SafeArea&lt;/code&gt; and &lt;code&gt;Semantics&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Building an app with minimal code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you need #
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://flutter.dev/docs/get-started/install" rel="noopener noreferrer"&gt;Flutter SDK&lt;/a&gt; Installed (&lt;a href="https://flutter.dev/docs/get-started/codelab" rel="noopener noreferrer"&gt;More Info&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.sf2&lt;/code&gt; SoundFont File like &lt;a href="https://github.com/rodydavis/flutter_piano/blob/5k/assets/sounds/Piano.sf2" rel="noopener noreferrer"&gt;this one&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Physical iOS device (iOS Simulator does not work with this plugin for playing the sounds) or Android Emulator/Device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up #
&lt;/h2&gt;

&lt;p&gt;You can either create a new project with Android Studio or VSCode using the GUI or navigate to the location you want your project and using this command in the terminal: &lt;code&gt;lutter create -i swift -a kotlin flutter_piano&lt;/code&gt;. Make sure to include Swift and Kotlin Support!&lt;/p&gt;

&lt;p&gt;Now that you have your project created it should look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fstarter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fstarter.png" alt="app-icon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start by adding some dependencies to our `pubspec.yaml'&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
dependencies: flutter: sdk: flutter tonic: ^0.2.3 flutter_midi: ^0.1.1+3 cupertino_icons: ^0.1.2&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and add the .sf2 file&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 assets: - assets/sounds/Piano.sf2&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you haven't already create a new folder at the top of your project call &lt;code&gt;assets&lt;/code&gt; and a subfolder called &lt;code&gt;sounds&lt;/code&gt; and place the .sf2 file there and make sure it is named &lt;code&gt;Piano.sf2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Because our app will only work in landscape we need to update those settings as well.&lt;/p&gt;

&lt;p&gt;navigate to the &lt;code&gt;/android/app/src/main/AndroidManifest.xml&lt;/code&gt; and add this line inside &lt;code&gt;&amp;lt;activity&lt;/code&gt; in the &lt;code&gt;&amp;lt;application&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
android:screenOrientation="landscape"&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&amp;lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.rodydavis.flutter_piano"&amp;gt; &amp;lt;!-- io.flutter.app.FlutterApplication is an android.app.Application that calls FlutterMain.startInitialization(this); in its onCreate method. In most cases you can leave this as-is, but you if you want to provide additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --&amp;gt; &amp;lt;application android:name="io.flutter.app.FlutterApplication" android:label="flutter_piano" android:icon="@mipmap/ic_launcher"&amp;gt; &amp;lt;activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:screenOrientation="landscape" android:windowSoftInputMode="adjustResize"&amp;gt; &amp;lt;!-- This keeps the window background of the activity showing until Flutter renders its first frame. It can be removed if there is no splash screen (such as the default splash screen defined in @style/LaunchTheme). --&amp;gt; &amp;lt;meta-data android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" android:value="true" /&amp;gt; &amp;lt;intent-filter&amp;gt; &amp;lt;action android:name="android.intent.action.MAIN"/&amp;gt; &amp;lt;category android:name="android.intent.category.LAUNCHER"/&amp;gt; &amp;lt;/intent-filter&amp;gt; &amp;lt;/activity&amp;gt; &amp;lt;/application&amp;gt;&amp;lt;/manifest&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;navigate to &lt;code&gt;/ios/Runner/info.plist&lt;/code&gt; and change:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
UISupportedInterfaceOrientations&lt;br&gt;
&lt;br&gt;
    UIInterfaceOrientationLandscapeLeft&lt;br&gt;
    UIInterfaceOrientationLandscapeRight&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now we can start with the UI! When you run the application now it should start in landscape!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Flandscape.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Flandscape.png" alt="landscape"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 #
&lt;/h2&gt;

&lt;p&gt;To make it eaiser to read lets remove the comments. Use "find and replace" and search for &lt;code&gt;\/\/.*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fcomments.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fcomments.png" alt="comments"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;choose the "select all occurrances" button and hit &lt;code&gt;backspace&lt;/code&gt; to delete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fselectall.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fselectall.png" alt="select all"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit save and you should see the code format for you.&lt;/p&gt;

&lt;p&gt;The 'main.dart' file should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); }}class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() =&amp;gt; _MyHomePageState();}class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: &amp;lt;Widget&amp;gt;[Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ),], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 #
&lt;/h2&gt;

&lt;p&gt;Delete the &lt;code&gt;MyHomePage&lt;/code&gt; widget so you are left with this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should get an error and thats ok, we will fix that next.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;MyHomePage(title: 'Flutter Demo Home Page')&lt;/code&gt; with a &lt;code&gt;Scaffold()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold(), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 #
&lt;/h2&gt;

&lt;p&gt;Change &lt;code&gt;MyApp&lt;/code&gt; to a &lt;code&gt;StatefulWidget&lt;/code&gt;. You can do this quickly by selecting &lt;code&gt;MyApp&lt;/code&gt; and choose "Convert to StatefulWidget" with the helper.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fconvert.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fconvert.png" alt="convert"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold(), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4 #
&lt;/h1&gt;

&lt;p&gt;Change the theme to dark. You can do this by setting the &lt;code&gt;ThemeData&lt;/code&gt; in &lt;code&gt;MaterialApp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;change:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
theme: ThemeData( primarySwatch: Colors.blue, ),&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to this&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
theme: ThemeData.dark(),&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add and &lt;code&gt;AppBar&lt;/code&gt; to the &lt;code&gt;Scaffold&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;appBar: AppBar(title: Text("Flutter Piano")),&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), ), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now build and run your app, it should look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fdarkmode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fdarkmode.png" alt="dark-mode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 #
&lt;/h2&gt;

&lt;p&gt;We need to add some imports to the top:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you get an error make sure they are added in the &lt;code&gt;pubspec.yaml&lt;/code&gt; from earlier, then restart the app. Be sure to run &lt;code&gt;flutter packages get&lt;/code&gt; everytime you add a dependency.&lt;/p&gt;

&lt;p&gt;Now we can add out &lt;code&gt;initState()&lt;/code&gt; to our app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); }&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the app and make sure you do not get any errors. If you are running this on the iOS Simulator you will get the following error:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Could Not Load Midi on this Device. (Cannot run on simulator), have you included the sound font?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It is ok for developing the UI but once we start with the midi you will need to plug in a real device.&lt;/p&gt;

&lt;p&gt;Your code so far should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), ), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 #
&lt;/h2&gt;

&lt;p&gt;To make Flutter development faster we start with containers and colors so we can make sure everything is the right size.&lt;/p&gt;

&lt;p&gt;Lets start by adding a &lt;code&gt;Drawer&lt;/code&gt; with a &lt;code&gt;ListView&lt;/code&gt; to our &lt;code&gt;Scaffold&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), drawer: Drawer(child: SafeArea(child: ListView(children: &amp;lt;Widget&amp;gt;[]))), ),&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should now get a menu icon that when you press looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fmenu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fmenu.png" alt="menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now lets add a ListView that scrolls Horizontially to the body of the &lt;code&gt;Scaffold&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 body: ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { return Container(); }, )&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We need 7 &lt;code&gt;itemCount&lt;/code&gt; for 7 octaves on the Piano.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), drawer: Drawer(child: SafeArea(child: ListView(children: &amp;lt;Widget&amp;gt;[]))), body: ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { return Container(); }, )), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7 #
&lt;/h2&gt;

&lt;p&gt;Now we need to build the octave section that will be repeated. Since every octave is identical we can repeat the octaves with minor adjustments.&lt;/p&gt;

&lt;p&gt;Lets add some parameters for use to define for our UI. Add these underneath the initState function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
double get keyWidth =&amp;gt; 80 + (80 * _widthRatio);double _widthRatio = 0.0;bool _showLabels = true;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will use these to dynamily update the keys.&lt;/p&gt;

&lt;p&gt;Under the &lt;code&gt;itemBuilder&lt;/code&gt; lets define which octave we are working with by adding:&lt;br&gt;&lt;br&gt;
&lt;code&gt;final int i = index * 12;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Our code should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); } double get keyWidth =&amp;gt; 80 + (80 * _widthRatio); double _widthRatio = 0.0; bool _showLabels = true; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), drawer: Drawer(child: SafeArea(child: ListView(children: &amp;lt;Widget&amp;gt;[]))), body: ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { final int i = index * 12; return Container(); }, )), ); }}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8 #
&lt;/h2&gt;

&lt;p&gt;Now we need to add a &lt;code&gt;Stack&lt;/code&gt; for our octave:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 return SafeArea( child: Stack(children: &amp;lt;Widget&amp;gt;[Row(mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[ _buildKey(24 + i, false), _buildKey(26 + i, false), _buildKey(28 + i, false), _buildKey(29 + i, false), _buildKey(31 + i, false), _buildKey(33 + i, false), _buildKey(35 + i, false),]), Positioned( left: 0.0, right: 0.0, bottom: 100, top: 0.0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[Container(width: keyWidth * .5), _buildKey(25 + i, true), _buildKey(27 + i, true), Container(width: keyWidth), _buildKey(30 + i, true), _buildKey(32 + i, true), _buildKey(34 + i, true), Container(width: keyWidth * .5),])), ]), );&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here we have defined which midi notes are played for each octave.&lt;/p&gt;

&lt;p&gt;Now add the function &lt;code&gt;_buildKey&lt;/code&gt; underneath our &lt;code&gt;build&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 Widget _buildKey(int midi, bool accidental) { if (accidental) { return Container( width: keyWidth, color: Colors.black, margin: EdgeInsets.symmetric(horizontal: 2.0), padding: EdgeInsets.symmetric(horizontal: keyWidth * .1), child: Material( elevation: 6.0, borderRadius: borderRadius, shadowColor: Color(0x802196F3), )); } return Container( width: keyWidth, color: Colors.white, margin: EdgeInsets.symmetric(horizontal: 2.0)); }&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also add &lt;code&gt;borderRadius&lt;/code&gt; to the bottom of &lt;code&gt;main.dart&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
const BorderRadiusGeometry borderRadius = BorderRadius.only( bottomLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0));&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your app should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fkeys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fkeys.png" alt="keys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your code should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); } double get keyWidth =&amp;gt; 80 + (80 * _widthRatio); double _widthRatio = 0.0; bool _showLabels = true; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), drawer: Drawer(child: SafeArea(child: ListView(children: &amp;lt;Widget&amp;gt;[]))), body: ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { final int i = index * 12; return SafeArea( child: Stack(children: &amp;lt;Widget&amp;gt;[Row(mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[ _buildKey(24 + i, false), _buildKey(26 + i, false), _buildKey(28 + i, false), _buildKey(29 + i, false), _buildKey(31 + i, false), _buildKey(33 + i, false), _buildKey(35 + i, false),]), Positioned( left: 0.0, right: 0.0, bottom: 100, top: 0.0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[Container(width: keyWidth * .5), _buildKey(25 + i, true), _buildKey(27 + i, true), Container(width: keyWidth), _buildKey(30 + i, true), _buildKey(32 + i, true), _buildKey(34 + i, true), Container(width: keyWidth * .5),])), ]), ); }, )), ); } Widget _buildKey(int midi, bool accidental) { if (accidental) { return Container( width: keyWidth, color: Colors.black, margin: EdgeInsets.symmetric(horizontal: 2.0), padding: EdgeInsets.symmetric(horizontal: keyWidth * .1), child: Material( elevation: 6.0, borderRadius: borderRadius, shadowColor: Color(0x802196F3), )); } return Container( width: keyWidth, color: Colors.white, margin: EdgeInsets.symmetric(horizontal: 2.0)); }}const BorderRadiusGeometry borderRadius = BorderRadius.only( bottomLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0));&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9 #
&lt;/h2&gt;

&lt;p&gt;Time to add midi by adding the following import to the top of the file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:tonic/tonic.dart';&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;-buildKey&lt;/code&gt; function you can add this line:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 final pitchName = Pitch.fromMidiNumber(midi).toString();&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can also create the piano key itself underneath it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 final pianoKey = Stack( children: &amp;lt;Widget&amp;gt;[Semantics( button: true, hint: pitchName, child: Material( borderRadius: borderRadius, color: accidental ? Colors.black : Colors.white, child: InkWell( borderRadius: borderRadius, highlightColor: Colors.grey, onTap: () {}, onTapDown: (_) =&amp;gt; FlutterMidi.playMidiNote(midi: midi), ))), Positioned( left: 0.0, right: 0.0, bottom: 20.0, child: _showLabels ? Text(pitchName, textAlign: TextAlign.center, style: TextStyle( color: !accidental ? Colors.black : Colors.white)) : Container()),], );&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remove the color from the container and replace it with &lt;code&gt;child: pianoKey,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 if (accidental) { return Container( width: keyWidth, margin: EdgeInsets.symmetric(horizontal: 2.0), padding: EdgeInsets.symmetric(horizontal: keyWidth * .1), child: Material( elevation: 6.0, borderRadius: borderRadius, shadowColor: Color(0x802196F3), child: pianoKey)); } return Container( width: keyWidth, child: pianoKey, margin: EdgeInsets.symmetric(horizontal: 2.0));&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The complete function should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
 Widget _buildKey(int midi, bool accidental) { final pitchName = Pitch.fromMidiNumber(midi).toString(); final pianoKey = Stack( children: &amp;lt;Widget&amp;gt;[Semantics( button: true, hint: pitchName, child: Material( borderRadius: borderRadius, color: accidental ? Colors.black : Colors.white, child: InkWell( borderRadius: borderRadius, highlightColor: Colors.grey, onTap: () {}, onTapDown: (_) =&amp;gt; FlutterMidi.playMidiNote(midi: midi), ))), Positioned( left: 0.0, right: 0.0, bottom: 20.0, child: _showLabels ? Text(pitchName, textAlign: TextAlign.center, style: TextStyle( color: !accidental ? Colors.black : Colors.white)) : Container()),], ); if (accidental) { return Container( width: keyWidth, margin: EdgeInsets.symmetric(horizontal: 2.0), padding: EdgeInsets.symmetric(horizontal: keyWidth * .1), child: Material( elevation: 6.0, borderRadius: borderRadius, shadowColor: Color(0x802196F3), child: pianoKey)); } return Container( width: keyWidth, child: pianoKey, margin: EdgeInsets.symmetric(horizontal: 2.0)); }&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now when you run the app it should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Flabels.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Flabels.png" alt="labels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost there! Now let's give our user some control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10 #
&lt;/h2&gt;

&lt;p&gt;Add these settings to the &lt;code&gt;Drawer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
Container(height: 20.0),ListTile(title: Text("Change Width")),Slider( activeColor: Colors.redAccent, inactiveColor: Colors.white, min: 0.0, max: 1.0, value: _widthRatio, onChanged: (double value) =&amp;gt; setState(() =&amp;gt; _widthRatio = value)),Divider(),ListTile( title: Text("Show Labels"), trailing: Switch( value: _showLabels, onChanged: (bool value) =&amp;gt; setState(() =&amp;gt; _showLabels = value))),Divider(),&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fsettings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Fsettings.png" alt="settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 11 #
&lt;/h2&gt;

&lt;p&gt;To start with &lt;code&gt;Middle C&lt;/code&gt; lets add an inital scroll offset to the &lt;code&gt;ListView&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;controller: ScrollController(initialScrollOffset: 1500.0),&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now when we start the app it should co to C4.&lt;/p&gt;

&lt;p&gt;The final App should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Ffinal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2Fimg%2Fflutter_piano%2Ffinal.png" alt="final"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final code should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
import 'package:flutter/material.dart';import 'package:flutter/services.dart';import 'package:flutter_midi/flutter_midi.dart';import 'package:tonic/tonic.dart';void main() =&amp;gt; runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() =&amp;gt; _MyAppState();}class _MyAppState extends State&amp;lt;MyApp&amp;gt; { @override initState() { FlutterMidi.unmute(); rootBundle.load("assets/sounds/Piano.sf2").then((sf2) { FlutterMidi.prepare(sf2: sf2, name: "Piano.sf2"); }); super.initState(); } double get keyWidth =&amp;gt; 80 + (80 * _widthRatio); double _widthRatio = 0.0; bool _showLabels = true; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark(), home: Scaffold( appBar: AppBar(title: Text("Flutter Piano")), drawer: Drawer( child: SafeArea( child: ListView(children: &amp;lt;Widget&amp;gt;[Container(height: 20.0), ListTile(title: Text("Change Width")), Slider( activeColor: Colors.redAccent, inactiveColor: Colors.white, min: 0.0, max: 1.0, value: _widthRatio, onChanged: (double value) =&amp;gt; setState(() =&amp;gt; _widthRatio = value)), Divider(), ListTile( title: Text("Show Labels"), trailing: Switch( value: _showLabels, onChanged: (bool value) =&amp;gt; setState(() =&amp;gt; _showLabels = value))), Divider(),]))), body: ListView.builder( itemCount: 7, scrollDirection: Axis.horizontal, controller: ScrollController(initialScrollOffset: 1500.0), itemBuilder: (BuildContext context, int index) { final int i = index * 12; return SafeArea( child: Stack(children: &amp;lt;Widget&amp;gt;[Row(mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[ _buildKey(24 + i, false), _buildKey(26 + i, false), _buildKey(28 + i, false), _buildKey(29 + i, false), _buildKey(31 + i, false), _buildKey(33 + i, false), _buildKey(35 + i, false),]), Positioned( left: 0.0, right: 0.0, bottom: 100, top: 0.0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: &amp;lt;Widget&amp;gt;[Container(width: keyWidth * .5), _buildKey(25 + i, true), _buildKey(27 + i, true), Container(width: keyWidth), _buildKey(30 + i, true), _buildKey(32 + i, true), _buildKey(34 + i, true), Container(width: keyWidth * .5),])), ]), ); }, )), ); } Widget _buildKey(int midi, bool accidental) { final pitchName = Pitch.fromMidiNumber(midi).toString(); final pianoKey = Stack( children: &amp;lt;Widget&amp;gt;[Semantics( button: true, hint: pitchName, child: Material( borderRadius: borderRadius, color: accidental ? Colors.black : Colors.white, child: InkWell( borderRadius: borderRadius, highlightColor: Colors.grey, onTap: () {}, onTapDown: (_) =&amp;gt; FlutterMidi.playMidiNote(midi: midi), ))), Positioned( left: 0.0, right: 0.0, bottom: 20.0, child: _showLabels ? Text(pitchName, textAlign: TextAlign.center, style: TextStyle( color: !accidental ? Colors.black : Colors.white)) : Container()),], ); if (accidental) { return Container( width: keyWidth, margin: EdgeInsets.symmetric(horizontal: 2.0), padding: EdgeInsets.symmetric(horizontal: keyWidth * .1), child: Material( elevation: 6.0, borderRadius: borderRadius, shadowColor: Color(0x802196F3), child: pianoKey)); } return Container( width: keyWidth, child: pianoKey, margin: EdgeInsets.symmetric(horizontal: 2.0)); }}const BorderRadiusGeometry borderRadius = BorderRadius.only( bottomLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0));&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion #
&lt;/h1&gt;

&lt;p&gt;If you delete &lt;code&gt;tests/&lt;/code&gt; and run &lt;code&gt;find . -name "*.dart" | xargs cat | wc -c&lt;/code&gt; you will see that the dart code only uses &lt;code&gt;5032&lt;/code&gt; bytes of space!&lt;/p&gt;

&lt;p&gt;Now we have a fully functional piano that you can play with and enjoy on iOS and Android.&lt;/p&gt;

&lt;p&gt;I was really inspired when creating this for the &lt;a href="https://flutter.dev/create" rel="noopener noreferrer"&gt;Flutter Create&lt;/a&gt; contest.&lt;/p&gt;

&lt;p&gt;Hope you learned something, if you have any questions you can always read out to me. This is an open source piano and would love PRs on the main project &lt;a href="https://github.com/rodydavis/flutter_piano" rel="noopener noreferrer"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2F.netlify%2Ffunctions%2Fga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fmaking-a-piano%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DBuilding%2520A%2520Piano%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frodydavis.com%2F.netlify%2Ffunctions%2Fga%3Fv%3D1%26_v%3Dj83%26t%3Dpageview%26dr%3Dhttps%253A%252F%252Frss-feed-reader.com%26_s%3D1%26dh%3Drodydavis.com%26dp%3D%252Fposts%252Fmaking-a-piano%252F%26ul%3Den-us%26de%3DUTF-8%26dt%3DBuilding%2520A%2520Piano%2520with%2520Flutter%26tid%3DG-JQNPVBL9DR"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
