<?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: Teodor Dermendzhiev</title>
    <description>The latest articles on DEV Community by Teodor Dermendzhiev (@tdermendjiev).</description>
    <link>https://dev.to/tdermendjiev</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%2F1121787%2Fe8e6c7ba-d606-4c98-a66d-d39c0278ea13.png</url>
      <title>DEV Community: Teodor Dermendzhiev</title>
      <link>https://dev.to/tdermendjiev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tdermendjiev"/>
    <language>en</language>
    <item>
      <title>CleanMyMac for developers</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Tue, 17 Oct 2023 08:43:37 +0000</pubDate>
      <link>https://dev.to/tdermendjiev/cleanmymac-for-developers-5491</link>
      <guid>https://dev.to/tdermendjiev/cleanmymac-for-developers-5491</guid>
      <description>&lt;p&gt;Back in 2014, I got my first Mac. It was a beauty, but there was one hitch: it only had 125 GB of disk space. Over the years, I've cycled through a bunch of Macs, but guess what? Disk space problems have stuck around, thanks to my multiple projects. Sure, some workplaces handed me 1 TB Macs, but my personal ones? I've always gone for the 512 GB version, mainly because it's easier on the wallet.&lt;/p&gt;

&lt;p&gt;I tried tools like CleanMyMacX, but they just didn't cut it. They couldn't spot the files that us developers know are a waste—things like node_modules, pesky caches, and old docker containers. That's why I decided to create something myself.&lt;/p&gt;

&lt;p&gt;Meet Roomy, my macOS app that packs in all the developer-friendly file wisdom I've gathered over the years. You can test it out &lt;a href="https://tdermendjievft.gumroad.com/l/roomy-alpha"&gt;right here&lt;/a&gt;, totally free.&lt;/p&gt;

&lt;p&gt;When you fire up Roomy, you'll see that, for now, it's all about cleaning up Xcode and JavaScript-related files. For JavaScript, you'll need to set your workspace path in the settings (the place where you keep your JavaScript projects). Check it out:&lt;/p&gt;

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

&lt;p&gt;Once you've got your workspace set up, head over to Scan-&amp;gt;Javascript. There, you can see how much space each of your projects' node_modules, build files, and caches is eating up.&lt;/p&gt;

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

&lt;p&gt;Here is my Xcode summary:&lt;/p&gt;

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

&lt;p&gt;I'm working hard on Roomy's core features, and your feedback is gold to me. Let me know if it's making your developer life easier and what else you'd like to see.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>ios</category>
    </item>
    <item>
      <title>How to build your embedded NativeScript app within the Xcode project</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Thu, 21 Sep 2023 18:31:32 +0000</pubDate>
      <link>https://dev.to/tdermendjiev/how-to-build-your-embedded-nativescript-app-within-the-xcode-project-3jo0</link>
      <guid>https://dev.to/tdermendjiev/how-to-build-your-embedded-nativescript-app-within-the-xcode-project-3jo0</guid>
      <description>&lt;p&gt;Recently, there's been growing interest in embedding NativeScript apps within native iOS applications. While this embedding feature in NativeScript is still relatively new and evolving, it offers promising capabilities. However, automating the build process in Xcode can present challenges due to discrepancies between the terminal and Xcode environments. In this post, we'll delve into these challenges and provide a solution for a unified build step.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Challenge&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Command Not Found&lt;/strong&gt;: When invoking tools like &lt;code&gt;ns&lt;/code&gt; from Xcode, you might encounter errors like "command not found". This is because Xcode doesn't share the same &lt;code&gt;PATH&lt;/code&gt; environment variable as your terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables Mismatch&lt;/strong&gt;: Xcode might not have access to all the environment variables set in your terminal profile, leading to unexpected behaviors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UTF-8 Encoding Issues&lt;/strong&gt;: Some tools, like CocoaPods, require the terminal to use UTF-8 encoding. If not set, this can lead to warnings or errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Solution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To address these challenges, we'll craft a build phase script for Xcode that sets up the environment correctly and then runs the necessary commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. The Build Phase Script&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here's the script:&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="c"&gt;#!/bin/zsh&lt;/span&gt;

&lt;span class="c"&gt;# This script should be invoked before Compile sources build phase&lt;/span&gt;

&lt;span class="c"&gt;# Ensure UTF-8 encoding&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US.UTF-8
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US.UTF-8

&lt;span class="c"&gt;### User Configuration ###&lt;/span&gt;

&lt;span class="c"&gt;# User's PATH&lt;/span&gt;
&lt;span class="c"&gt;# Example: "/Users/yourusername/.nvm/versions/node/v14.0.0/bin:/usr/local/bin:/usr/bin:..."&lt;/span&gt;
&lt;span class="c"&gt;# To get your current PATH, you can run "echo $PATH" in the terminal.&lt;/span&gt;
&lt;span class="nv"&gt;USER_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;PATH-VAR&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Path to the project&lt;/span&gt;
&lt;span class="c"&gt;# Example: "/Users/yourusername/workspace/myproject"&lt;/span&gt;
&lt;span class="c"&gt;# This should be the absolute path to your project directory.&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR-NS-PROJECT-ROOT-PATH&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;##########################&lt;/span&gt;

&lt;span class="c"&gt;# Source the user's .zshrc&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc

