<?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: Angelo Stavrow</title>
    <description>The latest articles on DEV Community by Angelo Stavrow (@angelostavrow).</description>
    <link>https://dev.to/angelostavrow</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%2F296093%2F806867ab-11c5-4589-8cac-8f4b82b17221.jpg</url>
      <title>DEV Community: Angelo Stavrow</title>
      <link>https://dev.to/angelostavrow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/angelostavrow/"/>
    <language>en</language>
    <item>
      <title>Stupid SwiftUI Tricks: Debugging Sheet Dismissal</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 14 Sep 2020 18:49:01 +0000</pubDate>
      <link>https://dev.to/writeas/stupid-swiftui-tricks-debugging-sheet-dismissal-1p60</link>
      <guid>https://dev.to/writeas/stupid-swiftui-tricks-debugging-sheet-dismissal-1p60</guid>
      <description>&lt;p&gt;Last week, I spent some time solving an odd bug with the WriteFreely client's iOS app.&lt;/p&gt;

&lt;p&gt;When you're looking at the list of posts, you can tap the gear button to get to the settings screen, which presents you with a form for logging into your WriteFreely instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yNpIzguU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/nLAzPbZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yNpIzguU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/nLAzPbZ.png" alt='"Screenshot of settings screen on an iPhone"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you might expect, you can tap the close button in the upper-right (ⓧ) to dismiss the sheet.&lt;/p&gt;

&lt;p&gt;Except… well, if you tapped into one of the login form's fields, you end up in a state where tapping on the close button didn't seem to have any effect.&lt;/p&gt;

&lt;p&gt;Okay, so that's not entirely true — if you added a print statement to the button's action, you'd find that the first tap &lt;em&gt;does&lt;/em&gt; register, toggling presenting view's the &lt;code&gt;isPresentingSettingsView&lt;/code&gt; flag correctly; it just doesn't have any effect.&lt;/p&gt;

&lt;p&gt;The workaround, while I'd been testing the app, was to dismiss the sheet is by swiping down on it — a standard (if somewhat undiscoverable) system gesture.&lt;/p&gt;

&lt;p&gt;Interestingly, when you'd tap in any form field, you'd also receive the following warning in Xcode's console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2020-09-11 09:56:01.927435-0400 WriteFreely-MultiPlatform[37593:6860302] [Presentation] Attempt to present &amp;lt;_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x7fb24a7297f0&amp;gt; on &amp;lt;_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7fb24c905ac0&amp;gt; (from &amp;lt;_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementGVS_18StyleContextWriterVS_23ContentListStyleContext___: 0x7fb24a711ec0&amp;gt;) which is already presenting &amp;lt;_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x7fb24c80eaf0&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot of cruft there, but it hints that SwiftUI is trying to present a view that's already being presented. This suggested to me that the hosting view is getting re-rendered when a login form field becomes the first responder, finds that the &lt;code&gt;isPresentingSettingsView&lt;/code&gt; flag is set, and tries to present the sheet again.&lt;/p&gt;

