<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Tales from the South Pole]]></title><description><![CDATA[Tech tips with a side of humour]]></description><link>https://peterpoliwoda.me/</link><image><url>https://peterpoliwoda.me/favicon.png</url><title>Tales from the South Pole</title><link>https://peterpoliwoda.me/</link></image><generator>Ghost 3.15</generator><lastBuildDate>Sat, 15 Nov 2025 21:02:49 GMT</lastBuildDate><atom:link href="https://peterpoliwoda.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[New App Launch: Train Your Brain with Kwarks!]]></title><description><![CDATA[<p>Hey everyone! I’m excited to introduce Kwarks — a sleek new puzzle app for iOS that challenges your abstract-reasoning skills in a way even advanced AI finds tough.</p><h2 id="how-it-works-">How it works: </h2><p>You’ll study sequences of 5×5 grid patterns, uncover the hidden logic, and pick the correct next pattern</p>]]></description><link>https://peterpoliwoda.me/new-app-launch-train-your-brain-with-kwarks-2/</link><guid isPermaLink="false">6914ac24504ff10767d1e8cd</guid><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Wed, 12 Nov 2025 15:52:53 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2025/11/IMG_4409.png" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2025/11/IMG_4409.png" alt="New App Launch: Train Your Brain with Kwarks!"><p>Hey everyone! I’m excited to introduce Kwarks — a sleek new puzzle app for iOS that challenges your abstract-reasoning skills in a way even advanced AI finds tough.</p><h2 id="how-it-works-">How it works: </h2><p>You’ll study sequences of 5×5 grid patterns, uncover the hidden logic, and pick the correct next pattern from five options. Ready to see if your human intuition still rules? </p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2025/11/IMG_4408.png" class="kg-image" alt="New App Launch: Train Your Brain with Kwarks!"></figure><h2 id="features-">Features:</h2><ul><li>150+ unique puzzles across easy, medium &amp; hard difficulty.</li><li>AI-resistant logic that keeps the game human-centric.</li><li>Minimalist design, no distractions—just you vs. the pattern.</li></ul><p>Whether you’re a puzzle lover, prepping for assessments, or just want a smarter way to kill 5 minutes between meetings—you’ll enjoy this. And if you’re a software engineer hoping to keep your pattern-matching muscle in shape—it’s especially for you.</p><p>Download it here: <a href="https://apps.apple.com/us/app/kwarks/id6754876586">App Store link</a></p><p>💡 Feedback welcome! If you spot a pattern that’s too sneaky (or too easy), I’m all ears.</p><p>Thanks for taking a look, and happy puzzling—may the best human brain win.</p><p>#Kwarks #AbstractReasoning #CognitiveTraining #AppLaunch #PuzzleGame #BrainTeasers</p>]]></content:encoded></item><item><title><![CDATA[Na ryby do Parku Pantanal]]></title><description><![CDATA[<p><strong>Cel podróży: Rio Miranda, Mato Grosso do Sul (Pantanal)</strong></p><blockquote>"Nie czytaj na głos: armia tu jest, dlatego nie ma mięsa z krokodyla. Carne do Jacare é proibido."</blockquote><p>Takimi słowami szwagier zaczął obiad pisząc je w swoim telefonie i pokazując mi go ukradkiem nad swoim talerzem. Mieliśmy dzisiaj spróbować mięsa z</p>]]></description><link>https://peterpoliwoda.me/na-ryby-do-parku-pantanal/</link><guid isPermaLink="false">64876a092fcad5581f0a1db2</guid><category><![CDATA[Travel memoir]]></category><category><![CDATA[Brazil]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Fri, 16 Jun 2023 13:27:51 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2023/06/ryba.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2023/06/ryba.jpeg" alt="Na ryby do Parku Pantanal"><p><strong>Cel podróży: Rio Miranda, Mato Grosso do Sul (Pantanal)</strong></p><blockquote>"Nie czytaj na głos: armia tu jest, dlatego nie ma mięsa z krokodyla. Carne do Jacare é proibido."</blockquote><p>Takimi słowami szwagier zaczął obiad pisząc je w swoim telefonie i pokazując mi go ukradkiem nad swoim talerzem. Mieliśmy dzisiaj spróbować mięsa z kajmana, lecz pech chciał, że owy zakazany smakołyk nie był tego dnia dostępny, mimo że w menu widniał dumnie na pozycji nr 5. Przyjechaliśmy do restauracji<em> Hotel e Restaurante da Cida</em>, w której dzień wcześniej próbowaliśmy bezskutecznie zostać na noc. Szwagier miał nam załatwić miejscówkę do spania w miejscu, do którego już raz niegdyś przyjechał na ryby kontaktując się z Doną tegoż przybytku przez WhatsApp, ale tym razem nie dostał od niej żadnego potwierdzenia, o czym niechcący zapomniał nas poinformować przed przyjazdem w środku nocy do ciemnego zaułka z wydawałoby się opuszczoną budą nad rzeką, która jedynymi gwiazdkami, jakie widziała to te, które nocą widać przez dziury w dachu obok przylepionej na ścianie kumkającej sobie z zadowolenia ropuchy.</p><p>Na całe szczęście po dobrych 20 minutach starania się obudzić naszą Donę, krzycząc, pukając, waląc w drzwi i trąbiąc klaksonem samochodu nikt nam nie otworzył i byliśmy "zmuszeni" znaleźć prawdziwy hotel, a nie budę, w której zatrzymują się zdesperowani rybacy. Ja i szwagierka byliśmy zdecywanie uradowani faktem, że znalezienie dwugwiazdkowego hotelu w środku nocy po uprzednim przejechaniu około 30km przez początek puszczy Pantanal do miasta, żeby znaleźć zasięg w telefonie trwało zawrotne 3 minuty i pare kliknięć w aplikacji mobilnej Booking Do tego mogliśmy spać spokojnie wiedząc, że większość zwierzaków-futrzaków z liczbą nóg od zera do ośmiu z równie zróżnicowaną liczbą kłów i szczękoczułek zostało po drugiej stronie serii pięciu zamkniętych po sobie nocą drzwi.</p><p>***</p><p>Płynąc łódką przyszedł mi do głowy fakt, że po 16 godzinach lotu wrzeszczącym samolotem, po ponad 15 godzinach jazdy wrzeszczącym samochodem z zepsutymi zegarami mierzącymi prędkość i stan paliwa, z wrzeszczącymi pasażerami, popłynęliśmy wrzeszczącą łódką z małej wrzeszczącej wioski, by w końcu zakotwiczając gdzieś na zakręcie rzeki zaznać... ciszy. Prawdziwej ciszy. Nie takiej ciszy gdzie w tle słuchać radio Miranda i przytłumione głosy lokalnej ludności cywilnej, tylko takiej, która wypełnia uszy ciepłem i przyjemnością zdaję się prawie zapomnianej już sztuki usłyszenia własnych myśli. Chwilę po tym jak piszczenie w uszach ustało i kiedy mój mózg w końcu jakby zaczerpnął powietrza uszami, do środka wtargnęły inne brzęczące dźwięki owadów, <em>baratas, cigarras</em> i innego rodzaju koników polnych, które przez lata przystosowały się do otaczających ich wrzeszczących ludzi.</p><p>Na ryby popłynęliśmy z zestawem wędek szwagra. Każdy z nas, w ciuchach z wielką, komicznie wyglądającą rybą i napisem <em>Pescador</em> na koszulce i spodniach własnoręcznie zdjętymi z manekina dzień wcześniej. Po drodze nad głowami przelatywały nam dwie błękitne ary z długimi falującymi ogonami, a wracając, podziwialiśmy lecącego w śmieszny sposób tukana. Kolorowy ptaszor wyglądał trochę niezdarnie, trzepocząc skrzydełkami jak koliber z odrobinę za ciężkim i nieaerodynamicznym dziobem w kształcie sierpa. Naszą tajną bronią okazała się <em>minhoca</em>* (czyt. minioka). Minhoca to taka olbrzymia dżdżownica, która idealnie nadaje się do łowienia gigantycznych ryb w Rio Miranda, ale niestety jest to oficjalnie zabronione z uwagi na jej bycie zagrożonym gatunkiem. Co z drugiej strony nie znaczy, że nie można jej po prostu kupić w każdym sklepie wędkarskim pytając czy nie mają państwo może <em>isca prohibida* </em>(pt. zakazanej przynęty) płacąc dwanaście Reali brazylijskich, czyli odpowiednik mniej więcej dziesięciu naszych złotych. Jedyne co trzeba zrobić to tylko na wszelki wypadek się odwrócić, by sprawdzić czy gdzieś za plecami nie panoszy się pan policjant z lokalnego przystanku policji leśnej, środowiskowej lub militarnej.</p><p>Mówią, że ryby głosu nie mają, ale te tutaj zdecydowanie wydają dźwięki, jak na przykład zgrzyty pancernej skorupy sumika Armau, który obcina palce ostrzami pod pachami, gdy go chwycić pod ramię. Są też i te bardziej znane. Piranii specjalnie przedstawiać nie trzeba. Te tutaj w teorii nie jedzą ludzi. Te z żółtym brzuszkiem są całkiem milusińskie. Wyglądają bardzo podobnie do rybki Paku mającej uśmiech podobny do tego od człowieka zważywszy na podobne uzębienie. Piranie, z zębami przypominającymi bardziej piłę do drewna, w ostatnich latach się rozprzestrzeniły, gdyż ich naturalnym konkurentem w łańcuchu pokarmowym są kajmany. Tych za to w dzisiejszych czasach coraz mniej, jako że ludzie stwierdzili, że idealnie nadają się na buty i torebki, bo są takie mięciutkie w dotyku. Połów piranii polega na wrzuceniu linki z haczykiem na kijku od bambusa z malutkim kawałkiem świeżego mięsa, który po mniej więcej dwóch sekundach zaczyna gotować wodę od stada szybko poruszających się i rozrywających na kawałki przynętę pływających morderców.</p><p>***</p><p>[TBC]</p>]]></content:encoded></item><item><title><![CDATA[What's the best dmg file type for distributing MacOS apps online?]]></title><description><![CDATA[<p>There is no one "best" DMG format as each format has its own advantages and disadvantages depending on your specific use case. Here are some of the most common DMG formats used in macOS:</p><ol><li>UDIF: This is the default and most widely supported DMG format. It supports compression, encryption, and</li></ol>]]></description><link>https://peterpoliwoda.me/whats-the-best-way-to-compress-dmg-files-when-distributing-macos-apps-online/</link><guid isPermaLink="false">6425b8ef2fcad5581f0a1d31</guid><category><![CDATA[MacOS]]></category><category><![CDATA[apps]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Thu, 30 Mar 2023 17:59:32 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2024/04/dmg-files.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2024/04/dmg-files.jpg" alt="What's the best dmg file type for distributing MacOS apps online?"><p>There is no one "best" DMG format as each format has its own advantages and disadvantages depending on your specific use case. Here are some of the most common DMG formats used in macOS:</p><ol><li>UDIF: This is the default and most widely supported DMG format. It supports compression, encryption, and segmented images. However, it is not as efficient as other formats when dealing with large files.</li><li>UDRO: This is a read-only format that is suitable for distributing software or data. It is smaller in size than the UDIF format because it does not support compression or encryption.</li><li>UDZO: This format supports read-only compression and is suitable for distributing large files over a network or loading from disk. However, it can take longer to create and may require more processing power.</li><li>UDBZ: This format supports read-write compression and is suitable for storing frequently accessed files on a disk image. However, it can take longer to read and write files to the image.</li><li>UDCO: This format supports copy-on-write compression and is suitable for creating disk images that are updated frequently. It is fast and efficient, but it can be complex to set up.</li></ol><p>Ultimately, the best DMG format depends on your specific needs and use case. If you need to distribute software or data, then UDRO may be the best option. If you need to distribute large files, then UDZO may be the best option. If you need read-write access, then UDBZ or UDIF may be the best option.</p><h2 id="what-s-the-most-typical-dmg-format-used-for-distributing-apps-over-the-internet">What's the most typical DMG format used for distributing apps over the internet?</h2><p>The most typical DMG format used for distributing apps on the internet is the UDIF format. This is because UDIF is the default and most widely supported format by macOS, making it easy for developers to create and distribute apps in this format. Additionally, UDIF supports compression and encryption, which can help reduce the size of the DMG file and provide added security. UDIF is also compatible with a wide range of third-party tools and utilities, making it easy for users to open and use the DMG file on their Mac. Overall, UDIF is a reliable and widely supported format for distributing apps on the internet.</p><h2 id="are-any-dmg-file-types-not-supported-by-older-versions-of-macos">Are any DMG file types not supported by older versions of MacOS?</h2><p>Yes, some of the DMG formats may not be fully supported by older versions of macOS. Here is a breakdown of DMG format support by macOS version:</p><ul><li>UDIF: This format is supported by all versions of macOS.</li><li>UDRO: This format is supported by all versions of macOS.</li><li>UDZO: This format is supported by macOS (OS X) 10.2 and later.</li><li>UDBZ: This format is supported by macOS (OS X) 10.4 and later.</li><li>UDCO: This format is supported by macOS (OS X) 10.5 and later.</li></ul><p>It's worth noting that while a particular DMG format may be supported by an older version of macOS, some features of that format may not be supported. For example, older versions of macOS may not support encryption or certain types of compression for a particular DMG format. Therefore, it's important to consider the specific features and requirements of your DMG file when selecting a format to ensure compatibility with the intended audience.</p><h2 id="what-does-it-mean-for-macos-in-2023">What does it mean for MacOS in 2023?</h2><p>It's worth noting that, MacOS was renamed from OS X in 2016 which I would like to remind you is already 7 years old.</p><ul><li>OS X 10.2, also known as Jaguar, was released on August 24, 2002.</li><li>OS X 10.4, also known as Tiger, was released on April 29, 2005.</li><li>OS X 10.4, also known as Leopard, was released on October 26, 2007</li></ul><p>These are fairly old versions of MacOS and are not so widely used any more. That being said if you'd like your application to support quite old versions of the operating system it is something you'd want to consider when building DMG files.</p><h3 id="change-to-64-bit-architecture-only">Change to 64-bit architecture only</h3><p>Starting from macOS 10.15 Catalina (released in October 7, 2019), the default architecture for macOS only supports 64-bit applications. This means that 32-bit applications are no longer supported by default on macOS Catalina and later versions. Prior to macOS Catalina, 32-bit and 64-bit applications were supported, but Apple had been warning developers for several years that 32-bit support would be phased out. The shift to 64-bit only architecture is part of Apple's ongoing efforts to improve performance and security on macOS.</p><h3 id="change-from-x64-to-arm-with-m1-processors">Change from x64 to ARM with M1 processors</h3><p>Apple first introduced the ARM-based architecture for their Macs with the release of the M1 chip in November 2020. The M1 chip is based on Apple's ARM-based A-series chips used in iPhones and iPads. With the release of the M1 chip, Apple began transitioning its Mac lineup to ARM-based architecture, starting with the MacBook Air, MacBook Pro, and Mac Mini. The transition is expected to take a few years, and during this time, Apple will continue to support and release new versions of macOS for both Intel-based and ARM-based Macs.</p><p>Today in 2023, Intel-based Macs are still quite wide-spread so your application should be build for both x64 as well as the still relatively new M1 ARM CPUs.</p><h2 id="are-udco-files-smaller-than-udzo-when-compressed">Are UDCO files smaller than UDZO when compressed?</h2><p>In general, UDCO files can be smaller than UDZO files when compressed because UDCO uses copy-on-write compression, which can be more efficient than read-only compression used by UDZO. With copy-on-write compression, the data is stored in blocks, and if a block is unchanged from the original data, it is not duplicated. Instead, the block points back to the original data, reducing the amount of storage required.</p><p>However, the actual compression ratio and resulting file size can depend on the specific data being compressed. For example, if the data being compressed is already highly compressed, such as a ZIP archive, then the compression ratio may not be as significant, regardless of the compression format used.</p><p>Ultimately, the choice of compression format depends on the specific use case and the type of data being compressed. Both UDCO and UDZO have their own advantages and disadvantages, and the best choice depends on the specific needs of the user.</p><h2 id="with-all-that-in-mind-which-format-is-best-for-me">With all that in mind, which format is best for me?</h2><p>As mentioned above, it all really depends on the type of application you are building. Newer DMG file formats might not be supported by older versions of MacOS but the choice ultimately comes down to your needs. The most important question are:</p><ul><li>Do you need to support read-only or read/write types of DMG files?</li><li>Do you want to use compression?</li><li>Do you need to encrypt your DMG files?</li></ul><p>At the end of the day the choice is up to your personal preferences and the nature of the application being distributed.<br></p>]]></content:encoded></item><item><title><![CDATA[How to trim mp3 audio files on MacOS. It's free!]]></title><description><![CDATA[<p>Use QuickTime to trim audio files on your Mac without any extra additional software. The feature is baked into your operating system just like a lot of other cool features you mightn't have known about.</p><p><strong>Open</strong> the <strong>QuickTime</strong> application:</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image.png" class="kg-image"></figure><p><strong>Select</strong> your audio file from the file browser</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image-1.png" class="kg-image"></figure><p>Once your file</p>]]></description><link>https://peterpoliwoda.me/how-to-trim-mp3-audio-files/</link><guid isPermaLink="false">635fbb8d7957fe05c0185440</guid><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Mon, 31 Oct 2022 12:22:33 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2022/11/18181822-79E3-43A9-9B2A-24522D99BD9D.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2022/11/18181822-79E3-43A9-9B2A-24522D99BD9D.jpeg" alt="How to trim mp3 audio files on MacOS. It's free!"><p>Use QuickTime to trim audio files on your Mac without any extra additional software. The feature is baked into your operating system just like a lot of other cool features you mightn't have known about.</p><p><strong>Open</strong> the <strong>QuickTime</strong> application:</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image.png" class="kg-image" alt="How to trim mp3 audio files on MacOS. It's free!"></figure><p><strong>Select</strong> your audio file from the file browser</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image-1.png" class="kg-image" alt="How to trim mp3 audio files on MacOS. It's free!"></figure><p>Once your file is opened press <strong>CMD+T</strong> or go to <strong>Edit &gt; Trim... </strong></p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image-2.png" class="kg-image" alt="How to trim mp3 audio files on MacOS. It's free!"></figure><p>Trim your audio file accordingly just as you would a video on iOS and press <strong>Trim</strong></p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image-3.png" class="kg-image" alt="How to trim mp3 audio files on MacOS. It's free!"></figure><p>And that's it! You now have a shorter audio file you can <strong>Export</strong> in <strong>File &gt; Export</strong></p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/10/image-5.png" class="kg-image" alt="How to trim mp3 audio files on MacOS. It's free!"></figure><p>Happy trimming!</p><p>Cześć!</p>]]></content:encoded></item><item><title><![CDATA[This MiBand watch face will blow your mind]]></title><description><![CDATA[<p>Here's something that I found out today. Well, it really wasn't today but rather in 2020 when the Xiaomi MiBand 4 was the latest smart band from the Chinese electronics company. Putting together a smart band watch face is as simple as putting together a bunch of pictures and a</p>]]></description><link>https://peterpoliwoda.me/this-miband-watch-face-will-blow-your-mind/</link><guid isPermaLink="false">62cb3d137957fe05c01853b5</guid><category><![CDATA[wearables]]></category><category><![CDATA[miband]]></category><category><![CDATA[Download]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Sun, 10 Jul 2022 21:32:14 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2022/07/7890C5BB-C77B-424B-BA4F-13AB76B7A0C7.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2022/07/7890C5BB-C77B-424B-BA4F-13AB76B7A0C7.jpeg" alt="This MiBand watch face will blow your mind"><p>Here's something that I found out today. Well, it really wasn't today but rather in 2020 when the Xiaomi MiBand 4 was the latest smart band from the Chinese electronics company. Putting together a smart band watch face is as simple as putting together a bunch of pictures and a JSON descriptor file, linking them all together and running the <a href="https://amazfitwatchfaces.com/forum/viewtopic.php?t=890">WF_Builder app</a>. Heres what a Chased by Penguins themed watch face looks like. Pretty neat huh? </p><p>The band files can be found <a href="https://github.com/peterpoliwoda/chased-by-penguins-watch-faces">on GitHub here</a> . I'll pop over the source in the same place at a later stage.</p>]]></content:encoded></item><item><title><![CDATA[How to make the first and last elements wrap inwards using flexbox]]></title><description><![CDATA[<p>Flexbox is quite a powerful tool once you get a handle on it. I found the <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">Complete guide to Flexbox</a> missing a particular edge case I needed, illustrated here below.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2021/12/image-5.png" class="kg-image"></figure><p>My use case was to only wrap the first and last columns in a flexbox so that on a mobile</p>]]></description><link>https://peterpoliwoda.me/how-to-make-the-first-and-last-elements-wrap-in-flexbox/</link><guid isPermaLink="false">61cc44421d7d594ba0858287</guid><category><![CDATA[code examples]]></category><category><![CDATA[CSS]]></category><category><![CDATA[coding]]></category><category><![CDATA[web]]></category><category><![CDATA[Tips&Tricks]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Thu, 10 Feb 2022 23:05:28 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2022/02/38CBEDAC-5E2C-417D-9224-4EED0C0B2241.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2022/02/38CBEDAC-5E2C-417D-9224-4EED0C0B2241.jpeg" alt="How to make the first and last elements wrap inwards using flexbox"><p>Flexbox is quite a powerful tool once you get a handle on it. I found the <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">Complete guide to Flexbox</a> missing a particular edge case I needed, illustrated here below.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2021/12/image-5.png" class="kg-image" alt="How to make the first and last elements wrap inwards using flexbox"></figure><p>My use case was to only wrap the first and last columns in a flexbox so that on a mobile phone view they would wrap from the sides inwards like the picture illustrates here:</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2021/12/image-3.png" class="kg-image" alt="How to make the first and last elements wrap inwards using flexbox"></figure><p>Turns out the CSS Trick to do this is quite straightforward.</p><p>First we need to define our HTML code to wrap our 3 internal divs with a single container wrapper.</p><pre><code class="language-html">&lt;div class="wrap"&gt;
    &lt;div class="a"&gt;A&lt;/div&gt;
    &lt;div class="b"&gt;B&lt;/div&gt;
    &lt;div class="c"&gt;C&lt;/div&gt;