&lt;span class="c"&gt;# Set PATH&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please set the USER_PATH in the script."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Run the ns command&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;ns prepare ios &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please set the PROJECT_PATH in the script."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2. Understanding and Addressing the Issues&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Command Not Found&lt;/strong&gt;: This issue arises because Xcode's &lt;code&gt;PATH&lt;/code&gt; doesn't include directories where some tools (like &lt;code&gt;ns&lt;/code&gt;) reside. By explicitly setting the &lt;code&gt;PATH&lt;/code&gt; in our script to match the terminal's &lt;code&gt;PATH&lt;/code&gt;, we ensure all command-line tools are accessible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables Mismatch&lt;/strong&gt;: By sourcing &lt;code&gt;~/.zshrc&lt;/code&gt;, we make sure that any environment variables set up in your terminal are also available in the Xcode script. This ensures a consistent environment between the two.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UTF-8 Encoding Issues&lt;/strong&gt;: The locale settings at the start of the script ensure that the environment uses UTF-8 encoding, addressing potential encoding issues with tools like CocoaPods.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Integrating the Script into Xcode&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Select your target in Xcode.&lt;/li&gt;
&lt;li&gt;Navigate to the "Build Phases" tab.&lt;/li&gt;
&lt;li&gt;Click the "+" and choose "New Run Script Phase".&lt;/li&gt;
&lt;li&gt;Ensure this script phase is positioned before the "Compile Sources" phase.&lt;/li&gt;
&lt;li&gt;Paste the content of your script.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With this setup, developers can enjoy a streamlined build process in Xcode that prepares both the NativeScript app and the iOS app embedding it. This approach ensures consistency between terminal and Xcode environments, leading to a smoother development experience.&lt;/p&gt;

&lt;p&gt;For more tips on embedding NativeScript apps, follow me on Twitter &lt;strong&gt;@nsteodor&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Shrinking `node_modules`: Strategies to Reclaim Your Disk Space</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Thu, 17 Aug 2023 16:49:49 +0000</pubDate>
      <link>https://dev.to/tdermendjiev/shrinking-nodemodules-strategies-to-reclaim-your-disk-space-31k9</link>
      <guid>https://dev.to/tdermendjiev/shrinking-nodemodules-strategies-to-reclaim-your-disk-space-31k9</guid>
      <description>&lt;p&gt;The &lt;code&gt;node_modules&lt;/code&gt; directory is notorious for gobbling up disk space in JavaScript projects. If left unchecked, it can quickly balloon in size, consuming valuable storage. Here are seven strategies to keep it lean and free up disk space:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Embrace &lt;code&gt;npm ci&lt;/code&gt; for Clean Installs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When setting up a project from an existing &lt;code&gt;package-lock.json&lt;/code&gt;, use &lt;code&gt;npm ci&lt;/code&gt;. It removes the existing &lt;code&gt;node_modules&lt;/code&gt; and installs dependencies afresh, ensuring no lingering, unnecessary packages occupy space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Prune Redundant Packages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Over time, you might accumulate packages that are no longer in use. After removing a package from &lt;code&gt;package.json&lt;/code&gt;, run &lt;code&gt;npm prune&lt;/code&gt; to ensure it and its orphaned dependencies are purged from &lt;code&gt;node_modules&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Limit Global Installations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While global installations can be convenient for CLI tools, be judicious. Only install packages globally that you use across projects to avoid multiple space-consuming installations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Visualize and Audit with &lt;code&gt;npm list&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;npm list&lt;/code&gt; to get an overview of your dependencies. Spotting and removing unnecessary large packages can free up significant space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Deduplicate with &lt;code&gt;npm dedupe&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redundant or duplicated packages can bloat &lt;code&gt;node_modules&lt;/code&gt;. Run &lt;code&gt;npm dedupe&lt;/code&gt; to consolidate and reduce the directory's size by eliminating these redundancies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Exclude from Version Control:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Committing &lt;code&gt;node_modules&lt;/code&gt; to version control not only bloats the repository but also your local clone. Always use a &lt;code&gt;.gitignore&lt;/code&gt; file to keep &lt;code&gt;node_modules&lt;/code&gt; out of version control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Regularly Update and Clean Cache:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeping dependencies updated can sometimes reduce their size due to optimizations. Additionally, clear the npm cache periodically using &lt;code&gt;npm cache clean --force&lt;/code&gt; to free up space taken by outdated or unused packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;node_modules&lt;/code&gt; directory, while essential, doesn't have to be a disk space hog. By implementing these strategies, you can maintain a lean project environment, ensuring efficient use of storage and a smoother development experience.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>7 Lesser-Known Features of `yarn.lock` and `package-lock.json` You Should Know</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Wed, 16 Aug 2023 09:23:06 +0000</pubDate>
      <link>https://dev.to/tdermendjiev/7-lesser-known-features-of-yarnlock-and-package-lockjson-you-should-know-17oc</link>
      <guid>https://dev.to/tdermendjiev/7-lesser-known-features-of-yarnlock-and-package-lockjson-you-should-know-17oc</guid>
      <description>&lt;p&gt;&lt;strong&gt;1. Deterministic vs. Reproducible Builds:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic Builds:&lt;/strong&gt; Both lock files ensure that given a codebase and a lock file, the same dependencies will be installed every time. This ensures consistency across installations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible Builds:&lt;/strong&gt; Yarn took it a step further by guaranteeing reproducible builds even before npm 5. This means that installations are identical across different machines, ensuring that the environment-specific bugs are minimized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Dive Deep with Special Commands:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yarn’s &lt;code&gt;why&lt;/code&gt; Command:&lt;/strong&gt; Ever puzzled why a certain package is in your project? Yarn offers the &lt;code&gt;why&lt;/code&gt; command. By running &lt;code&gt;yarn why &amp;lt;package-name&amp;gt;&lt;/code&gt;, you can trace the reasons a package was installed, whether it's a direct dependency, a sub-dependency, or due to a version conflict.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm’s &lt;code&gt;fund&lt;/code&gt; Command:&lt;/strong&gt; Open-source projects thrive on community support. With &lt;code&gt;npm fund&lt;/code&gt;, you can view funding URLs associated with your installed packages, allowing you to financially support projects you rely on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Yarn’s Secret Weapon - The Resolutions Field:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependency conflicts can be a headache. Yarn offers a unique solution with its &lt;code&gt;resolutions&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt;. This allows developers to specify which version of a nested dependency should be used, overriding the version specified by the parent package. It's a powerful tool for ensuring consistency and resolving conflicts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Merging Lock Files Isn’t a Nightmare:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contrary to popular belief, lock files are designed to handle merges gracefully. If two developers update different sets of dependencies simultaneously, version control systems like Git can often merge the changes without conflicts. If conflicts do arise, the best practice is to accept both changes and then run a fresh install to regenerate the lock file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. The Power of Integrity Hashes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both lock files include an &lt;code&gt;integrity&lt;/code&gt; field, which provides a hash of the package content, ensuring it hasn't been tampered with since its publication. What's fascinating is that this hash is algorithm-agnostic. So, if the npm registry starts supporting a new hashing algorithm in the future, your existing lock file remains compatible, ensuring smooth transitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Unearth the Hidden Cache:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both npm and Yarn store downloaded packages in a local cache to speed up future installations. Before fetching from the remote registry, they first check this local cache. This not only speeds up installations but can also be a lifesaver when working offline. You can explore and manage this cache using &lt;code&gt;npm cache ls&lt;/code&gt; for npm and &lt;code&gt;yarn cache list&lt;/code&gt; for Yarn.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Recognize the Importance of Lock File Versions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;lockfileVersion&lt;/code&gt; field in &lt;code&gt;package-lock.json&lt;/code&gt; indicates the lock file's format version. For instance, &lt;code&gt;lockfileVersion: 2&lt;/code&gt; was introduced with npm v7 and brought enhanced features like workspaces support and improved peer dependencies handling. Recognizing this version can help diagnose issues related to incompatible npm versions or provide insights into available features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While &lt;code&gt;yarn.lock&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; might seem like simple manifest files at first glance, they're packed with features and nuances that can significantly streamline and improve your development workflow. By understanding and leveraging these lesser-known aspects, developers can navigate the world of dependency management with greater confidence and efficiency.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Your Mac is Slow: Diagnosing Disk Space Issues</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Mon, 14 Aug 2023 12:45:49 +0000</pubDate>
      <link>https://dev.to/tdermendjiev/why-your-mac-is-slow-diagnosing-disk-space-issues-54ac</link>
      <guid>https://dev.to/tdermendjiev/why-your-mac-is-slow-diagnosing-disk-space-issues-54ac</guid>
      <description>&lt;p&gt;The spinning rainbow wheel. Every Mac user's dreaded nemesis. As developers, we often push our machines to the limit, running multiple IDEs, spinning up local servers, and juggling between browsers with dozens of tabs. But when our trusty Mac starts to lag, it's not just an inconvenience—it's a productivity killer. And while there could be a myriad of reasons for the slowdown, one of the most overlooked culprits is disk space (or the lack thereof).&lt;/p&gt;