&lt;p&gt;Okay! This is something we can test! Here's what the settings view looked like:&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;struct&lt;/span&gt; &lt;span class="kt"&gt;SettingsView&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;@EnvironmentObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;WriteFreelyModel&lt;/span&gt;

    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&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;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;largeTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;Spacer&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="nv"&gt;action&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;isPresented&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="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&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;"xmark.circle"&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="kt"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login Details"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;AccountView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="kt"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Appearance"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;PreferencesView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preferences&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="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;(For debugging purposes, I've simplified this a &lt;em&gt;tiny&lt;/em&gt; bit: originally that &lt;code&gt;HStack&lt;/code&gt; was in a separate &lt;code&gt;SettingsHeaderView&lt;/code&gt; struct.)&lt;/p&gt;

&lt;p&gt;To test the hypothesis, I started by commenting out the entire &lt;code&gt;Form&lt;/code&gt;. Everything then worked fine in presenting and dismissing the sheet, but of course, it's not a very useful sheet without that form. 😅&lt;/p&gt;

&lt;p&gt;If I just included the appearance form, that works fine too. That narrows things down here — or so I thought.&lt;/p&gt;




&lt;p&gt;There are two ways to dismiss a sheet. The first is to pass the hosting view's presentation state as a binding to the presented sheet, which is what you see in the above listing. Simplified, the &lt;code&gt;SettingsView&lt;/code&gt; is presented from the &lt;code&gt;PostListView&lt;/code&gt; like this:&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="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&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;isPresentingSettingsView&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="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&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;"gear"&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;isPresentingSettingsView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;SettingsView&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="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;isPresentingSettingsView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use @Environment(.presentationMode) in the &lt;code&gt;SettingsView&lt;/code&gt; to dismiss itself. You declare the property wrapper at the top of the struct like so:&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;@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;presentationMode&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;presentationMode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and call its &lt;code&gt;dismiss()&lt;/code&gt; method in a button action, like so:&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="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;presentationMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wrappedValue&lt;/span&gt;&lt;span class="o"&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="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&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;"xmark.circle"&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;Interestingly enough, using this method to dismiss the sheet no longer triggered the console warning when I tapped into any login form field. Could it be? Was the problem solved? 😃&lt;/p&gt;

&lt;p&gt;Nope. 😬&lt;/p&gt;

&lt;p&gt;If you filled out the form and logged in, then that same warning was logged three times in the console. If you logged out, the warning was logged again. But this looked like progress! It seemed likely that something in the account views was triggering this, so I explored that a little deeper.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AccountView&lt;/code&gt; swaps between an &lt;code&gt;AccountLoginView&lt;/code&gt; and an &lt;code&gt;AccountLogoutView&lt;/code&gt; based on the state of an &lt;code&gt;isLoggedIn&lt;/code&gt; flag in the &lt;code&gt;AccountModel&lt;/code&gt;. My prime suspect was the &lt;code&gt;AccountLoginView&lt;/code&gt;, which has an &lt;code&gt;.alert(isPresented:)&lt;/code&gt; modifier attached to it. If there's an error logging in, this is triggered and an alert is presented depending on which of the three &lt;code&gt;AccountError&lt;/code&gt; cases are present. Because the &lt;code&gt;.alert(isPresented:)&lt;/code&gt; and &lt;code&gt;.sheet(isPresented:)&lt;/code&gt; modifiers work similarly, maybe some wires were getting crossed there? This is, of course, a beta framework running on a beta operating system in a beta IDE!&lt;/p&gt;

&lt;p&gt;So, I started with an easy test: commenting out the &lt;code&gt;.alert(isPresented:)&lt;/code&gt; modifier, and see what happens on login. &lt;/p&gt;

&lt;p&gt;You guessed it: this doesn't change the behaviour — the warnings are still logged, and the sheet can't be dismissed.&lt;/p&gt;

&lt;p&gt;Digging further and further, setting breakpoints and stepping through code, commenting out blocks to see if they were the culprit, got me nowhere. I finally started searching DuckDuckGo for &lt;code&gt;SwiftUI "Attempt to present" "which is already presenting"&lt;/code&gt; and eventually found this &lt;a href="https://forums.swift.org/t/actionsheet-and-modal-seems-have-bug-in-navigationview/29590/10"&gt;year-old forum comment on Swift.org&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is it the current recommendation, to put &lt;em&gt;modal&lt;/em&gt; views &amp;amp; the triggers outside &lt;code&gt;NavigationView&lt;/code&gt;, or is it only to circumvent an existing bug?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤦&lt;/p&gt;

&lt;p&gt;Yep. Taking the &lt;code&gt;.sheet(isPresented:)&lt;/code&gt; modifier out of the PostListView and attaching it to an EmptyView outside of the NavigationView solved the issue. Nothing in the docs on &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview"&gt;NavigationView&lt;/a&gt;, &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview-view-modifiers"&gt;View Modifiers&lt;/a&gt;, or &lt;a href="https://developer.apple.com/documentation/swiftui/navigationview/sheet(ispresented:ondismiss:content:)"&gt;sheet&lt;/a&gt; suggests this could be a thing.&lt;/p&gt;




&lt;p&gt;So, yeah, the title of this post is a bit misleading — it turns out that I spent a couple of hours trying to figure out what was happening, when an undocumented bug in the framework was the cause.&lt;/p&gt;

&lt;p&gt;Again: this is a beta framework, on a beta operating system, and frankly the amount of SwiftUI documentation that's already out there is surprisingly good. But it's a little frustrating to have spent a couple of hours debugging a warning that could have been avoided with a one-line disclaimer in the documentation. Hopefully, this will be helpful to anyone that searches for a similar issue!&lt;/p&gt;

&lt;p&gt;For those of you that want to see the code, &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/commit/6ea10fb434393fd5a19f34f35a9c768617eb32cc"&gt;here's the fix&lt;/a&gt; in the app.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>swift</category>
      <category>ios</category>
      <category>showdev</category>
    </item>
    <item>
      <title>WriteFreely Mac/iOS App: A Progress Update</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Fri, 28 Aug 2020 20:21:58 +0000</pubDate>
      <link>https://dev.to/writeas/writefreely-mac-ios-app-a-progress-update-15o8</link>
      <guid>https://dev.to/writeas/writefreely-mac-ios-app-a-progress-update-15o8</guid>
      <description>&lt;p&gt;It feels like it was just yesterday when I &lt;a href="https://dev.to/angelostavrow/the-promise-vs-reality-of-swiftui-multiplatform-4pf9"&gt;shared some initial screenshots&lt;/a&gt; of the WriteFreely app prototype running on iOS and Mac.&lt;/p&gt;

&lt;p&gt;(In fact, it was almost four weeks ago.)&lt;/p&gt;

&lt;p&gt;I've been making a lot of progress on this, and I'd love to show you where the app is today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pictures, Worthy of A Thousand Words
&lt;/h2&gt;

&lt;p&gt;How about starting with a few screenshots? This is subject to change, but it's likely not going to change &lt;em&gt;much&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfeHuJPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/HLSuQGs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfeHuJPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/HLSuQGs.png" alt='"iPhones and iPad showing various screenshots of the WriteFreely app"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above, you'll see the current version of the app, running on iPhone and iPad.&lt;/p&gt;

&lt;p&gt;The first iPhone screenshot shows the list of posts in my &lt;em&gt;Testing&lt;/em&gt; collection, a private blog which I'm using for —you guessed it— testing. Tapping on the &lt;strong&gt;&amp;lt; Collections&lt;/strong&gt; navigation link at the top-left will let you choose another collection; other buttons above and below the list let you create a new local post, refresh from the server, and access settings and preferences.&lt;/p&gt;

&lt;p&gt;Tap on any post in the list, and you'll be taken to the app's editor, as shown in the second iPhone screenshot. Any edits you make here are saved locally, and if you like, you can publish it to your WriteFreely instance.&lt;/p&gt;

&lt;p&gt;The iPad screenshot shows the same navigation hierarchy, and like the iPhone version, it works just as you'd expect on iPadOS — a sidebar to choose the collection, a table to choose a post in the collection, and the selected post in the editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q_8sli4i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/JlLaWv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q_8sli4i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/JlLaWv2.png" alt='"The WriteFreely app running on the Mac"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the app running on the Mac — the same kind of navigation, as suited to the Mac. You can more clearly see how the app's editor departs a bit from the web editor, too, with a defined area for the post's title (if you choose to add one), and another text area below for your post's content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WCmD4EId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/wDaM902.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WCmD4EId--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/wDaM902.png" alt="&amp;quot;The WriteFreely app in 'dark mode' on iPhone and Mac&amp;quot;"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh, and of course, "dark mode" looks great. You can choose to have the app display a light scheme, a dark scheme, or to simply follow your system's setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will It Post?
&lt;/h2&gt;

&lt;p&gt;The basic flow of the app right now is straightforward. On launch, you're not logged in to your account, but you can create new posts that only live on your device. These posts currently show a "draft" badge, though that's going to &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues/36"&gt;get a less-confusing name&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of the time of writing, the app doesn't yet store anything on disk except for your colour-scheme preference and your logged-in state, so if you quit the app —or if it crashes, which at this stage, it does every so often— you lose whatever you had. So, naturally, &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues/16"&gt;adding local storage&lt;/a&gt; is a priority.&lt;/p&gt;

&lt;p&gt;To guard against that, you can log in to your WriteFreely account to pull in your collections and posts from the server. Those posts show up as "published", and you can edit them, at which point the "published" badge changes to "edited"; when you're done, you can push those changes to the server, which has the "edited" badge go back to "published".&lt;/p&gt;

&lt;p&gt;Similarly, your local posts (the ones with the "draft" badge) can be published to your WriteFreely Drafts —see why it needs to be renamed?— at which point the badge changes to "published".&lt;/p&gt;

&lt;p&gt;Tapping the reload button in the post list will fetch new content from the server. Because data loss is unacceptable, the app checks to see what the last-updated timestamps are between the local and remote copies of a post, and then applies the following rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the local copy is unchanged and the remote copy is newer, replace the local copy with the remote copy;&lt;/li&gt;
&lt;li&gt;If the local copy has unpublished changes and the remote copy is older, discard the remote copy; and finally,&lt;/li&gt;
&lt;li&gt;If the local copy has unpublished changes and the remote copy is newer, notify the user and prompt them to make a decision:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vPvmJ5eh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/XG1RChc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vPvmJ5eh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/XG1RChc.png" alt='"A portion of a screenshot warning that a newer copy of the post exists on the server."'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This last bit is still under construction. Once the work is complete, you'll be able to choose which copy to keep — and of course, you'll be prompted to confirm your choice, since this will replace the local copy with the remote copy, or vice-versa.&lt;/p&gt;

&lt;p&gt;If you log out of your account, everything is purged from the device — your collection list, your local &lt;em&gt;and&lt;/em&gt; published posts, your logged in state, everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still To Do
&lt;/h2&gt;

&lt;p&gt;The easiest thing would be just link to the &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/issues?q=is%3Aopen+is%3Aissue+milestone%3A1.0.0-beta"&gt;list of GitHub issues for the 1.0 beta&lt;/a&gt; — but I'm going to discuss some of the more pertinent things here, to save you a click.&lt;/p&gt;

&lt;p&gt;Of course, saving content to your device is going to be important. There are also a couple of bugs that need fixing because they hang the app or make buttons unresponsive, so that's no good. But mostly, these to-do items fix get the user experience close to that of the WriteFreely web app.&lt;/p&gt;

&lt;p&gt;For example — right now the app launches and drops you in the post list. It should instead drop you in the editor with a new (or recent) local post. Those local posts default to Drafts (i.e., not part of a collection), but you should be able to choose a collection for them.&lt;/p&gt;

&lt;p&gt;Departing from other WriteFreely clients, there's an "All Posts" list which, as the name implies, shows all posts across your Drafts and your collections, all together. It doesn't, however, tell you which post belongs to which collection (neither does the editor), so that needs fixing.&lt;/p&gt;

&lt;p&gt;And there are a few things that haven't been added as issues just yet —and maybe they won't make it into the first release— that include, off the top of my head:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorting the list (by created date or last-updated date, ascending or descending)&lt;/li&gt;
&lt;li&gt;Changing the logout behaviour to keep only your local posts, and adding another setting to delete everything (the current logout behaviour)&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're getting closer and closer to a public beta, so that's exciting! And if you want to &lt;a href="https://write.as/angelo/contributing-to-the-writefreely-swiftui-client"&gt;contribute&lt;/a&gt;, please don't hesitate to &lt;a href="https://discuss.write.as/t/writefreely-swiftui-multiplatform-app-mac-iphone-ipad/1664"&gt;reach out in this forum topic&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>writefreely</category>
      <category>showdev</category>
    </item>
    <item>
      <title>
The Promise vs Reality of SwiftUI Multiplatform</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 03 Aug 2020 20:16:37 +0000</pubDate>
      <link>https://dev.to/writeas/the-promise-vs-reality-of-swiftui-multiplatform-4pf9</link>
      <guid>https://dev.to/writeas/the-promise-vs-reality-of-swiftui-multiplatform-4pf9</guid>
      <description>&lt;p&gt;Happy August, friends! How about an update to &lt;a href="https://dev.to/angelostavrow/writefreely-swiftui-client-35l2"&gt;last week's post&lt;/a&gt; on the SwiftUI app for WriteFreely?&lt;/p&gt;

&lt;p&gt;Work progressed pretty well on the shared code last week, to the point that we've got a preliminary UI that displays a list of posts, which you can filter by collection. Tapping on any post loads it into an editor, where you can make changes to the title and content.&lt;/p&gt;

&lt;p&gt;On iPhone and iPad, there's also a new post button that will create a new draft. Why only on iPhone and iPad? Well, pull up a chair, and let's chat a little bit about the gap between the &lt;em&gt;promise&lt;/em&gt; and the &lt;em&gt;reality&lt;/em&gt; of multi-platform SwiftUI apps.&lt;/p&gt;

&lt;p&gt;While I was working on the post, I &lt;a href="https://twitter.com/AngeloStavrow/status/1287828639527182337"&gt;tweeted&lt;/a&gt; that I managed to flesh out some of the key UI features for the app across iPhone, iPad, and Mac in &lt;em&gt;a day&lt;/em&gt;, thanks to SwiftUI and live previews in Xcode 12, which show you what your views will look like next to your code, and update in realtime &lt;em&gt;as&lt;/em&gt; you code.&lt;/p&gt;

&lt;p&gt;I further stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is building a native app with the speed of iteration that I've only ever encountered when building for the web.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(I'm looking at you, HTML and CSS. I am &lt;em&gt;not&lt;/em&gt; looking at you, React.)&lt;/p&gt;