&lt;/div&gt;
</code></pre><p>Next, some CSS magic:</p><pre><code class="language-css">.wrap {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
.wrap &gt; .a {
    background: #0f1e33;
    order: 1;
}
.wrap &gt; .b {
    background: orange;
    order: 2;
    flex: 1;
}
.wrap &gt; .c {
    background: #53a8e1;
    order: 3;
}
@media screen and (max-width: 500px) {
    .wrap &gt; .b {
        background: orange;
        order: 1;
        min-width: 100%;
    }
    .wrap &gt; .a {
        background: #0f1e33;
        order: 2;
    }
    .wrap &gt; .c {
        background: #53a8e1;
        order: 3;
    }
}</code></pre><p></p><p>And if you really want to spice things up with some colours:</p><pre><code class="language-css">/* Backup CSS code */

body {
  font-family: 'Roboto', sans-serif;
  font-weight: 400;
  font-size: 26px;
  color: #141414;
}

.wrap {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
.wrap &gt; .a {
    background: #0f1e33;
    color: white;
    order: 1;
    padding: 10px;
}
.wrap &gt; .b {
    background: orange;
    color: white;
    order: 2;
    flex: 1;
    padding: 10px;
}
.wrap &gt; .c {
    background: #53a8e1;
    color: white;
    order: 3;
    padding: 10px;
}
@media screen and (max-width: 500px) {
    .wrap &gt; .b {
        order: 1;
        min-width: calc(100% - 20px);
    }
    .wrap &gt; .a {
        order: 2;
    }
    .wrap &gt; .c {
        order: 3;
    }
}</code></pre><h2 id="has-this-been-helpful-to-you">Has this been helpful to you?</h2><p>You can support my work by sharing this article with others, or perhaps <a href="https://ko-fi.com/peterpoliwoda">buy me a cup of coffee</a> 😊 </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAWEAAADkCAYAAABJ9ZUIAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Gkqr6gAAAYBpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHPK0RRFMc/Hhr5XSgLi0lYoUGJjcWIobAYT/m1efPmvRk1M17vjSRbZTtFiY1fC/4CtspaKSIlGxtrYoOe88zUTDL3du/53O8953TuuaCoCT3plAUgmUrb4VDQPzs37/c9o1ArswmfpjvW5PSoStHxcUeJZ2+6vFzF/f4dVVHD0aGkQnhIt+y08JjwxGra8nhbuFGPa1HhU+FOWwoUvvX0SJZfPI5l+ctjWw0Pg1Iv7I8VcKSA9bidFJaX05ZMrOi5eryXVBupmWmxrbJacAgTIoifcUYYpp8eBmXvp4teuuVEkfjAb/wUyxKry26xhs0SMeKk6RR1RbIbYk3RDZkJ1rz+/+2rY/b1ZrNXB6H8yXXf2sG3Bd8Z1/08dN3vIyh9hItUPn75AAbeRc/ktbZ9qNuAs8u8FtmB801ofrA0W/uVSmUppgmvJ1AzBw3XULmQ7VnunuN7UNflq65gdw86xL9u8QcNCme9tGiQ+AAAAAlwSFlzAAAOxAAADsQBlSsOGwAAHfBJREFUeJzt3X1sVFd6BvAn2Wi19liCCmOjthhjb1OWhdhlg0SCU3s1FBZGXVySarakAieFtCAlmHw1kRLisEgQsoSPbGET0K6xFKujBWJHGqgtRjtu7MQShNp8eKlg7DG0ko2NFktzzVb80z/u3DAYz9xzZ+7HuXOfn2Rhe87MvJnYj8+899xzH0EW/IFgNYBqAOXZ3N9kfQDuOF2EC8Qj4VDc6SKI6EGPiA70B4J1ABoA1AOYYVE9RE6bgPqHnfRFdW6/A6AvEg7pjfM03RBOhm8TgFqriyGivNUP9Y9bFECU78ruyxjC/kCwCcB79pRCRB4yDDWQ26CGsmdbitOGsD8QnAngAICN9pZDRB7VjmQoe22WnC6E2wCstbkWIiIA6ALQDDWQ836G/FAI+wPBAwC2OVALEdFUx6GGcZvThVjlgRBOHoT7nTOlEBGlNQx1dnwg32bHU0M4DmCeyB1LS4pRtfiHmFM624q6hPVdHHD0+d0iNhSHokw6XQaRGY4DaMqX3vG3IewPBBsA/EbvDpXz52HrSxtRtXihlXUROWJkdAyjt8acLkN6CUVBbHA4/e0JBdcHh3HxsqWTpHaoM+OolU9itdQQjkNnFrzSX4utL21Akc9ndV1ElCdig3H0XRpAf/LDgndkXVBnxlGzH9gOjwCAPxCsB/B5poGV8+dh354dDGAiyokWyp1nuxAbSj+bzkIXgMZIOOSqMx61EG6GzprgTw7tQWVFuQ0lEZFXjIyOof/SAHp6z+Gr3vNmPayresbfAYCKxxcdADAz3aCV/lr87Zq/sa0oIvKGoiIfvl9Rjh//9dNY6a9Faels3Pyf/821ZVEN4IWKxxd9b+jalag5lVrnkeTZcX/INGjf7h08EEdEtokNxnGy/Qx6es/lGsjDABpk7hc/orc22OcrRHvo1/ZVRESUlFAUdJztwqn20xi9NZ7LQ7VDDWPp1hg/qjegcn659VUQEU2jyOfDs2vX4LNf/xJvNG5BaUlxtg+1FkA8uQhBKrohTEQkg1UravHZr3+Jfbt34IlFWbVHZwD43B8INifbsFJgCBORq1QtXoiP9uzIZWa8EUBf8gpBjmMIE5EraTPjNxq3wOcrNHr3eQD+yx8INlpQmiEMYSJyNTWMP8a6n67O5u77nW5PMISJyPWKfD5sfWkjPjm0J5t+8UYAUaeCmCFMRHmjsqIcH+3ZgS2bNxhtUVRBXT1he5+YIUxEeefZtWvwyaEPjM6KZ0CdEdu6jI0hTER5aU7pbHy0Zwc2rH/OyN20ZWwN1lT1sEcB1GUa8P0KoT3eiYiktGH9c/jk0B6jy9l+Y1cQ686Ei4q4dSURuVtlRTk++dhwe8KWIGY7gog8ocjnw0d7dhhdymZ5EDOEichTtr60EW80bjFyF0uDmCFMRJ6zakUt9u3eYWQZm2VBzBAmIk+qWrwQHxkPYtPXETOEicizKivKjQZx1OwgZggTkacZDOIZAEzda4IhTESeZzCIqwC0mfXcDGEiIhgO4lp/INhkxvMyhImIkgwG8XvJa3TmhCFMRJSisqIcb24XXkfclmt/mCFMRDTF8mVLsWXzBpGhM5Bjf5ghTEQ0jWfXrsFKf63I0NpcLpPEECYiSmPrSxtQOV9oJ8kmfyBYns1zMISJiNIo8vnw/juvixyomwGgOZvnYAgTEWUwp3Q2tm7eKDK0NpurcjCEiYh0rFpRi6eXPSky9IDR1RKPZVcSEblNT+859F8cwPXB4Ydum1M6G3NKZ5v6fD5fIaoXL0RlRbmpj+uUN7dvwfMvvgxFmcw0bB6ARgBNoo/LECbygL37j6Az0pX29ouXrXvuyvnzsHJFLVatqEWRz71X6iny+fDm9i14b9c+vaGN/kCwORIOxUUel+0IojzXcbYrYwBbLTY0jCNHW/D8iy9j7/4jGBkdc6yWXC1ftlSkLTEDBmbCDGGiPNdx1rkATqUok+iMdOEf/8ndYbx180aR1RIbRZesMYSJyHZaGLe0nkBCUZwux5A5pbPx7No1IkObRAYxhInIMS2tJ/D8iy+jp/ec06UYsm7tatNmwwxhInKUokzivV37sGPXL1wzKy7y+UTXDjfpDWAIE5EUvuo976pZ8aoVtSgtKdYbpjsb5hI1Itqe/Ne0S/YAqAaw1uidtFnxup+uxtaXhGaajtqw/u/x4YEjesMakGFGzBAmor5IOBQ1+0GTZ441Qg0hoV1wNKe+OIPrg8PY+e5rUq8tXrWiFi2tv8XorfFMwzKevMF2BBFZIhIO3YmEQ02RcKgcwAsAHj5VL4OLlwfw2ls7pe8Tr9NfKTHDHwg2pLuRIUxElouEQ81QWxTvA5gQvV9saBjPv/gyYoNxiyrL3aoVtSIrJRrS3cAQJiJbaDNjqGHcLno/RZnEq2/vlDaIi3w+rNLf/L023QE69oStMjkJ3Lihfn71avpxZWVAYSFQXKx+5DvtdblxQ/18fFz9mEp7PQoL1ddIe53I9ZJ7KtQn36IfgHqab0aKMokdu36BTz7+QMoe8bq1a3DqizN6wxowTW+YIWyGyUk1aK9eVcMlU+jqmTsXmDdPDZ0FC9R/3Wx8HLhwQX1NhoeB27ezf6xZs4Af/EB9XZYsYSi7XCQcavYHglGo12ir0hs/emscr721E/v27JAuiOeUzsYTixbi4uWBTMMawBA2kRYuX34J3Lxp3uPevPng4xUUqMGzZIl7gmd8HOjsBL75JrfQner2baC7W/0A7r8mbnld6CGRcCievGz8AQC6a9JiQ8PYu/8Idr7zuuW1GbVqRa1eCM/zB4LVkXCoL/WbDGGjtBDIZbZrxN27athfuKB+vWQJUFOj/isbu18b7XUpKACeeQZYudIbLR2DFMlXF0TCoTsAGvyB4B0A2/TGf9V7Hi2tJ7Bh/XPWF2fA8qeexOGjhXr7DTdAXbL2LYawqO5u4PPPzZ3ZZUMLnlmz7gePk7PAyUl11vvll869NnfvqjV0dqp/oOrrGcYpYkOZV4ZZsUY4G5FwqNEfCPYB+I3e2JbWE1i+7EmpNowv8vmwfNlSvW1D66Z+g6sj9Fy4ALz2GnDsmPMBnOr2baCtTa2trU0NQztNTj74/LK8Nt3dwOuvA62t9r8mlLPkUrYXRMbKuNfE8qd09xqumrpKgiGczvg4sGcPcOiQPAEznbt3HwxDO3R3A+++qz7f3bv2PKdRnZ3qa6K1ccg1kkF8XG/c6K1xnGrXXZFgq+XLlooMe+BioAzh6Vy4oIaMXb1NM6SGsVV1a3+YZHtXkM7du+of0UOHOCt2mUg41ACBtcQtrSek2xxe4MobdalfMISnam1Vf2llneHpuX37/gzezOBpa1Pf5rvpD5NG+6Oqrdsmt2gA0K83aO9+3Q10bCUwG65L/YIhnOrYMfVtbD4wazY/Pn6/9eBmt28Du3ffX95G0tNWTeiNu3h5AP2XMi4Ns1XV4oV6Q2b4A8Fq7QuGMKDOGN99N/9+QbVZcbYBqgW5meugnXT3rvqHNt/+P+ex5Jra9/XGHf/shA3ViJlTOltkn+E67ROGMKC2IPIlaKbT1ma8PeH2tkwmDGJXSe43kXGd3cXLA1L1hqsW/1BvSJ32CUO4tdUbv5AXLqhvx6fbpyHV5KQ6e86Xtkw6DGK3adQb0NIqz2xYoCXBdgQA9Zcw38Mm1c2bmQ9QjY+rQe3Gg2/ZOHbMO/+tLhcJh9oAZDwLoqf3nDTrhgVCeF5y03sPh/D4OPDZZ05XYb+7d9WgnRrEN27kV/9X1MGD+u8OSBbNmW5UlEn0fH3eplIym1M6W2SP4WrAyyF87Fh+9jtFTA3iGzfUr734emgH60h6yZM4MvaGZbpIaOX8cr0hdYBXQ1jbWtHLtCDu7vZuAGuuXnX/EjzvaM5041e9csyEAaD6Cd2WRDmghnB5plECU2r38WIbYjraLNDLAazp6GBbwh2a9QbIMhuurNC9tmk5IBDC35dolyJTdHe745Rbspd22jdJLXlVjoxn0cUGDV1P1DJzSmbrDakFvNiO+PxzpysgWdm5FzLlIprpxr6Lcpw9J7LNpj8QnOmtEL56lbNgyoxrh90g41sWnatb2ErgzLlqb4Uwf8FIT3c3e8Py69MbIMvZc6UlJXpDPDYT/uYbpysgN/DSCTwulNzYJ2Pjd/SWHCE8p1S3L+yhmfCFC1wFQGL4x9oN4pluvD6Y8WbbCISwhw7M8YALibp9m3sPyy+a6Uadi23aRmCJb513Qvj3v3e6AnITHj9wtURCjj0kRJb4eieEvbYnAuWGf7RlF89043VJ1gqL8EYIsxVBRvGPtuziThdgknJvhDAv8kjZ4B9vypHIlpbeCGEeZKFs8OeGbOCNECbKBt9BkQ28EcJ8W0nZ4M8N2cAbIUxEJCmGMBG5UV2mG0XOVJMFQ5gonTE59h8g4xjCRPmA256SDRjCROkUFDhdAaVXl+lGN12WjSFMlM483WuEkaTcdFk2b4TwggVOV0BE5qp1ugAR/Zd0r/Ix4Y0QJqK84Q8EZ+qNEThdWBZ93gjhYt3rPBE9jD83sqrOdKPAdd2kwhAmSoc/N7Kqy3SjwHXdbCNyhQ9vhHBZmdMVkBvx50ZWGWfC1U/I04oQuMJH1BshXFjI5UZkHGfCsqrLdGNlhTyrWjgTTsXlRmQUZ8LS8QeC1QBmZBpTOb/cllpEJBK6M2GPHJgDuEyNjOHPi6waMt1YWlIs1SnLsaG43pA7DGGi6fDnRVb1mW5cvmypXXXoSiiKbk84Eg55pCcM8K0lGcMQlk6yFZGxr1gl0UG5mP7FRicAL/WECwv5i0ViCgr4syKnxkw3+nyFUs2EBc6W6wO8FMIAsGSJ0xWQG/zoR05XQFMkz5JzTSsCEFoZwRAmmhZ/TmTUCJ1VEcufetKmUsTEGMLTKC4G5s51ugqS2axZDGHJJGfBGVsRpSXFUs2ER0bHMHprXG+YB0MYAJ55xukKSGb8+ZCR7ix41Yo6eyoRJLJ7WiQc8mgI19Q4XQHJjD8fUvEHguUA3ss0xucrxLq1q+0pSJBACEe1T7wXwoWF/EWj6dXU8FRl+TTrDXh27RoU+Xw2lCKu/9IVvSFR7RPvhTDAEKbp1Wc8+E428weCjdDZvF3GWXBsMC7SD45qn3gzhBcs4DpQehBnwVJJnpixX2+cjLPgPgP9YMCrIQxwNkwP4ixYGsk+cFRvXGlJsXSzYADoPNulN6Qt9Qtvh/CsWU5XQTJYuZKzYEkkl6O1QWc1BABsfWmjdLPgkdExxIZ0T1eOpn7h3RAGgM2bna6AnFZQwFmwJJIBHAVQpTf26WVPSrUuWNMZ0Z0FA5wJp2BvmDZvVlfMkKOSPeAoBAK4tKQYb27fYnlN2eg4G9Ub0h4Jh+6kfsPbIQwAmzY5XQE5ZckSnh0nAX8gWAfBAPb5CrHzndela0MA6tpggVURbVO/wRAuLubbUS8qKOAfYAn4A8EmAL+DQA8YALZu3ojKinIrS8pah/4BuQlME8KPWVKN29TXA998A9y86XQlZJdt29iGcFByBUQzdNYBp9qyeQNWrRAebquEooj0g9umtiIAzoTv27yZFwP1ivXreSzAIf5AcGZy9jsEAwG80l+LZ9eusayuXJ1qPyMyrHm6bzKENWVlwPPPO10FWa2mRl2SRrbzB4INAOLQ2QtiqpX+WmkPxAHqLPhk+2m9YcORcCg63Q1sR6SqqQGuXgW6u52uhKywYAH7wDZLLjtrgLoTmuFLnr/RuEXaFoSm5+vzuteSA3Ag3Q0M4ak2bQLGx9Uwpvwxdy7wyitOV+EZySVnjVCvhiF00C2Vz1eIrZs3Sh/AANDS+lu9IRPIsBERQ3g6r7wC7N7NA3X5YtYs4O23eSDOQsllZgCgha/hWa+mcv48vLl9i7SrIFKdbD8tsiyteboDchqG8HQKC9Wj5+++C9y963Q1lIuCAq6E0OEPBKNTvtUHIDU0qgHMnOaupk9T1/10NTY8/5yU64CnSigKWlpPiAxN24oAGMLpFRers6fduxnEblVQoP4/LCtzuhLZTQ1T23sA6llwW1G1WJ5L1us51X5GpBd8PBIOxTMN4OqITMrK1F9iLl1zp23bGMAusGH9c/jk4w9cFcAjo2Ois+AmvQGcCevRlq4dO+Z0JWTEpk1cCyy5lf5abFj/HOaUzna6FMMOHz0uMkx3FgwwhMVoew8ziN1h0ybuFy0xN4cvAPT0nsNXvef1hk1A5wrRGoawKAaxOzCAH1I5f57IHreWKi0pxqoVdVi3drUrDrqlk1AUHP5UaBZ8INOKiFQMYSMYxHJbv54BPI03t2/Bq2/vFDmIZLqV/losf0rOvX+z0fLZCZElacPQWRGRiiFsFINYTjwdOa3KinJ8tHsHjreeQP+lAUvCuLSkGKUlJQCA6icWorRkNpY/9aSrZ71T9fSew6kvhPaIaBSdBQMM4ezU1Khn1bU9tCsdOaGmhqcj66isKMfOd15/4Hv901yQMqEoiA0+2LooLZk9bf823ffzUUJRsHf/EZGhXZFwyFAwMISzVV+vBjH3mXAWAzhr6ZaE5UvrwEw7fr5P5B3EBNR9MgzhOuFc8CCQsxjAZIPDnx7Hxcu6l7EHgCaRJWlTMYRzxSB2BgOYbNBxtku0D9wVCYeED8alYgibgUFsLwYw2SA2GBc9KSOrNoSGIWwWBrE9GMBkg4SiGFnW15BNG0LDEDYTg9haDGCyQUJR8NpbwgF83OhqiKkYwmbbtIlXb7YCA5hsoAWw4BmG/ZFwqCHX52QIW6G+noFhJgYw2cBgAE9AvWpIzhjCVmFwmIOvI9nAYAADQF0ufeBUDGErMUByw9ePbJBFAL8QCYf6zHp+hrDVGCTZ4etGNsgigLdHwqFmM2vgact24KY/xjCAyQaxwTj27j9iJICPZ3tCRiYMYbswiMUwgMkGscG40e09j5uxEmI6DGE7MYgzYwCTDTrOduHDA0I7omksC2CAIWy/mhr1unW8ivODGMBkMfWqGC3ojHQZuZulAQzwwJwzeBXnBzGAyWKxwThee2undAEMMISdwyBWMYDJYifbT+PVtw2tgACAg3YEMMAQdpbXg5gBTBYaGR3Dq2/txJGjLUYv6fRCJBwSulKyGRjCTvNqEK9fzwAmy5xsP41/fuVfRTdj10wA+Duz1wHr4YE5GZSVAfv2qQfrbt50uhrrcbc5skhsMI5/+7TFaPgC6hWS6808E04UQ1gWhYXqjDjfg5gBTBZIKApaPjshehWMqbqgBrDwFZLNxBCWSb4HMQOYTJZQFJxqP4OT7aeN9n0170fCoSaTyzKEISybfA1iBjCZrONsF1paf4vRW+PZ3H0Y6hUxouZWZRxDWEaFhcDPf66eWdfd7XQ1uWMAk0kSiqJefLP9dLbhCwDtUAPYkfbDVAxhmWmrB9waxAUF6qy+rMzpSsjlRkbH0BnpyqXtACQvyJnr5YjMxhCWnVuDmAFMJui/NICOs11Gz3SbznEAjbLMflMxhN1g0ya1RdHZ6XQlYhjAlANt1ttxNppLy0EjTe83HYawW6xfr4aa7DuwMYApCyOjY+jpPYfOs11GTy9OZwJAkxX7/5qNIewmsm+FOXcusHkzA5iExAbj6Ok9j56vz5kVvJqDUANYutbDdBjCbiNrEM+dq86ACwudroQkFRuM4/rgMPovDaCn91wuB9jSOQ41fONmP7CVGMJuVFMDFBcDBw/KsScxA5imSCgKYsnAvT4YR/+lAStCV+PK8NUwhN1qwYL7J3U4GcQMYE+LDcaRUCbRf2kAiYSC64PDiA3FrQxczQSAZgAH3Bq+Goawm2k7sDkVxEuW3F+5QdJKKAp6vj6P0VtjD3y/avHCtPcZGR17aHzfxfub4mSxQY5ZhgE0AWhzS89XD0PY7bQgPngQuH3bvuflXsCuEBuMY8euX5ix1MtpxwE0y7zULFsM4XxQVqae5mzXfhMMYNdweQB3QW055M2sdzoM4Xxh18Y/K1eqa5ZJev2XBtwYwO0AolCDN+5sKfZgCOcTq4OYG/GQ+Yahhm4UeT7jTYchnG+s2oGNAUzmmMD90I06cSUL2TCE85WZG/8wgCk7EwD6oAZuH4A+r7QYjGAI57Ncg5j7QJAYbYuzKIA7uB+4nmstZIMhnO82bVLPrmszuIUqA9j1rg/G9YYMA8g0qA9qqKaKJv+9w1aCORjCXlBfrwax6H4Ts2YB27YxgF1O4Ky1Zqevr0YMYe8Q3fiHpyET2epRpwsgG+mdZMEAJrIdZ8Jeo+3AdvTog6c5cx8IIkc8hocb7w8YGR1D1WKbqiF7LFgA7NsHXL2qfl1crH5QXkndcCeNuA1lkI7HoB4BXZtuwNSdlCiPLFjgdAXkrLjTBRB7wkR5a/TWLadLIAEMYaI8JbB5D9f5SoAhTJSHRkb124g8o00ODGGiPCRwLKffjjpIH0OYKA/1X9JdGcFZsCQYwkR5SKAdEbWhDBLAECbKQzH9zXt0B5A9GMJEeSahKIgNDesN48oISTCEifKMQD8Y3IZSHrohnEgodtRBRCbp1z9duUtvANnnUegcJb0+qPu2hogkIjATjtpQBgl6FDq9IUXhTJjILUZGx0T6wVEbSiFBujNhgf+hRCSJnt5zekMmIuFQ1IZSSNCjIg16geUuRCSBzrO67d6oDWWQAdqBuYynMPYJHG0lImcJtiIMXvGVrKaFcMbZcIwH54ikd6r9tMgwhrBkhEJYoM9ERA5KKAo6IrqtiHbunCYfLYSjmQYpyiT7wkQS6zjbJXSJextKIYMeBb49e2Yi08AO/YY/ETlEoBUxHAmH2IqQUOoZcxn/Bwm81SEiB7S0nhC5ikazDaVQFoRDWFEmORsmkkxCUXBS7IDcAatroex8G8LJtypsSRC5yOFPW0R6wcd5QE5eUzfwac40+OLlAaEdmojIev2XBtAp1iZssrgUysHUENZ9y7J3/2GLSiEiUQlFEf1dPB4Jh+IWl0M5+E7qF0PXrtypeHzRjwGUp7uD9tanavFCSwsjovR27T2Eq/99XW/YBICfDF278kcbSqIsTbefcJPenU62n+a6YSKHnGw/ja96z4sMPcBesPy+M/UbQ9euxPVmw/fu3cPvr17Dj2ufxne/+10r6yOiFD295/DhgV+JDO2PhEM/s7oeyl26K2s06N0xNjSMw5+2mFsNEaUVG4xj7/4josMbLCyFTPTQTBj4tjf8JwCWZbpzbGgYI6NjWP7UUkuKIyJVbDCOV9/eKbIcDQDej4RD/251TWSOaUMYACoeX9QL4B8AzMz0AAxiImv19J7Djl37RAO4KxIONVhcEpkobQgPXbvyx4rHF0UB/IvegzCIiaxxsv00PjzwK9y7d09k+DCAOq6GcJe0IQwAQ9eujFQ8vmgCwE/0Hig2NIyer89h6Y+qUVTkM61AIi9KKAp27T2Ez784I3qXCQA/4Zpg98kYwgAwdO1Kb8Xji+YDqNYb+4c7E+iIdKFs7p+i7M//zJQCibym/9IA3t6xW2QdsGYCQJ3IpcpIProhDADJtsRqAHP0xt67dw/R//wa1wfj+MFf/gVnxUSCRkbHcPhoC44cFdoPItWWSDj0H1bVRdZ6RHSgPxCcCXXz9yrR+/h8hXh27RqsW7saRT6GMdF0EoqCU+1ncLL9tNHwnQDQwH2C3U04hIHsghi4H8Yr/bWYUzrbyF2J8tbI6BhOtZ9GR0ToqhhTsQWRJwyFMPBtELcBqM3mCZ9e9iRWrajF8mVcSUHe1HG2Cz2950RPPZ5OP9QZMAM4DxgOYY0/EGwGsDHb+/t8hVi+bCmqFi9E1eKFnCFT3kooCnq+Po/+SwPo6T2Xzaw3VTvUAOaeEHki6xAGAH8g2AB1+8sZuRZSWlKMyopyfL+iHFWLF6LIV4jKivJcH5bIdrHBOK4PDiM2GEf/pQHEhobNeNgJAE2RcIhXyMgzOYUwAPgDwXKom8Fn1Z4gIl1dUGe/cacLIfPlHMIafyDYCHUbzJxnxUQEQJ39NkbCoWanCyHrCK0TFpE8qeNXAP4PwF8B+J5Zj03kMRMAPgDws0g41Ot0MWQt02bCqZIrKBqTH5wZE4mZgHqMhZuxe4glIaxJhnE91DA2tLaYyEP6oYZvG8PXeywN4VT+QLAa6kbT9QDm2fW8RJIahrrevpnrfb3NthBOlVxRUQ+gLvnBlgV5QRfUM07bGLykcSSEp0qGcnXKx8zkvwxncqt+AHEAfQCikXAo6mg1JK3/B3DUdh/UDJ/eAAAAAElFTkSuQmCC" class="kg-image" alt="How to make the first and last elements wrap inwards using flexbox"><figcaption>https://ko-fi.com/peterpoliwoda</figcaption></figure><hr>]]></content:encoded></item><item><title><![CDATA[What can the Apple Watch do? The complete guide of the Apple Watch features]]></title><description><![CDATA[<p>OK, it's been a while using the Apple Watch and I thought I might say a few words to give it some credit. Overall it's been very good to me. I think I'm generally healthier and move my butt a lot more effectively than I used to on a day-to-day</p>]]></description><link>https://peterpoliwoda.me/what-can-the-apple-watch-do/</link><guid isPermaLink="false">5f7dd5e41d7d594ba08579c6</guid><category><![CDATA[Apple Watch]]></category><category><![CDATA[iOS]]></category><category><![CDATA[sports]]></category><category><![CDATA[health]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Wed, 02 Feb 2022 19:13:22 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2021/02/AB8A3071-5349-4319-8609-6603A1AF5E3C.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2021/02/AB8A3071-5349-4319-8609-6603A1AF5E3C.jpeg" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"><p>OK, it's been a while using the Apple Watch and I thought I might say a few words to give it some credit. Overall it's been very good to me. I think I'm generally healthier and move my butt a lot more effectively than I used to on a day-to-day basis. Never before did I go for a walk out in the cold, wet and dark night just to move a little, which now also allows for listening to podcasts, audiobooks and of course "Closing My Rings™️".</p><h1 id="how-does-it-compare-with-a-smart-band">How does it compare with a smart band?</h1><p>My smart wrist band story started with a Xiaomi MiBand 4 which proved to be a life saver in the confined life during the 2020 coronavirus pandemic. I got it a couple of months before the crazy started and even then it was an interesting little device which primarily showed my step count for the day and lasted for weeks on a single battery charge. Having something like that is a no brainer, especially that it costs in the region of €30 and allows you to see a little bit more into your daily life routine. With the smart band, I started walking a bit more and it gave me a quick view into how many steps did I take on a given day. It also solved another problem, which is: I don't always have my phone with me, especially at home. This meant that the difference between sitting on the couch for a couple of hours vs walking around the house, cooking, cleaning or doing other things you don't typically use your phone with hasn't been recorded. As it turns out, even standing up a little to get a coffee adds up throughout the day. It showed the periods of inactivity during the day when I needed to focus and the times I was going to grab a snack. It turned out to be a helpful unobtrusive way to see into my own life. When summer came and the days were longer and the mornings weren't as chilly, I started to run every working day morning to stay sane during the full lockdown period. It proved invaluable in those days. A morning routine like that seriously helped to alleviate the growing mental unease.</p><p>The smart band has its limitations however. The morning routine was fun but I felt like I can do better. I got myself a pair of wireless AirPods to remove the need for sticking cables into the phone every morning and being annoyed with pulling on cables throughout the run. The AirPods solved that problem but the one with the phone remained. As mentioned before, having the smart band was a great way to get rid of the phone and yet still have the benefits of tracking my daily life in an unobtrusive way. Removing the phone on the run removed two aspects I didn't want to lose:</p><ol><li><strong>Music</strong>: Running without a soundtrack is just not as fun, and I found that I got bored a lot easier and didn't push myself as much as I did with a nice motivational track.</li><li><strong>GPS tracking</strong>: Less important, because it is possible to measure the length of the run just using the accelerometer in the device, but it didn't seem as accurate in the long run. I wanted to see how much progress I was making in a more reliable way. The next generation of the MiBand does have a GPS tracker and a bit more functionality nowadays. If you're looking for an entry level smart band to start your adventure, in my fairly limited and biased opinion, that's the way to go.</li></ol><h1 id="enter-apple-watch">Enter Apple Watch</h1><p>I decided to get an Apple Watch as an experiment. I've had a Moto 360 smart watch for a little while and when paired with an Android phone it was quite fun to use, though I had to disable the constant buzzing on both my wrist and in my pocket so that I wouldn't go insane. Buzz buzz buzz... </p><p>I was excited to see what happened in the world of smart watch tech in the couple of years. As it turned out, quite a bit!</p><h2 id="the-freedom-of-not-having-a-phone">The freedom of not having a phone </h2><p>This is the reason I bought the Watch. Has it stood up to the task?  <br>- <strong>Absolutely yes.</strong></p><p>As this was the sole reason to buy the watch and use it for working out, it has been the tool for the job. Running with music, podcasts, audiobooks, walking with meditation apps, from a physical health device perspective it solved all of the issues.  After a little research I went with the bigger 44mm, Wifi-only version. I have tried both the smaller and the bigger one in a shop felt like the bigger one would be just as good, especially that it has a bigger battery which should last longer. After using it for a good couple of months now, I still think I'd like to try out the smaller one for a while to see if it would be more comfortable. It is quite a bulky machine on the wrist and being white it tends to be a little too "showy". Still, even with that, I do wear it every day. Next time I'll probably go with a smaller version. Mainly because even though it's a very powerful device, it's literally like a phone on your wrist, it is not a watch. It's still expanding and it's becoming more and more a health device, like a smart band on steroids that does multiple things well. It won't be a super accurate running device tracking all of your stats. It will be your running partner. Something you grow to like over the time you use it.</p><h3 id="should-i-buy-the-wifi-or-lte-version-of-the-apple-watch">Should I buy the Wifi or LTE version of the Apple Watch?</h3><p>To add a little on connectivity. The <strong>GPS</strong> works perfectly well on the Wifi only version and so does <strong>Apple Pay</strong> which turned out was a nice addition. After my bank finally decided to add support for Apple Pay, I stopped taking my wallet to the shop and I find myself paying with the watch most of the time for almost anything. In Ireland, Apple Pay doesn't have limits on the transaction amount, like the €50 on typical contactless transactions with your card. Which means you can buy coffee, groceries or a TV using your Watch.</p><blockquote><strong>TL;DR:</strong> Apple Pay works offline.<strong> <u>You don't need internet connectivity on your Apple Watch to pay for groceries.</u></strong></blockquote><p>When it comes to connectivity, the only time I find it would be useful to have the 4G version is when I want to stay in touch with the world, when waiting for an important text or an email or generally when going for a walk at lunch time. That's when I do take the phone with me and that sorts out the issue. On the other hand, it's good to be able to disconnect sometimes but still be able to take music or an audiobook with you.</p><h2 id="how-do-i-listen-to-music-on-the-apple-watch">How do I listen to music on the Apple Watch?</h2><p>Since we're on the topic of phone-less music, what can I do with watch you, might ask. Well, it turns out quite a lot. You can synchronise your own iTunes playlists for offline listening. While there are apps like Overcast or Audible which synchronise their content when the watch is on your wrist, most of Apple's content: Apple Music, Podcasts, etc. synchronise when the Watch is put onto a charger. Why that is I am not quite sure, the most compelling reason must be battery optimisation and the fact how the WatchOS communicates with iOS as it isn't just as straight forward as all processes running in the background on all the time. Some apps also say that putting your Apple Watch on a charger will speed up the process of sending data between the phone and the watch. It can be a bit frustrating at times when you want to sync something specific NOW. The watch works best if you let it do its thing in the background. Subscribe to a podcast or make a playlist available offline and let it synchronise whenever it's charging or whenever it feels is most appropriate to do so. This way you don't feel it and content appears on your wrist. I do find that some of my podcasts are missing sometimes or they are not completely in sync. </p><p>When it comes to audiobooks, you have Audible. It downloads the books to the watch and treats them like a copy. The app does need a little bit more work. The offline (on Watch) vs online (on Phone) players are different and don't synchronise fully when they need to and it generally feels like the app was made once by somebody who wanted to support the Watch but never went deep into the core of how WatchOS works internally to make it work as good as it could. It does what it should but it's quite buggy. There have been times when I opened the audible app and the <em>On Phone</em> player showed me the song I was playing through Apple Music before opening the app. Most of the time it works though and I haven't seen a good alternative yet so I guess we're stuck with it at the moment. I would also like to see other audiobook apps, like for example <strong><a href="https://audioteka.com">Audioteka.com</a></strong> to release an app for the Watch so that I could listen to books in Polish for example.</p><p>Music-wise, you do get Apple Music. There have been <a href="https://www.pocket-lint.com/smartwatches/news/apple/154490-spotify-music-streaming-now-works-on-apple-watch-without-the-iphone">some news</a> that you can now use Spotify to stream music directly from your Watch without the phone but since I don't use Spotify any more, I am yet to find out how that works. Maybe it's release on a per-region basis. With my free subscription I don't seem to be able to stream content when my phone is not around and my Watch is connected to wifi.  As of writing this post the only way to store music for offline is Apple Music, having a playlist downloaded to your phone or perhaps use a podcasting app to subscribe to some music podcasts. Apple Music is a little different to Spotify, there are ways to make it work if you're thinking about moving. You can sync your playlists and the content discovery however different, still exists. You do get a 3 month trial so it's worth a shot just to check it out fore if it's for you for free. Music handling of Spotify on the watch is really good too. It's a solid app to control your home speakers for example. It works blazing fast. You can play music to any device connected to your account. </p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/10/music-podcasts-spotify.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><h2 id="sports">Sports</h2><h3 id="nike-running-app">Nike running app</h3><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/10/nike-watch.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>It's one of the most well-thought-out fit for purpose 3rd-party app on the Apple Watch. I has four quick look screens showing your latest runs, starting a run, guided runs and a settings screen. During the run you can get prompts in the regular or guided runs and you get quick access to workout controls such as pausing, resuming, etc., your running stats as well as quick access to the built-in Music Player. You can control the music volume using the digital crown just like in most music players on the Watch.</p><h3 id="apple-workouts-app">Apple Workouts App</h3><p>The Nike running app is one of the best apps out there for running there's no doubt about that. Sometimes however you might want to go for a swim, or an outdoor walk in which case the app to use would be the built-in Workouts app. It has a ton of workouts built-in and there are new ones being added in every once in a while with software updates. Additionally, the Workouts app is not as selfish with some of the data like the GPS tracking points which in case of the Nike app is not saved into Apple Health's activity repository data. GPS tracking from the Workouts app can then be imported into other apps like Google Fit or Strava. I find the swimming tracking pretty good and better than the Swimming activity tracking on the MiBand which would only give you the main swimming style instead of sharing all of them.</p><h3 id="airpods-or-any-other-bluetooth-headphones">AirPods or any other bluetooth headphones</h3><p>The Watch does have a speaker but you wouldn't be able to use it to play music while working out, nor should you do it as it would drain the battery, annoy everybody around you and you wouldn't be able to hear the music properly anyway. The watch works great with AirPods as they are small enough to put them in and get out of the house with just the watch. At times the bluetooth connectivity can do some funny things, especially if you're near the house but not quite inside. I found that with some 3rd party apps (especially <a href="https://amzn.to/3B0YCV6">Amazon's Audible</a>) the phone would sometimes steal my music when I pass near the house or better yet play the audiobook on both devices and give my wife a bit of a scare when randomly starting to talk in the dark room.</p><h2 id="activity-rings">Activity rings</h2><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/02/incoming-56BE7F15-2257-40FE-BF4A-8318A7B8B222.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>This might be the most recognisable feature of the Apple Watch. It doesn't only count your steps like most other smart bands out there but looks at your active burnt calories. You burn calories by raising your heart rate level to a certain point and generally by 'moving'. This is why the red circle is called the <strong>Move </strong>ring. </p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/02/incoming-CC657C98-39BC-45D0-ACFC-BF93893E4738.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>The green <strong>Exercise</strong> ring shows the Active Exercise minutes. You can earn them by running a workout or by doing some moderate activities which will raise your heart rate to around 100 BPMs. A brisk walk would do that, a run is even better. Doing a couple of flights of stairs might earn you a bit of the green ring also.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/02/incoming-668B366E-46F2-4804-B645-E707C46DE569.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>The blue ring counts the hours in the day in which you were <strong>Standing </strong>and moved just a little bit. Standing up without moving much might count as a "standing hour". The watch can also remind you it's time to stand up if you've been sitting down for too long by a notification and a gentle nudge.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/02/incoming-C488FB2A-74A7-4171-80BE-655E56497005.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>All of these setings can be edited so that you can hit goals that are more appropriate for you.</p><p>The watch still measures your steps count just like any other band, so if you were joining a work steps competition you can still do that and get relatively accurate results with some extra data.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2022/02/incoming-2C1B8786-A61A-46FC-B233-6761358F9557.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><h1 id="battery-life">Battery life</h1><p>As we've been living the pandemic life, the battery hasn't been a problem. The Watch would last for about a day and a half generally with a moderate use with workouts, GPS tracking, Apple Pay payments almost every day. Going away proved pretty OK too. The battery charges pretty fast and I generally like to keep it between 30%-60% during the day anyway to make sure the battery keeps happy and is not overcharged or left undercharged for too long either. I would generally charge it in the morning or right before going to sleep to be able to track my sleep quality.</p><h1 id="the-nice-to-have-s">The Nice-to-Have's</h1><h2 id="sleep-tracking">Sleep tracking</h2><p>I tend to sleep with the Watch on my wrist to track my sleep quality using <a href="https://apps.apple.com/us/app/pillow-sleep-cycle-tracker/id878691772">Pillow</a>. This is where the smaller one would come in handy as the bulky size can be a little annoying at times at night in which case it lands on the nightstand nearby. It's still comfortable enough to sleep in though. The watch would also send you a gentle reminder that it's time to go to bed, and if necessary will remind you to charge it before doing so.</p><h3 id="alarm-clocks">Alarm clocks</h3><p>I really like the alarm clock slightly tapping me on the wrist to wake up instead of a buzzing alarm clock. You will need to set your watch to Silent mode to make sure it won't also wake everybody else in the house. Having it linked to sleep analysis is a nice feature. </p><h2 id="so-long-passwords-">So long passwords... </h2><p>When using a MacBook Pro, unlocking the laptop is really neat. Once paired and set up to trust each other, all it takes to log into the device is to press any key and be close enough to it. It's secure enough that it won't just unlock even if you're standing 1-2 metres away from the laptop.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2020/10/IMG_3334.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"><figcaption>The Watch makes a clickitty-click which is a really pleasant, almost rewarding sound for some reason.</figcaption></figure><h2 id="breathing">Breathing</h2><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/10/breathing.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>Now this is pretty interesting. It goes into the list of nice to have and low-barrier mindfulness. 1 Min = 7 breaths. And you feel a little better because of it. Useful in lockdown times.</p><h2 id="control-keynote">Control Keynote</h2><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/10/keynote.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure><p>You can do that. There are 3 hoops you need to jump through and the connection is finicky but it works. Is it really better than a clicker? Not sure yet. Probably won't be needing a clicker for a while in today's world</p><h2 id="shortcuts">Shortcuts</h2><p>Recently I also found that I can do a lot more using shortcuts. It's the little things that you would think some apps would do for you but don't. Like caffeine intake. How many cups of coffee do you drink a day?</p><p></p><h1 id="other-3rd-party-apps-games">Other 3rd party apps... Games?</h1><p>As the Apple Watch has its own App Store now, you can download apps directly on from the watch. There aren't many choices yet as it's quite a niche device and there's only so much it can do, but you are able to get a browser, if you're into that, many sleep tracking apps, sports apps... and then, there's this... <br>because of course who wouldn't want to play a mini-football game on a mini watch screen.</p><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/10/watch-football.jpg" class="kg-image" alt="What can the Apple Watch do? The complete guide of the Apple Watch features"></figure>]]></content:encoded></item><item><title><![CDATA[How to debug schema version of sqlite database in moor flutter]]></title><description><![CDATA[<p>Short answers to complex questions. To check the schema version of a moor created database you will need to use a PRAGMA statement. As the <a href="https://www.sqlite.org/pragma.html">documentation</a> puts it:</p><blockquote>The PRAGMA statement is an SQL extension specific to SQLite and used to modify the operation of the SQLite library or to</blockquote>]]></description><link>https://peterpoliwoda.me/how-to-check-sqlite-version-in-flutter-moor/</link><guid isPermaLink="false">6133714e1d7d594ba085805b</guid><category><![CDATA[quick answers]]></category><category><![CDATA[code examples]]></category><category><![CDATA[flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Sat, 04 Sep 2021 13:51:17 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2021/09/B0FBCCBE-719E-4B7E-9E9D-A73C163C91D8.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2021/09/B0FBCCBE-719E-4B7E-9E9D-A73C163C91D8.jpeg" alt="How to debug schema version of sqlite database in moor flutter"><p>Short answers to complex questions. To check the schema version of a moor created database you will need to use a PRAGMA statement. As the <a href="https://www.sqlite.org/pragma.html">documentation</a> puts it:</p><blockquote>The PRAGMA statement is an SQL extension specific to SQLite and used to modify the operation of the SQLite library or to query the SQLite library for internal (non-table) data.</blockquote><p>Long story short, moor uses the default name for the sqlite schema. In SQLite, the default schema is called `main`. Running the PRAGMA `schema_version` command on the main schema will do the trick!</p><pre><code class="language-SQL">PRAGMA main.schema_version;
</code></pre><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2021/09/image.png" class="kg-image" alt="How to debug schema version of sqlite database in moor flutter"></figure><p>Read more on migrations at the official moor plugin documentation here: <a href="https://moor.simonbinder.eu/docs/advanced-features/migrations/">https://moor.simonbinder.eu/docs/advanced-features/migrations/</a> </p><p>Read more on SQLite pragma statements at: <a href="https://www.sqlite.org/pragma.html#pragma_schema_version">https://www.sqlite.org/pragma.html#pragma_schema_version</a></p>]]></content:encoded></item><item><title><![CDATA[Developer insight: Why does Dart have two constant variable variants?]]></title><description><![CDATA[<blockquote><strong>What's the difference between const and final in dart? </strong><br><strong>TL;DR: </strong>const is set at coding-time and pre-compiled; final is assigned at runtime but only once.</blockquote><p>For a while now, I've been coding a little in Dart by writing a Flutter mobile app. It's a very fun and easy to</p>]]></description><link>https://peterpoliwoda.me/whats-the-difference-between-const-and-final-in-dart/</link><guid isPermaLink="false">6022a1141d7d594ba0857ad2</guid><category><![CDATA[Dart]]></category><category><![CDATA[DartLang]]></category><category><![CDATA[code examples]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Thu, 18 Feb 2021 21:00:43 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2022/03/whats-the-difference-downloaded.jpeg" medium="image"/><content:encoded><![CDATA[<blockquote><strong>What's the difference between const and final in dart? </strong><br><strong>TL;DR: </strong>const is set at coding-time and pre-compiled; final is assigned at runtime but only once.</blockquote><img src="https://peterpoliwoda.me/content/images/2022/03/whats-the-difference-downloaded.jpeg" alt="Developer insight: Why does Dart have two constant variable variants?"><p>For a while now, I've been coding a little in Dart by writing a Flutter mobile app. It's a very fun and easy to follow React-ish, SwiftUI-ish kind of language in which you can easily create beautifully looking applications with minimal effort by describing the UI a self-enclosing objects and functions. So far so good.</p><p>After leaving my tutorial Candyland and starting to write real code I hit a couple of walls and had to come back to re-read what I've just learnt. Coming from a JavaScript / Node.js environment background I was struck by the amount of <strong>types</strong>, <strong>data types</strong>, <strong>function types</strong>, <strong>return types</strong>... ugh, why can't everything just be a <strong>var </strong>like in my hippy language?! Well.. it kind of can... but not really... Dart is... well, special. It also has a <strong>const</strong>, a <strong>final</strong> and a <strong>static </strong>which are kind of similar but... no, they are really completely different and they confuse me and everybody in my current office. For clarification I'm going to add that since it's the beginning of 2021 that makes it literally just me in the home office , because we're at a Level 5 lockdown and I'm stuck at home and the only person to talk to about code return types is Mr Rubber Ducky. </p><p>In case you are unfamiliar with the concept of <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">Rubber Ducky Debugging</a>, let me introduce you. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2021/02/mr_rubber_ducky.jpg" class="kg-image" alt="Developer insight: Why does Dart have two constant variable variants?"><figcaption>That's 'Mr' Rubber Ducky to you. He's a developer's best friend.</figcaption></figure><p>Enough babbling, let's go one by one for my own benefit to get to know the types. What I want to know is: What is the difference between a final and a const in Dart?</p><p>For Numbers, Strings, etc, I would like to send here: <a href="https://flutter-examples.com/list-of-all-data-types-available-in-dart-flutter/">https://flutter-examples.com/list-of-all-data-types-available-in-dart-flutter/</a> </p><h2 id="const">Const</h2><p>From what we know from programming, a constant value is one that doesn't ever change, and if you do try to change it your code will blow up. Now, in Dart there's a little bit more to it than that. In Dart a <code>const</code> is a specific value that is <strong>assigned once <u>at compile time</u>.</strong> What this means in practice is, a const in Dart should only really hold things that are set in stone and not dynamically generated. A good example are some reusable numbers, strings, doubles, etc. Once the value is assigned at compile time, it is baked into the app code and is stored in zeros and ones. A const cannot hold any dynamically generated data, like for example a <code>Date.now()</code> or a `Color` widget because this <u>value won't be known to the compiler ahead of time</u>.</p><pre><code class="language-JavaScript">// You can do:
const a = 10;

// But this will fail
const b = DateTime.now();

// A common use case for example could be reusing a colour like so:
const myConstantColour = Color('0xFF1D1E33');</code></pre><h2 id="final">Final</h2><p>So whats a final in that case? A final in Dart acts more like a const in JavaScript you know and love. A final is an immutable value which can only be assigned once at any given time. It can hold any value which is assigned once and then never changed again. It's something of a loose constant which you'll see appear in your code a lot.</p><h2 id="static">Static</h2><p>The post wouldn't be complete if we didn't mention the keyword <code>static</code>.</p><p>Used in a <code>Class</code> context, a <code>static</code> value is static to the Class. Which means in human terms that the value should be accessed statically from the Class like in this example: </p><pre><code class="language-JavaScript">class Ducky {

static String material = 'Rubber';

}
</code></pre><p>In this case, in another object the static value will be referred to through the Class itself not through the instantiated object (i.e. Ducky duckyObject = new Ducky() in this case)</p><pre><code>Ducky.material == Rubber</code></pre>]]></content:encoded></item><item><title><![CDATA[How can I stream my data from Cloudant in Node.js]]></title><description><![CDATA[When it comes to querying data from a database, the frequently spotted attitude of "let's just run this query and it'll be fine" can lead to serious bottlenecks and cause trouble when...]]></description><link>https://peterpoliwoda.me/how-can-i-stream-my-data-from-cloudant-in-nodejs/</link><guid isPermaLink="false">5ec188d41d7d594ba08572c7</guid><category><![CDATA[Node.js]]></category><category><![CDATA[cloudant]]></category><category><![CDATA[code examples]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Mon, 01 Jun 2020 11:36:53 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2021/09/4281CB46-43AB-450A-9CF1-9CE0DDE3D099.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2021/09/4281CB46-43AB-450A-9CF1-9CE0DDE3D099.jpeg" alt="How can I stream my data from Cloudant in Node.js"><p>While considering hosting applications on <a href="https://cloud.ibm.com">IBM Cloud</a> you might be tempted to use Node.js and Cloudant in your application stack. Cloudant is one of the most commonly used NoSQL database on IBM Cloud. It's based on Apache CouchDB and hosted in the cloud. If you're using Cloudant in your application you should consider using streams.</p><blockquote><strong>Tip:</strong> for development purposes you can run a local instance of CouchDB and your app will be mostly compatible with it.</blockquote><h3 id="handling-big-queries">Handling big queries</h3><p>When it comes to querying data from a database, the frequently spotted attitude of "<em>let's just run this query and return the data in an API and it'll be fine"</em> can lead to serious bottlenecks and cause trouble when the queried data turns out to be a lot bigger than anticipated. Querying hundreds of megabytes of data from a database is generally a bad idea and in most cases can and should be avoided. The memory burden and performance cost of each request can be quite high and can lead to unexpected consequences. In simple terms, every time you make a query your data needs to be stored somewhere to be processed.</p><h3 id="what-does-this-memory-usage-mean-anyway">What does this 'memory usage' mean anyway?</h3><p>Take a typical REST API database query example from any available tutorials out on the inter-webs. For demonstration purposes we'll make one adjustment. Let's assume that your database has 30,000 medium sized JSON objects and for some good reason you want to return them all at once without pagination (and not just because you made a terrible mistake when you designed the system in the first place; never thinking there will eventually be so many records in there...).</p><p>What happens in this instance is, you make a request to an API endpoint which queries a database for some records. The application waits for the database to go through its tables or collections (1st bottleneck) after which the data is send through the network (2nd bottleneck) to the application and stored in a variable (3rd bottleneck). As this variable is stored somewhere in memory, it means there is a single object of 600MB of JSON data in your app which will be eventually returned through your API (4th bottleneck). Only then it can be safely disposed off by the garbage collector. In a typical example, it's a very synchronous process. It takes a some time to make the query, pass the requested records to the app, handle them in the app and eventually pass the results to an API endpoint. If you're lucky enough this process can lead to a timeout of said API endpoint before the data starts to download through it. Before it gets to that though, your app will most likely blow up.</p><h3 id="nodejs-streams-to-the-rescue">NodeJS streams to the rescue</h3><p>According to the <a href="https://nodejs.org/api/stream.html#stream_stream"><em>Node.js documentation</em></a>:</p><blockquote>A stream is an abstract interface for working with streaming data in Node.js. The <code>stream</code> module provides an API for implementing the stream interface. There are many stream objects provided by Node.js. For instance, a <a href="https://nodejs.org/api/http.html#http_class_http_incomingmessage">request to an HTTP server</a> and <a href="https://nodejs.org/api/process.html#process_process_stdout"><code>process.stdout</code></a> are both stream instances. Streams can be readable, writable, or both. All streams are instances of <a href="https://nodejs.org/api/events.html#events_class_eventemitter"><code>EventEmitter</code></a>.</blockquote><p>In other words, we can use a stream to prevent data clogging by making the data pass through the app bit by bit, instead of synchronously passing gigantic numbers of records at a time. Did I already mention it's a bad idea?</p><h3 id="pipes">Pipes</h3><p>The official Cloudant Node.js library includes streaming as standard. Functions such as List() or Find() have equivalents with a <code>*AsStream</code> suffix. Instead of a <code>Promise</code> they return a <code>request</code> object which may be piped as a stream. For example:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">cloudant.db.listAsStream()
  .on('error', function(error) {
    console.log('ERROR');  })
  .on('end', function(error) {
    console.log('DONE');  })
  .pipe(process.stdout);</code></pre><figcaption>*AsStream functions example using the <strong>@cloudant/cloudant </strong>npm module</figcaption></figure><p>Note that there are no callbacks when using the streams functions and event listeners must be used instead.</p><p>Below we'll build a fully fledged example to test the streams in action.</p><p>First, let's set up a simple express app and pass along the Express response object to a newly created service. Let's call it <code>dbService.js</code>. </p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">const express = require('express');