&lt;p&gt;But why does a lack of disk space cause such a significant slowdown? And more importantly, how can we diagnose what's eating up our precious gigabytes?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Understanding Disk Space and Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Disk space isn't just a place to store your files; it's a crucial component of your Mac's performance. When you're running low on space, your system has to work harder, often leading to noticeable slowdowns. But before we dive into the nitty-gritty, let's get a foundational understanding of how disk space impacts performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSDs vs. HDDs:&lt;/strong&gt; Modern Macs come equipped with Solid State Drives (SSDs), which are faster and more reliable than the older Hard Disk Drives (HDDs). However, SSDs have a catch: as they fill up, their performance can degrade. While HDDs suffer from fragmentation issues when they're near capacity, SSDs struggle with longer write times. So, if you're pushing your SSD to its limits, you're likely compromising its speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swap Space and Virtual Memory:&lt;/strong&gt; macOS, like other operating systems, uses a concept called "swap space." When your RAM is full, macOS will start "swapping" data between the RAM and the disk. This virtual memory allows your Mac to handle more tasks than the RAM could alone. But here's the kicker: if your disk is nearly full, this swapping process becomes sluggish, leading to those dreaded slowdowns.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Common Disk Space Culprits for Developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As developers, we have a unique relationship with our machines. We demand performance, but we also consume a lot of space with our tools and projects. Let's explore some of the most common space-hogging culprits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development Environments:&lt;/strong&gt; Virtual machines, Docker containers, and local databases can take up a significant chunk of space. For instance, a single Docker image might be lightweight, but if you have multiple containers and images, it adds up quickly.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Version Control:&lt;/strong&gt; Git is fantastic, but it can also be a space hog. Cloned repositories, especially those with extensive histories or large files, can consume more space than you might expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build Artifacts:&lt;/strong&gt; After compiling your code, you're left with binaries, logs, and temporary files. While they might seem insignificant individually, they accumulate over time, especially if you're working on multiple projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caches:&lt;/strong&gt; IDEs, browsers, and other tools use caches to speed up operations. But these caches, if not managed, can grow and consume a surprising amount of disk space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large Datasets:&lt;/strong&gt; Whether you're working with machine learning models or testing large-scale applications, datasets can be massive. And while they're essential for your work, they're also prime candidates for space consumption.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;macOS-Specific Tools and Methods to Diagnose Space Consumption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Knowledge is power. Before we can reclaim our disk space, we need to know what's occupying it. Thankfully, macOS offers a plethora of tools, both built-in and third-party, to help us diagnose our storage woes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in Tools&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;About This Mac&lt;/strong&gt;: Navigate to the Apple menu &amp;gt; About This Mac &amp;gt; Storage. Here, you'll get a bird's-eye view of your storage distribution. It categorizes space usage into apps, photos, audio, movies, and backups, giving you a quick idea of where to start cleaning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Disk Utility&lt;/strong&gt;: Found in the Utilities folder, Disk Utility isn't just for repairing disks. The 'Info' button provides details about the selected drive, including capacity, used space, and free space.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Third-Party Tools&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://daisydiskapp.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;DaisyDisk&lt;/strong&gt;&lt;/a&gt;: With its intuitive and visually appealing interface, DaisyDisk provides a graphical representation of your storage, allowing you to quickly identify and delete large files and folders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.omnigroup.com/more" rel="noopener noreferrer"&gt;&lt;strong&gt;OmniDiskSweeper&lt;/strong&gt;&lt;/a&gt;: This tool offers a more detailed, list-based view of files and directories sorted by size. It's perfect for those who prefer granularity over graphical representations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://grandperspectiv.sourceforge.net/" rel="noopener noreferrer"&gt;&lt;strong&gt;GrandPerspective&lt;/strong&gt;&lt;/a&gt;: Another visual tool, GrandPerspective uses treemaps to display the file system's content, making it easier to spot large files or directories at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.titanium-software.fr/en/onyx.html" rel="noopener noreferrer"&gt;&lt;strong&gt;OnyX&lt;/strong&gt;&lt;/a&gt;: OnyX is a multifunctional tool for maintaining and optimizing your Mac. It offers a deep dive into the macOS file system, allowing users to verify startup disks, rebuild databases, and clear out a variety of caches. For developers, its detailed logs and automation scripts can be a lifesaver. The tool provides a granular level of control, making it perfect for those who know their way around macOS and want a powerful tool to keep their system in top shape.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Terminal Commands&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;For those who love the command line, macOS offers powerful utilities to dissect storage usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;du&lt;/code&gt;: Short for "disk usage", this command provides a summary of directory space usage. For instance, &lt;code&gt;du -sh *&lt;/code&gt; will show the size of each file and directory in the current directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;df&lt;/code&gt;: This "disk free" command displays the amount of disk space used and available on your Mac's file systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ncdu&lt;/code&gt;: An NCurses-based disk usage viewer, &lt;code&gt;ncdu&lt;/code&gt; offers a more interactive and user-friendly way to explore disk usage from the terminal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Tips for Regular Maintenance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we've equipped ourselves with the tools to diagnose, let's talk about prevention. Regular maintenance can keep disk space issues at bay, ensuring your Mac runs smoothly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduled Cleanups&lt;/strong&gt;: Set calendar reminders or use automation tools like Automator or Hazel to perform routine cleanups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;External Storage&lt;/strong&gt;: Consider offloading large datasets or infrequently accessed files to external drives or cloud storage solutions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regularly Update and Prune Development Tools&lt;/strong&gt;: Outdated SDKs, libraries, or tools can accumulate over time. Periodically review and remove what's no longer needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mind Your Clones&lt;/strong&gt;: When working with Git, consider using shallow clones or sparse checkouts for large repositories. This ensures you only pull the data you need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Actionable Steps to Reclaim Disk Space and Boost Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we're armed with knowledge and tools, it's time to roll up our sleeves and get to work. Here's a step-by-step guide to decluttering your Mac and ensuring it runs at its optimal best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start with the Low-Hanging Fruit&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Empty the Trash&lt;/strong&gt;: It sounds basic, but you'd be surprised how much space is occupied by files you've already decided to delete.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear Browser Caches&lt;/strong&gt;: Web browsers store a lot of data. Periodically clearing their caches can free up a significant amount of space.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delete Old Downloads&lt;/strong&gt;: The Downloads folder is often a graveyard of old files. Sort by size and date, and purge what you don't need.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Dive Deeper with Diagnostic Tools&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use tools like &lt;strong&gt;DaisyDisk&lt;/strong&gt; or &lt;strong&gt;OmniDiskSweeper&lt;/strong&gt; to identify large files or folders. Be cautious and ensure you're not deleting essential files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For developers, pay special attention to old project folders, outdated virtual machines, or stale Docker images.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Manage Your Development Environment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimize Docker&lt;/strong&gt;: Regularly prune unused Docker objects using commands like &lt;code&gt;docker system prune&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clean Up IDEs&lt;/strong&gt;: Integrated Development Environments can accumulate cache and log files. Tools like JetBrains' IDEs have built-in cache invalidation options.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Archive Old Projects&lt;/strong&gt;: Instead of deleting, consider archiving old projects to external storage or cloud solutions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Automate Regular Cleanups&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use macOS's &lt;a href="https://support.apple.com/bg-bg/guide/automator/welcome/mac" rel="noopener noreferrer"&gt;&lt;strong&gt;Automator&lt;/strong&gt;&lt;/a&gt; to create custom workflows for routine cleanups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider third-party apps like &lt;a href="https://www.noodlesoft.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Hazel&lt;/strong&gt;&lt;/a&gt; that can monitor folders and perform actions based on custom rules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Consider Upgrades&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;If you're consistently running out of space, it might be time to consider hardware solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upgrade Your Internal Storage&lt;/strong&gt;: If your Mac model allows, consider upgrading to a larger SSD.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;External SSDs&lt;/strong&gt;: Modern external SSDs are fast and can be a seamless extension of your internal storage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A cluttered disk isn't just about lost space; it's about lost productivity. By regularly diagnosing and addressing disk space issues, you ensure that your Mac remains a reliable partner in your coding endeavors. But our journey into optimizing our Macs doesn't end here. In upcoming articles, we'll delve deeper into specific cases we touched upon today, like managing Docker images, optimizing web projects, mastering CLI commands for disk management, and more. Remember, a clean Mac is a happy Mac, and a happy Mac makes for a happy developer. Stay tuned for more insights and actionable tips to keep your development environment in peak condition!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>ios</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Implement a Cross-Platform Login Screen with Angular within a WKWebView</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Wed, 26 Jul 2023 13:23:31 +0000</pubDate>
      <link>https://dev.to/chattywebviews/how-to-implement-a-cross-platform-login-screen-with-angular-within-a-wkwebview-1jab</link>
      <guid>https://dev.to/chattywebviews/how-to-implement-a-cross-platform-login-screen-with-angular-within-a-wkwebview-1jab</guid>
      <description>&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;You can develop cross-platform components for your mobile apps using webviews without necessarily having to rely on hybrid frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In many cases, especially when you're building a native app, you might want to have a cross-platform authentication flow. This could be because you want to maintain the native performance and feel for the rest of your app, but need a quick and efficient way to handle authentication. &lt;/p&gt;