&lt;p&gt;So here's what that latest iteration looks like so far, on iPhone and iPad:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4oXdnOet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/32UKJhY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oXdnOet--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/32UKJhY.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, after I installed the macOS 11 (a.k.a. "Big Sur") beta, I was able to build the Mac app to compare:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--He1Te_oL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/KT6IOMu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--He1Te_oL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.snap.as/KT6IOMu.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is in no way a final UI! There's plenty of work still to be done, including replacing the unusual "picker" view for selecting the Collection with a proper sidebar, and adapting the spacing a little better on the Mac app (you can't preview anything for a Mac target, even in Xcode, until you upgrade your own machine to the macOS 11 beta).&lt;/p&gt;

&lt;h2&gt;
  
  
  The DX of SwiftUI, as illustrated by &lt;code&gt;ToolbarItemPlacement&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One thing that's very helpful in moving quickly with SwiftUI is how its declarative nature lets you specify the &lt;em&gt;semantics&lt;/em&gt; of an element's placement in your view, and letting the framework figure out what that should mean on iPhone vs iPad vs Mac. A great example of this focus on developer experience (DX) is the &lt;a href="https://developer.apple.com/documentation/swiftui/toolbaritemplacement"&gt;&lt;code&gt;ToolbarItemPlacement&lt;/code&gt;&lt;/a&gt; API, which lets you semantically state that a particular item that you add to a toolbar is a &lt;code&gt;.primaryAction&lt;/code&gt;, or a &lt;code&gt;.status&lt;/code&gt; element, and so on.&lt;/p&gt;

&lt;p&gt;This is used in the TextEditor view to place a post status badge in the &lt;code&gt;.status&lt;/code&gt; area and a publish button in the &lt;code&gt;.primaryAction&lt;/code&gt; area. You can see in the screenshots I shared above how these elements are placed differently depending on whether it's being shown on an iPhone, an iPad, or a Mac — and I didn't have to write a single line of code to customize the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Letdown of SwiftUI, as illustrated by broken buttons
&lt;/h2&gt;

&lt;p&gt;But life in SwiftUI is not all roses. And that's where the new-draft feature isn't quite working fully, yet. Building the iPhone and iPad versions of the app, tapping the new-draft button above the list of posts works beautifully — it slides a new draft into the list, and then opens it in the text editor for your next great essay. I was thrilled to have this working by the end of my workday on Friday.&lt;/p&gt;

&lt;p&gt;I finished installing the Big Sur beta on my Mac this morning, excited to see how everything looked and worked in the Mac app. I expected to have some little UI refinements (that list of posts, for example, needs a healthy dose of padding), but otherwise figured that it'd be a similar experience to, at the very least, the iPad app.&lt;/p&gt;

&lt;p&gt;And mostly, it is. The picker is replaced by a popup menu, and you can choose a collection to narrow down the list of posts. You can tap a post in the list, and it'll load it in the text editor, ready for changes. If the post's status changes, it'll instantly update the badge both in the toolbar and in the post list.&lt;/p&gt;

&lt;p&gt;And then I tried to create a new draft. On the Mac, clicking on that new-draft button (which appears, interestingly enough, next to the post-status badge in the screenshot) does… nothing. Not a thing. No errors are thrown, no views are drawn, nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reality of Multiplatform
&lt;/h2&gt;

&lt;p&gt;I suppose I shouldn't use the word &lt;em&gt;letdown&lt;/em&gt;, because "multiplatform" is &lt;em&gt;not&lt;/em&gt; a panacea: the reality is that an app expects different things between an iPhone, an iPad, and a Mac. The issue I'm facing with that new-draft button isn't some failing of SwiftUI; it's that the paradigm for how a Mac app treats navigation vs buttons vs instantiating new windows is very different from that of an iPhone, and I haven't structured things the way the framework expects.&lt;/p&gt;

&lt;p&gt;So, fine. I'll need to spend a little more time on adapting the Mac client. I would have &lt;em&gt;liked&lt;/em&gt; it if it all Just Worked™, but I've got to say: despite a couple of little issues that I need to fix, I'm really impressed with just how much of it worked right out of the box, with no extra work on my part.&lt;/p&gt;

&lt;p&gt;Native-app development may not yet happen at the pace of web development, but it's closer than ever.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>showdev</category>
      <category>writefreely</category>
    </item>
    <item>
      <title>SwiftUI Previews: Fix 'Cannot convert value of type SomeType to expected argument type EnvironmentObject&lt;SomeType&gt;' errors</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Fri, 31 Jul 2020 18:36:19 +0000</pubDate>
      <link>https://dev.to/angelostavrow/swiftui-previews-fix-cannot-convert-value-of-type-sometype-to-expected-argument-type-environmentobject-sometype-errors-2nk4</link>
      <guid>https://dev.to/angelostavrow/swiftui-previews-fix-cannot-convert-value-of-type-sometype-to-expected-argument-type-environmentobject-sometype-errors-2nk4</guid>
      <description>&lt;p&gt;Using &lt;code&gt;@EnvironmentObject&lt;/code&gt; is a great way to &lt;a href="https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views"&gt;share data between your views&lt;/a&gt;. Previously, I'd been using &lt;code&gt;@ObservedObject&lt;/code&gt;s all over my views, and it felt clumsy.&lt;/p&gt;

&lt;p&gt;Hot tip: by setting an &lt;code&gt;environmentObject&lt;/code&gt; for a &lt;code&gt;NavigationView&lt;/code&gt;, any children of this &lt;code&gt;NavigationView&lt;/code&gt; can then add a property like &lt;code&gt;@EnvironmentObject var someType: SomeType&lt;/code&gt;. SwiftUI then gives them access to the observed object, without you having to pass the object down the navigation tree and through views that don't need access to it:&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="cm"&gt;/* ContentView.swift */&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&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;@ObservedObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;someType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SomeType&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;NavigationView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;MyView&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;environmentObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;someType&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="cm"&gt;/* MyView.swift */&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyView&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="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;SomeTypeList&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="cm"&gt;/* SomeTypeList.swift */&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SomeTypeList&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;@EnvironmentObject&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;someType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SomeType&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="c1"&gt;// Do something with someType&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;But! If you were passing it in to a child view as an &lt;code&gt;ObservedObject&lt;/code&gt; and had set up some test object for use in your SwiftUI preview? If you try to pass in your test object, you'll get an error:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cannot convert value of type '&lt;code&gt;SomeType&lt;/code&gt;' to expected argument type '&lt;code&gt;EnvironmentObject&amp;lt;SomeType&amp;gt;&lt;/code&gt;'&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To fix this, use the &lt;code&gt;environmentObject&lt;/code&gt; modifier on your child view's preview provider:&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;SomeTypeList_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;SomeTypeList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testSomeType&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;Yay, your preview works again!&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
    </item>
    <item>
      <title>WriteFreely SwiftUI Client</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 27 Jul 2020 17:00:29 +0000</pubDate>
      <link>https://dev.to/writeas/writefreely-swiftui-client-35l2</link>
      <guid>https://dev.to/writeas/writefreely-swiftui-client-35l2</guid>
      <description>&lt;p&gt;Hot on the heels of the developer-preview release of the &lt;a href="https://write.as/angelo/writefreely-swift-package-v0-1-0-released"&gt;WriteFreely Swift package&lt;/a&gt;, I'm happy to announce that work has started on a new (but related) project: a WriteFreely multiplatform SwiftUI client app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A multiwhatsit who now?
&lt;/h2&gt;

&lt;p&gt;Right. Let's clear up the jargon, first.&lt;/p&gt;

&lt;p&gt;Apple announced &lt;a href="https://developer.apple.com/xcode/swiftui/"&gt;SwiftUI&lt;/a&gt; at their World Wide Developer Conference (WWDC) last year. Initially, SwiftUI let you build user interfaces across all Apple platforms —iPhone, iPad, Mac, Watch, and Apple TV— with a single, declarative framework, ostensibly replacing the need for using AppKit (on the Mac) and UIKit (on iOS) in building your apps.&lt;/p&gt;

&lt;p&gt;This year, Apple took SwiftUI further: you can now build entire apps in SwiftUI. And while you can still build a platform-specific app (e.g., just for the Mac, or just for Apple TV), a new template named &lt;em&gt;multiplatform&lt;/em&gt; further coalesces developing for the Apple ecosystem.&lt;/p&gt;

&lt;p&gt;A multiplatform app project in Xcode contains a &lt;strong&gt;Shared&lt;/strong&gt; folder, in which the bulk of your app's code lives. There are also &lt;strong&gt;iOS&lt;/strong&gt; and &lt;strong&gt;macOS&lt;/strong&gt; folders for platform-specific customizations, but the idea is that you can use SwiftUI components to build an app that will run natively on iPhone, iPad, and Mac with almost no such customization necessary.&lt;/p&gt;

&lt;p&gt;That's pretty cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, what exactly is being built?
&lt;/h2&gt;

&lt;p&gt;This is a WriteFreely client app. You'll be able to log in to your WriteFreely instance, write new drafts, publish them to a collection, and edit them, thanks to the WriteFreely Swift package. And it'll be available for iPhone, iPad, and Mac, thanks to SwiftUI.&lt;/p&gt;

&lt;p&gt;Just like the Swift package, it'll be built in the open — the repository is &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform"&gt;on GitHub&lt;/a&gt;, and if you're interested in helping, check out the &lt;a href="https://github.com/writeas/writefreely-swiftui-multiplatform/blob/main/CONTRIBUTING.md"&gt;CONTRIBUTING&lt;/a&gt; doc. Developers of all experience levels are welcome to join — just let me know in &lt;a href="https://discuss.write.as/t/writefreely-swiftui-multiplatform-app-mac-iphone-ipad/1664"&gt;this forum topic&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The goal is to go from a new SwiftUI project to a complete working app in time for the release of iOS 14 and macOS 11. The launch usually happens in September, so we want to have the app done by the end of August. That's a tight deadline, so we're limiting the scope initially to handle the key actions WriteFreely users would want: creating drafts, publishing them to their WriteFreely instance (either as a draft, or to a collection), and updating those posts.&lt;/p&gt;

&lt;p&gt;As we build the client app, we'll be updating the Swift package as necessary. As far as I know, this will be the first WriteFreely client to consume the package, so I expect that we'll find a few surprises that trip us up as we go along. While I tried to build it as robustly as possible, that's the nature of packages that wrap an API — you only find out about their shortcomings when you start building projects with them! 😅&lt;/p&gt;

&lt;p&gt;I'll be posting here on both the progress of the work, and the learnings that inevitably come from building a new app in a beta IDE, against a beta framework, while running beta operating systems. I've already found one UX deficiency in &lt;a href="https://angelostavrow.com/post/using-swift-packages-in-multiplatform-swiftui-apps/"&gt;adding packages to multiplatform apps&lt;/a&gt;, and I'm sure there will be more surprises to share.&lt;/p&gt;

&lt;p&gt;More TK!&lt;/p&gt;

</description>
      <category>writefreely</category>
      <category>swift</category>
      <category>swiftui</category>
      <category>showdev</category>
    </item>
    <item>
      <title>WriteFreely Swift Package</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Mon, 29 Jun 2020 17:56:15 +0000</pubDate>
      <link>https://dev.to/angelostavrow/writefreely-swift-package-b9g</link>
      <guid>https://dev.to/angelostavrow/writefreely-swift-package-b9g</guid>
      <description>&lt;p&gt;It's been a while since I posted an update here, and I wanted to announce a new project: a &lt;a href="https://github.com/writeas/writefreely-swift"&gt;WriteFreely Swift package&lt;/a&gt; that you can drop into your Mac and iOS apps.&lt;/p&gt;

&lt;p&gt;You can find it here: &lt;a href="https://github.com/writeas/writefreely-swift"&gt;https://github.com/writeas/writefreely-swift&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's WriteFreely?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://writefreely.org/"&gt;WriteFreely&lt;/a&gt; is an open-source platform for writing on the web. It powers the Write.as service (find me &lt;a href="https://write.as/angelo"&gt;here&lt;/a&gt;!), and lets you build writing communities on the web.&lt;/p&gt;

&lt;p&gt;I was introduced to Write.as and WriteFreely while working with Glitch, where I got to chat about the service and &lt;a href="https://write.as/principles"&gt;its principles&lt;/a&gt; with Matt and CJ. Matt wrote a thoughtful &lt;a href="https://glitch.com/glimmer/post/write-as-privacy-centric-web-platforms"&gt;post on Glimmer&lt;/a&gt; about the importance of privacy for creating on the web, and CJ built a tonne of cool sample apps that connected to the Write.as service for their &lt;a href="https://glitch.com/@writeas"&gt;Glitch team&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cool, Tell Me More About The Project
&lt;/h2&gt;

&lt;p&gt;This project represents a couple of firsts for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the first time I've worked on a Swift package&lt;/li&gt;
&lt;li&gt;This is the first time I've wrapped a RESTful API in Swift&lt;/li&gt;
&lt;li&gt;This is the first time I've worked with &lt;a href="https://developer.apple.com/documentation/foundation/urlsession"&gt;&lt;code&gt;URLSession&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/swift/result"&gt;&lt;code&gt;Result&lt;/code&gt;&lt;/a&gt; in Swift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now, it's an alpha/developer-preview release. There's a lot of room for improvement here, and I'm looking forward to working towards a 1.0 with the WF community.&lt;/p&gt;

&lt;p&gt;As I mention in &lt;a href="https://discuss.write.as/t/writefreely-swift-package/1564"&gt;this forum topic&lt;/a&gt; and my &lt;a href="https://write.as/angelo/writefreely-swift-package-v0-1-0-released"&gt;Write.as post&lt;/a&gt;, the design for the &lt;code&gt;WriteFreelyClient&lt;/code&gt; is to leverage completion blocks that return a &lt;code&gt;Result&lt;/code&gt; tuple with either a &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Post&lt;/code&gt;, or &lt;code&gt;Collection&lt;/code&gt; (or an array of these types where that makes sense), or an &lt;code&gt;Error&lt;/code&gt; on failure. That makes it pretty easy to build completion handlers:&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;loginHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;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;do&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&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;get&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;"Hello, &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&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="k"&gt;catch&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;error&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="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://your.writefreely.host/"&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;fatalError&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;WriteFreelyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&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="s"&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="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;loginHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next For The Project?
&lt;/h2&gt;

&lt;p&gt;Good question! There are definitely some major to-do items that are obvious to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a test suite (there will be some refactoring required to facilitate this)&lt;/li&gt;
&lt;li&gt;Create generic-ish request templates to &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; out the &lt;code&gt;WriteFreelyClient&lt;/code&gt; public methods&lt;/li&gt;
&lt;li&gt;Extend for use with the Write.as platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mostly, though, I'm excited for people to try it out and let me know how it works for them!&lt;/p&gt;

</description>
      <category>writeas</category>
      <category>swift</category>
      <category>writefreely</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Dev Diary Week 4 - Saving to SQLite</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Fri, 15 May 2020 16:02:05 +0000</pubDate>
      <link>https://dev.to/glitch/dev-diary-week-4-saving-to-sqlite-eoe</link>
      <guid>https://dev.to/glitch/dev-diary-week-4-saving-to-sqlite-eoe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is an entry in a weekly development diary on building a feed-aggregator-based blog on &lt;a href="https://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt;. Only a small part of building an app is code; the bulk of your time is spent planning, experimenting, making mistakes, getting frustrated, and making progress in baby steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/glitch/dev-diary-week-3-parsing-opml-files-3kic"&gt;Last time&lt;/a&gt;, I set up an OPML file with some default feeds that get passed to the feedparsing API. This is a good start, but we don't want the site to want to have to request feeds every time it's loaded — that takes time, and slow sites are a terrible user experience. It's time to add a database!&lt;/p&gt;

&lt;p&gt;As I mentioned, storing the feed content in a database saves us from having to send packets back and forth across the internet to get content every time the site loads. There are a couple of practical considerations here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How often should we check for new content?&lt;/li&gt;
&lt;li&gt;How do we deal with updates to content that's already in the database?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first question is fairly straightforward. We'll want to track the last time we checked for new content on each feed and, if it's been more than a certain amount of time, we can check again for new content. The actual time interval here doesn't matter; what does matter is knowing that we'll be tracking this.&lt;/p&gt;

&lt;p&gt;The second question is the kind of thing that sounds like it could become very complex, but should be straightforward because of the project scope I defined in &lt;a href="https://dev.to/glitch/dev-diary-week-2-reading-a-feed-4mno"&gt;week 2&lt;/a&gt;. Specifically, we'll be generating a &lt;a href="https://en.wikipedia.org/wiki/Linklog"&gt;linklog&lt;/a&gt;-style blog that only shares a title, a link back to the source content, and &lt;em&gt;maybe&lt;/em&gt; a summary if we can get one from the feed. As such, we don't have to worry too much about how to handle the &lt;em&gt;content&lt;/em&gt; of the post changing, though this doesn't help in the case of "&lt;a href="https://github.com/Ranchero-Software/NetNewsWire/blob/master/Technotes/Reruns.md"&gt;reruns&lt;/a&gt;" — something all feed-reading apps have to deal with.&lt;/p&gt;

&lt;p&gt;So, this week, I'm planning on doing the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determining what the database structure should look like&lt;/li&gt;
&lt;li&gt;Creating a way to check when the feed's database entry was last updated&lt;/li&gt;
&lt;li&gt;Creating a way to add feed items to the database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This feels a bit ambitious, as I haven't really worked with SQLite before, but we'll see how it goes!&lt;/p&gt;

&lt;p&gt;I think we can start with two tables here: a &lt;code&gt;feeds&lt;/code&gt; table, which will store metadata about each feed, and an &lt;code&gt;entries&lt;/code&gt; table, which will store entries from the various feeds. The theoretical &lt;a href="https://www.sqlite.org/limits.html"&gt;limit&lt;/a&gt; for the number of rows in any SQLite table is 2&lt;sup&gt;64&lt;/sup&gt;, though that will realistically be constrained by the 140TB maximum database size — still &lt;em&gt;plenty&lt;/em&gt; of room for storing the data we're interested in.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;feed&lt;/code&gt; table will start off with the following rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; (&lt;code&gt;INTEGER&lt;/code&gt;): the unique ID for the row, autoincrementing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; (&lt;code&gt;TEXT&lt;/code&gt;): the feed title&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feedUrl&lt;/code&gt; (&lt;code&gt;TEXT&lt;/code&gt;): the url of the feed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastFetched&lt;/code&gt; (&lt;code&gt;INTEGER&lt;/code&gt;): the timestamp when we last checked the feed (as Unix Time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;entry&lt;/code&gt; table will start off with the following rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; (&lt;code&gt;INTEGER&lt;/code&gt;): the unique ID for the row, autoincrementing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;guid&lt;/code&gt; (&lt;code&gt;TEXT&lt;/code&gt;): the feed GUID for the entry&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; (&lt;code&gt;TEXT&lt;/code&gt;): the entry title&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;entryUrl&lt;/code&gt; (&lt;code&gt;TEXT&lt;/code&gt;): the url of the entry&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;publishDate&lt;/code&gt; (&lt;code&gt;INTEGER&lt;/code&gt;): the date and time when the entry was published (as Unix Time)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feedId&lt;/code&gt;: a foreign key matching this entry to the correct feed in the &lt;code&gt;feeds&lt;/code&gt; table&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How did it go?
&lt;/h2&gt;

&lt;p&gt;All the planning and research work took a lot more time than I expected! Implementing persistent storage is more complex than the work I've been doing so far, and while I'm fairly comfortable working in MySQL or T-SQL, I've never worked with SQLite before, so I needed to do more digging to understand its limitations and built-in types, for example.&lt;/p&gt;

&lt;p&gt;I added a &lt;strong&gt;&lt;a href="https://glitch.com/edit/#!/rss-firehose-save2sqlite?path=storageController.js"&gt;storageController.js&lt;/a&gt;&lt;/strong&gt; file that has some methods for initializing and updating the database. These still need some work, but basically here's how &lt;code&gt;storageController&lt;/code&gt; works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the app loads, it calls &lt;code&gt;initialize()&lt;/code&gt;, which checks to see if the database file already exists. If it does, great! It will just output the number of feeds and entries to the server's console. If it doesn't, it'll create it based on a given schema.&lt;/li&gt;
&lt;li&gt;When the database needs to be updated, it calls &lt;code&gt;insertNewestEntries()&lt;/code&gt; and passes in a &lt;code&gt;feedObject&lt;/code&gt; from &lt;strong&gt;feedparser.js&lt;/strong&gt;. It then follows a couple of paths:

&lt;ul&gt;
&lt;li&gt;If the feed already exists in the database, it adds any new entries (according to what the latest &lt;code&gt;publishDate&lt;/code&gt; is in the feed's stored articles).&lt;/li&gt;
&lt;li&gt;If it doesn't, it creates the record for the feed's metadata in the &lt;code&gt;feed&lt;/code&gt; table, and then inserts as many articles as it has in the &lt;code&gt;entry&lt;/code&gt; table.&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;There are a few helper functions in the module as well, for things like getting the ID of a feed, and getting the last published date for a given feed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What went well?
&lt;/h3&gt;

&lt;p&gt;We're using the &lt;a href="https://github.com/mapbox/node-sqlite3"&gt;&lt;code&gt;sqlite3&lt;/code&gt;&lt;/a&gt; npm module and it's really quite a pleasure to work with. I had some example code to look at from the original starter app, which was very helpful.&lt;/p&gt;

&lt;h3&gt;
  
  
  What did I have trouble with?
&lt;/h3&gt;

&lt;p&gt;Learning the ins and outs of a new database engine can take a while! There were a lot of new concepts to wrap my head around and it took me a lot longer than I thought to get this work done — I didn't post an update last week because of it! This is why it's important to build some flexibility into your estimates.&lt;/p&gt;

&lt;h3&gt;
  
  
  What did I learn?
&lt;/h3&gt;

&lt;p&gt;I learned a whole lot about using SQLite! While I'm pretty comfortable writing SQL, I'd never used this particular database engine. Luckily, once things are set up, writing queries is pretty similar to MySQL or T-SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  What will I work on next week?
&lt;/h3&gt;

&lt;p&gt;I still have some work to do here! For example, when insert the newest entries in the database, the code doesn't update the feed's &lt;code&gt;lastFetched&lt;/code&gt; property. We'll need that later.&lt;/p&gt;

&lt;p&gt;We also need some way to retrieve everything as a single combined feed, where the entries are ordered by published date. This will be the basis for the content we show on the blog!&lt;/p&gt;

&lt;p&gt;And finally, we need to remove some of the endpoints I created in the first couple of weeks — they should be replaced by one to refresh all feeds that are found in the OPML file. Right now I'm adding a &lt;code&gt;DISALLOW_WRITE&lt;/code&gt; flag to be sure that no one can add stuff to the database via the &lt;code&gt;/api/parse/:feed&lt;/code&gt; endpoint.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/rss-firehose-save2sqlite?path=index.html" alt="rss-firehose-save2sqlite on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Do you have a rule of thumb for estimates?
&lt;/h2&gt;

&lt;p&gt;You never know what kind of interesting problems you'll stumble up when you're working on an app, but it's almost a guarantee that it'll take longer than you expect. How much of a "safety margin" do you generally add to your estimates for unexpected surprises? Has that margin gotten smaller as you get more experienced? What kind of factors, if any, influence your safety margins?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://glitch.com/pricing?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=handbook&amp;amp;utm_content=dev"&gt;Give your Glitch apps superpowers - keep them awake, lift rate limits, and get more memory and disk space.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Dev Diary Week 3 - Parsing OPML Files</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Thu, 30 Apr 2020 15:48:04 +0000</pubDate>
      <link>https://dev.to/glitch/dev-diary-week-3-parsing-opml-files-3kic</link>
      <guid>https://dev.to/glitch/dev-diary-week-3-parsing-opml-files-3kic</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is an entry in a weekly development diary on building a feed-aggregator-based blog on &lt;a href="https://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt;. Only a small part of building an app is code; the bulk of your time is spent planning, experimenting, making mistakes, getting frustrated, and making progress in baby steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/glitch/dev-diary-week-2-reading-a-feed-4mno"&gt;Last time&lt;/a&gt;, I worked on an API endpoint for parsing RSS feeds. It lets you pass in an RSS feed URL, and returns some JSON feed metadata and an array of feed entries. That's a good start! However, I want to combine entries from multiple feeds into one "content firehose" feed, so this week we'll look at how to do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing an OPML file
&lt;/h2&gt;

&lt;p&gt;Now, I &lt;em&gt;could&lt;/em&gt; hard-code an array for feed URLs (maybe as a JSON file), but there's an open standard called &lt;a href="http://dev.opml.org/"&gt;OPML&lt;/a&gt; that I can use instead. This is an "outline" file, and essentially each entry for a feed URL looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;outline&lt;/span&gt;
  &lt;span class="na"&gt;text=&lt;/span&gt;&lt;span class="s"&gt;"What's displayed (usually title)"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Title of feed"&lt;/span&gt;
  &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"rss"&lt;/span&gt;
  &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"RSS"&lt;/span&gt;
  &lt;span class="na"&gt;htmlUrl=&lt;/span&gt;&lt;span class="s"&gt;"https://link/to/site/"&lt;/span&gt;
  &lt;span class="na"&gt;xmlUrl=&lt;/span&gt;&lt;span class="s"&gt;"https://link/to/feed/"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The advantage to using an OPML file is that they're portable. You can export your susbcriptions to an OPML file to use with this app, and anyone can get your OPML file and import it into their feed reader of choice. Here's &lt;a href="https://gist.github.com/AngeloStavrow/804c557c5e9e7f39433dde145d953fda"&gt;mine&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;So, this week, I'm planning on doing the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a default OPML file for this app.&lt;/li&gt;
&lt;li&gt;Parse the OPML file to get each individual feed URL.&lt;/li&gt;
&lt;li&gt;Pass each feed to the feed parsing endpoint.&lt;/li&gt;
&lt;li&gt;Collect all feed entries as JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And, just as we used the &lt;a href="https://www.npmjs.com/package/feedparser/"&gt;feedparser&lt;/a&gt; npm package last week, we'll use the &lt;a href="https://www.npmjs.com/package/opmlparser/"&gt;opmlparser&lt;/a&gt; package (by the same author) to handle the parsing work this week.&lt;/p&gt;
&lt;h2&gt;
  
  
  How did it go?
&lt;/h2&gt;

&lt;p&gt;I managed to complete all of this work in about two hours between meetings, thanks (again) to great example code in the &lt;a href="https://github.com/danmactough/node-opmlparser"&gt;opmlparser GitHub repository&lt;/a&gt;. This serves as a great reminder that writing great docs is just as important as writing solid code for your library/framework/app!&lt;/p&gt;

&lt;p&gt;I worked off a &lt;a href="https://glitch.com/~rss-firehose-parseopml?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;remix&lt;/a&gt; of my &lt;a href="https://glitch.com/~rss-firehose?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;existing work&lt;/a&gt; and added the opmlparser package to &lt;strong&gt;package.json&lt;/strong&gt;, and then added the &lt;a href="https://glitch.com/edit/#!/rss-firehose-parseopml?path=opmlparser.js%3A1%3A0"&gt;opmlparser.js&lt;/a&gt; file to build out the functionality I needed. I also added &lt;a href="https://glitch.com/edit/#!/rss-firehose-parseopml?path=public%2Fsubscriptions.opml%3A1%3A0"&gt;a &lt;strong&gt;subscriptions.opml&lt;/strong&gt; file&lt;/a&gt; to the &lt;strong&gt;public&lt;/strong&gt; directory that included two feeds: the Glitch team's posts on Dev.to, and my own posts on Dev.to. This will give us an interesting de-duplication problem to solve for next week, since both feeds contain my posts.&lt;/p&gt;

&lt;p&gt;I then added a &lt;code&gt;/opml&lt;/code&gt; &lt;a href="https://glitch.com/edit/#!/rss-firehose-parseopml?path=server.js%3A55%3A0"&gt;route&lt;/a&gt; to &lt;strong&gt;server.js&lt;/strong&gt; which does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gets the feeds in the &lt;strong&gt;subscriptions.opml&lt;/strong&gt; file;&lt;/li&gt;
&lt;li&gt;Sends them to the &lt;code&gt;/api/parse/:feed&lt;/code&gt; endpoint;&lt;/li&gt;
&lt;li&gt;Returns the feed content from each feed in the OPML file to the caller.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will probably change over time — for now, I wanted a way to see that the OPML file was being parsed correctly.&lt;/p&gt;
&lt;h3&gt;
  
  
  What went well?
&lt;/h3&gt;

&lt;p&gt;Integrating the opmlparser library was really straightforward. Since it was built by the same author, the code in &lt;strong&gt;opmlparser.js&lt;/strong&gt; is pretty much the same as in &lt;strong&gt;feedparser.js&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  What did I have trouble with?
&lt;/h3&gt;

&lt;p&gt;I got a bit lost in async spaghetti while trying to send each feed in the OPML file to the &lt;code&gt;/api/parse/:feed&lt;/code&gt; endpoint. 😂 There's lots of room for improvement here, but because I expect to rewrite this elsewhere, I didn't want to spend &lt;em&gt;too&lt;/em&gt; much time on it.&lt;/p&gt;
&lt;h3&gt;
  
  
  What did I learn?
&lt;/h3&gt;

&lt;p&gt;I learned a lot about OPML files! I'm used to working with JSON data, and looking at XML sometimes takes a while to figure out. At the end of the day, though, OPML is a variation on an outline document. Thinking about it this way helped me reason through things.&lt;/p&gt;
&lt;h3&gt;
  
  
  What will I work on next week?
&lt;/h3&gt;

&lt;p&gt;Next week, I'm going to start working on getting feeds into a database!&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/rss-firehose-parseopml?path=index.html" alt="rss-firehose-parseopml on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  What's in &lt;em&gt;your&lt;/em&gt; OPML file?
&lt;/h2&gt;

&lt;p&gt;OPML files were created to be shared! They're an open standard that most any feed reader will import or export. Here's what's in &lt;a href="https://gist.github.com/AngeloStavrow/804c557c5e9e7f39433dde145d953fda"&gt;mine&lt;/a&gt; — share yours in the comments!&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Dev Diary Week 2 - Reading a Feed</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Thu, 23 Apr 2020 15:09:50 +0000</pubDate>
      <link>https://dev.to/glitch/dev-diary-week-2-reading-a-feed-4mno</link>
      <guid>https://dev.to/glitch/dev-diary-week-2-reading-a-feed-4mno</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is an entry in a weekly development diary on building a feed-aggregator-based blog on &lt;a href="https://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt;. Only a small part of building an app is code; the bulk of your time is spent planning, experimenting, making mistakes, getting frustrated, and making progress in baby steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;As I discussed in &lt;a href="https://dev.to/glitch/dev-diary-week-1-the-kickoff-1ed5/"&gt;last week's entry&lt;/a&gt;, I'm building a web app that will do three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pull in &lt;a href="https://cyber.harvard.edu/rss/rss.html"&gt;RSS&lt;/a&gt; feeds of my content from various platforms;&lt;/li&gt;
&lt;li&gt;combine them into a single superfeed; and&lt;/li&gt;
&lt;li&gt;automatically generate a blog from that firehose feed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we look at this project as a black box, here's how it should work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INPUT          +---------------+          OUTPUTS
               |               |
               |               +----&amp;gt; Aggregated RSS feed
OPML file ----&amp;gt;+ ~rss-firehose |
               |               +----&amp;gt; "Content firehose" blog
               |               |
               +---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In other words, to get your own "content firehose" blog, you would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://glitch.com/help/remix/"&gt;Remix&lt;/a&gt; the &lt;code&gt;~rss-firehose&lt;/code&gt; Glitch app.&lt;/li&gt;
&lt;li&gt;Drop in an &lt;a href="http://dev.opml.org/"&gt;OPML file&lt;/a&gt; of your own content feeds.&lt;/li&gt;
&lt;li&gt;There is no step three!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We're not even close to this yet — right now, &lt;code&gt;~rss-firehose&lt;/code&gt; is just a simple remix of the &lt;code&gt;~hello-sqlite&lt;/code&gt; starter app on Glitch. Let's think about the steps to get us from where we are, to where we want to be! As a side benefit, this will give me a rough outline for the work I want to do each week. I only have a few hours per week to dedicate to this project, so I want to be thoughtful about how I approach that time.&lt;/p&gt;

&lt;p&gt;Roughly speaking, here's how I expect things to go. This might change! I'm &lt;em&gt;estimating&lt;/em&gt; based on a few things, like how comfortable I am working in Node, SQLite, and how deeply I understand the overall scope of the project. I've built &lt;a href="https://themes.gohugo.io/indigo/"&gt;blog templates&lt;/a&gt; before, but I've never built a feed reader!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/glitch/dev-diary-week-1-the-kickoff-1ed5/"&gt;Week 1&lt;/a&gt;:&lt;/strong&gt; Define the scope of the project ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 2:&lt;/strong&gt; Pull in a single RSS feed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 3:&lt;/strong&gt; Parse an OPML file into multiple RSS feeds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 4:&lt;/strong&gt; Save feed entries to a SQLite database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 5:&lt;/strong&gt; Generate an aggregated "firehose" RSS feed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 6:&lt;/strong&gt; Create a basic &lt;a href="https://en.wikipedia.org/wiki/Linklog"&gt;linklog&lt;/a&gt; front-end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 7:&lt;/strong&gt; Improve the front-end design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With our pre-requisite week 1 done, let's move on to week 2.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reading an RSS Feed
&lt;/h2&gt;

&lt;p&gt;Here's what I plan on doing this week; the next section will discuss how it went, and what I learned.&lt;/p&gt;

&lt;p&gt;We want to implement a way to read a single RSS feed in our app. Now, we &lt;em&gt;could&lt;/em&gt; figure out how to implement a basic RSS feed reader, but there are a few different formats — I can think of at least three different versions of the RSS spec itself, plus &lt;a href="https://tools.ietf.org/html/rfc4287/"&gt;Atom&lt;/a&gt;, plus... well, you get the point.&lt;/p&gt;

&lt;p&gt;Instead, we can build upon the work of those that came before us. RSS has been around for a &lt;em&gt;very&lt;/em&gt; long time, so there are great packages we can use in our project to parse feeds in our app.&lt;/p&gt;

&lt;p&gt;The package I see being used most in similar projects is &lt;a href="https://www.npmjs.com/package/feedparser/"&gt;feedparser&lt;/a&gt;, so we're going to add that to our app. I've never used feedparser before, so I'm going use my time budget for the week experimenting with reading a single RSS feed and outputting it to the server console.&lt;/p&gt;

&lt;p&gt;Just to make it a bit more fun, I'll work with &lt;a href="https://dev.to/feed/angelostavrow/"&gt;my own Dev.to feed&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  How did it go?
&lt;/h2&gt;

&lt;p&gt;I worked on the app over a few days, for a total of about four hours.&lt;/p&gt;

&lt;p&gt;I started by remixing the &lt;a href="https://glitch.com/~rss-firehose/?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;actual app&lt;/a&gt; so that it's not affected by this week's work. In &lt;a href="https://glitch.com/~rss-firehose-readfeed/?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;my remix&lt;/a&gt;, I added the feedparser package to &lt;strong&gt;package.json&lt;/strong&gt; and got down to work.&lt;/p&gt;

&lt;p&gt;When I'm working off a remix of a Glitch starter app, I usually start by deleting all the files and code I don't need. I'm skipped that this time, because it's nice to have some boilerplate code that I can examine for the SQLite stuff that will come later.&lt;/p&gt;

&lt;p&gt;I then added &lt;a href="https://glitch.com/edit/#!/rss-firehose-readfeed?path=feedparser.js:1:0"&gt;feedparser.js&lt;/a&gt;, which takes a feed, tries to parse it, and returns a &lt;code&gt;feedResponse&lt;/code&gt; object. The parsing-related work was mostly taken straight from the &lt;a href="https://github.com/danmactough/node-feedparser#usage"&gt;feedparser package README&lt;/a&gt;. The &lt;code&gt;feedResponse&lt;/code&gt; object has two properties that get initialized as the parser does its thing: a &lt;code&gt;meta&lt;/code&gt; object containing feed metadata, and an &lt;code&gt;items&lt;/code&gt; array for feed entries.&lt;/p&gt;

&lt;p&gt;Finally, I added &lt;a href="https://glitch.com/edit/#!/rss-firehose-readfeed?path=server.js:54:0"&gt;an &lt;code&gt;/api/parse/:feed&lt;/code&gt; endpoint to server.js&lt;/a&gt; — you can add a feed address to this endpoint in the browser, and it'll return the JSON of the &lt;code&gt;feedResponse&lt;/code&gt; object. Try it by opening &lt;a href="https://rss-firehose-readfeed.glitch.me/api/parse/https%3A%2F%2Fdev.to%2Ffeed%2Fangelostavrow%2F"&gt;this link&lt;/a&gt; in a new tab, or add an RSS/Atom feed of your choice at the end of this link (but make sure it's &lt;a href="https://www.urlencoder.org/"&gt;url-encoded&lt;/a&gt;): &lt;a href="https://rss-firehose-readfeed.glitch.me/api/parse/"&gt;https://rss-firehose-readfeed.glitch.me/api/parse/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I usually like to ask myself a few questions at the end of my workweek, and I think they're a good fit here too.&lt;/p&gt;
&lt;h3&gt;
  
  
  What went well?
&lt;/h3&gt;

&lt;p&gt;Generally, adding the feedparser library went well. The README and sample code is thorough enough that I could follow along and make it do what I needed it to do. &lt;/p&gt;
&lt;h3&gt;
  
  
  What did I have trouble with?
&lt;/h3&gt;

&lt;p&gt;I'm not very experienced with using the (&lt;a href="https://github.com/request/request/issues/3142"&gt;deprecated&lt;/a&gt;) &lt;a href="https://www.npmjs.com/package/request/"&gt;request&lt;/a&gt; library, and spent a while trying to get port the feedparser sample code to newer libraries in vain. Not wanting to blow my time budget for the week porting code that otherwise worked fine, I decided instead to just stick with request. I can always change that later.&lt;/p&gt;
&lt;h3&gt;
  
  
  What did I learn?
&lt;/h3&gt;

&lt;p&gt;I learned how to use the feedparser library! I didn't dive into any of the more complex example code with additional dependencies, because the feeds I tested with worked fine as-is.&lt;/p&gt;
&lt;h3&gt;
  
  
  What will I work on next week?
&lt;/h3&gt;

&lt;p&gt;Next week, I'm going to work on adding some feeds to an OPML file, parsing that file, and handing them off to the &lt;code&gt;/api/parse/:feed&lt;/code&gt; endpoint I created this week!&lt;/p&gt;

&lt;p&gt;So, that's week 2 wrapped up! Here's where the app is at now:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/rss-firehose-readfeed?path=index.html" alt="rss-firehose-readfeed on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Do you use a feed reader?
&lt;/h2&gt;

&lt;p&gt;Personally, I like &lt;a href="https://ranchero.com/netnewswire/"&gt;NetNewsWire&lt;/a&gt;, and I sync feeds between my devices using &lt;a href="https://feedbin.com/"&gt;Feedbin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which on is your favourite? Let me know in the comments!&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Dev Diary Week 1 - The Kickoff</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Thu, 09 Apr 2020 15:06:49 +0000</pubDate>
      <link>https://dev.to/glitch/dev-diary-week-1-the-kickoff-1ed5</link>
      <guid>https://dev.to/glitch/dev-diary-week-1-the-kickoff-1ed5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post is an entry in a weekly development diary on building a feed-aggregator-based blog on &lt;a href="https://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt;. Only a small part of building an app is code; the bulk of your time is spent planning, experimenting, making mistakes, getting frustrated, and making progress in baby steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The build
&lt;/h2&gt;

&lt;p&gt;Like many of you, my online life is lived across several platforms: I've got a &lt;a href="https://angelostavrow.com/"&gt;personal blog&lt;/a&gt;, a &lt;a href="https://micro.blog/angelostavrow/"&gt;microblog&lt;/a&gt;, a &lt;a href="https://www.makebeforebreak.com"&gt;podcast&lt;/a&gt;, &lt;a href="https://github.com/AngeloStavrow/"&gt;open-source projects on GitHub&lt;/a&gt;, and &lt;a href="https://www.flickr.com/photos/astavrow/"&gt;photography on Flickr&lt;/a&gt;, and my posts here, to name a few. Some of these are more active than others, so I'd like to create a way to pull all of that content together, and turn them into a firehose of my stuff that can be shared in a single place.&lt;/p&gt;

&lt;p&gt;I plan on building this on &lt;a href="https://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt;. What's Glitch, you say? Glitch lets you build full-stack web applications right in the browser, for free, &lt;em&gt;and&lt;/em&gt; deploys your changes live to the web as you type. It also lets me remix any public app, giving me a perfect copy of it to kick off my own work. You can learn more about Glitch &lt;a href="https://glitch.com/create"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For my app, remixing the &lt;a href="https://dev.to/glitch/hello-to-the-new-hello-sqlite-5044"&gt;&lt;code&gt;~hello-sqlite&lt;/code&gt;&lt;/a&gt; starter app gives me a nice jumping-off point. It's a full-stack app powered by &lt;a href="https://nodejs.org/"&gt;Node.js&lt;/a&gt; and &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;, backed by a &lt;a href="https://www.sqlite.org/"&gt;SQLite&lt;/a&gt; database. It's embedded below:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/rss-firehose?path=index.html" alt="rss-firehose on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;I'm also taking some inspiration from &lt;a href="https://glitch.com/~lmo-feeder"&gt;&lt;code&gt;~lmo-feeder&lt;/code&gt;&lt;/a&gt;, too! Just like that project, I'll add a pretty front-end on it and have an auto-generated blog. For the first iteration, I'll create that with the &lt;a href="https://pugjs.org/"&gt;Pug&lt;/a&gt; templating engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it all works
&lt;/h2&gt;

&lt;p&gt;The magic that lets me do all of this is &lt;a href="https://cyber.harvard.edu/rss/rss.html"&gt;RSS&lt;/a&gt;. Essentially, RSS feeds are an XML-based document that can be subscribed to in a feed reader. You can see the &lt;a href="https://dev.to/feed/angelostavrow/"&gt;feed of my posts here on dev.to&lt;/a&gt;, which looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;rss&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;channel&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Angelo Stavrow&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;author&amp;gt;&lt;/span&gt;Angelo Stavrow&lt;span class="nt"&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Web and iOS/Mac developer. Podcaster. Your biggest fan.&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;https://dev.to/angelostavrow&lt;span class="nt"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;language&amp;gt;&lt;/span&gt;en&lt;span class="nt"&gt;&amp;lt;/language&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;create-react-app and Express, together on Glitch&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;author&amp;gt;&lt;/span&gt;Angelo Stavrow&lt;span class="nt"&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;pubDate&amp;gt;&lt;/span&gt;Fri, 14 Feb 2020 15:52:54 +0000&lt;span class="nt"&gt;&amp;lt;/pubDate&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;https://dev.to/glitch/create-react-app-and-express-together-on-glitch-28gi&lt;span class="nt"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;guid&amp;gt;&lt;/span&gt;https://dev.to/glitch/create-react-app-and-express-together-on-glitch-28gi&lt;span class="nt"&gt;&amp;lt;/guid&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;p&lt;span class="ni"&gt;&amp;amp;gt;&amp;amp;lt;&lt;/span&gt;a href="http://glitch.com?utm_medium=weblink&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_source=dev.to&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_campaign=blog&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_content=dev"&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;Glitch&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/a&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; is the fastest way to get an app or site up and running live on the web, but it runs apps on only one port. Sometimes you need more, like when you're building a &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;a href="https://glitch.com/culture/react-starter-kit/?utm_medium=weblink&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_source=dev.to&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_campaign=blog&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_content=dev"&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;React front end&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/a&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; with create-react-app and the back-end with &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;a href="https://glitch.com/~hello-express?utm_medium=weblink&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_source=dev.to&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_campaign=blog&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;amp;utm_content=dev"&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;Express&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/a&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;. Here's how to work around this constraint, with a proxy middleware and some port-switching logic!&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/p&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;

      [...]

      &lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/channel&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/rss&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of different variations on this format, but they're all nicely defined. To define the feeds I'm interested in, my plan is to use an &lt;a href="http://dev.opml.org/"&gt;OPML file&lt;/a&gt;. OPML is another XML-based format in which you can list a set of RSS feeds. Many RSS readers use OPML files for importing and exporting your list of feed subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking out loud
&lt;/h2&gt;

&lt;p&gt;While I do keep a "normal" journal, I'm very new to the idea of a development journal. I first tried this in February while working on another app, and found it to be very helpful! Writing out my thought process, my learnings, and my frustrations keeps me moving forward and provides a historical record that's far richer than any collection of code comments and commit messages, in my experience.&lt;/p&gt;

&lt;p&gt;(You should still write useful comments and commits, though!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you keep a development journal?
&lt;/h2&gt;

&lt;p&gt;If so, have you found it to be helpful? If not, is there a reason you don't? Let me know in the comments!&lt;/p&gt;

</description>
      <category>rss</category>
      <category>express</category>
      <category>sqlite</category>
      <category>showdev</category>
    </item>
    <item>
      <title>create-react-app and Express, together on Glitch</title>
      <dc:creator>Angelo Stavrow</dc:creator>
      <pubDate>Fri, 14 Feb 2020 15:52:54 +0000</pubDate>
      <link>https://dev.to/glitch/create-react-app-and-express-together-on-glitch-28gi</link>
      <guid>https://dev.to/glitch/create-react-app-and-express-together-on-glitch-28gi</guid>
      <description>&lt;p&gt;&lt;a href="http://glitch.com?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Glitch&lt;/a&gt; is the fastest way to get an app or site up and running live on the web, but it runs apps on only one port. Sometimes you need more, like when you're building a &lt;a href="https://glitch.com/culture/react-starter-kit/?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;React front end&lt;/a&gt; with create-react-app and the back-end with &lt;a href="https://glitch.com/~hello-express?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;Express&lt;/a&gt;. Here's how to work around this constraint, with a proxy middleware and some port-switching logic!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Remote development on Glitch has a lot of benefits — it's simply the fastest way to get an app or site up and running live on the web, abstracting away of a lot of the fiddly parts of web development. While this very much makes it a go-to for most ideas that I want to build, I will occasionally run into little speed-bumps that aren't really an issue in a typical local-development setup.&lt;/p&gt;

&lt;p&gt;One such speed-bump is that Glitch only serves content on one port. This is typically not an issue, unless you're trying to run both a webpack development server for front-end work &lt;em&gt;and&lt;/em&gt; a back-end server in the same project, at the same time — you get one port for serving resources, but both the front-end and back-end servers each want their own port! This is a common scenario when you're building your front end with &lt;a href="https://create-react-app.dev/"&gt;create-react-app&lt;/a&gt;, and your back end with &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;What's more — Glitch's watcher provides live previews of your changes to a file, but so does create-react-app's Webpack development server!&lt;/p&gt;

&lt;p&gt;Ugh. Can't we all just get along?&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;Spoiler: Yes. Yes, we can.&lt;/em&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;I ran into exactly this problem when our team built the &lt;a href="https://capitol-royale.glitch.me"&gt;web app&lt;/a&gt; for Capitol Records' &lt;a href="https://capitolroyale.com"&gt;&lt;em&gt;Capitol Royale&lt;/em&gt;&lt;/a&gt; event in November.&lt;/p&gt;

&lt;p&gt;When you create a Node app on Glitch based on the &lt;a href="https://glitch.com/~hello-express?utm_medium=weblink&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=blog&amp;amp;utm_content=dev"&gt;hello-express starter app&lt;/a&gt;, the starter project includes a &lt;strong&gt;server.js&lt;/strong&gt; file that implements a basic Express server. This server handles routing to various parts of your app, and is set up to listen on port 3000.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Similarly, if you use &lt;code&gt;create-react-app&lt;/code&gt; to, uh, create a React app, &lt;code&gt;react-scripts&lt;/code&gt; starts a webpack development server that, by default, &lt;em&gt;also&lt;/em&gt; listens on port 3000.&lt;/p&gt;

&lt;p&gt;So what happens when you're working on a React app, but you also want to add an Express backend?&lt;/p&gt;

&lt;p&gt;Essentially, we got it working such that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you set your &lt;code&gt;start&lt;/code&gt; script to &lt;code&gt;"npm run production"&lt;/code&gt; in &lt;strong&gt;package.json&lt;/strong&gt;, it will build the React app and Express will serve the static bundle over port 3000. &lt;em&gt;BUT!&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;If you set your &lt;code&gt;start&lt;/code&gt; script to &lt;code&gt;"npm run development"&lt;/code&gt; in &lt;strong&gt;package.json&lt;/strong&gt;, it will concurrently start the webpack dev server/watcher &lt;em&gt;and&lt;/em&gt; the Express server. The latter will be listening on port 3001, &lt;em&gt;but&lt;/em&gt; you don't need to change anything in your code because: proxies!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  😲 WHAT IS THIS SORCERY‽
&lt;/h2&gt;

&lt;p&gt;This works thanks to a couple of moving parts: a proxy that listens for requests on a certain port and forwards them to another, and a bit of scripting and server-side logic that checks for an environment variable to know which port to listen to. Let's dig in!&lt;/p&gt;

&lt;h3&gt;
  
  
  The proxy
&lt;/h3&gt;

&lt;p&gt;Since we only have one port to work with, we want to watch for requests to some endpoints and forward them through a proxy to our backend server.&lt;/p&gt;

&lt;p&gt;If you create &lt;strong&gt;src/setupProxy.js&lt;/strong&gt;, React will register it automatically when you start the development server (&lt;a href="https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually"&gt;details here&lt;/a&gt;). So, add this to your &lt;strong&gt;src/setupProxy.js&lt;/strong&gt; file:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http-proxy-middleware&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This proxy redirects requests to /api endpoints to&lt;/span&gt;
&lt;span class="c1"&gt;// the Express server running on port 3001.&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3001&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;What React and the proxy are doing here, essentially, is working together to say, "okay, any request to &lt;code&gt;/api&lt;/code&gt; &lt;em&gt;isn't&lt;/em&gt; a request for a static asset, so pass it on to the target" — that target being the Express server. Don't forget to add &lt;a href="https://github.com/chimurai/http-proxy-middleware"&gt;&lt;code&gt;http-proxy-middleware&lt;/code&gt;&lt;/a&gt; to your &lt;strong&gt;package.json&lt;/strong&gt; file, since   &lt;strong&gt;src/setupProxy.js&lt;/strong&gt; &lt;code&gt;require&lt;/code&gt;s it.&lt;/p&gt;

&lt;p&gt;Fun fact! "Globbing" is a weird-sounding word, but what it means is that just adding "&lt;code&gt;/api&lt;/code&gt;" in the proxy is enough to correctly route "&lt;code&gt;/api/ping&lt;/code&gt;", "&lt;code&gt;/api/user/:userid&lt;/code&gt;", etc., to the target — we don't have to add every possible route in the function, which makes our lives much easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  The ports
&lt;/h3&gt;

&lt;p&gt;With proxying in place, the port situation is less confusing now. However, Glitch will still only serve one port, so we have to do some switching based on what mode we're working in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;development&lt;/code&gt; mode, the webpack dev server listens on port &lt;code&gt;3000&lt;/code&gt;, so we set Express to listen to port &lt;code&gt;3001&lt;/code&gt;, and proxy any requests to &lt;code&gt;/api&lt;/code&gt; endpoints through as described above.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;production&lt;/code&gt; mode, there &lt;em&gt;is&lt;/em&gt; no webpack dev server, so we set Express to listen to port &lt;code&gt;3000&lt;/code&gt;, and Express serves the static resources directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The switching
&lt;/h3&gt;

&lt;p&gt;Depending on whether you &lt;code&gt;npm run production&lt;/code&gt; or &lt;code&gt;npm run development&lt;/code&gt;, different servers and/or proxies are started, &lt;em&gt;and&lt;/em&gt; &lt;code&gt;NODE_ENV&lt;/code&gt; is set to either &lt;code&gt;production&lt;/code&gt; or &lt;code&gt;development&lt;/code&gt; — you don't want to have to change this in an &lt;strong&gt;.env&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Setting the value of &lt;code&gt;NODE_ENV&lt;/code&gt; is best done in &lt;strong&gt;package.json&lt;/strong&gt;'s &lt;code&gt;scripts&lt;/code&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;"scripts"&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;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_ENV=development concurrently --kill-others &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run client&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm run server&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build &amp;amp;&amp;amp; NODE_ENV=production npm run server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node server/server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-scripts build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-scripts test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-scripts eject"&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;What we're doing in the above scripts is defining a &lt;code&gt;development&lt;/code&gt; and a &lt;code&gt;production&lt;/code&gt; script.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;development&lt;/code&gt;, we set the &lt;code&gt;NODE_ENV&lt;/code&gt; to &lt;code&gt;development&lt;/code&gt;, and use the &lt;a href="https://github.com/kimmobrunfeldt/concurrently"&gt;&lt;code&gt;concurrently&lt;/code&gt;&lt;/a&gt; package to run both the front-end Webpack server (&lt;code&gt;client&lt;/code&gt;) and Express server (&lt;code&gt;server&lt;/code&gt;) scripts at the same time. This is like opening two terminals on your local machine, running &lt;code&gt;npm run client&lt;/code&gt; in one, and &lt;code&gt;npm run server&lt;/code&gt; in the other.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;production&lt;/code&gt;, we build the React app, set the &lt;code&gt;NODE_ENV&lt;/code&gt; to &lt;code&gt;production&lt;/code&gt;, and then start the Express server.&lt;/p&gt;

&lt;p&gt;That works great! However, there's still the issue of telling Express which port to use in each mode — we want it to use port 3001 in &lt;code&gt;development&lt;/code&gt;, and port 3000 in &lt;code&gt;production&lt;/code&gt;. We handle this in &lt;strong&gt;server/server.js&lt;/strong&gt;, where there's a bit of logic towards the end of the file that checks the value of &lt;code&gt;NODE_ENV&lt;/code&gt; and sets the listener's port appropriately:&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❇️ NODE_ENV is&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&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="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&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="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ Not seeing your changes as you develop?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ Do you need to set 'start': 'npm run development' in package.json?&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❇️ Express server is running on port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;port&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;Specifically, if &lt;code&gt;NODE_ENV=development&lt;/code&gt;, the port Express listens on is set to &lt;code&gt;3001&lt;/code&gt; and serving assets is delegated to the webpack dev server. We also print a message to the console, suggesting that if you're not seeing your changes as you develop, you might need to check that you've changed the &lt;code&gt;start&lt;/code&gt; script in &lt;strong&gt;package.json&lt;/strong&gt; to &lt;code&gt;npm run development&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Likewise, if &lt;code&gt;NODE_ENV=production&lt;/code&gt;, the port Express listens on is set to &lt;code&gt;3000&lt;/code&gt; and a route is added to serve the static resources from the &lt;code&gt;/build&lt;/code&gt; directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  👀 Watching the Watcher
&lt;/h2&gt;

&lt;p&gt;We've now got requests going where they need to go: in development mode, we're serving the webpack development server and proxying API requests to the Express server, and in production mode, we have the Express server handling both the API endpoints and serving the static resources. But we're not quite done yet!&lt;/p&gt;

&lt;p&gt;There's one last thing we want to sort out with our project: file watching. Both the Webpack development server &lt;em&gt;and&lt;/em&gt; Glitch watch for changes to files, and automatically update the live app as you type. This rapid feedback look is really handy for previewing your modifications, but we don't want the watchers to interfere with each other!&lt;/p&gt;

&lt;p&gt;The Webpack watcher only kicks in when the project is in &lt;code&gt;development&lt;/code&gt; mode, and watches for changes in the &lt;strong&gt;/src&lt;/strong&gt; directory. We can't really reach in and change much there, but we don't need to — all we really need is to tell the Glitch watcher to only look at what's changing in the &lt;strong&gt;/server&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;We do that by adding a special &lt;a href="https://glitch.com/help/restart/"&gt;&lt;strong&gt;watch.json&lt;/strong&gt;&lt;/a&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"install"&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;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^package&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.json$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.env$"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"restart"&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;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^public/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^src/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^server/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"^watch&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.json$"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"throttle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&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;Here, we specify a couple of conditions for Glitch's watcher.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We only want to run &lt;code&gt;install&lt;/code&gt; scripts when changes are made to the &lt;strong&gt;package.json&lt;/strong&gt; and &lt;strong&gt;.env&lt;/strong&gt; files. Installation can take a while, so we don't want to trigger it with any other changes.&lt;/li&gt;
&lt;li&gt;We only want to &lt;code&gt;restart&lt;/code&gt; the project when changes are made in the &lt;strong&gt;/server&lt;/strong&gt; folder, or to the &lt;strong&gt;watch.json&lt;/strong&gt; file. We're including &lt;strong&gt;watch.json&lt;/strong&gt; in case we need to kick off a restart — a change to the &lt;code&gt;throttle&lt;/code&gt; value will trigger this. We're also explicitly ignoring any files in the &lt;strong&gt;/public&lt;/strong&gt; and &lt;strong&gt;/src&lt;/strong&gt; directories from kicking off a restart — we only want the Webpack watcher to handle these files.&lt;/li&gt;
&lt;li&gt;We're setting a &lt;code&gt;throttle&lt;/code&gt; of 100, which means that the Glitch watcher will wait 100 milliseconds before restarting anything. If that seems too quick, you can increase it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And with those changes, we're ready to go! If you want a one-click solution, remix this starter app to get going:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/starter-cra-and-express?path=index.html" alt="starter-cra-and-express on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;Add API routes in &lt;strong&gt;server/server.js&lt;/strong&gt; (I've started you off with a &lt;code&gt;GET&lt;/code&gt; route to &lt;code&gt;/api/ping&lt;/code&gt; for checking that the server is up) and build your React app in the &lt;strong&gt;src&lt;/strong&gt; directory (the &lt;code&gt;create-react-app&lt;/code&gt; starter app you know and love is already there for you to start hacking on).&lt;/p&gt;

&lt;p&gt;Have fun, and don't forget to show us what you build!&lt;/p&gt;




&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; There's some subtlety to port handling in Glitch that, for the purposes of this article, I'm going to skip explaining. Long story short, Glitch does some work in the background and actually serves apps running on a couple of ports; the default &lt;code&gt;~hello-node&lt;/code&gt; project's Express server uses 3000, but ports 8000 and 8080 would also work, for example.&lt;/p&gt;

</description>
      <category>react</category>
      <category>express</category>
      <category>glitch</category>
      <category>proxies</category>
    </item>
  </channel>
</rss>