const dbService = require('./dbService'); // We will create a service next

const app = express();

app.get('/', (req, res) =&gt; {
  dbService.getRecords(res); // Passing the express res object to dbService
});

app.listen(3000).on('listening', () =&gt; console.log('Listening on port 3000'));
</code></pre><figcaption>Pretty self explanatory - Simple Express server in your entry point <strong>index.js</strong> file</figcaption></figure><p>The application will import a <strong>dbService </strong>module and pass along the express <strong>response </strong>object. The response object is a <code>stream</code> which means you can pipe a stream of data into it. Here's the dbService in full:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">const Cloudant = require('@cloudant/cloudant');
const cloudant = Cloudant('https://username-bluemix:password@username-bluemix.cloudantnosqldb.appdomain.cloud');

module.exports = {
  getRecords(res) {
    const db = cloudant.use('your-cloudant-db-name');

    const querySelector = {
      selector: {
        something: {
          $eq: 'here',
        }
      }
    };

    db.findAsStream(querySelector)
      .on('error', (error) =&gt; {
        console.error('ERROR', error);
        // Make sure you end your request on error
        // as otherwise the API will hang
        res.end('Error finding data');
      })
      .on('end', (error) =&gt; {
        if (error) {
          console.error('ERROR on DONE');
        }
        console.log('DONE');
        res.end();
      })
      .pipe(res);
  },
};</code></pre><figcaption><strong>dbService.js</strong> module handling the database connection through streams</figcaption></figure><p>The <code>getRecords()</code> function assumes there is a JSON document in your Cloudant collection* called <strong>'your-cloudant-db-name'</strong> which looks something like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-JSON">{
  "something": "here"
}</code></pre><figcaption>The selector queries for documents with an attribute 'something' equal to the word 'here'.</figcaption></figure><p><em>*Collections in cloudant are called 'databases'.</em></p><p>The <code>findAsStream()</code> function returns a stream object which can be piped to the res object. We should also end the response after the read is finished on the <code>end</code> event as well as on an error to prevent the API from hanging.</p><p>And that's it, it's fairly easy to take the burder off your strained application.</p><p>Leave a comment below if you like this example and let me know if there's anything you'd like me to cover next.</p><p>Cześć!</p><h2 id="has-this-been-helpful-to-you">Has this been helpful to you?</h2><p>You can support my work by sharing this article with others, or perhaps <a href="https://ko-fi.com/peterpoliwoda">buy me a cup of coffee</a> 😊 </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="data:image/png;base64,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" class="kg-image" alt="How can I stream my data from Cloudant in Node.js"><figcaption>https://ko-fi.com/peterpoliwoda</figcaption></figure><hr>]]></content:encoded></item><item><title><![CDATA[How to use an iPad as a whiteboard in Zoom?]]></title><description><![CDATA[Most online whiteboarding apps don't give you the freedom of expression and dynamics of a real whiteboard. Use your iPad to tackle this with an easy screen-sharing trick.]]></description><link>https://peterpoliwoda.me/how-to-use-ipad-for-whiteboarding/</link><guid isPermaLink="false">5ec7ef311d7d594ba0857409</guid><category><![CDATA[iPad]]></category><category><![CDATA[Tips&Tricks]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Fri, 22 May 2020 17:12:56 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2020/06/F3347080-7FEF-4CC7-92DC-AB938E2A43EE.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2020/06/F3347080-7FEF-4CC7-92DC-AB938E2A43EE.jpeg" alt="How to use an iPad as a whiteboard in Zoom?"><p>As we're all stuck and home and as far as productivity goes, apparently we're still doing pretty alright. But let's face it, there are times where you need to brainstorm some new ideas and a whiteboard has always been the simplest goto utility we could grab and draw a couple of boxes to explain what's on our mind. Being a visual person myself, sometimes it's just easier to draw a bunch of boxes on a piece of paper or a napkin to understand the idea being discussed better.</p><p>There are many helpful tools out there in which you can doodle and put together some quick designs with your colleagues or friends. There's Mural.ly, InvisionApp, Slack now has a quick drawing on everybody's screen sharing feature which I love and Zoom and WebEx themselves actually each now have a built-in whiteboard feature.</p><p>However, none of them give you the same freedom of expression and dynamics as a piece of paper or a whiteboard on a wall which everybody can see and using a mouse is just not the same. The closest available remotely utility would be to draw on an iPad screen using a stylus pen.</p><p>The good news here is, if you're a proud owner of a Mac and an iPad you don't need any extra apps. Just use your operating system's built-in functionality.</p><blockquote>TL;DR - You don't need any extra apps! Just use QuickTime and Apple Notes!</blockquote><figure class="kg-card kg-image-card"><img src="https://peterpoliwoda.me/content/images/2020/06/867FE5B0-FEC0-4B00-8C04-AB12E2831B9F.png" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"></figure><p>To show your iPad's screen on a Mac you don't need any extra tools. QuickTime on MacOS Catalina has a built-in feature which allows you to <strong>Record a Movie</strong> using an iPad's screen plugged in to the USB port on your desktop.</p><p><strong><code>Important:</code> </strong><u>Plug in your iPad to your Mac using a USB cable before doing this.</u></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2020/05/1.-QuickTime-select-New-Movie-Recording.png" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"><figcaption>1. Select New Movie Recording from the QuickTime File menu</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2020/05/2.-Select-camera-source.png" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"><figcaption>2. In the Record button dropdown menu select your iPad as the camera source</figcaption></figure><p>There's no need for any extra apps or spending unnecessary €€€s. QuickTime allows us to show the screen before recording and doesn't block the view with any buttons. Once you move the mouse cursor away from the window all it shows is the iPad screen and your doodles. Nice and clean!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2020/05/3.-Your-screen-will-show-up-in-a-preview.png" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"><figcaption>3. Your iPad screen will show in the QuickTime preview with no lag.</figcaption></figure><p>You can record the screen for reference if you like, but there is no need to do it. Now when you share your screen on any Zoom or WebEx call, you can also draw on an iPad whiteboard and make the most out of it!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://peterpoliwoda.me/content/images/2020/05/4.-Thumbs-up.png" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"><figcaption>Well done! Screenshot from the Mac.</figcaption></figure><p>The app shown in the demo screenshots above is called <strong><a href="https://www.gingerlabs.com/">Notability</a></strong>. It's great for note taking in general. It enables you to move things out of the way and has an endless paper feature, so you'll never run out of space. You can also doodle in <strong>Procreate</strong> or just plain old <strong>Apple Notes</strong>. The possibilities are endless because all you're doing is just sharing what's on the iPad screen. All you need to do is hop on that Zoom call and get going.</p><p>Angry Birds anybody?</p><h2 id="has-this-been-helpful-to-you">Has this been helpful to you?</h2><p>You can support my work by sharing this article with others, or perhaps <a href="https://ko-fi.com/peterpoliwoda">buy me a cup of coffee</a> 😊 </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAWEAAADkCAYAAABJ9ZUIAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Gkqr6gAAAYBpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHPK0RRFMc/Hhr5XSgLi0lYoUGJjcWIobAYT/m1efPmvRk1M17vjSRbZTtFiY1fC/4CtspaKSIlGxtrYoOe88zUTDL3du/53O8953TuuaCoCT3plAUgmUrb4VDQPzs37/c9o1ArswmfpjvW5PSoStHxcUeJZ2+6vFzF/f4dVVHD0aGkQnhIt+y08JjwxGra8nhbuFGPa1HhU+FOWwoUvvX0SJZfPI5l+ctjWw0Pg1Iv7I8VcKSA9bidFJaX05ZMrOi5eryXVBupmWmxrbJacAgTIoifcUYYpp8eBmXvp4teuuVEkfjAb/wUyxKry26xhs0SMeKk6RR1RbIbYk3RDZkJ1rz+/+2rY/b1ZrNXB6H8yXXf2sG3Bd8Z1/08dN3vIyh9hItUPn75AAbeRc/ktbZ9qNuAs8u8FtmB801ofrA0W/uVSmUppgmvJ1AzBw3XULmQ7VnunuN7UNflq65gdw86xL9u8QcNCme9tGiQ+AAAAAlwSFlzAAAOxAAADsQBlSsOGwAAHfBJREFUeJzt3X1sVFd6BvAn2Wi19liCCmOjthhjb1OWhdhlg0SCU3s1FBZGXVySarakAieFtCAlmHw1kRLisEgQsoSPbGET0K6xFKujBWJHGqgtRjtu7MQShNp8eKlg7DG0ko2NFktzzVb80z/u3DAYz9xzZ+7HuXOfn2Rhe87MvJnYj8+899xzH0EW/IFgNYBqAOXZ3N9kfQDuOF2EC8Qj4VDc6SKI6EGPiA70B4J1ABoA1AOYYVE9RE6bgPqHnfRFdW6/A6AvEg7pjfM03RBOhm8TgFqriyGivNUP9Y9bFECU78ruyxjC/kCwCcB79pRCRB4yDDWQ26CGsmdbitOGsD8QnAngAICN9pZDRB7VjmQoe22WnC6E2wCstbkWIiIA6ALQDDWQ836G/FAI+wPBAwC2OVALEdFUx6GGcZvThVjlgRBOHoT7nTOlEBGlNQx1dnwg32bHU0M4DmCeyB1LS4pRtfiHmFM624q6hPVdHHD0+d0iNhSHokw6XQaRGY4DaMqX3vG3IewPBBsA/EbvDpXz52HrSxtRtXihlXUROWJkdAyjt8acLkN6CUVBbHA4/e0JBdcHh3HxsqWTpHaoM+OolU9itdQQjkNnFrzSX4utL21Akc9ndV1ElCdig3H0XRpAf/LDgndkXVBnxlGzH9gOjwCAPxCsB/B5poGV8+dh354dDGAiyokWyp1nuxAbSj+bzkIXgMZIOOSqMx61EG6GzprgTw7tQWVFuQ0lEZFXjIyOof/SAHp6z+Gr3vNmPayresbfAYCKxxcdADAz3aCV/lr87Zq/sa0oIvKGoiIfvl9Rjh//9dNY6a9Faels3Pyf/821ZVEN4IWKxxd9b+jalag5lVrnkeTZcX/INGjf7h08EEdEtokNxnGy/Qx6es/lGsjDABpk7hc/orc22OcrRHvo1/ZVRESUlFAUdJztwqn20xi9NZ7LQ7VDDWPp1hg/qjegcn659VUQEU2jyOfDs2vX4LNf/xJvNG5BaUlxtg+1FkA8uQhBKrohTEQkg1UravHZr3+Jfbt34IlFWbVHZwD43B8INifbsFJgCBORq1QtXoiP9uzIZWa8EUBf8gpBjmMIE5EraTPjNxq3wOcrNHr3eQD+yx8INlpQmiEMYSJyNTWMP8a6n67O5u77nW5PMISJyPWKfD5sfWkjPjm0J5t+8UYAUaeCmCFMRHmjsqIcH+3ZgS2bNxhtUVRBXT1he5+YIUxEeefZtWvwyaEPjM6KZ0CdEdu6jI0hTER5aU7pbHy0Zwc2rH/OyN20ZWwN1lT1sEcB1GUa8P0KoT3eiYiktGH9c/jk0B6jy9l+Y1cQ686Ei4q4dSURuVtlRTk++dhwe8KWIGY7gog8ocjnw0d7dhhdymZ5EDOEichTtr60EW80bjFyF0uDmCFMRJ6zakUt9u3eYWQZm2VBzBAmIk+qWrwQHxkPYtPXETOEicizKivKjQZx1OwgZggTkacZDOIZAEzda4IhTESeZzCIqwC0mfXcDGEiIhgO4lp/INhkxvMyhImIkgwG8XvJa3TmhCFMRJSisqIcb24XXkfclmt/mCFMRDTF8mVLsWXzBpGhM5Bjf5ghTEQ0jWfXrsFKf63I0NpcLpPEECYiSmPrSxtQOV9oJ8kmfyBYns1zMISJiNIo8vnw/juvixyomwGgOZvnYAgTEWUwp3Q2tm7eKDK0NpurcjCEiYh0rFpRi6eXPSky9IDR1RKPZVcSEblNT+859F8cwPXB4Ydum1M6G3NKZ5v6fD5fIaoXL0RlRbmpj+uUN7dvwfMvvgxFmcw0bB6ARgBNoo/LECbygL37j6Az0pX29ouXrXvuyvnzsHJFLVatqEWRz71X6iny+fDm9i14b9c+vaGN/kCwORIOxUUel+0IojzXcbYrYwBbLTY0jCNHW/D8iy9j7/4jGBkdc6yWXC1ftlSkLTEDBmbCDGGiPNdx1rkATqUok+iMdOEf/8ndYbx180aR1RIbRZesMYSJyHZaGLe0nkBCUZwux5A5pbPx7No1IkObRAYxhInIMS2tJ/D8iy+jp/ec06UYsm7tatNmwwxhInKUokzivV37sGPXL1wzKy7y+UTXDjfpDWAIE5EUvuo976pZ8aoVtSgtKdYbpjsb5hI1Itqe/Ne0S/YAqAaw1uidtFnxup+uxtaXhGaajtqw/u/x4YEjesMakGFGzBAmor5IOBQ1+0GTZ441Qg0hoV1wNKe+OIPrg8PY+e5rUq8tXrWiFi2tv8XorfFMwzKevMF2BBFZIhIO3YmEQ02RcKgcwAsAHj5VL4OLlwfw2ls7pe8Tr9NfKTHDHwg2pLuRIUxElouEQ81QWxTvA5gQvV9saBjPv/gyYoNxiyrL3aoVtSIrJRrS3cAQJiJbaDNjqGHcLno/RZnEq2/vlDaIi3w+rNLf/L023QE69oStMjkJ3Lihfn71avpxZWVAYSFQXKx+5DvtdblxQ/18fFz9mEp7PQoL1ddIe53I9ZJ7KtQn36IfgHqab0aKMokdu36BTz7+QMoe8bq1a3DqizN6wxowTW+YIWyGyUk1aK9eVcMlU+jqmTsXmDdPDZ0FC9R/3Wx8HLhwQX1NhoeB27ezf6xZs4Af/EB9XZYsYSi7XCQcavYHglGo12ir0hs/emscr721E/v27JAuiOeUzsYTixbi4uWBTMMawBA2kRYuX34J3Lxp3uPevPng4xUUqMGzZIl7gmd8HOjsBL75JrfQner2baC7W/0A7r8mbnld6CGRcCievGz8AQC6a9JiQ8PYu/8Idr7zuuW1GbVqRa1eCM/zB4LVkXCoL/WbDGGjtBDIZbZrxN27athfuKB+vWQJUFOj/isbu18b7XUpKACeeQZYudIbLR2DFMlXF0TCoTsAGvyB4B0A2/TGf9V7Hi2tJ7Bh/XPWF2fA8qeexOGjhXr7DTdAXbL2LYawqO5u4PPPzZ3ZZUMLnlmz7gePk7PAyUl11vvll869NnfvqjV0dqp/oOrrGcYpYkOZV4ZZsUY4G5FwqNEfCPYB+I3e2JbWE1i+7EmpNowv8vmwfNlSvW1D66Z+g6sj9Fy4ALz2GnDsmPMBnOr2baCtTa2trU0NQztNTj74/LK8Nt3dwOuvA62t9r8mlLPkUrYXRMbKuNfE8qd09xqumrpKgiGczvg4sGcPcOiQPAEznbt3HwxDO3R3A+++qz7f3bv2PKdRnZ3qa6K1ccg1kkF8XG/c6K1xnGrXXZFgq+XLlooMe+BioAzh6Vy4oIaMXb1NM6SGsVV1a3+YZHtXkM7du+of0UOHOCt2mUg41ACBtcQtrSek2xxe4MobdalfMISnam1Vf2llneHpuX37/gzezOBpa1Pf5rvpD5NG+6Oqrdsmt2gA0K83aO9+3Q10bCUwG65L/YIhnOrYMfVtbD4wazY/Pn6/9eBmt28Du3ffX95G0tNWTeiNu3h5AP2XMi4Ns1XV4oV6Q2b4A8Fq7QuGMKDOGN99N/9+QbVZcbYBqgW5meugnXT3rvqHNt/+P+ex5Jra9/XGHf/shA3ViJlTOltkn+E67ROGMKC2IPIlaKbT1ma8PeH2tkwmDGJXSe43kXGd3cXLA1L1hqsW/1BvSJ32CUO4tdUbv5AXLqhvx6fbpyHV5KQ6e86Xtkw6DGK3adQb0NIqz2xYoCXBdgQA9Zcw38Mm1c2bmQ9QjY+rQe3Gg2/ZOHbMO/+tLhcJh9oAZDwLoqf3nDTrhgVCeF5y03sPh/D4OPDZZ05XYb+7d9WgnRrEN27kV/9X1MGD+u8OSBbNmW5UlEn0fH3eplIym1M6W2SP4WrAyyF87Fh+9jtFTA3iGzfUr734emgH60h6yZM4MvaGZbpIaOX8cr0hdYBXQ1jbWtHLtCDu7vZuAGuuXnX/EjzvaM5041e9csyEAaD6Cd2WRDmghnB5plECU2r38WIbYjraLNDLAazp6GBbwh2a9QbIMhuurNC9tmk5IBDC35dolyJTdHe745Rbspd22jdJLXlVjoxn0cUGDV1P1DJzSmbrDakFvNiO+PxzpysgWdm5FzLlIprpxr6Lcpw9J7LNpj8QnOmtEL56lbNgyoxrh90g41sWnatb2ErgzLlqb4Uwf8FIT3c3e8Py69MbIMvZc6UlJXpDPDYT/uYbpysgN/DSCTwulNzYJ2Pjd/SWHCE8p1S3L+yhmfCFC1wFQGL4x9oN4pluvD6Y8WbbCISwhw7M8YALibp9m3sPyy+a6Uadi23aRmCJb513Qvj3v3e6AnITHj9wtURCjj0kRJb4eieEvbYnAuWGf7RlF89043VJ1gqL8EYIsxVBRvGPtuziThdgknJvhDAv8kjZ4B9vypHIlpbeCGEeZKFs8OeGbOCNECbKBt9BkQ28EcJ8W0nZ4M8N2cAbIUxEJCmGMBG5UV2mG0XOVJMFQ5gonTE59h8g4xjCRPmA256SDRjCROkUFDhdAaVXl+lGN12WjSFMlM483WuEkaTcdFk2b4TwggVOV0BE5qp1ugAR/Zd0r/Ix4Y0QJqK84Q8EZ+qNEThdWBZ93gjhYt3rPBE9jD83sqrOdKPAdd2kwhAmSoc/N7Kqy3SjwHXdbCNyhQ9vhHBZmdMVkBvx50ZWGWfC1U/I04oQuMJH1BshXFjI5UZkHGfCsqrLdGNlhTyrWjgTTsXlRmQUZ8LS8QeC1QBmZBpTOb/cllpEJBK6M2GPHJgDuEyNjOHPi6waMt1YWlIs1SnLsaG43pA7DGGi6fDnRVb1mW5cvmypXXXoSiiKbk84Eg55pCcM8K0lGcMQlk6yFZGxr1gl0UG5mP7FRicAL/WECwv5i0ViCgr4syKnxkw3+nyFUs2EBc6W6wO8FMIAsGSJ0xWQG/zoR05XQFMkz5JzTSsCEFoZwRAmmhZ/TmTUCJ1VEcufetKmUsTEGMLTKC4G5s51ugqS2axZDGHJJGfBGVsRpSXFUs2ER0bHMHprXG+YB0MYAJ55xukKSGb8+ZCR7ix41Yo6eyoRJLJ7WiQc8mgI19Q4XQHJjD8fUvEHguUA3ss0xucrxLq1q+0pSJBACEe1T7wXwoWF/EWj6dXU8FRl+TTrDXh27RoU+Xw2lCKu/9IVvSFR7RPvhTDAEKbp1Wc8+E428weCjdDZvF3GWXBsMC7SD45qn3gzhBcs4DpQehBnwVJJnpixX2+cjLPgPgP9YMCrIQxwNkwP4ixYGsk+cFRvXGlJsXSzYADoPNulN6Qt9Qtvh/CsWU5XQTJYuZKzYEkkl6O1QWc1BABsfWmjdLPgkdExxIZ0T1eOpn7h3RAGgM2bna6AnFZQwFmwJJIBHAVQpTf26WVPSrUuWNMZ0Z0FA5wJp2BvmDZvVlfMkKOSPeAoBAK4tKQYb27fYnlN2eg4G9Ub0h4Jh+6kfsPbIQwAmzY5XQE5ZckSnh0nAX8gWAfBAPb5CrHzndela0MA6tpggVURbVO/wRAuLubbUS8qKOAfYAn4A8EmAL+DQA8YALZu3ojKinIrS8pah/4BuQlME8KPWVKN29TXA998A9y86XQlZJdt29iGcFByBUQzdNYBp9qyeQNWrRAebquEooj0g9umtiIAzoTv27yZFwP1ivXreSzAIf5AcGZy9jsEAwG80l+LZ9eusayuXJ1qPyMyrHm6bzKENWVlwPPPO10FWa2mRl2SRrbzB4INAOLQ2QtiqpX+WmkPxAHqLPhk+2m9YcORcCg63Q1sR6SqqQGuXgW6u52uhKywYAH7wDZLLjtrgLoTmuFLnr/RuEXaFoSm5+vzuteSA3Ag3Q0M4ak2bQLGx9Uwpvwxdy7wyitOV+EZySVnjVCvhiF00C2Vz1eIrZs3Sh/AANDS+lu9IRPIsBERQ3g6r7wC7N7NA3X5YtYs4O23eSDOQsllZgCgha/hWa+mcv48vLl9i7SrIFKdbD8tsiyteboDchqG8HQKC9Wj5+++C9y963Q1lIuCAq6E0OEPBKNTvtUHIDU0qgHMnOaupk9T1/10NTY8/5yU64CnSigKWlpPiAxN24oAGMLpFRers6fduxnEblVQoP4/LCtzuhLZTQ1T23sA6llwW1G1WJ5L1us51X5GpBd8PBIOxTMN4OqITMrK1F9iLl1zp23bGMAusGH9c/jk4w9cFcAjo2Ois+AmvQGcCevRlq4dO+Z0JWTEpk1cCyy5lf5abFj/HOaUzna6FMMOHz0uMkx3FgwwhMVoew8ziN1h0ybuFy0xN4cvAPT0nsNXvef1hk1A5wrRGoawKAaxOzCAH1I5f57IHreWKi0pxqoVdVi3drUrDrqlk1AUHP5UaBZ8INOKiFQMYSMYxHJbv54BPI03t2/Bq2/vFDmIZLqV/losf0rOvX+z0fLZCZElacPQWRGRiiFsFINYTjwdOa3KinJ8tHsHjreeQP+lAUvCuLSkGKUlJQCA6icWorRkNpY/9aSrZ71T9fSew6kvhPaIaBSdBQMM4ezU1Khn1bU9tCsdOaGmhqcj66isKMfOd15/4Hv901yQMqEoiA0+2LooLZk9bf823ffzUUJRsHf/EZGhXZFwyFAwMISzVV+vBjH3mXAWAzhr6ZaE5UvrwEw7fr5P5B3EBNR9MgzhOuFc8CCQsxjAZIPDnx7Hxcu6l7EHgCaRJWlTMYRzxSB2BgOYbNBxtku0D9wVCYeED8alYgibgUFsLwYw2SA2GBc9KSOrNoSGIWwWBrE9GMBkg4SiGFnW15BNG0LDEDYTg9haDGCyQUJR8NpbwgF83OhqiKkYwmbbtIlXb7YCA5hsoAWw4BmG/ZFwqCHX52QIW6G+noFhJgYw2cBgAE9AvWpIzhjCVmFwmIOvI9nAYAADQF0ufeBUDGErMUByw9ePbJBFAL8QCYf6zHp+hrDVGCTZ4etGNsgigLdHwqFmM2vgact24KY/xjCAyQaxwTj27j9iJICPZ3tCRiYMYbswiMUwgMkGscG40e09j5uxEmI6DGE7MYgzYwCTDTrOduHDA0I7omksC2CAIWy/mhr1unW8ivODGMBkMfWqGC3ojHQZuZulAQzwwJwzeBXnBzGAyWKxwThee2undAEMMISdwyBWMYDJYifbT+PVtw2tgACAg3YEMMAQdpbXg5gBTBYaGR3Dq2/txJGjLUYv6fRCJBwSulKyGRjCTvNqEK9fzwAmy5xsP41/fuVfRTdj10wA+Duz1wHr4YE5GZSVAfv2qQfrbt50uhrrcbc5skhsMI5/+7TFaPgC6hWS6808E04UQ1gWhYXqjDjfg5gBTBZIKApaPjshehWMqbqgBrDwFZLNxBCWSb4HMQOYTJZQFJxqP4OT7aeN9n0170fCoSaTyzKEISybfA1iBjCZrONsF1paf4vRW+PZ3H0Y6hUxouZWZRxDWEaFhcDPf66eWdfd7XQ1uWMAk0kSiqJefLP9dLbhCwDtUAPYkfbDVAxhmWmrB9waxAUF6qy+rMzpSsjlRkbH0BnpyqXtACQvyJnr5YjMxhCWnVuDmAFMJui/NICOs11Gz3SbznEAjbLMflMxhN1g0ya1RdHZ6XQlYhjAlANt1ttxNppLy0EjTe83HYawW6xfr4aa7DuwMYApCyOjY+jpPYfOs11GTy9OZwJAkxX7/5qNIewmsm+FOXcusHkzA5iExAbj6Ok9j56vz5kVvJqDUANYutbDdBjCbiNrEM+dq86ACwudroQkFRuM4/rgMPovDaCn91wuB9jSOQ41fONmP7CVGMJuVFMDFBcDBw/KsScxA5imSCgKYsnAvT4YR/+lAStCV+PK8NUwhN1qwYL7J3U4GcQMYE+LDcaRUCbRf2kAiYSC64PDiA3FrQxczQSAZgAH3Bq+Goawm2k7sDkVxEuW3F+5QdJKKAp6vj6P0VtjD3y/avHCtPcZGR17aHzfxfub4mSxQY5ZhgE0AWhzS89XD0PY7bQgPngQuH3bvuflXsCuEBuMY8euX5ix1MtpxwE0y7zULFsM4XxQVqae5mzXfhMMYNdweQB3QW055M2sdzoM4Xxh18Y/K1eqa5ZJev2XBtwYwO0AolCDN+5sKfZgCOcTq4OYG/GQ+Yahhm4UeT7jTYchnG+s2oGNAUzmmMD90I06cSUL2TCE85WZG/8wgCk7EwD6oAZuH4A+r7QYjGAI57Ncg5j7QJAYbYuzKIA7uB+4nmstZIMhnO82bVLPrmszuIUqA9j1rg/G9YYMA8g0qA9qqKaKJv+9w1aCORjCXlBfrwax6H4Ts2YB27YxgF1O4Ky1Zqevr0YMYe8Q3fiHpyET2epRpwsgG+mdZMEAJrIdZ8Jeo+3AdvTog6c5cx8IIkc8hocb7w8YGR1D1WKbqiF7LFgA7NsHXL2qfl1crH5QXkndcCeNuA1lkI7HoB4BXZtuwNSdlCiPLFjgdAXkrLjTBRB7wkR5a/TWLadLIAEMYaI8JbB5D9f5SoAhTJSHRkb124g8o00ODGGiPCRwLKffjjpIH0OYKA/1X9JdGcFZsCQYwkR5SKAdEbWhDBLAECbKQzH9zXt0B5A9GMJEeSahKIgNDesN48oISTCEifKMQD8Y3IZSHrohnEgodtRBRCbp1z9duUtvANnnUegcJb0+qPu2hogkIjATjtpQBgl6FDq9IUXhTJjILUZGx0T6wVEbSiFBujNhgf+hRCSJnt5zekMmIuFQ1IZSSNCjIg16geUuRCSBzrO67d6oDWWQAdqBuYynMPYJHG0lImcJtiIMXvGVrKaFcMbZcIwH54ikd6r9tMgwhrBkhEJYoM9ERA5KKAo6IrqtiHbunCYfLYSjmQYpyiT7wkQS6zjbJXSJextKIYMeBb49e2Yi08AO/YY/ETlEoBUxHAmH2IqQUOoZcxn/Bwm81SEiB7S0nhC5ikazDaVQFoRDWFEmORsmkkxCUXBS7IDcAatroex8G8LJtypsSRC5yOFPW0R6wcd5QE5eUzfwac40+OLlAaEdmojIev2XBtAp1iZssrgUysHUENZ9y7J3/2GLSiEiUQlFEf1dPB4Jh+IWl0M5+E7qF0PXrtypeHzRjwGUp7uD9tanavFCSwsjovR27T2Eq/99XW/YBICfDF278kcbSqIsTbefcJPenU62n+a6YSKHnGw/ja96z4sMPcBesPy+M/UbQ9euxPVmw/fu3cPvr17Dj2ufxne/+10r6yOiFD295/DhgV+JDO2PhEM/s7oeyl26K2s06N0xNjSMw5+2mFsNEaUVG4xj7/4josMbLCyFTPTQTBj4tjf8JwCWZbpzbGgYI6NjWP7UUkuKIyJVbDCOV9/eKbIcDQDej4RD/251TWSOaUMYACoeX9QL4B8AzMz0AAxiImv19J7Djl37RAO4KxIONVhcEpkobQgPXbvyx4rHF0UB/IvegzCIiaxxsv00PjzwK9y7d09k+DCAOq6GcJe0IQwAQ9eujFQ8vmgCwE/0Hig2NIyer89h6Y+qUVTkM61AIi9KKAp27T2Ez784I3qXCQA/4Zpg98kYwgAwdO1Kb8Xji+YDqNYb+4c7E+iIdKFs7p+i7M//zJQCibym/9IA3t6xW2QdsGYCQJ3IpcpIProhDADJtsRqAHP0xt67dw/R//wa1wfj+MFf/gVnxUSCRkbHcPhoC44cFdoPItWWSDj0H1bVRdZ6RHSgPxCcCXXz9yrR+/h8hXh27RqsW7saRT6GMdF0EoqCU+1ncLL9tNHwnQDQwH2C3U04hIHsghi4H8Yr/bWYUzrbyF2J8tbI6BhOtZ9GR0ToqhhTsQWRJwyFMPBtELcBqM3mCZ9e9iRWrajF8mVcSUHe1HG2Cz2950RPPZ5OP9QZMAM4DxgOYY0/EGwGsDHb+/t8hVi+bCmqFi9E1eKFnCFT3kooCnq+Po/+SwPo6T2Xzaw3VTvUAOaeEHki6xAGAH8g2AB1+8sZuRZSWlKMyopyfL+iHFWLF6LIV4jKivJcH5bIdrHBOK4PDiM2GEf/pQHEhobNeNgJAE2RcIhXyMgzOYUwAPgDwXKom8Fn1Z4gIl1dUGe/cacLIfPlHMIafyDYCHUbzJxnxUQEQJ39NkbCoWanCyHrCK0TFpE8qeNXAP4PwF8B+J5Zj03kMRMAPgDws0g41Ot0MWQt02bCqZIrKBqTH5wZE4mZgHqMhZuxe4glIaxJhnE91DA2tLaYyEP6oYZvG8PXeywN4VT+QLAa6kbT9QDm2fW8RJIahrrevpnrfb3NthBOlVxRUQ+gLvnBlgV5QRfUM07bGLykcSSEp0qGcnXKx8zkvwxncqt+AHEAfQCikXAo6mg1JK3/B3DUdh/UDJ/eAAAAAElFTkSuQmCC" class="kg-image" alt="How to use an iPad as a whiteboard in Zoom?"><figcaption>https://ko-fi.com/peterpoliwoda</figcaption></figure><hr>]]></content:encoded></item><item><title><![CDATA[What the documentation doesn't tell you about the React useEffect hook?]]></title><description><![CDATA[<p>When approaching the useEffect hook in React and to be fair hooks in general, the first thing is to forget what you think you know because you will get confused and you'll try to include a square block in a circular hole. When approaching creating components in React you should</p>]]></description><link>https://peterpoliwoda.me/what-the-documentation-doesnt-tell-you-about-the-react-useeffect-hook/</link><guid isPermaLink="false">6127785d1d7d594ba0857e25</guid><category><![CDATA[React]]></category><category><![CDATA[coding]]></category><category><![CDATA[web]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Wed, 11 Sep 2019 17:27:00 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2021/09/5B940A82-C953-41C3-BECB-BC348038EB63.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://peterpoliwoda.me/content/images/2021/09/5B940A82-C953-41C3-BECB-BC348038EB63.jpeg" alt="What the documentation doesn't tell you about the React useEffect hook?"><p>When approaching the useEffect hook in React and to be fair hooks in general, the first thing is to forget what you think you know because you will get confused and you'll try to include a square block in a circular hole. When approaching creating components in React you should probably already be thinking "reactively" if you're not already doing so. What it means and how does it fit within the useEffect hook as an example is something that many front-end developers already know and will find this completely obvious. On the other hand, it took me a while to understand the concept of a reactive component enough to be able to explain it to a grandma in one or two sentences. The moment you create a self-enclosed component with its own Axios API calls based on the props you pass to it feels really good. I could finally say "I get it" now. So here's the deal. For those of you who don't yet grasp the concepts, building your lego blocks in React can be done the ugly way - creating a standalone piece of code with a class component and an API call in the constructor, coming from an older version of Angular this was the concept I initially understood, - the <code>OnInit()</code> function is being run when the component builds. That's great, it used to work fine with the older concepts, but that's not how it's done any more for a good while now and you may think you're doing it logically right when in fact that's not the way to go in React (nor is it Angular any more btw for a long while...).</p><h2 id="effects">Effects</h2><p>Did you know you can add many <strong>useEffects</strong> listeners to a single component? Did you know sometimes you have to add more than one if you want to tie in state changes together? A 'side-effect' as it's nicely called by the documentation is basically something that happens when the state of any of the items in the dependency array changes. You can throw in some props or state variables and (and this is important, because the eslint will keep annoying you) functions which are used in the effect. The change of a variable is pretty self-explanatory, it's pretty cool when you tie in a variable to an API call and it calls and updates everything for you automagically. This automagical way ties in to our Reactive way of coding. We observe changes and then do something when that change happens. It is quite a paradigm shift which can be seen increasingly in other languages, like Swift or Flutter or even good old Java with RxJava or RxJS in JavaScript.</p><p>Our good old friend ESLint will also suggest to include a reference to the function in the dependency array of our <code>useEffect()</code>. When it comes to functions, what you need to know is if they are included within the main body of the functional component, on the next render their memory reference will change. This means that it will technically be a different <code>fetchMyDataStuff()</code> function than the one you first rendered. This is why you might also get another ESLint suggestion to enclose your <code>fetchMyDataStuff()</code> in a <code>useCallback</code> effect. The documentation explains it with a very clear <code>Returns a </code><a href="https://en.wikipedia.org/wiki/Memoization" rel="nofollow noopener noreferrer"><code>memoized</code></a><code> callback.</code> which for those of us who prefer plain English means that the enclosed function will be stored in memory, so that it doesn't change every time you re-render your component.</p><blockquote>As Wikipedia explains: "In <a href="https://en.wikipedia.org/wiki/Computing">computing</a>, <strong>memoization</strong> or <strong>memoisation</strong> is an <a href="https://en.wikipedia.org/wiki/Optimization_(computer_science)">optimisation</a> technique used primarily to speed up <a href="https://en.wikipedia.org/wiki/Computer_programs">computer programs</a> by storing the results of expensive <a href="https://en.wikipedia.org/wiki/Subroutine">function calls</a> and returning the cached result when the same inputs occur again. Memoization has also been used in other contexts (and for purposes other than speed gains), such as in simple <a href="https://en.wikipedia.org/wiki/Mutual_recursion">mutually recursive</a> descent parsing.<sup><a href="https://en.wikipedia.org/wiki/Memoization#cite_note-Norvig1991-1">[1]</a></sup> Although related to <a href="https://en.wikipedia.org/wiki/Cache_(computing)">caching</a>, memoization refers to a specific case of this optimisation, distinguishing it from forms of caching such as <a href="https://en.wikipedia.org/wiki/Buffer_(computer_science)">buffering</a> or <a href="https://en.wikipedia.org/wiki/Page_replacement_algorithm">page replacement</a>. In the context of some <a href="https://en.wikipedia.org/wiki/Logic_programming">logic programming</a> languages, memoization is also known as <a href="https://en.wikipedia.org/wiki/Prolog#Tabling">tabling</a>.<sup><a href="https://en.wikipedia.org/wiki/Memoization#cite_note-Warren1999-2">[2]</a></sup></blockquote><p>Going by this logic, the useCallback function being memoised will not only prevent your linting errors from annoying you, but more importantly, it will cache your function and speed up your app's performance.</p><h2 id="so-components">So components?</h2><p>To conclude, when writing your app in React and building component, hooks or no hooks, try to think about it as a self-enclosed piece of logic which depends on the prop data you provide it AND what's more important here, asynchronously changes by reacting to the changes in the web app and re-renders itself and its content without the need for external handling. Otherwise things get out of hand very quickly.</p>]]></content:encoded></item><item><title><![CDATA[Continuous Integration part 2: Setting up Travis checks in Github]]></title><description><![CDATA[Setting up TravisCI status checks on a PR is easy. Here are a few steps to set up your Github repository PRs with quality control.]]></description><link>https://peterpoliwoda.me/travis-checks-in-github-continuous-integration/</link><guid isPermaLink="false">5eb995f965bbc443491e2782</guid><category><![CDATA[ci/cd]]></category><category><![CDATA[agile]]></category><category><![CDATA[code examples]]></category><category><![CDATA[github]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Sun, 24 Mar 2019 20:40:27 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2020/05/CI_CD-Stages_Github-second-barrier.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://peterpoliwoda.me/content/images/2020/05/CI_CD-Stages_Github-second-barrier.jpeg" alt="Continuous Integration part 2: Setting up Travis checks in Github"><p>If you followed <a href="https://peterpoliwoda.me/agile-development-continuous-deployment-framework-with-git/">part 1 of this guide</a> you should have a local eslint check script on your git repository and a pre-commit hook that prevents you from uploading dodgy code to Github. In this part we will look at one of many ways you can work with Github, taking into account code reviews and working in a team. We will enable the same eslint check in our secondary stage in <a href="https://travis-ci.com/">Travis CI</a> on a Pull Request (from now called a PR). This is the point before the code gets merged into the main repository; a place where you would usually review the code and check for potential mistakes, typos and generally bad code you might've missed. This is what we'll use Travis for.</p>
<h1 id="setupagithubrepository">Set up a Github repository</h1>
<p>Before we continue you will need to create a new empty Github repo. I created one initialised with a README.md file.</p>
<p><img src="https://peterpoliwoda.me/content/images/2018/05/Empty-github-repository.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>We will plug the newly created remote Github repository to our local git folder from step 1. To do that we will need to <strong>add</strong> a <strong>remote</strong> in our local git. To be in line with the standards we will call it <code>origin</code>. Open your git folder and run the command below:</p>
<pre><code class="language-powershell">$ git remote add origin https://github.com/YOUR_USERNAME/REPO_NAME.git
</code></pre>
<p>This will connect the local repository to the remote which will enable you to push code over to Github.<br>
<em>Note: this is using the HTTPS method to fetch and push to simplify the steps in this post. You should familiarise yourself with the <a href="https://help.github.com/articles/connecting-to-github-with-ssh/">SSH method</a> which in the long run is easier to work with, mainly beause you won't have to type in your login details all the time.</em></p>
<h1 id="agileworkflowwithgithub">Agile workflow with Github</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/05/CI_CD-Github-Workflow.jpg" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>There is no right or wrong way of using Github really. The way you use it though generally depends on the size of your team. As a single user you could use a single <em>master</em> branch and use the repository as a one-to-one mirror of your local folder. That's perfectly OK, but by doing so you won't be able to use the full potential of git and Github.<br>
In our case, we'll extend the single user work flow with an extra check &amp; review layer, which will come in handy if the team grows. We'll introduce the concept of branches and pull requests. It also gives you the ability to take another look at your own code in context before it's merged with the master repo.</p>
<p>Here are a few concepts that you will need to know.</p>
<h2 id="branches">Branches</h2>
<blockquote>
<p>Git branches are effectively a pointer to a snapshot of your changes. When you want to add a new feature or fix a bug—no matter how big or how small—you spawn a new branch to encapsulate your changes. [Atlasian]</p>
</blockquote>
<p>We will use branches to encapsulate a single task we want to tackle. This way we'll be able to come back to it any time. We will also be able to revert it easily if something goes wrong.<br>
In my previous post I wrote about Github Projects and how you can use it to manage your project. Let's say the first task is to push the code that we wrote earlier to a Github repository. We will also include a file to enable Travis at the pull request stage.</p>
<p>Let's first fetch the latest changes from the remote and then create a branch in our local git repo folder with the <code>git checkout -b [task_number] [remote_branch]</code> command:</p>
<pre><code class="language-powershell">$ git fetch origin
$ git checkout -b task_1 origin/master
</code></pre>
<p>These commands will do a couple of things. Our local repository will download all the changes from the remote repo, but won't merge them into the master branch. The second command will create a copy of the remote repo on a clean branch called task_1, it will then put everything you already had in your folder on top of that. Keep in mind doing stuff this way might cause merge conflicts. In our case that shouldn't matter because our remote repository at this stage should be empty.</p>
<blockquote>
<p>The default branch in git is called <code>master</code> and the <code>origin</code> is typically your own repository fork.</p>
</blockquote>
<p>In case you were working with more people you could create an organisation level repository, which other team members (including yourself) would fork in Github, in a way creating seprate remote instances (origins). In our case we only have a single remote repository so we can disregard this.</p>
<p>Two things to keep in mind - first, we will now refer to the remote repository as the <code>origin</code>. Second, the <code>master</code> branch is our production branch. That being said, we will deploy the production ready code from the master branch.</p>
<h3 id="commits">Commits</h3>
<p>A commit is a packaged delta of your local changes. It can be a single file, or a bunch of lines in many files. <em>This also includes removed lines!</em> Git is flexible when it comes to the number of commits on a branch. I tend to stick to a single commit on a single branch, because it makes it easier to review code, but it's not a rule.</p>
<h3 id="push">Push</h3>
<p>A push is a git command that takes all commits on your current branch and pushes them to the remote repository. It can create a branch you pushed on the remote if it doesn't exist yet. You can also push to an existing remote branch.</p>
<h3 id="pullrequest">Pull request</h3>
<p>A pull request is a piece of code you want to suggest to copy over from one branch to another. As an example in the above workflow it would be the new task branch being pulled into the master branch. Notice that the code is pulled into the master and not pushed directly to it. It is a suggestion that can be rejected, amended or closed without resolution. At this stage team members would usually request each other to take a look at the code before it gets merged into the master branch.</p>
<p>Using Travis we will be able to test the code in this PR in context of what branch it's getting merged into before it is approved. Some quick quality checks can go a long way and speed up the review process.</p>
<p>Here we set the second barrier.</p>
<h1 id="secondbarrier">Second barrier</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/05/CI_CD-Stages_Github.jpeg" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- PeterPoliwoda.me -->
<p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-1019709672090523" data-ad-slot="2112777543" data-ad-format="auto"></ins></p>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
<p>A pull request (PR) can be blocked from merging into the master repository if the code is not good enough. That is if any checks like eslint, unit tests, end-to-end tests, etc. fail the quality control. Using Travis we are able to run these checks here. Another advantage of merging code this way is that if there were more than one PR at the time and they had conflicting changes i.e. two people made changes to the same file at the same time, this fact would be caught here and block the user merging code before resolving the conflicts.</p>
<h2 id="travis">Travis</h2>
<p>Let's add a new file to enable Travis here. We will call it <code>.travis.yml</code>.</p>
<pre><code class="language-powershell">$ touch .travis.yml
</code></pre>
<p>Then add in the code below:</p>
<pre><code class="language-yaml">language: node_js
node_js:
  - &quot;10.15.3&quot;