&lt;p&gt;You could go for a full-fledged hybrid app framework like Ionic or React Native, but that might be overkill if you're only looking to make a few parts of your app cross-platform. Plus, transitioning to a hybrid app framework comes with its own set of challenges, like learning a new programming language or dealing with the nuances of a different development environment. Or you might just have a web developer to help you with few screens...&lt;/p&gt;

&lt;p&gt;This is where webviews come in. By creating a login screen using a webview, you can write the code once with a framework you (or whoever is writing the component) are already familiar with (Angular, React, Vue or whatever) and use it across multiple platforms. It's a lightweight solution that doesn't require a full hybrid app framework, making it perfect for simpler features like a login screen.&lt;/p&gt;

&lt;p&gt;And to make things even easier, we're going to use an open-source library I created called &lt;strong&gt;ChattyWebviews&lt;/strong&gt;. It's designed to facilitate communication between webviews and native classes, making it a breeze to integrate webview-based features into your native apps. If you find the article useful, consider giving it a star, so I can continue working on it - &lt;a href="https://github.com/ChattyWebviews/ChattyWebviews"&gt;https://github.com/ChattyWebviews/ChattyWebviews&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating the Webview Login Screen&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the purpose of this tutorial, we'll use Ionic to create our login screen. Ionic is a popular framework for building cross-platform applications using web technologies. It's a great choice for our login screen because it allows us to write our code with javascript and have the native iOS or Android look.&lt;/p&gt;

&lt;p&gt;First, let's install Ionic if you haven't done so already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @ionic/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a new Ionic project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ionic start LoginDemo blank &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;angular
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the new project directory:&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;LoginDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create a simple login form. Open &lt;code&gt;src/app/home/home.page.html&lt;/code&gt; and replace its content with 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="nt"&gt;&amp;lt;ion-header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ion-toolbar&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-title&amp;gt;&lt;/span&gt;
      Login
    &lt;span class="nt"&gt;&amp;lt;/ion-title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ion-toolbar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ion-header&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ion-content&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ion-padding"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;(ngSubmit)=&lt;/span&gt;&lt;span class="s"&gt;"onSubmit()"&lt;/span&gt; &lt;span class="na"&gt;#loginForm&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="na"&gt;ngForm&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-item&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ion-label&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"floating"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Username&lt;span class="nt"&gt;&amp;lt;/ion-label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ion-input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;ngModel&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ion-item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-item&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ion-label&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"floating"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/ion-label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ion-input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;ngModel&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ion-item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ion-button&lt;/span&gt; &lt;span class="na"&gt;expand=&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;[disabled]=&lt;/span&gt;&lt;span class="s"&gt;"!loginForm.valid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/ion-button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ion-content&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;src/app/home/home.page.ts&lt;/code&gt; add the &lt;code&gt;onSubmit()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Component&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="s1"&gt;@angular/core&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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.page.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.page.scss&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;//here we will send the message to the native class&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;This creates a simple login form with fields for username and password. The &lt;code&gt;(ngSubmit)&lt;/code&gt; directive is an Angular feature that allows us to run a function when the form is submitted. &lt;/p&gt;

&lt;p&gt;In the next section, we'll look at how to handle the form submission and communicate with our native app using ChattyWebviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Handling Form Submission with ChattyWebviews&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we have our login form, we need to handle the form submission. When the user submits the form, we want to send the username and password to our native app. This is where ChattyWebviews comes in.&lt;/p&gt;

&lt;p&gt;First, install ChattyWebviews in your Ionic project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @chatty-webviews/js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the AppComponent and attach the messaging service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Component&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="s1"&gt;@angular/core&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;attach&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="s1"&gt;@chatty-webviews/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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.component.html&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;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;attach&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;Next, open &lt;code&gt;src/app/home/home.page.ts&lt;/code&gt; and replace its content with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ViewChild&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="s1"&gt;@angular/core&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;NgForm&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="s1"&gt;@angular/forms&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;sendMessage&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="s1"&gt;@chatty-webviews/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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.page.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home.page.scss&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ViewChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loginForm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;loginForm&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NgForm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&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;loginForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;loginForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;In this code, we're importing &lt;code&gt;sendMessage&lt;/code&gt; from &lt;code&gt;@chatty-webviews/js&lt;/code&gt;. We're also defining an &lt;code&gt;onSubmit&lt;/code&gt; method that gets called when the form is submitted. In this method, we're sending a message to our native app with the username and password.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Setting Up the Native App&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We need to set up our native app to display the login screen and handle messages from the webview. We'll be using SwiftUI for this. &lt;/p&gt;

&lt;p&gt;Before writing the code, we need to do few things. First, add the ChattyWebviews' SPM package by going to File-&amp;gt;Add Packages and pasting the repository url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/ChattyWebviews/ChattyWebviews
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also need to build the Ionic web app package by running &lt;code&gt;ionic build&lt;/code&gt;. When the build is done, drag the &lt;code&gt;www&lt;/code&gt; folder inside the Xcode project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---EkaB3X9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5q0zmvt1pyhl6zklx17r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---EkaB3X9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5q0zmvt1pyhl6zklx17r.png" alt="Drag the www folder in the Xcode project tree" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It's important to select &lt;code&gt;Create folder references&lt;/code&gt;. If you want the package to be automatically updated after every build, leave &lt;code&gt;Copy items if needed&lt;/code&gt; unchecked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NzvrCWJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6vv2mf4u6wiqm91sq11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NzvrCWJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6vv2mf4u6wiqm91sq11.png" alt="Select Create folder references" width="800" height="498"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here's the code for our main view, &lt;code&gt;ContentView&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showingSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"globe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imageScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accentColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;showingSheet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;showingSheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kt"&gt;SheetView&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;showingSheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showingSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;showingSheet&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupModules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setupModules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"www"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;module&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;In this code, we're creating a SwiftUI view with a button that displays a login screen when clicked. The login screen is a &lt;code&gt;SheetView&lt;/code&gt;, which we'll define next.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating the Webview Login Screen&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Next, we'll create the &lt;code&gt;SheetView&lt;/code&gt; that displays the login screen. This view is a &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;, which means it's a wrapper for a UIKit view controller that can be used in SwiftUI.&lt;/p&gt;

&lt;p&gt;Here's the code for &lt;code&gt;SheetView&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SheetView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewControllerRepresentable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CWMessageDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;typealias&lt;/span&gt; &lt;span class="kt"&gt;UIViewControllerType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWViewController&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dismiss&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didReceive&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getModules&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;scheduleVC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWebview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scheduleVC&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messageDelegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheduleVC&lt;/span&gt;&lt;span class="o"&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;In this code, we're creating a &lt;code&gt;CWViewController&lt;/code&gt; that displays the login screen. We're also setting up a message delegate to handle messages from the webview. When a message is received, we print it and dismiss the login screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Handling Messages in the Native App&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, we need to handle the messages that we receive from the webview. This involves setting up a &lt;code&gt;CWMessageDelegate&lt;/code&gt; in our native app.&lt;/p&gt;