install:
  - npm install
script:
  - npm test
</code></pre>
<p>We will set the language of the scripts to Nodejs version 10. This is the current LTS support version as I write this. This could be any newer <em>even numbered</em> version if you read this at a later stage.<br>
The <strong>install:</strong> will be executed before any of the <strong>scripts</strong> run. We will only have single commands in each of them but you can run anything you like. As this is a YAML file you can list script commands in a dashed list. If you'd like you could run a full end-to-end test suite here. In our case, we will run the <code>npm test</code> command which at the moment only runs the eslint tests. If we ever extend the test command, Travis will pick it up automatically.</p>
<p>Travis checks will get picked up and show in the PR as a green tick if your code passes the lint tests.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/Travis-PR-check-passed.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<h2 id="pushtogithub">Push to Github</h2>
<p>OK, so we're at the stage where we can start adding in some code to our commit and push it into the remote to see if we set the barrier correctly. If you followed the previous post you'll have a commit hook and eslint set up in your local repository. We will need to add in the new files to the commit and push the extra travis file to your remote repository, in our case also being the origin. We can do that easily with the <code>git add .</code> command</p>
<pre><code class="language-powershell">$ git add .
$ git commit -m &quot;[Task 1] Push some files to repo and enable travis&quot;
</code></pre>
<p>After the commit check runs you will need to push the local commits to your remote repository. This will create a new branch in your origin called <code>task_1</code> just like your local branch</p>
<pre><code class="language-powershell">$ git push origin task_1
</code></pre>
<p>We will now move over to the browser and open up your Github repository.</p>
<h1 id="githubsourcecontrolstage">Github source control stage</h1>
<h2 id="createpr">Create PR</h2>
<p>Your Github repository should automatically show a yellow box on the top of the code page showing the newly created branch. Press the green button to <code>Compare [code] &amp; [create a] pull request</code></p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/create-PR-from-branch.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<h2 id="prreviewpage">PR review page</h2>
<p>In Github your PR should look something like this. If you scroll down you can see your commit and all the changes you made in files added to this commit. The description is optional. You can now press <code>Create pull request</code>.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/create-PR.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>We are almost there. Your PR should now be there, waiting to be reviewed. This is where you can possibly assign some reviews if you'd like. Send somebody the link to the PR and they could look at the changes in context of the whole code, which makes it really handy.</p>
<h2 id="enabletravischecks">Enable Travis checks</h2>
<p>The PR is created, files are there but something is missing. Well, Github doesn't automatically add any app integrations, but it's nice enough to suggest them in your PR. Click on <code>Several apps are available</code> and add the <strong>Travis Github App</strong> to your Github repositories.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/suggest-github-app-integration.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/search-for-travis-in-apps.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/install-travis-github-app.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/authorise-travis.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>Once you're done you will see the Travis page. To check if everything is added properly to the repository you can take a look at your repository <strong>Settings</strong> tab. In the <strong>Integrations &amp; services section</strong> you will find the Travis CI app.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/Travis-listed-in-Settings-.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<h2 id="isitrunningyet">Is it running yet?</h2>
<p>Almost. The PR we already created will not be picked up because we enabled Travis after the fact. The checks will run on the next push. Let's create another commit and push it into the same branch.<br>
You can make a small change to your code now. Let's add a comment at the end of the lintMe.js file.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/Small-code-change.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>You can then git add the file, create another commit and push.</p>
<pre><code class="language-powershell">$ git add lintMe.js
$ git commit -m &quot;[Task 1] Run travis now&quot;
$ git push origin task_1
</code></pre>
<p>Now your Travis checks should be picked up by the app and your eslint tests will run.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/Travis-check-finally-showing-on-PR.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>You can see both of your commits and the checks below in yellow. Yours will most likely have two checks on branch and PR. Once they're finished they will either give you a green tick, or a red cross depending if the files that you pushed were passing the eslint checks. If you did follow <a href="https://peterpoliwoda.me/agile-development-continuous-deployment-framework-with-git/">part 1 of this guide</a> your commit hooks should prevent you from pushing changes that won't pass in Travis.</p>
<p>If they are failing, you might want to check if you pushed the <strong>package.json</strong> file and Travis is running the <code>npm install</code> and <code>npm test</code> scripts correctly. Also make sure you don't push your <em>node_modules/</em> folder because that could cause trouble.</p>
<h2 id="twoticksoronetick">Two ticks or one tick?</h2>
<p>By default you'll see two ticks here rather than just the one in the screen shot. This is due to the fact that Travis is running a build <strong>on the branch</strong> and a <strong>build on the Pull Request</strong>.</p>
<p>You can go to your Travis page by clicking on the <code>Details</code> link in the PR. In <strong>Travis / Settings</strong> you can deselect the option to run builds on branches if you don't want to.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/Travis-settings.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/travis-disable-run-on-branch-.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>You could potentially leave it ON, and for example utilise some of the deploy capabilities of Travis. This is something I will talk about in the next post, which will be about the <strong>Third Barrier</strong> at the <strong>deployment</strong> stage. I will also mention how to run your app in <strong>IBM Cloud / Bluemix</strong>.</p>
<h2 id="preventingbrokencodefromgettingmerged">Preventing broken code from getting merged</h2>
<p>For a meaningful PR status check we can do one more thing. In the repository settings you can block the PR from getting merged into some of your branches. As we decided that the <code>master</code> branch is going to be <em>production code</em>, we wouldn't want to push anything to this branch if the status checks are not passing. It's a small thing that could be overwritten with administration privileges, if you really want to, but it will be screaming at you that it's broken, just in case you didn't notice the red X crosses everywhere.</p>
<p>You can do it in the <strong>Branches</strong> section of your repo <strong>Settings</strong>.</p>
<p><img src="https://peterpoliwoda.me/content/images/2019/03/passing-status-checks.png" alt="Continuous Integration part 2: Setting up Travis checks in Github"></p>
<p>Oooff! OK, this was a long one, but I hope it was somewhat useful. As usual more to come.<br>
Stay tuned.<br>
Cześć!</p>
<!--kg-card-end: markdown--><h2 id="has-this-been-helpful-to-you">Has this been helpful to you?</h2><p>You can support my work by sharing this article with others, or perhaps <a href="https://ko-fi.com/peterpoliwoda">buy me a cup of coffee</a> 😊 </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="data:image/png;base64,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" class="kg-image" alt="Continuous Integration part 2: Setting up Travis checks in Github"><figcaption>https://ko-fi.com/peterpoliwoda</figcaption></figure><hr>]]></content:encoded></item><item><title><![CDATA[Developer hints: Why do my node.js app ports keep growing?]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Working with NodeJS sometimes you get to see some strange things in your dev environment. For example, when working with Cloud Foundry your apps would automatically assign a port number to the running app and sometimes mess up your configuration when working with other people on the same project. But</p>]]></description><link>https://peterpoliwoda.me/developer-hints-why-do-my-cloud-foundry-node-app-ports-keep-growing/</link><guid isPermaLink="false">5eb995f965bbc443491e2785</guid><category><![CDATA[Node.js]]></category><category><![CDATA[code examples]]></category><category><![CDATA[bluemix]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Fri, 18 Jan 2019 12:33:18 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2020/06/91E7BA63-1295-42EA-BBF3-BB4CF069148C.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://peterpoliwoda.me/content/images/2020/06/91E7BA63-1295-42EA-BBF3-BB4CF069148C.jpeg" alt="Developer hints: Why do my node.js app ports keep growing?"><p>Working with NodeJS sometimes you get to see some strange things in your dev environment. For example, when working with Cloud Foundry your apps would automatically assign a port number to the running app and sometimes mess up your configuration when working with other people on the same project. But worry not, here's the answer. The (in)famous <code>ports</code> module.<br>
<img src="https://peterpoliwoda.me/content/images/2019/05/growing-ports-animation.GIF" alt="Developer hints: Why do my node.js app ports keep growing?"></p>
<blockquote>
<p>Why do my Cloud Foundry app ports keep increasing exponentially?<br>
~ Peter (2018)</p>
</blockquote>
<p>Turns out the IBM platform-as-a-service platform using cloud foundry cli (now <code>ibmcloud</code> and before that also <code>bluemix-cli</code> or <code>bx</code>) uses a handy little node module called <code>ports</code>.</p>
<p>Where ports comes in is it assign your <strong>package.json</strong> app name to a port number and then remembers it whenever you run a new app. Where it also comes in handy is, whenever you run a new app, it increases the port number by 1 each time you run a new app that takes into account the cfenv.port ENVIRONMENT VARIABLE.</p>
<p>Once you dig through the code you find that the ports module stores the port numbers in a JSON file in your home folder - on Mac <code>~/.ports.json</code> or if you're using Windows it would be under your <code>%USERPROFILE%/.ports.json</code> which would typically translate to <code>c:\Users\Peter</code> or something like that. You can use the ports.json file to assign your apps a PORT instead of running the EXPORT PORT=XXXX.</p>
<pre><code class="language-JSON">{
    &quot;6001&quot;: {
        &quot;name&quot;: &quot;MicoDashboard&quot;
    },
    &quot;6003&quot;: {
        &quot;name&quot;: &quot;BikeStand&quot;
    },
    &quot;6004&quot;: {
        &quot;name&quot;: &quot;automation-workshop&quot;
    },
    ...
    &quot;6013&quot;: {
        &quot;name&quot;: &quot;Innovation Sandwich&quot;
    }
}
</code></pre>
<p><em>Sample config you might find.</em></p>
<p>Have fun Noding. Cześć!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Tutorial: how to unzip files on iOS without extra apps]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I noticed a lot of people using some specialised apps just to unzip archives on iOS. I downloaded some zipped fonts from Google Fonts to use with Affinity Photo and then got stuck. I looked online and most websites recommend getting some extra apps. I noticed that there is a</p>]]></description><link>https://peterpoliwoda.me/tutorial-how-to-unzip-files-on-ios-without-extra-apps/</link><guid isPermaLink="false">5eb995f965bbc443491e2784</guid><category><![CDATA[tutorials]]></category><category><![CDATA[iOS]]></category><category><![CDATA[iPad]]></category><dc:creator><![CDATA[Peter Poliwoda]]></dc:creator><pubDate>Sun, 04 Nov 2018 20:14:44 GMT</pubDate><media:content url="https://peterpoliwoda.me/content/images/2020/06/89EFF755-E300-4127-B4A4-9D2B60950050.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://peterpoliwoda.me/content/images/2020/06/89EFF755-E300-4127-B4A4-9D2B60950050.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"><p>I noticed a lot of people using some specialised apps just to unzip archives on iOS. I downloaded some zipped fonts from Google Fonts to use with Affinity Photo and then got stuck. I looked online and most websites recommend getting some extra apps. I noticed that there is a handy share option in the iOS built-in Files app preview section. I will show you step by step how to go about it.</p>
<h1 id="step1downloadthezipfileintoafolderontheipadoriphone">Step 1: Download the zip file into a folder on the iPad or iPhone</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/E2800F5F-55F7-41EB-A9CE-DFA7C8EE3CD0.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<h1 id="step2openthefileandpressonpreviewcontent">Step 2: Open the file and press on Preview Content...</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/25CFCBA8-CF9D-486F-8246-E0EB0DDCF5E1.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<h1 id="step3onceinpreviewpressthesharebuttoninthetoprightcorner">Step 3: Once in preview, press the Share button in the top right corner</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/52BA77E4-1D37-4E9A-88C0-2437067734FB.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<h1 id="step4youcannowsavethepreviewedfiledirectlybacktoyourfiles">Step 4: You can now save the previewed file directly back to your Files</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/2778CEF6-E638-46AB-8F2C-D8EC664F8A64.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<h1 id="step5choosethelocationfortheunzippedfileandpressadd">Step 5: Choose the location for the unzipped file and press Add</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/F195BA7B-3FE2-45DB-870A-8248D278D709.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<h1 id="step6congratsfileunzipped">Step 6: Congrats, file unzipped!</h1>
<p><img src="https://peterpoliwoda.me/content/images/2018/11/11AF2B77-4329-4C1F-BB2F-19FA2132F33F.jpeg" alt="Tutorial: how to unzip files on iOS without extra apps"></p>
<p>There you have it.  Font file unzipped! At the moment when writing this on iOS 12.1 you will still need a third party app to compress files into a ZIP archive.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>