&lt;p&gt;Here's the code for the message delegate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;SheetView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CWMessageDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didReceive&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dismiss&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;In this code, we're defining a method that gets called when a message is received from the webview. We're printing the message and dismissing the login screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching Login Credentials in Native Code
&lt;/h2&gt;

&lt;p&gt;In our &lt;code&gt;SheetView&lt;/code&gt; class, we have already set up a &lt;code&gt;CWMessageDelegate&lt;/code&gt; to handle messages from the webview. When a message is received, the &lt;code&gt;controller(_:didReceive:)&lt;/code&gt; method is called. This is where we can fetch the login credentials.&lt;/p&gt;

&lt;p&gt;Here's how we can modify this method to fetch the login credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didReceive&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"login"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Username: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;, Password: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;dismiss&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;In this code, we're checking if the message topic is "login". If it is, we're fetching the username and password from the message body and printing them, then dismissing the modal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t1C_K9wv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5z359xvikcpvs4o621d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t1C_K9wv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5z359xvikcpvs4o621d.gif" alt="Demo gif" width="388" height="790"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Login in Native Code
&lt;/h2&gt;

&lt;p&gt;Now that we have the login credentials, we can handle the login in our native code. This could involve validating the credentials, fetching user data from a server, etc.&lt;/p&gt;

&lt;p&gt;Here's an example of how you might handle the login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate the credentials&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="nf"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid credentials"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch user data from server&lt;/span&gt;
    &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;userData&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// Handle user data&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;In this code, we're validating the credentials and fetching user data from a server. The &lt;code&gt;validateCredentials(username:password:)&lt;/code&gt; and &lt;code&gt;fetchUserData(username:completion:)&lt;/code&gt; methods are not defined here, as their implementation would depend on your specific use case.&lt;/p&gt;

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

&lt;p&gt;With this setup, you can create a simple login screen using a webview and handle the login in your native code. This approach is quick and easy, and it doesn't require a full hybrid app framework like Capacitor. It also allows you to reuse your web code across different platforms, which can save you a lot of time and effort.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ios</category>
      <category>mobile</category>
      <category>swift</category>
    </item>
    <item>
      <title>Microfrontends in mobile apps</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Wed, 19 Jul 2023 11:40:09 +0000</pubDate>
      <link>https://dev.to/chattywebviews/microfrontends-in-mobile-apps-176b</link>
      <guid>https://dev.to/chattywebviews/microfrontends-in-mobile-apps-176b</guid>
      <description>&lt;h2&gt;
  
  
  Microfrontends: Breaking Down the Monolith
&lt;/h2&gt;

&lt;p&gt;Microfrontends take the concept of microservices and apply it to the frontend. Instead of a monolithic frontend where all components are tightly coupled, microfrontends break the UI into smaller, independent pieces. Each microfrontend corresponds to a specific feature or business domain and can be developed, tested, and deployed independently.&lt;/p&gt;

&lt;p&gt;This approach is particularly beneficial for small teams or startups. It allows for faster iterations on individual features, reduces the scope of each update, and leads to a more agile development process. &lt;/p&gt;

&lt;p&gt;For instance, consider an e-commerce app. With a microfrontend approach, the product listing, shopping cart, and user profile could each be a separate microfrontend, developed and deployed independently. This reduces the risk of unexpected side effects and makes each deployment less risky.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webviews: A Bridge Between Native and Web
&lt;/h2&gt;

&lt;p&gt;Webviews are a powerful tool that can be used to implement a microfrontend architecture in mobile apps. They are essentially mini web browsers embedded within your app, capable of rendering HTML, CSS, and JavaScript. This means you can develop individual features of your app as mini web apps and then integrate them into your native app using webviews.&lt;/p&gt;

&lt;p&gt;Let's go back to our e-commerce app example. Suppose you want to develop the product listing, shopping cart, and user profile features separately. Each of these features can be developed as a separate web app by a web developer. The web developer can use their favorite framework (Angular, React, Vue) to create the UI and logic for these features. Once developed, these web apps can be integrated into the native app using webviews.&lt;/p&gt;

&lt;p&gt;This approach has several benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Reusability&lt;/strong&gt;: Since web technologies are platform-agnostic, the same code can be used for both iOS and Android apps. This can significantly reduce the development time and effort.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster Iterations&lt;/strong&gt;: Web developers can quickly iterate on the web app features without needing to understand the intricacies of native app development. This can lead to faster feature development and bug fixes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resource Optimization&lt;/strong&gt;: If you're a small team or a startup, you might not have the resources to hire separate iOS and Android developers. By using webviews, you can leverage your existing web development skills to create mobile apps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of it like having multiple smaller Ionic apps within your native app. Ionic serves the UI in a webview, much like what I'm proposing here. The difference is that instead of having one large Ionic app, you have multiple smaller web apps, each serving a specific feature of your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing ChattyWebviews
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://chattywebviews.com/"&gt;ChattyWebviews&lt;/a&gt; is a framework designed to streamline the communication between webviews and native classes in mobile apps.&lt;/p&gt;

&lt;p&gt;Here's a simple example of how you can use ChattyWebviews to send a message from a webview to a native class:&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="c1"&gt;// In your webview&lt;/span&gt;
&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update-items&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="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;itemsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-items&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's how you can listen for this message in your native class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your native class (iOS)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didReceive&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"get-items"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;verbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itemsDataStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ItemsResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"update-items"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDictionary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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;ChattyWebviews also supports Over-The-Air (OTA) updates. This means you can update the content of your webviews remotely, without needing to push an update through the app store. &lt;/p&gt;

&lt;p&gt;Using webviews to create microfrontends in our mobile apps can make our development process more efficient. It allows us to reuse code and iterate faster. ChattyWebviews, while still in its early stages and currently only supporting iOS (Android support is coming soon), can be a helpful tool in this approach.&lt;/p&gt;

&lt;p&gt;I hope this post has piqued your interest. I'm looking forward to hearing your thoughts and feedback. Happy coding! &lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Over-the-air updates for WKWebView content</title>
      <dc:creator>Teodor Dermendzhiev</dc:creator>
      <pubDate>Tue, 18 Jul 2023 12:03:44 +0000</pubDate>
      <link>https://dev.to/chattywebviews/over-the-air-updates-for-wkwebview-content-3cnb</link>
      <guid>https://dev.to/chattywebviews/over-the-air-updates-for-wkwebview-content-3cnb</guid>
      <description>&lt;h2&gt;
  
  
  ChattyWebviews is a simple tool based on Firebase to help you get more from your webviews
&lt;/h2&gt;

&lt;p&gt;Webviews enable developers to reuse code across different platforms, leading to significant time and resource savings. Major companies like Netflix have successfully leveraged webviews in their apps, using web technologies to deliver consistent user experiences across iOS and Android.&lt;/p&gt;

&lt;p&gt;Netflix, for instance, initially used webviews to rapidly experiment with and iterate on their user interface. As Jordanna Kwok, Senior Software Engineer at Netflix, explains in an interview with Hacking with Swift:&lt;br&gt;
&lt;strong&gt;"Webviews allowed us to A/B test large changes and get almost immediate feedback."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This ability to move fast and adapt quickly is especially crucial for startups and new product development, where the landscape is constantly changing and speed to market can be a key differentiator.&lt;/p&gt;

&lt;p&gt;One of the key advantages of webviews is the ability to update the JavaScript code remotely, allowing for real-time updates to the webview content. This is where Over-the-Air (OTA) updates come into play, enabling developers to push updates directly to the webview components of an app, bypassing the traditional app store approval process.&lt;/p&gt;

&lt;p&gt;In this blog post, I'll introduce you to &lt;a href="https://chattywebviews.com/"&gt;ChattyWebviews&lt;/a&gt;, a simple framework that bridges the gap between web and native environments. ChattyWebviews facilitates seamless communication between webviews and native classes, making it an good solution for developers aiming to maintain agility in their app updates and promote UI reusability across platforms.&lt;/p&gt;

&lt;p&gt;ChattyWebviews is not an alternative to native development or any cross-platform framework. It can be used alsongside any of them.&lt;/p&gt;

&lt;p&gt;I'll guide you through the process of implementing OTA updates for the webview components of a standard iOS app using ChattyWebviews. We'll create a simple app with a login screen living in a webview. This login screen will not only be reusable between iOS and Android but also updatable using ChattyWebviews' OTA update feature.&lt;/p&gt;

&lt;p&gt;So, let's dive in and explore how you can enhance your cross-platform app development process with Chatty Webviews and OTA updates.&lt;/p&gt;

&lt;p&gt;We'll be using a simple app called &lt;strong&gt;CWLoginDemo&lt;/strong&gt; for this purpose. You can create this app from scratch, or you can clone the project directly from &lt;a href="https://github.com/ChattyWebviews/CWLoginDemo"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The CWLoginDemo app consists of a home screen with a login button. When the login button is tapped, a modal screen is presented, displaying the login page. This login page is not a typical iOS view, but an Angular app running inside a webview.&lt;/p&gt;

&lt;p&gt;The login page we're using is a modified version of a &lt;a href="https://jasonwatmore.com/post/2015/03/10/angularjs-user-registration-and-login-example-tutorial"&gt;project&lt;/a&gt; by Jason Watmore. When the user enters their username and password into the fields on the login page, the native class will receive the message, process the login, and close the modal.&lt;br&gt;
Let's get into the details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up the CWLoginDemo App
&lt;/h2&gt;

&lt;p&gt;Start by creating a new iOS app project and name it &lt;strong&gt;CWLoginDemo&lt;/strong&gt;. Alternatively, you can clone the project directly from &lt;a href="https://github.com/ChattyWebviews/CWLoginDemo"&gt;here&lt;/a&gt;. The app should consist of a home screen with a login button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showingSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;


    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"globe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imageScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;large&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accentColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;showingSheet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;showingSheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kt"&gt;SheetView&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;showingSheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showingSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;showingSheet&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupXPModules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;//1&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setupXPModules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;module&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="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SheetView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewControllerRepresentable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CWMessageDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;typealias&lt;/span&gt; &lt;span class="kt"&gt;UIViewControllerType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWViewController&lt;/span&gt;

    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dismiss&lt;/span&gt;

    &lt;span class="c1"&gt;//3&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didReceive&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;//2&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getModules&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;scheduleVC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWebview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scheduleVC&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messageDelegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheduleVC&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;uiViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ChattyWebviews&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;CWViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&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;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView_Previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreviewProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ContentView&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;ol&gt;
&lt;li&gt;&lt;p&gt;First we need to initialize the web module with the name of our angular project contents (inside it's dist folder).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The makeUIViewController method returns a UIViewController, so we create the container of our webview app using WebViewFactory.createWebview. Remember to be careful with force unwrapping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Above we set the messageDelegate property to self so the SheetView struct receives messages from the login webview. Here we just print the message and dismiss it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, for this to work you will have to install &lt;a href="https://github.com/ChattyWebviews/ChattyWebviews"&gt;ChattyWebviews SDK&lt;/a&gt;. For the sake of this tutorial I used SPM, but you can also use Cocoapods or copy-paste the source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating the Webview Login Page
&lt;/h2&gt;

&lt;p&gt;The login page is an Angular app that we'll run inside a webview. When the login button on the home screen is tapped, a modal screen with the webview login page should be presented.&lt;/p&gt;

&lt;p&gt;First, install the &lt;a href="https://github.com/ChattyWebviews/chatty-webviews-js"&gt;chatty-webviews-js&lt;/a&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @chatty-webviews/js &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you have it installed, don't forget to call &lt;em&gt;attach()&lt;/em&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Component&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="s1"&gt;@angular/core&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;attach&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="s1"&gt;@chatty-webviews/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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.component.html&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;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;attach&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;Then in &lt;strong&gt;login.component.ts&lt;/strong&gt; I replaced the &lt;strong&gt;this.accountService.login&lt;/strong&gt; call with &lt;em&gt;sendMessage()&lt;/em&gt;. Here is the entire component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnInit&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="s1"&gt;@angular/core&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;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&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="s1"&gt;@angular/forms&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;sendMessage&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="s1"&gt;@chatty-webviews/js&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;AlertService&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="s1"&gt;../_services&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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login.component.html&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;LoginComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;submitted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;formBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;alertService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AlertService&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="nx"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&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;form&lt;/span&gt; &lt;span class="o"&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;formBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&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="c1"&gt;// convenience getter for easy access to form fields&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;f&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;()&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;submitted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// reset alerts on submit&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;alertService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// stop here if form is invalid&lt;/span&gt;
        &lt;span class="k"&gt;if&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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="p"&gt;;&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;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login-successful&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="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Teodor&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user enters their username and password into the fields on the login page, the native class will receive the message. This message should trigger the processing of the login and the closing of the modal.&lt;/p&gt;

&lt;p&gt;Now build the project:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;When the build completes drag the project folder from inside the dist directory to your Xcode project. It's important to select &lt;strong&gt;"Create folder references"&lt;/strong&gt;. If you leave &lt;strong&gt;"Copy items if needed"&lt;/strong&gt; unchecked you won't need to drag the package folder after every build, but it won't be in the Xcode project repository, so decide based on your project structure.&lt;/p&gt;

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

&lt;p&gt;Now, if you run the app and tap on the Login button you should see this:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Setting Up Your Environment for Over-the-Air Updates
&lt;/h2&gt;

&lt;p&gt;Before we dive into the process of implementing over-the-air updates, we need to set up our environment. This involves creating a Firebase project and deploying the backend function to Firebase. Here's how to do it:&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Firebase Project
&lt;/h2&gt;

&lt;p&gt;First, you'll need to create a Firebase project.&lt;/p&gt;

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

&lt;p&gt;As part of this process, make sure to enable authentication with email/password, storage, and the Firestore database. Please note that you'll need to have billing enabled to deploy the functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Clone the Backend Repository
&lt;/h2&gt;

&lt;p&gt;Next, clone the backend repository from here. Once you've cloned the repository, run &lt;code&gt;npm install&lt;/code&gt; to install the necessary dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install Firebase Tools
&lt;/h2&gt;

&lt;p&gt;If you haven't installed Firebase tools yet, you can do so with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; firebase-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, log in to Firebase using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Deploy Functions to Firebase
&lt;/h2&gt;

&lt;p&gt;Now, it's time to deploy the functions to Firebase. You can do this with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase deploy - only functions - project &amp;lt;your-project-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find your project ID by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase projects:list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If you encounter an error that says "Error: There was an error deploying functions" it's likely related to your Node.js version. Ensure you're using Node.js 16 (or the same version as specified in the functions' package.json):&lt;/em&gt;&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="nl"&gt;"engines"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16"&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;After you've deployed the function successfully, copy it's url from the function's dashboard:&lt;/p&gt;

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

&lt;p&gt;With these steps, you've successfully set up your environment for over-the-air updates. In the next section, we'll dive into how to implement these updates in your iOS app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing the updates
&lt;/h2&gt;

&lt;p&gt;Now that we have our environment set up, we can proceed to implement over-the-air updates. This process involves using the ChattyWebviews CLI and setting up some environment variables on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install ChattyWebviews CLI
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/ChattyWebviews/chatty-webviews-cli"&gt;ChattyWebviews CLI&lt;/a&gt; is a command-line interface that simplifies the process of managing your ChattyWebviews projects. To install it, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @chatty-webviews/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Set Up Environment Variables
&lt;/h2&gt;

&lt;p&gt;Next, you need to set up some environment variables on your machine. These variables will store the configuration details of your Firebase project. Here's how to do it:&lt;/p&gt;

&lt;p&gt;On a Unix-based system like Linux or macOS, you can set environment variables using the &lt;code&gt;export&lt;/code&gt; command:&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;export &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_api_key&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_AUTH_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_auth_domain&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_project_id&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_STORAGE_BUCKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_storage_bucket&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_app_id&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, you can use the &lt;code&gt;set&lt;/code&gt; command:&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;set &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_api_key&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_AUTH_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_auth_domain&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_project_id&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_STORAGE_BUCKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_storage_bucket&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;FIREBASE_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your_app_id&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands set the environment variables for the current session only. If you want to set them permanently, you'll need to add these lines to your shell's startup file (&lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.bash_profile&lt;/code&gt;, or &lt;code&gt;.zshrc&lt;/code&gt; for Unix-based systems, or Environment Variables on Windows).&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;your_api_key&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;your_auth_domain&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;your_project_id&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;your_storage_bucket&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;your_app_id&amp;gt;&lt;/code&gt; with your actual Firebase project details. You can get them by creating a web project from the Firebase console:&lt;/p&gt;

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

&lt;p&gt;With the ChattyWebviews CLI installed and your environment variables set, you're now ready to push over-the-air updates to your app. First, tell ChattyWebviews more about your project by calling this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chatty init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you for email, password and the path to your package directory - in our case I pasted the absolute path to &lt;em&gt;dist/demo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now go to &lt;strong&gt;login.component.ts&lt;/strong&gt; and update something in the UI - e.g. replace the login button title with capital letters.&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="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;[disabled]=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"spinner-border spinner-border-sm me-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
   LOGIN
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the package:&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;p&gt;And push the update to Firebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chatty ci release --version 1.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before testing the update you need to add few things to the setupXPModules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setupXPModules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ModuleSynchronizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateCheckUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your-checkForUpdate-url&amp;gt;"&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CWModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;currentHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kt"&gt;WebViewFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="kt"&gt;ModuleSynchronizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateIfNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"tester@test.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"cw-login-demo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Module &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; updated! Please restart the app."&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;When you run the app the login screen will be updated.&lt;/p&gt;

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

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
