<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ross Butler on Medium]]></title>
        <description><![CDATA[Stories by Ross Butler on Medium]]></description>
        <link>https://medium.com/@rwbutler?source=rss-48f7d614a0e6------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*kOC3sC-WqdWYVX_WY1laFw@2x.jpeg</url>
            <title>Stories by Ross Butler on Medium</title>
            <link>https://medium.com/@rwbutler?source=rss-48f7d614a0e6------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 28 Apr 2026 22:01:55 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@rwbutler/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Using the Google Play Developer API to Retrieve Reviews for your Android App]]></title>
            <link>https://medium.com/go-city/using-the-google-play-developer-api-to-retrieve-reviews-for-your-android-app-392a48645402?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/392a48645402</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[api]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Fri, 13 Jan 2023 13:04:14 GMT</pubDate>
            <atom:updated>2023-01-13T13:04:14.085Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NvQ0kKDzCOuaS3vSsQZQOw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@redaquamedia?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Denny Müller</a> on <a href="https://unsplash.com/wallpapers/android?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><em>Monitoring what users are saying about your app is key to providing a good experience and in turn, achieving a good star rating in the Google Play Store. It’s possible for Android developers to check feedback in the store on a regular basis but as a manual task, it’s easy to forget to do it. Having a reviews API allows Android developers to automate how they receive feedback — e.g. publishing reviews to a team Slack channel or adding alert triggers based on certain conditions e.g. in the event of a one-star review.</em></p><p>The following provides a step-by-step breakdown of how to access the Google Play Developer API in order to obtain reviews for your apps:</p><blockquote>Note: You will only be able to retrieve reviews for apps you have developed using this method.</blockquote><h3>1) Enable API Access</h3><p>The first step is to enable API access in the <a href="https://play.google.com/console">Google Play Console</a>. Sign in to the console as the owner of the Google Play Developer account as only the owner is able to enable API access. From the menu on the left-hand side of the page, scroll down to and expand the Setup section of the menu. Select API access from within the Setup submenu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*Folf_JAso0k3xBI-FkIKkg.png" /></figure><p>You’ll be asked whether you wish to enable API access from this screen. As part of this, you’ll need to either create a new Google Cloud account or link an existing one. Follow the instructions provided to link your Google Play Developer account to a Google Cloud account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DUKcOuOpJMOg5gXTqVoVSw.png" /><figcaption>Setup -&gt; API Access Screen in the Google Play Console</figcaption></figure><h3>2) Configure an OAuth2 Client ID</h3><p>From the API access page above, scroll down to the section on APIs:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1009/1*t0XhvtpFE6M6P36drEkv9w.png" /><figcaption>APIs Section of the API Access Screen</figcaption></figure><p>Select the button labelled “View API details”. This will take you to your linked Google Cloud account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/244/1*yQoHZTr-viGdWgsliB-3tA.png" /><figcaption>Google Cloud Console Menu</figcaption></figure><p>You will need to configure an Oauth consent screen in order to allow your Oauth client (once you have configured it below) to access your Google account.</p><p>From the left-hand menu (also shown in the screenshot), select <em>Oauth consent screen</em>. Configuring a consent screen is a 4-step process comprising the following steps:</p><ul><li>Oauth consent screen</li><li>Scopes</li><li>Optional info</li><li>Summary</li></ul><p>On the Oauth consent screen stage (first stage), you will need to complete the following fields (the rest can be left blank):</p><ul><li>App name (use the app name you specified previously when creating the Oauth client ID)</li><li>User support email (you can use your own email address here)</li><li>Authorized domains (if the authorized redirect URI you are planning to use is <a href="https://www.my">https://www.my</a>domain.com/myPage then you will need to specify your domain mydomain.com here)</li><li>Developer contact information (you can use your own email address here as well)</li></ul><p>On the <em>Scopes</em>, <em>Optional info</em> and <em>Summary</em> screens you shouldn’t need to specify any further information so click <em>Save &amp; Continue</em> through each screen until you reach the end of the process.</p><p>The Oauth consent screen page should now have a section as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/586/1*6BZIjFaDNhbOBiVNsw7ZyA.png" /></figure><p>You will need to ensure that the Publishing status is set to <em>In production </em>before you can use of the API so make sure that you click the button to publish here. If this page isn’t published you won’t be able to give your Oauth application access to your Google account.</p><p>We now need to configure our Oauth client ID. To do so, from the left-hand menu again, navigate to Credentials. Then from that page select <em>Create Credentials -&gt; OAuth Client ID</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/443/1*znzTBsNXZbSOB_9PLV8FLw.png" /></figure><p>The following screen allows you to configure the metadata for an OAuth client. From the Application type drop-down select Web application, then enter a name for your client (which can be whatever you want to name it).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/655/1*N5rBPcPoZyA1KRex2m5tUA.png" /></figure><p>You will also need to specify an authorised redirect URI. Make sure you enter your own website URL here because an authorization token (named the <em>code</em> parameter) will be sent to this URL later on (you wouldn’t want another website operator obtaining this information). The URL can be the front page of your own website — you don’t need to code a page specifically to read the value of the authorization token.</p><h3>3) Authorize the Oauth Client</h3><p>Having created an Oauth client ID, construct a URL which will allow you to grant access to your Google account to the Oauth client you just created. The Google account you use here must have access to the Google Play Console in order to access your app reviews.</p><p>In the URL below, fill in the template with:</p><p>https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&amp;response_type=code&amp;access_type=offline&amp;redirect_uri=&lt;<em>your redirect URI</em>&gt;&amp;client_id=&lt;<em>your client ID</em>&gt;</p><ul><li>redirect_uri — The redirect URI you filled in when creating the Oauth client ID above (remember the domain must also have been authorized when creating the Oauth consent screen).</li><li>client_id — The client ID you were given following the successful creation of your Oauth client ID.</li></ul><p>Your client ID should be of the form:</p><p>&lt;identifier&gt;.apps.googleusercontent.com</p><p>Now open the URL you just created in your web browser. You will be asked to login to your Google account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/559/1*tUG4DZRhYc2LbRVerleaMA.png" /><figcaption>*Note that instead of gocity.com the dialog should state the domain for the redirect URI you specified earlier.</figcaption></figure><p>On the following screen after logging in, you will be asked whether you want to allow the Oauth application you just created to access your Google account. Confirm that you wish to allow access to your newly-created application then <em>Sign in with Google</em> should redirect you to the URL you specified as the redirect URI when creating the Oauth client ID.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/511/1*R1CCx13dkYd6O7YeGYDMVQ.png" /><figcaption>Oauth Consent Screen Configured Earlier</figcaption></figure><p>If the URL you specified as the redirect URI was:</p><pre>https://www.mydomain.com/myPage</pre><p>then you will notice that the URL you have been redirected to looks as follows:</p><p><a href="https://www.my">https://www.my</a>domain.com/myPage?code=&lt;<em>code</em>&gt;&amp;scope=<a href="https://www.googleapis.com/auth/androidpublisher">https://www.googleapis.com/auth/androidpublisher</a></p><p>Copy &amp; paste the value of the code parameter above as you will need this in order to obtain an access token to access the reviews API. It’s worth making a note of this code for future reference as you may need it should your access_token / refresh_token (obtained as part of step 4 below) expire in the future.</p><h3>4) Obtain an Access Token</h3><p>Using a client that allows you to make API requests such as <a href="https://curl.se">cURL</a> or <a href="https://www.postman.com">Postman</a>, construct an API request to:</p><pre>https://accounts.google.com/o/oauth2/token</pre><p>The request will need to be a POST request in order that you may specify a JSON body which should look as follows using Postman:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/713/1*owc4v1n0gevDye5bfPc3kg.png" /><figcaption>POST Request Configured in Postman for Obtaining an Access Token</figcaption></figure><ul><li>code The value for the code parameter should be the value you obtained after signing into your Google account and authorising your Oauth client application.</li><li>client_id The value of the client id parameter should be the value you were provided with immediately after creating your Oauth client ID.</li><li>client_secret At the same time as receiving your client id, you should also have received a client secret.</li><li>redirect_uri The value of the redirect URI should be the one you specified when specifying the authorised redirect URI for your Oauth client ID e.g. <a href="https://www.mydomain.com/myPage">https://www.mydomain.com/myPage</a>.</li><li>grant_type The value of this should be authorization_code as shown above.</li></ul><p>All being well, the JSON response you receive from the endpoint should look something like as follows:</p><pre>{<br>    &quot;access_token&quot;: &quot;your access token&quot;,<br>    &quot;expires_in&quot;: 3599,<br>    &quot;refresh_token&quot;: &quot;your refresh token&quot;,<br>    &quot;scope&quot;: &quot;https://www.googleapis.com/auth/androidpublisher&quot;,<br>    &quot;token_type&quot;: &quot;Bearer&quot;<br>}</pre><p>Copy &amp; paste the value of the access_token field as you will need this for the final step.</p><h4>5) Make the Reviews API Call</h4><p>All that’s left is to make the API request to:</p><pre>https://androidpublisher.googleapis.com/androidpublisher/v3/applications/&lt;bundle identifier&gt;/reviews</pre><p>Ensure that you fill in the bundle identifier for the app you want to retrieve reviews for in the above URL. You’ll need to use the Bearer Token method of authorisation to access this endpoint. This involves adding an Authorization header to your request of the form:</p><pre>Authorization: Bearer &lt;Bearer Token&gt;</pre><p>The value of the bearer token to be used above is the access_token you received in step 4 earlier.</p><p>You should be able to configure this via the Authorization tab in Postman (see screenshot).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s_REfFCraGjsrNaT6h526A.png" /></figure><p>And that’s it! 🪄</p><p><em>After making the final API call to the reviews API, you should receive a JSON response containing your reviews. By default, this API seems to return reviews for the current week only so you’ll likely need to call it on a weekly basis.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=392a48645402" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/using-the-google-play-developer-api-to-retrieve-reviews-for-your-android-app-392a48645402">Using the Google Play Developer API to Retrieve Reviews for your Android App</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[More Fun Retrospectives — Christmas Retro ]]></title>
            <link>https://medium.com/go-city/a-christmas-retrospective-3496e5a00f60?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/3496e5a00f60</guid>
            <category><![CDATA[scrum]]></category>
            <category><![CDATA[retrospectives]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[agile-retrospective]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 14:51:02 GMT</pubDate>
            <atom:updated>2023-03-14T18:21:26.077Z</atom:updated>
            <content:encoded><![CDATA[<h4>The festive season is the perfect time to reflect on the year’s achievements.</h4><p>For those that follow this blog, you’ll be aware that for some time now I’ve been trying to create a series of <a href="https://medium.com/@rwbutler/list/more-interesting-sprint-retrospectives-20b421934888"><em>More Interesting Sprint Retrospectives</em></a><em>.</em> Retros which do not simply result in a three-column board consisting of <em>What Went Well</em>, <em>What Didn’t Go So Well</em> and <em>What Could Be Improved </em>(or some derivation of this).</p><p>It’s not that there’s anything wrong with these retrospectives per se, only that they tend to become tired very quickly and before long a team can fall into a pattern of rehashing the same old ideas each sprint whereas changing the format can encourage a team to approach the challenge in new &amp; inventive ways.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1023/1*wJHID9GJ4gm594-SCE4NOw.png" /><figcaption>Photo by <a href="https://unsplash.com/@anniespratt?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Annie Spratt</a> on <a href="https://unsplash.com/s/photos/christmas?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The retro I’m covering this time is fairly concise, with a focus on reflecting not just on the most recent sprint but on the entire year.</p><p>The retro consists of four rounds, each posing a question to the team. Allow the team around two mins per question to write down their thoughts either onto physical post-it notes or virtual ones via a tool such as <a href="https://miro.com">Miro</a>.</p><p>Often participants tend to pick a different colour of post-it note for each individual — however, for this retro, I recommend asking the team to use a particular colour of post-it note for each round.</p><h4>New Year’s Resolutions 📝</h4><p>In the first round of this retro we pose the question:</p><p><em>What new things should we try or what should we do differently next year?</em></p><p>Post-it note colour: Green</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FX16Svr4dy05PZ3cPYG3DQ.png" /><figcaption>Photo by <a href="https://unsplash.com/@joannakosinska?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Joanna Kosinska</a> on <a href="https://unsplash.com/@joannakosinska?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Often, people equate the New Year with a fresh start which makes it a good time to think about changing up the things that haven’t necessarily worked so well for the team over the course of the year.</p><p>Sometimes it’s worth considering (for example) whether a process is being followed for good reason or simply because that’s the way things have always been? These are the kind of insights this round attempts to elicit from the team.</p><h4>Santa’s List 🎅</h4><p>This playful round asks:</p><p><em>What would you write to ask Santa for?</em></p><p>Post-it note colour: Pink</p><p>Are there better tools that might be used to achieve a common task within the team than those currently in place? The question is deliberately open-ended meaning that you’re likely to receive a wide variety of responses — some asking for the more tangible, whilst others may be of a conceptual nature.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1022/1*KQWWqWnqVwDP4qwln_6TiA.png" /><figcaption>Photo by <a href="https://unsplash.com/@tonicuenca?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Toni Cuenca</a> on <a href="https://unsplash.com/s/photos/christmas-background?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>Let’s Raise a Toast 🥂</h4><p><em>Here’s to the things we achieved this year!</em></p><p>Post-it note colour: Yellow</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jo9XeNdXWFGE3qzbPnLF8g.png" /><figcaption>Photo by <a href="https://unsplash.com/@alexandernaglestad?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alexander Naglestad</a> on <a href="https://unsplash.com/s/photos/champagne?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>I like this round because it provides an upbeat finish to the year and gives the team time to reflect on all that they have achieved, not just in the current sprint but over the course of the entire year. There’s also scope to give thanks to colleagues here. It’s important not just to think about what could have been done better but about all that was achieved so that the team can see just how far they have come.</p><p><em>This point in the retro, before the final round, is a good point in time to review the post-it notes that have been written so far and to discuss the thoughts therein. However, should you prefer, you can opt simply to review all of the notes at the end instead.</em></p><h4>Actions ⏭</h4><p>Now, having considered everything discussed in the previous rounds:</p><p><em>What actions should we take in the New Year?</em></p><p>Post-it note colour: Blue</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1022/1*t6SX31COofvdKjIwV-yLqQ.png" /><figcaption>Photo by <a href="https://unsplash.com/@sharonmccutcheon?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alexander Grey</a> on <a href="https://unsplash.com/s/photos/christmas-background?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The ultimate aim of each retro is not only to review and discuss as a team the challenges of each sprint and the potential ways in which you might improve, but to generate actions - tasks which will take longer to resolve than a conversation amongst the team within the confines of the retro.</p><p>This round allows the team to consider which of the post-it notes discussed in the previous rounds are within the team’s gift to change, and if they are, generate actions which can be taken as a result.</p><h4>Wrapping Up 🎁</h4><p>At the end of this retro hopefully, you’ll have:</p><ul><li>Discussed new approaches to established tasks which may have been overlooked because “<em>that’s how they’ve always been done</em>”.</li><li>Considered the things that are lacking within the team and what can be done to address those gaps.</li><li>Celebrated all the great things the team has achieved over the course of the sprint and the entire year.</li><li>Of course, generated actions which can be carried forward into the New Year, allowing the team to continuously self-improve.</li></ul><p><strong>Slides used for this retrospective can be found </strong><a href="https://github.com/rwbutler/talks/blob/master/22-12-14-christmas-retrospective.pdf"><strong>here</strong></a><strong>.</strong></p><p><strong>Important Consideration</strong>: <em>Please be aware that not all members of your team may celebrate Christmas, in which case you should consider adapting this retro to be inclusive of those celebrating holidays such as Hannukah etc.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3496e5a00f60" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/a-christmas-retrospective-3496e5a00f60">More Fun Retrospectives — Christmas Retro 🎄</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Determining Reachability & Internet Connectivity Using Combine]]></title>
            <link>https://medium.com/go-city/determining-reachability-internet-connectivity-using-combine-66f738396d33?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/66f738396d33</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Wed, 14 Sep 2022 20:59:17 GMT</pubDate>
            <atom:updated>2022-09-14T20:59:17.410Z</atom:updated>
            <content:encoded><![CDATA[<h4>Subscribing to changes in Reachability &amp; Internet Connectivity State Using a Combine Publisher</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RLe0O5xSB63vLswJjKlB0Q.jpeg" /></figure><blockquote>Preface: This article was originally written whilst iOS 14 was in beta and was intended to serve as the third article in a series of articles on Internet connectivity within iOS. In fact, it ended up being published subsequently to <a href="https://medium.com/@rwbutler/detecting-internet-connectivity-using-opencombine-1a11e36d233b">Detecting Internet Connectivity using OpenCombine</a> which was written at a later date. The intended ordering of articles in the series is as follows:</blockquote><blockquote>1) <a href="https://medium.com/@rwbutler/solving-the-captive-portal-problem-on-ios-9a53ba2b381e">Solving the Captive Portal Problem on iOS</a></blockquote><blockquote>2) <a href="https://medium.com/@rwbutler/nwpathmonitor-the-new-reachability-de101a5a8835">Detecting Internet Access on iOS 12+</a></blockquote><blockquote>3) Determining Reachability &amp; Internet Connectivity Using Combine</blockquote><blockquote>4) <a href="https://medium.com/@rwbutler/detecting-internet-connectivity-using-opencombine-1a11e36d233b">Detecting Internet Connectivity using OpenCombine</a></blockquote><blockquote>Minor adjustments have been made to bring the article up to date with the release of iOS 16.</blockquote><h3>Reactive Programming &amp; iOS</h3><p>Functional reactive programming has proven a popular paradigm in recent years within a number of software development ecosystems and the iOS development community is no exception. The previous few years gave rise to a number of third party frameworks which endeavored to fill the gap left by the lack of native FRP support in iOS prior to iOS 13. These include frameworks such as <a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a>, <a href="https://github.com/ReactiveX/RxSwift">RxSwift</a>, <a href="https://github.com/ReactiveCocoa/ReactiveSwift">ReactiveSwift</a>, <a href="https://github.com/Thomvis/BrightFutures">BrightFutures</a>, <a href="https://github.com/ReSwift/ReSwift">ReSwift</a>, <a href="https://github.com/mxcl/PromiseKit">PromiseKit</a> and more.</p><p>iOS 13 introduced Apple’s reactive programming framework Combine, and with the release of iOS 16 many apps will have either already adopted it or be in the position to limit support to iOS 13 (or above) and adopt it if they so choose.</p><p>Combine introduces the concepts of a Publisher and Subscriber (following the traditional <a href="https://en.wikipedia.org/wiki/Publish–subscribe_pattern">publisher-subscriber pattern</a>) whereby a publisher emits a sequence of events to which a subscriber may elect to be notified. One useful application of this model might be to create a publisher to which we can subscribe in order to let our app know of changes in Internet connection state.</p><h3>Detecting Internet Connectivity</h3><p>In the past Reachability was used by many iOS developers as the de facto means of determining whether or not an app had an Internet connection -whether by using Apple’s <a href="https://developer.apple.com/library/archive/samplecode/Reachability/Introduction/Intro.html">original sample code</a> directly or a third-party adaptation of this.</p><p>In a previous blog post, <a href="https://medium.com/@rwbutler/solving-the-captive-portal-problem-on-ios-9a53ba2b381e"><em>Solving the Captive Portal Problem</em></a>, I discussed how Reachability doesn’t provide a true measure of whether Internet connectivity is present but rather, indicates <em>only that an interface is available that might allow a connection</em>. This means that there are many scenarios in which the use of Reachability may yield as misleading result indicating that a connection is available where in fact there is not. This might include when connecting to a public WiFi network with a captive portal such as in the local coffee shop or when connecting to an access point or network which isn’t connected to the Internet. iOS solves the captive portal problem through implementation of the <a href="https://en.wikipedia.org/wiki/WISPr">WISPr</a> standard.</p><p><a href="https://github.com/rwbutler/Connectivity">Connectivity</a> is an open-source framework available under MIT license which wraps Reachability in order to listen for changes in network link state and endeavors to replicate iOS’s means of detecting captive portals in order to provide a reliable measure of whether or not Internet connectivity is present.</p><p>The advent of iOS 12 saw the introduction of the Network framework and <a href="https://developer.apple.com/documentation/network/nwpathmonitor">NWPathMonitor</a> which improves on Reachability (for more information check out a follow up blog post <a href="https://medium.com/@rwbutler/nwpathmonitor-the-new-reachability-de101a5a8835"><em>Detecting Internet Access on iOS 12+</em></a>) and thus the Connectivity framework uses NWPathMonitor over Reachability for detecting changes in network link state on iOS 12 and above.</p><p>This framework has been updated to allow users on iOS 13 and above to make use of Combine in order to allow an app to be notified of changes in connectivity with only a few elegant lines of code.</p><p><a href="https://github.com/rwbutler/Connectivity">rwbutler/Connectivity</a></p><p>Traditionally Connectivity allows developers to be notified of changes in Internet connectivity by assigning a closure to the whenConnected and whenDisconnected properties of the Connectivity object which gets invoked by the library when Internet connectivity changes. Alternatively, developers may observe the default NotificationCenter listening for either a ConnectivityDidChange or ReachabilityDidChange notification to listen for changes in connectivity or reachability respectively.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N9rkPS2EUotHhMUpoEF9nQ.png" /><figcaption>Assigning a closure to Connectivity which will be invoked on changes in Internet connection state</figcaption></figure><h3>Combine Publishers</h3><p>Combine provides us with a more elegant means of observing changes in Internet connectivity. On iOS 13 and above, Connectivity provides a Publisher which emits an event every time a change in Internet connectivity occurs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/676/1*OM7PrCPsRZm-vwPxrZ0vBA.png" /><figcaption>Observing changes in connectivity using the Combine Publisher provided by Connectivity</figcaption></figure><p>In the example code above (which is provided as part of the sample app in the Connectivity <a href="https://github.com/rwbutler/Connectivity">repository on GitHub</a>), we first create an instance of Connectivity.Publisher in order that we can receive an event each time the app’s Internet connection state changes.</p><p>The next step erases the type of publisher from Connectivity.Publisher to AnyPublisher using <a href="https://developer.apple.com/documentation/combine/publisher/3241548-erasetoanypublisher">eraseToAnyPublisher</a>. This is performed as a means of abstraction so that any subsequent code downstream of this function call (in this case of call to sink) is now working with an instance of AnyPublisher rather than aConnectivity.Publisherinstance. This helps us keep the downstream code independent of any of the specific implementation details of Connectivity.Publisher.</p><p>Just as you might use a protocol to abstract away from away from the details of the class or struct which implements that protocol, eraseToAnyPublisher performs a similar function for a Publisher. Using a protocol rather than a specific implementation means that should the implementation change in future (so long as it still conforms to the protocol), the calling code which uses the protocol reference needn’t change at all. eraseToAnyPublisher performs a similar function for us when working with publishers meaning that any subsequent function calls using the publisher needn’t change at all should the implementation of the publisher change in future since the downstream code is working with a generic AnyPublisher type rather than a more specific implementation.</p><p>The final part is probably the most important in which we create a subscriber which listens / subscribes to the series of events emitted by the Connectivity.Publisher. It’s possible to create your own subscriber but Combine provides two subscribers out of the box which are suitable for most use cases.</p><p>The built-in subscribers provided by Combine are:</p><ul><li><a href="https://developer.apple.com/documentation/combine/subscribers/sink">Sink</a> — A subscriber which can be used to receive an unlimited number of values as well as notification of a completion or error.</li><li><a href="https://developer.apple.com/documentation/combine/subscribers/assign">Assign</a> — As the name suggests, assigns a sequence of values to the property specified by a given key path.</li></ul><h3>A More Elegant Syntax</h3><p>Connectivity was designed to provide an interface as similar to Reachability as possible in order to make learning how to use the framework as simple as possible for developers who have used Reachability in the past. It is compatible with Objective-C and supports iOS 9 and above so that as many apps as possible are supported. Reachability is used as a component to determine whether a network interface state change occurs prior to iOS 12, with NWPathMonitor being used on iOS 12 and above.</p><p>For developers who need only support iOS 13 &amp; above, Hyperconnectivity is a spin-off of the Connectivity project whose goal is to provide the functionality of Connectivity as part of a pure Swift project. By ditching compatibility with Objective-C, Hyperconnectivity is able to reimagine what Reachability would look like written using all that modern Swift has to offer. The intention was that users of Hyperconnectivity would favour the use of Combine publishers over the closure-based syntax of Connectivity (<em>Update: Connectivity now also offers Combine publishers on iOS 13 &amp; above</em>).</p><p>Hyperconnectivity provides two publishers:</p><ul><li>Hyperconnectivity.Publisher (aliased as Publishers.Connectivity) — notifies the caller of changes in true Internet connectivity providing the same functionality for the detection of captive portals as Connectivity does.</li><li>Publishers.Reachability — For apps unconcerned with whether an actual Internet connection is present and simply need to know whether the Internet is reachable or not, this publisher is provided to emulate the behavior of Reachability using the newer NWPathMonitor. This publisher does not provide detection of captive portals — it will only report whether or not a network interface is available e.g. whether cellular or WiFi is enabled.</li></ul><p><a href="https://github.com/rwbutler/Hyperconnectivity">rwbutler/Hyperconnectivity</a></p><p>A comparison of the functionality offered by Connectivity versus Hyperconnectivity can be found on the <a href="https://github.com/rwbutler/Hyperconnectivity">Hyperconnectivity GitHub repository</a>. The process of subscribing to be notified of changes in Internet connectivity follows much the same pattern as when using Connectivity however:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NXrGpMLMmsiPHIMNfFfJfQ.png" /><figcaption>Observing changes in connectivity using the Combine Publisher provided by Hyperconnectivity</figcaption></figure><h3>Summary</h3><ul><li>Hyperconnectivity is a framework which eschews Objective-C support in favour of a more modern syntax but only supports iOS 13 and above.</li><li>Connectivity provides Obj-C and backwards compatibility to iOS 9.</li><li>Both Connectivity &amp; Hyperconnectivity provide Combine publishers to allow clients to be notified of changes in Connectivity or Reachability where on iOS 13 or above.</li><li>If you found this article of interest you may find the subsequent article in this series, <a href="https://medium.com/@rwbutler/detecting-internet-connectivity-using-opencombine-1a11e36d233b">Detecting Internet Connectivity using OpenCombine</a>, of interest which demonstrates detection of Internet connectivity using <a href="https://github.com/OpenCombine/OpenCombine">OpenCombine</a> in place of Combine.</li></ul><p><em>The full code is provided as part of the sample app in the </em><a href="https://github.com/rwbutler/Connectivity"><em>Connectivity repository on GitHub</em></a>. <em>Both </em><a href="https://github.com/rwbutler/connectivity"><em>Connectivity</em></a><em> and </em><a href="https://github.com/rwbutler/Hyperconnectivity"><em>Hyperconnectivity</em></a><em> can be found open-sourced on GitHub under MIT license and are compatible with Cocoapods, Carthage and Swift Package Manager.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/150/1*ZsETKTbuCbEw0Q-kH9t35A.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66f738396d33" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/determining-reachability-internet-connectivity-using-combine-66f738396d33">Determining Reachability &amp; Internet Connectivity Using Combine</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[More Fun Retrospectives — Emoji Retro]]></title>
            <link>https://medium.com/go-city/more-fun-retrospectives-emoji-retro-b5d30ebd6520?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/b5d30ebd6520</guid>
            <category><![CDATA[retrospectives]]></category>
            <category><![CDATA[agile-methodology]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Thu, 11 Aug 2022 16:43:51 GMT</pubDate>
            <atom:updated>2022-08-11T16:43:51.677Z</atom:updated>
            <content:encoded><![CDATA[<h3>More Fun Retrospectives — Emoji Retro</h3><h4>Ideas to liven up your Sprint Retrospectives!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M9CXMcukQZ6kWRT4t3pMQg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@jasongoodman_youxventures?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jason Goodman</a> on <a href="https://unsplash.com/s/photos/agile?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><em>I originally intended to write this blog post ahead of </em><a href="https://medium.com/go-city/taking-pride-in-your-retrospectives-feefecf2205d"><em>Taking Pride in your Retrospectives</em></a><em>. However, due to time constraints, I ended up writing this post later and the other earlier whilst it made sense to post that article to coincide with the occurrence of </em><a href="https://prideinlondon.org"><em>Pride in London</em></a><em>. You can think of this blog post as a prequel to that one.</em></p><p>Over the course of a number of sprints, we’d tried quite a few different formats for our sprint retrospectives. <a href="https://miro.com/templates/retrospective-tool/">Miro</a> has a template for putting together a retro on a shared digital board and whilst sites such as <a href="https://www.funretrospectives.com">FunRetrospectives.com</a>, <a href="https://easyretro.io/publicboard/Oh0BLz8MhMWJvbTDat4kIa7eO402/2f0b0aa2-facc-4313-8771-ca6a4276f46d">EasyRetro</a>, and particularly <a href="https://www.parabol.co">parabol</a> are great for running an online retro and offer a number of different templates — ultimately, what tends to result is a board with (usually three) columns. When faced with a board with a fixed number of columns each sprint, retros can start to feel repetitive and wearying.</p><p>Retrospectives offer a means for team members to express themselves and given that we are a software development team it made sense to utilise software to enable self-expression as part of the retro. Technology and software in particular have developed a number of means of enabling users to express sentiment particularly whilst online. One of the earliest of these means was the emoji. The emoji was developed to allow individuals to better express themselves online than would otherwise be possible with typed text alone. Given this, it made sense to incorporate the use of emoji within a retro to facilitate self-expression.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/719/1*OlCxst1m3DxaDxp39y9ITw.png" /></figure><h3>How it Works</h3><h4>Round 1 — Expression</h4><p>The first round is essentially about engaging the members of the team such that they feel able to participate and contribute to the discussion. It is also about giving each member of the team the opportunity to express themselves in some small way. If each team member gets a chance to speak near the start of the retro then the likelihood of them speaking up throughout is higher. In contrast, if one or two team members are louder and dominate conversation from the beginning then others may feel less inclined to speak up.</p><p>In this round, ask each team member one by one to pick one of the emoji from the following list (although you may wish to customise the selection of emoji used for your retro):</p><p>😊🤠🐌💨💩🥳😍🤯🤬🤣😭🙏</p><p>Next, ask them why they made their choice — the choice they made may correspond to their current mood or may have been made for some other amusing reason. Either way usually makes for a good conversation starter!</p><p>Asking each team member in turn also ensures that each team member gets the opportunity to speak without being talked over and also allows some minor self-expression.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/947/1*gbKGfC-l9UvdpUYAZgBA8g.png" /><figcaption>Ask each team member to pick an emoji — then ask what determined their selection?</figcaption></figure><p>For each emoji above, there is an associated icebreaker question to ask the team member. I’ve taken these questions from EasyRetro’s list of <a href="https://easyretro.io/icebreaker-questions/"><em>300+ Icebreaker Questions to Try Today</em></a> but you may prefer to use your own questions.</p><p>😊 — You’re stuck in a zombie apocalypse — which two famous people (living or dead) do you want to team up with to survive, and why?</p><p>🤠 — What fictional character would you like to be for a day, and what would you do?</p><p>🐌 — You have unlimited financing to invent the most ridiculous thing you can think of: what do you make?</p><p>💨 — If you could bring back any past fashion trend, what would it be?</p><p>💩 — What’s the most bizarre food you’ve ever eaten?</p><p>🥳 — Which TV show has the best theme song?</p><p>😍 — If you were stuck in a lift with a stranger, what song would you suggest you sing to keep your spirits up?</p><p>🤯 — If you had the freedom to commit a crime and were guaranteed to get away with it, what would you do?</p><p>🤬 — What would you prefer: Harry Potter’s Cloak of Invisibility or Iron Man’s armour, and why?</p><p>🤣 — Which two politicians (living or dead) would you love to see in a cage fight?</p><p>😭 — What song would you be forced to listen to on repeat in hell?</p><p>🙏 — If you could add a silly new rule to your favourite sport, what would it be?</p><p>When you’ve asked each team member the icebreaker question associated with their choice of emoji the round ends.</p><p>I also ran this retro a second time in which I introduced an alternative first round. In the alternative first round, I provided everyone with a pen and paper and asked everyone to draw themselves as emoji. After providing everyone with enough time to draw, go around the room in turn and ask them to introduce what they’ve drawn.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZmnEFI9u-TwIHY4ZKsEqMQ.png" /></figure><h4>Round 2— Reflection</h4><p>The second round is the main event in the retro whereby the team reflects on the sprint that has just ended.</p><p>For this round, I purchased some sticker packs of emojis and placed the emoji listed below on a whiteboard. The team were then given 10–15 minutes to write their reflections on post-it notes clustered around the most relevant emoji e.g. things they were happy about would be clustered around the happy emoji whilst things they felt went too slowly would be placed around the snail emoji.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ls8Q5znNshzkLXDYFVrAPQ.png" /><figcaption>Reflections clustered around the happy emoji</figcaption></figure><p>This works equally well remotely if the emoji are spread out on a virtual whiteboard e.g. <a href="https://miro.com">Miro</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*jvo24hfLNsYcNDNUsXvyZA.png" /></figure><p>At the end of the round, the team goes through each post-it on the board and discusses the reflection written upon it. Inevitably, some of the reflections will be very similar to, or duplicates of one another. In this case, I recommend clustering these post-its together.</p><p>After running this retro for the first time I discovered that the funny emoji was the only which wasn’t used at all — it was originally intended to lighten the mood but on reflection was probably a poor choice. On running this retro a second time, the funny emoji was replaced with a worried emoji (see below).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mLa5Q_qjCiHRocHZWx_Cig.png" /></figure><p>After discussing each reflection on the whiteboard the round is considered complete.</p><h4>Round 3— Consideration</h4><p>The third round is very quick and in the spirit of continuous improvement, asks each team member which emoji they would remove and which they would add for next time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*HU26K2rjXycWlARCcWANvg.png" /></figure><p>This is how we ended up replacing the funny emoji with the worried emoji mentioned above. Asking these questions allows the retro itself to be improved for future iterations.</p><h4>Round 4— Actuation</h4><p>Round four is a discussion in which we ask the team, based on the reflections just covered (which should still be visible on the whiteboard) what actions could be generated to improve the sprint for the next time around.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/1*FTp1fDklVxQ2V9e-Iq0iAg.png" /></figure><p>Obvious candidates are where a number of post-its are clustered together because team members have shared the same thought or concern.</p><p>If each reflection only occurs once (has been echoed by multiple team members) or if there are too many actions to be tackled within the next sprint, then a dot voting mechanism may be applied to triage the most important actions.</p><p>In this round, it is also important to go through the actions that were generated as a result of the previous sprint’s retrospective (regardless of whether that retro was an emoji retro or not) and the outcomes of each.</p><p>If the team do not see outcomes generated as a result of the actions they will start to become disenfranchised with retrospectives and will be less inclined to contribute in future. Arguably, this makes this the most important round of the retrospective.</p><h4>Round 5— Introspection</h4><p>The final round is a very quick emotional check-in to see how each team member is feeling at the end of the retro. Has it been cathartic? Were some of the discussions heated? Are the team comforted by the fact that their concerns will be addressed?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FjhPuBHy_puVkKCtnF9xNw.png" /></figure><p>Asking each team member to choose an emoji is essentially asking them to choose an emotion to describe their state at the end of the retro process so you may garner some quick insights here that will allow you to tailor how your retro is run the next time around. You may also get a sense of how the team is feeling more generally here — are they feeling pressure to deliver quickly? Do they feel as though their product suggestions are being heard?</p><h4>Wrapping Up</h4><p>At the end of this retro hopefully, you’ll have:</p><ul><li>Gained some insights into how the team felt about the previous sprint by discussing the reflections raised on the whiteboard (Reflection round)</li><li>Discussed the actions generated as part of the previous retrospective and the progress on each (Actuation round)</li><li>Generate some new actions to be tackled as part of the next sprint which can be reviewed as part of the next retro (Actuation round)</li><li>Garnered some insight into how the team are feeling presently (Introspection round and to some extent via the Expression round as well)</li></ul><p><strong>Slides used for this retrospective can be found </strong><a href="https://github.com/rwbutler/talks/blob/master/15-06-22-emoji-retrospective-updated.pdf"><strong>here</strong></a><strong> and </strong><a href="https://github.com/rwbutler/talks/blob/master/27-07-22-emoji-retrospective-2.pdf"><strong>here</strong></a><strong>.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b5d30ebd6520" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/more-fun-retrospectives-emoji-retro-b5d30ebd6520">More Fun Retrospectives — Emoji Retro</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Taking Pride In Your Retrospectives]]></title>
            <link>https://medium.com/go-city/taking-pride-in-your-retrospectives-feefecf2205d?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/feefecf2205d</guid>
            <category><![CDATA[agile-methodology]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[scrum]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Thu, 07 Jul 2022 14:05:23 GMT</pubDate>
            <atom:updated>2023-06-28T11:40:26.584Z</atom:updated>
            <content:encoded><![CDATA[<h4>An opportunity to create a fun, educational &amp; inclusive retrospective. 🏳️‍🌈</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wjXylIEsTgdkUuViDy21NA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@ctj?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Cecilie Johnsen</a> on <a href="https://unsplash.com/s/photos/pride?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><em>For some time I’d been looking for ways to create more interesting and engaging retrospectives that didn’t feel formulaic. Many retrospectives follow a similar pattern making them feel tired leading to a decrease in buy-in and participation.</em></p><p><em>Last week was Pride in London which felt as though it were an opportunity to create a retrospective that is not only inclusive &amp; celebrates diversity, but educational too. Tying the theme of the retro to current events helped to keep the retro feeling fresh.</em></p><p>Here’s how the Pride retrospective was run within our team in order that you may take and run it with your own team (adapting as necessary). A complete slide deck is included at the end.</p><h3>Opening — Guess the Flag</h3><p>The opening for this retrospective is a little game called Guess the Flag — as well as being educational, it’s also fun and gets people talking so that by the time we arrive at the main event hopefully people will have warmed up and feel comfortable sharing their thoughts and feelings.</p><p>The way the game works is fairly straightforward — ask each member of the team to guess what they think each flag stands for. They will be awarded 1 point for each correct answer and the winner will be announced at the end. When I ran this retro there was a small reward for the person who scored the most points at the end of the retro.</p><p>If running this retro in person, answers can be written down or if running virtually then get each team member to send their answers to you via Slack or an alternative service.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cTHGZdrPOkv0tLyGj8eW6A.png" /></figure><p>I found that this opening game got the team into the spirit of pride, started people talking and was educational in the cases where they were unsure of the correct answer. After receiving a list of answers from each team member, the next slide in the retro deck (see the end of the article for the slide deck) goes through the answers. This is the first of two rounds of this game — the second round follows later.</p><h3>Round 1 — Self-Expression</h3><p>This brings us on to the first round of the retrospective titled Self-Expression. Each round in this retro has been given a name that relates to an important theme of pride — in this case, allowing individuals to express their authentic self.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LbGgOIVEevzOgnQNe1oJuw.png" /></figure><p>In the self-expression round, the colours of the <a href="https://www.vam.ac.uk/articles/the-progress-pride-flag">Pride Progress Flag</a> have been listed out. The Pride Progress flag includes “<a href="https://www.bbc.co.uk/newsround/57607955">black, brown, pink, pale blue and white stripes, to represent marginalised people of colour in the LGBTQ+ community, as well as the trans community, and those living with HIV/AIDS</a>”. Team members will now be familiar with the Pride Progress flag (even if they weren’t previously) after having gone through the answers to the Guess the Flag round.</p><p>Ask each team member in turn the question which colour in the list they would be and why? I found the answers I received from posing this question were fun and sometimes unexpected. As a team leader, the answers have the potential to provide you with insight into how the team are feeling about the work that they are doing as well as how they are feeling more broadly. You won’t always receive answers which provide an insight into emotional state but the answer is at least likely to be fun.</p><p>Each person in the team gets the chance to speak and open up to the rest of the team without the possibility of interruption which is important especially in teams where there might be some team members who are naturally more subdued than others.</p><h3>Round 2 - Pride</h3><p>In this second round of the retro, ask each team member to name something they were proud of in the sprint.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eVz-VRMU_dkz3rkXanLtqQ.png" /></figure><p>I really enjoy this round because it generates a “good mood” feeling in the room and puts the emphasis on the progress that has been made. It’s important not only to focus on the things that could be improved in a sprint but to celebrates the successes.</p><p>The classic retro can too easily become a negative affair inciting criticism whereas a round like this promotes the positive, helping to ensure that not only do the team stop doing the things that didn’t work well but also that <em>they keep doing the things that did work well</em>.</p><h3>Interlude — Meaning of the Stripes</h3><p>This interlude is an educational segment explaining the meaning of the stripes in the classic rainbow pride flag as well as the more modern progress flag.</p><p>It highlights that frequently colours are associated feelings &amp; emotions which serves nicely as a segue into the next, and main, round of the retrospective.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LlmA39t4PhSrvQVVy6rX2A.png" /></figure><p>Meaning of the stripes isn’t a core round to the retro so if you are pressed for time it’s possible to drop this section and / or the guess the flag game.</p><p>More information on the meaning of the colours in the stripes can be found here:</p><ul><li><a href="https://www.verywellmind.com/what-the-colors-of-the-new-pride-flag-mean-5189173">https://www.verywellmind.com/what-the-colors-of-the-new-pride-flag-mean-5189173</a></li><li><a href="https://www.vam.ac.uk/articles/the-progress-pride-flag">https://www.vam.ac.uk/articles/the-progress-pride-flag</a></li><li><a href="https://en.wikipedia.org/wiki/Rainbow_flag_(LGBT)#Variations">https://en.wikipedia.org/wiki/Rainbow_flag_(LGBT)#Variations</a></li></ul><h3>Round 3 — Colour Psychology</h3><p>This is the main round in the retrospective leading on from the interlude explaining the meaning of the colours used in the pride flags. <a href="https://en.wikipedia.org/wiki/Color_psychology">Colours are frequently associated with emotions</a> and therefore make a good vehicle for allowing team members to express themselves.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lkFcMw8CtOMUQQ4O7IIR3g.png" /></figure><p>In this round, I have listed out the colours used in the progress flag as well as some of the emotions most commonly associated with each colour. Either on a physical whiteboard or a digital board, such as <a href="https://miro.com">Miro</a>, list out each of the colours above. Next, ask the members of the team to add post-it notes (either physical or digital) expressing thoughts, feelings or concerns relating to events which occurred in the sprint next to the relevant colour / emotion. When running this round of the retro — I set a 10 minute timer and and allowed the team to add notes until the timer elapsed.</p><p>At the end of the 10 minute period, go through each note and if people are willing to share ask what made them associate the note with a particular colour (this works best in teams with a high degree of trust and psychological safety). The emotion they chose that made them associate the note with a particular colour can be illuminating.</p><h3>Interlude — Guess the Flag 2</h3><p>Following the main round, we have one final round of guess the flag which increases the difficult a little in comparison to round 1. Again, ask each team member to send over their answers either on paper or via Slack (/ messaging service) and then in the subsequent slide go through the answers. Total up the scores each person received over the two rounds and declare a winner! 🎉</p><p>As well as being educational, it keeps the feeling of fun running through the retro and breaks up the core rounds of the retro which can feel more like work. It also helps distinguish this retro from others which often simply feel like variations on the same classic format.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bI3VmwoM6Xraj9tlPFA84g.png" /></figure><h3>Round 4 — Pride, Protest, March</h3><p>The penultimate round is really just a variation of the classic start, stop, continue round which most will have experienced if they taken part in a retro before - <em>but this time with a pride twist!</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x-AMWXPHNSGmMKFCioD3bA.png" /></figure><p>In this round pose the following questions to the team:</p><ul><li>What should we proud of? i.e. what positives should the team take away as things to keep doing in subsequent sprints. This is particularly important if the positive was something that was only introduced recently as it can often be the case that something is done well once and then forgotten about because of the fact that it went well.</li><li>What should we protest? i.e. what did not work well and the team should endeavour to avoid repeating in future. Something may have been raised in the main colour psychology round that stands out as something the team need to stop doing or a situation that the team should endeavour to avoid.</li><li>What should we be marching towards? i.e. what is the ideal end state of the team and what do the team need to start doing in order to get to that state. Are there things the team could start doing that might solve issues that were raised in the colour psychology round?</li></ul><p>The aim here is to generate concrete actions that can be taken to improve the team’s effectiveness going into the next sprint and beyond. Having just discussed a number of thoughts, feelings &amp; concerns in the main round there will be a number of things here that will be fresh in people’s minds. Ensure that for each action there is an owner who will be accountable for its completion.</p><h3>Round 5 — Progress</h3><p>The final round of the retro is the progress round. The name of the round is a reference to the progress flag but is also a nod to the key reason why you would run a retro in the first place i.e. making progress in the ways in which your team work together through iterative improvement.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ySEqMSqRzFwQdPKRlYqV7g.png" /></figure><p>It is key that the team feel as though progress is being made each time a retro is run. Otherwise, before long the team will become disenfranchised and come to view retrospectives as a waste of time. Therefore, as well as generating actions (as we did in the previous round), it is important to review the actions that were created as a result of the previous retrospective in order that the team can see the progress being made.</p><h3>Wrapping Up</h3><p>By now you should have discussed how the team felt about the previous sprint, reviewed the actions from the previous retro and generated some new actions to be reviewed next time. All that’s left to do is to crown the winner of the Guess the Flag quiz (and potentially award a prize). In case of our team, the quiz ranking determined the doughnut picking order!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p3L4-_9yfAqtqbFMGhLteA.png" /></figure><p>Hopefully you’ll have had some fun and potentially learnt something new along the way! 🏳️‍🌈</p><p>Slides used for this retrospective can be found<strong> </strong><a href="https://github.com/rwbutler/talks/blob/master/22-06-29-pride-retrospective.pdf"><strong>here</strong></a><strong>.</strong></p><p><strong>Update: </strong>A newer set of slides updated for 2023 can be found <a href="https://github.com/rwbutler/talks/blob/master/23-06-28-pride-retrospective-2023.pdf"><strong>here</strong></a>.</p><p><strong><em>Note:</em></strong><em> In 2021 an updated version of the Pride Progress Flag was released to include the intersex flag. This adds a purple circle inside a yellow triangle to the chevron of the Pride Progress flag. Future iterations of this retro might be updated to use this iteration of the flag to increase inclusivity even further.</em></p><p><a href="https://en.wikipedia.org/wiki/Rainbow_flag_(LGBT)#Variations">Rainbow flag (LGBT) - Wikipedia</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*omq3GwXqiNDyBOGYoFPuCw.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=feefecf2205d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/taking-pride-in-your-retrospectives-feefecf2205d">Taking Pride In Your Retrospectives</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Monitoring Screen Brightness Using Combine]]></title>
            <link>https://medium.com/go-city/screen-brightness-using-combine-54764122906f?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/54764122906f</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[combine]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Wed, 08 Jun 2022 06:59:30 GMT</pubDate>
            <atom:updated>2022-07-12T13:18:15.491Z</atom:updated>
            <content:encoded><![CDATA[<h4>Leveraging the power of Combine to observe changes in screen brightness making barcodes &amp; QR codes more easily scannable.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zP2REsA4yDHdJyHwZ0URcA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@proxyclick?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Proxyclick Visitor Management System</a> on <a href="https://unsplash.com/s/photos/qr-code?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><em>This article assumes some basic knowledge of Swift programming as well as of the </em><a href="https://developer.apple.com/documentation/combine"><em>Combine</em></a><em> framework. Namely, it assumes you know what </em><a href="https://developer.apple.com/documentation/combine/publisher"><em>Publisher</em></a><em>s are and why you might use them.</em></p><p>Apps which present barcodes or QR codes to be scanned frequently raise the screen brightness to maximum when presenting the code to allow it to be more easily scanned. This means that the member of staff scanning the QR code doesn’t need to instruct a customer to manually raise the screen brightness when scanning difficulties are encountered.</p><p>For example, we may wish to create a screen containing a QR code that when presented raises the screen brightness to full — and then decreases the screen brightness to the original value when closed so as to avoid wasting the customer’s battery. In order to achieve this we’ll need to be able to:</p><ul><li>Monitor the current screen brightness in order to obtain the original screen brightness level</li><li>Set the screen brightness in order to set to full and then back again to the original value</li></ul><h4>A Simple Implementation</h4><p>Where using Swift and Combine in an iOS app we can easily write a service which will allow us to subscribe to the current screen brightness by using <a href="https://developer.apple.com/documentation/foundation/notificationcenter/3329353-publisher">the publisher that is provided to us on NotificationCenter</a> as part of<a href="https://developer.apple.com/documentation/foundation"> Foundation </a>framework:</p><pre><strong>let</strong> notificationCenter = NotificationCenter.default</pre><pre><strong>let</strong> brightnessPublisher = notificationCenter.publisher(for: UIScreen.brightnessDidChangeNotification).eraseToAnyPublisher()</pre><p>Here’s a simplistic initial implementation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0885a97dd78eed06740f62918ec4603d/href">https://medium.com/media/0885a97dd78eed06740f62918ec4603d/href</a></iframe><p>In the above example we’ve:</p><ul><li>Created a protocol for our screen brightness service — this will make it easy for us to test our code wherever we call the newly created service. All we’ll need to do is create a mock screen brightness service which conforms to the same protocol and inject that into our view model (or wherever else we choose to make use of the service).</li><li>Defined a typealias for our publisher so that if we ever want to change the type of our publisher we can simply update the declaration and the type of publisher will change everywhere throughout our code.</li><li>Declared that our service publishes a stream of Double values. After all, it’s not very convenient to work with a stream of Notification objects and have to convert to a brightness value each time.</li><li>In the service implementation, we’ve mapped the NotificationCenter publisher to the type of publisher that we want i.e. one which publishes a stream of Double values, not Notification objects.</li><li>Type-erased the mapped publisher by calling eraseToAnyPublisher. This means that any calling code that makes use of our service will receive a publisher of type AnyPublisher. This means that the calling code is agnostic of the publisher implementation. The key benefit of this is that allows us to change the implementation of our publisher without breaking the calling code so long as we conform to the protocol.</li></ul><p>An important point to note is that the Notification object itself does not provide us with the current screen brightness value, it only tells us that the screen brightness has changed. Therefore we need to obtain the current screen brightness on receiving the notification by calling UIScreen.main.brightness.</p><h4>A Testable Implementation</h4><p>We’ve created a protocol for our screen brightness service to conform to in order to allow us to test our calling code however the service implementation itself isn’t very unit-testable right now.</p><p>Primarily this is because we can’t inject a screen brightness value and test that our publisher publishes the correct value.</p><p>In order to fix this let’s create a protocol for UIScreen as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff8cba22ac570f6645fafece0cd7931a/href">https://medium.com/media/ff8cba22ac570f6645fafece0cd7931a/href</a></iframe><p>In the above, we’ve created the protocol ScreenBrightness and we’ve created an extension on UIScreen declaring conformance to it. There’s no need to add any code to the extension because UIScreen already conforms to this protocol as it already has a property called brightness which we can get and set.</p><p>We can now pass any object that conforms to the ScreenBrightness protocol into our service e.g.</p><pre>let screenBrightnessService = UIKitScreenBrightnessService2(screenBrightness: UIScreen.main)</pre><p>Note that the property we’ve declared as part of the protocol will have both getters and setters. That’s because as we mentioned earlier we’ll want to be able to update the screen brightness as well as observe its current value.</p><p>In the above example, we’ve also added a method to set the screen brightness which will update the screen brightness via the newly added screenBrightness property we’ve just added.</p><p>We could have made the property an internal variable rather than a let constant however in this case we wanted to enforce that the calling code must receive the screen brightness by observing the published value rather than by accessing the current screen brightness directly.</p><p>Our implementation isn’t yet unit-testable because whilst we can control the screen brightness value that the service reads we can’t yet trigger a notification from our test that will indicate a change in screen brightness to the service and thus trigger the new screen brightness to be published by the service.</p><p>When we obtain a publisher for NotificationCenter the type of that publisher is NotificationCenter.Publisher. Ideally, we’d like our service to be agnostic to this publisher’s specific implementation. Let’s make use of eraseToAnyPublisher again to fix this.</p><pre><strong>let</strong> notificationCenter = NotificationCenter.default</pre><pre><strong>let</strong> notificationPublisher = notificationCenter.publisher(for: UIScreen.brightnessDidChangeNotification).eraseToAnyPublisher()</pre><p>The type of notificationPublisher is AnyPublisher&lt;Notification, Never&gt; which means that the publisher we observe to be made aware of changes in screen brightness needn’t be provided by NotificationCenter any longer. All our service knows about the implementation is that it publishes Notification objects and never produces any errors (Never is a special type of error signifying this publisher never produces any errors).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2525c088ea0eb7c500608302c1b9f513/href">https://medium.com/media/2525c088ea0eb7c500608302c1b9f513/href</a></iframe><p>We now pass in our notification publisher via the initializer meaning that we can now control when our service reacts to changes in screen brightness as part of a unit test.</p><p>We initialize the service as follows:</p><pre><strong>let</strong> notificationCenter = NotificationCenter.default</pre><pre><strong>let</strong> notificationPublisher = notificationCenter.publisher(for: UIScreen.brightnessDidChangeNotification).eraseToAnyPublisher()</pre><pre>let screenBrightnessService = UIKitScreenBrightnessService2(<br>     notificationPublisher: notificationPublisher<br>     screenBrightness: UIScreen.main<br>)</pre><h4>A Robust Implementation</h4><p>Our implementation may now be unit-testable but there’s a slight problem here however because the publisher provided by our service will only publish values when a notification is sent i.e. when there’s a change in screen brightness.</p><p>If our goal is to observe the current screen brightness, set the brightness to maximum when our screen is displayed and then reset the brightness to the original value then we’ll need to know what the original screen brightness value was before increasing it to the maximum.</p><p>As things stand we’d only be informed of the change in screen brightness once either we update the brightness programmatically or the user updates the screen brightness via the UI.</p><p>For this publisher to be of any use we likely want to receive the current screen brightness value on subscription to the publisher. This way we’ll always know what the current screen brightness is even before we make an adjustment to it.</p><p>Without changing the protocol our service conforms to, let’s update the implementation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ca00e51fc957072dc6003e159458243/href">https://medium.com/media/2ca00e51fc957072dc6003e159458243/href</a></iframe><p>In the above code, we’ve updated our implementation with a new instance variable called brightnessSubject which is a CurrentValueSubject. Subjects in Combine terminology are still publishers but whereas you cannot send a value to a Combine publisher (you can only subscribe to a publisher), you can send a value to a subject.</p><p>There are currently two types of subject provided by Combine:</p><ul><li>CurrentValueSubject — always has a current value which may be inspected at any time via the value property. When you update the value property, this new value is published to all subscribers. If you subscribe to a CurrentValueSubject after the value has been published, you will still receive the current value of the subject on subscription.</li><li>PassthroughSubject — you may send values to this subject which will be relayed on to any active subscribers. However, if a subscriber subscribes to this subject after a value has been sent, it will not receive this value on subscription. It will only receive a value the next time a value is sent to the subject after subscription.</li></ul><p>In our example, we choose to use a CurrentValueSubject because it means that on subscription, the calling code will immediately receive the current screen brightness value, even if it subscribes to the subject after that value has been provided to the subject.</p><p>Notice that we have amended our initializer such that when we initialize the service, we also initialize the subject with the current screen brightness value. Any subscribers to our subject will immediately receive this value on subscription meaning that we’ll be able to store the value of the screen brightness prior to us setting it to full as part of the calling code.</p><p>Modifying our service implementation to use a subject rather than mapping the notification publisher as we did in the previous step would mean that our service would no longer conform to the protocol we declared at the start unless we add a new property as follows:</p><pre>var publisher: AnyPublisher&lt;Double, Never&gt; {           brightnessSubject.eraseToAnyPublisher()    <br>}</pre><p>By type-erasing the subject to AnyPublisher it means that our service remains conformant to the protocol and the calling code which makes use of our service remains unaware of the underlying implementation details of our publisher.</p><p>The one glaring change we’ve made to the code that we’ve yet to discuss is that we now subscribe to the notification publisher as part of a separate function rather than putting this logic in the initializer just to neaten things up a little.</p><p>Here, we compact map the screen brightness value from a CGFloat to a Double. Swift 5.5 performs this conversion for us automatically but wouldn’t do so with an optional CGFloat? hence the guard statement and the compactMap as our calling code isn’t interested in nil values which should only ever occur in theory were our service to be released from memory.</p><p>The next line will assign our screen brightness value to the value property of our CurrentValueSubject i.e. we store the value to the subject any time the screen brightness is updated</p><p>Finally, we store the AnyCancellable object which is returned as a result of subscribing to the notification publisher to a set. This keeps our subscription alive until our service( and thus the cancellables property) gets released from memory.</p><p>Equally we could have simply created an instance variable which stores a single AnyCancellable value e.g.</p><pre>var cancellable: AnyCancellable?<br>...<br>cancellable = notificationPublisher.<br>     compactMap { [weak self] _ -&gt; Double? in<br>...</pre><p>Then all that’s left to do is to make use of the new service as part of your calling code. If your application follows an <a href="https://en.wikipedia.org/wiki/Model–view–viewmodel">MVVM architecture</a> you might choose to make use of the service as part of a view model as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/97ef5df6df5c6eaa7d47454c641371b2/href">https://medium.com/media/97ef5df6df5c6eaa7d47454c641371b2/href</a></iframe><p>With your view model created as above, you’d be able to inject it into your UIViewController for the screen you wish to increase the brightness of. Then in the viewDidAppear and viewDidDisappear methods of your view controller you’d simply call the corresponding methods of your view model.</p><p>An example project containing all of the code listed above can be found on <a href="https://github.com/gocityengineering/screen-brightness-using-combine">GitHub</a>.</p><p><em>If you’re interested in implementing something similar on Android, my colleague Barry Irvine has </em><a href="https://medium.com/p/db7562a4c388"><em>written a post on managing screen brightness when using Jetpack Compose.</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54764122906f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/go-city/screen-brightness-using-combine-54764122906f">Monitoring Screen Brightness Using Combine</a> was originally published in <a href="https://medium.com/go-city">Go City Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Detecting Internet Connectivity using OpenCombine]]></title>
            <link>https://medium.com/@rwbutler/detecting-internet-connectivity-using-opencombine-1a11e36d233b?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/1a11e36d233b</guid>
            <category><![CDATA[connectivity]]></category>
            <category><![CDATA[combine]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Wed, 26 Jan 2022 00:05:01 GMT</pubDate>
            <atom:updated>2022-01-26T12:29:15.790Z</atom:updated>
            <content:encoded><![CDATA[<h4>You’ve been tasked with writing an app that needs support ageing devices yet you still want to use modern programming techniques — what next?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pSvf9wUda-DKyxkk" /><figcaption>Photo by <a href="https://unsplash.com/@jjying?utm_source=medium&amp;utm_medium=referral">JJ Ying</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><em>This article is the third in a series on detecting Internet connectivity in iOS apps. In the first article, </em><a href="https://medium.com/@rwbutler/solving-the-captive-portal-problem-on-ios-9a53ba2b381e"><em>Solving the Captive Portal Problem on iOS</em></a><em> I wrote about how Apple’s Reachability sample code (and open source variants) was once the defacto method used by iOS developers to determine whether or not an Internet connection was available but covered that it did not provide a means of doing so reliably. Subsequently, in </em><a href="https://medium.com/@rwbutler/nwpathmonitor-the-new-reachability-de101a5a8835"><em>Detecting Internet Access on iOS 12</em></a><em>, I wrote about how iOS 12 introduced the Network framework which provided an in-built method of detecting changes in network interface state rather than needing to include Reachability sample code (or an open source alternative).</em></p><h4>Reactive Programming &amp; iOS</h4><blockquote>If you are already familiar with reactive programming &amp; Combine feel free to skip down to the next section on the Challenges of Combine or the subsequent section on Internet Connectivity &amp; Combine.</blockquote><p>In the early days of iOS app development, nearly all apps followed the <a href="https://en.wikipedia.org/wiki/Model–view–controller">Model-View-Controller</a> (MVC) architectural pattern as the template for a new iOS project generated by Xcode followed this pattern and it was in the manner in which Apple expected iOS apps to be built.</p><p>As more sophisticated apps with larger codebases began to emerge developers frequently ran into issues such as the App Delegate or a View Controller violating the <a href="https://en.wikipedia.org/wiki/Single-responsibility_principle">Single Responsibility Principle</a> and being used to perform any number of tasks resulting in these components frequently becoming bloated and difficult to navigate. These components also suffered from being hard to unit test.</p><p>Developers began to formulate solutions to these problems, and particularly as developers from other platforms joined the iOS ecosphere, they brought with them ideas from the platforms from which they’d just switched. This resulted in the proposal of a number of different architectural patterns which could be applied to iOS development including MVP, MVVM, Elm and VIPER to name but a few.</p><p>Arguably, Model-View-ViewModel (MVVM) grew to be one of the most popular architectural patterns applied to the development of new iOS apps. Originally developed at Microsoft and used in popular technologies such as Windows Presentation Foundation (WPF), Silverlight and .NET, this pattern introduced the concept of the view model, distinct from the model which encapsulated the business logic, which more accurately reflected the state of the UI. Presenting all of the data and logic required by the view in a readily-consumable form (usually involving transforming the data held by the model to achieve this) using a view model meant that the amount of logic required by a view could be kept to a minimum whilst at the same time the view model could easily be unit tested in contrast to a View Controller.</p><p>With the MVVM architectural pattern and similar alternatives growing in popularity, new challenges arose such as how best to keep the view model and the interface components in sync with one another? Frequently, ensuring that a property in the view model was used to populate a text field and then any changes made by the user to that text field propagated back to the property in the view model involved writing large amounts of error-prone “glue” code to ensure that these properties and their associated UI components were kept in sync.</p><p>At a similar time, reactive programming was rapidly gaining traction in web development and provided a solution to the challenge of having to write large amounts of glue code to keep the view model and UI in sync with one another. Frameworks such as ReactiveSwift and RxSwift emerged based on the ideas popularised in web development, including components which operated on an event stream allowing view model properties to be updated as changes to the UI were made and vice-versa meaning that large amounts of boilerplate code were no longer necessary.</p><p>Eventually Apple observed the popularity of these programming concepts and frameworks and created their own reactive framework — Combine. Being built into iOS (and being endorsed by Apple) ensured that Combine was sure to gain traction with developers in contrast to frameworks such as RxSwift which were not baked into the system.</p><h4>The Challenges of Combine</h4><p>With iOS 15 out in the wild, it’s perfectly plausible for iOS developers to build apps using Combine (even given that Combine is only available in iOS 13 and beyond) following the golden rule that most iOS apps support the latest and the previous version of iOS currently released.</p><p>Unfortunately however, not all iOS apps (even greenfield ones) can afford the luxury of the having to support only the latest two versions of iOS and have to cater for the wider pool of iOS devices still in service. How then, can we apply modern reactive programming techniques whilst still providing support for the majority of iOS devices still in use?</p><p>One option is that we could opt for a third-party reactive framework such as RxSwift (which supports back to iOS 9 at the time of writing). However if we anticipate that we might want to raise the deployment target of our app in future and switch to using Combine then a framework such as RxSwift might not prove the best choice given the amount of time it would take a remove the framework from our app in the future (given that reactive frameworks are pervasive throughout apps and difficult to isolate to within a single component as we might try to do with most third party frameworks) and switch to Combine syntax.</p><p>Thankfully a couple of frameworks have emerged in the form of <a href="https://github.com/OpenCombine/OpenCombine">OpenCombine</a> and <a href="https://github.com/cx-org/CombineX">CombineX</a> which aim to provide open source implementations of the Combine framework. By implementing the same syntax as Combine and providing support for iOS devices prior to iOS 13 (effectively acting as a shim for Combine), these frameworks allow developers to develop using Combine syntax whilst supporting older devices and then move to Combine proper at some point in the future when it becomes feasible to raise the deployment target for the app.</p><h4>Internet Connectivity &amp; Combine</h4><p>Combine provides publishers for all sorts of things allowing us to observe changes in UserDefaults, the results of a URLSession invocation or even to merge multiple publishers into a single event stream. However one notable omission is the ability to subscribe to a publisher to observe changes in Internet connectivity state.</p><p><a href="https://github.com/rwbutler/Connectivity">Connectivity</a> is an open source framework which aside from solving such issues as the capture portal problem (see <a href="https://medium.com/@rwbutler/solving-the-captive-portal-problem-on-ios-9a53ba2b381e"><em>Solving the Captive Portal Problem on iOS</em></a>) provides Combine publishers on iOS 13 and above, allowing a developer to observe changes in Internet connectivity using Combine syntax. <a href="https://github.com/rwbutler/Hyperconnectivity">Hyperconnectivity</a>, an offshoot of the Connectivity project, also provides Combine publishers allowing developers to observe Internet connection status, eschewing support for iOS 9 (and the SystemConfiguration framework) in favour of a more modern elegant syntax (using the Network framework introduced in iOS 12). Since in this article we’re concerned with providing support for using Combine with older versions of iOS we’ll focus on the former rather than the latter.</p><p><a href="https://github.com/rwbutler/OpenConnectivity">OpenConnectivity</a> is a lightweight Swift package which allows developers using OpenCombine to utilise publishers on platforms to observe changes in Internet connectivity where Combine proper is unavailable i.e. prior to iOS 13. To make use of it simply integrate the package using Swift Package Manager (SPM) as normal using the package URL.</p><p><a href="https://github.com/rwbutler/OpenConnectivity">GitHub - rwbutler/OpenConnectivity</a></p><p>Next, you’ll likely want to integrate the framework into your app in such a way as it is contained to within a single component such that references to the framework don’t “leak” throughout your app making it harder to remove in the future should you choose to do so (the fact that references to RxSwift are leaked across your app’s codebase is one of the major disadvantages of using it as opposed to Combine).</p><p>Here’s an example of how this can be achieved:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d3df383ae93d6215c146954965b3524/href">https://medium.com/media/0d3df383ae93d6215c146954965b3524/href</a></iframe><blockquote>Note that in a real app you likely wouldn’t want to observe Internet connectivity changes from the App Delegate.</blockquote><p>In the above example we create a new model object ConnectivityResult which is used as part of the result type of the publisher whilst the publisher itself is type-erased to AnyPublisher. This leaves us free to subscribe to the publisher to observe changes in Internet connectivity without having to worry about leaking library objects into the app’s codebase.</p><h3>Summary</h3><p>OpenConnectivity allows you to use Combine syntax to observe changes in Internet connectivity even on platforms where Combine is unavailable.</p><h3>Advantages</h3><ul><li>Using OpenConnectivity allows you to use reactive programming paradigms in the form of OpenCombine even prior to iOS 13 to observe changes in Internet connectivity.</li><li>Integration is simple using Swift Package Manager.</li><li>Using OpenConnectivity as opposed to Reachability (SystemConfiguration) or the Network framework provides robustness and the ability to detect captive portals (see previous articles).</li><li>Using OpenCombine rather than an alternative such as RxSwift means that when you’re ready to switch to Combine, the transition should be a relatively simple one rather than a painful ordeal.</li><li>When the time comes that you wish to switch from OpenCombine to Combine proper, simply switch from using OpenConnectivity to Connectivity with minimal effort.</li></ul><h3>Disadvantages</h3><ul><li>OpenCombine doesn’t yet provide all of the functionality that Combine offers.</li><li>RxSwift (and some of its rivals) have been in development far longer than OpenCombine and thus offers more functionality and at the time of writing more integration options (e.g. Cocoapods and Carthage).</li></ul><p><em>OpenConnectivity can be found open-sourced on </em><a href="https://github.com/rwbutler/OpenConnectivity"><em>GitHub</em></a><em> under MIT license and is compatible with </em><a href="https://www.swift.org/package-manager/"><em>Swift Package Manager</em></a><em>.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/150/0*bIeoIUEm1sZhuwDo.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a11e36d233b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Protect Yourself Against Commits Forged In Your Name Using GitHub’s New Vigilant Mode]]></title>
            <link>https://medium.com/@rwbutler/protect-yourself-against-commits-forged-in-your-name-using-githubs-new-vigilant-mode-84ed00a71f94?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/84ed00a71f94</guid>
            <category><![CDATA[hacking]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Wed, 11 Aug 2021 22:48:50 GMT</pubDate>
            <atom:updated>2021-08-11T23:13:40.686Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_JjDhhBlJWH0fAc_LshG9w.jpeg" /></figure><p><em>In a previous post, </em><a href="https://medium.com/@rwbutler/signing-commits-using-gpg-on-macos-7210362d15"><em>Signing Commits Using GPG on macOS</em></a><em>, I explained why it is generally a good idea to sign your Git commits and how to do so. In essence, it’s possible for any user to generate a commit masquerading as another user if the settings for a repository allow unsigned commits.</em></p><p>Since the time of writing of that previous post, GitHub has introduced a couple of changes which enable developers to protect their repositories against commit forgery as part of <em>Branch Protection</em> as well as a new feature called <em>Vigilant Mode </em>which enables developers to protect themselves against commits being fraudulently authored in their name<em>.</em></p><h4>Branch Protection</h4><p>Branch protection isn’t a new feature — it can be found in virtually every version control hosting service these days. It’s generally enabled to protect a branch against developers accidentally pushing commits directly to that branch without the work first having been code reviewed by another developer. For example, in an organisation practising <a href="https://nvie.com/posts/a-successful-git-branching-model/">Gitflow</a>, you might protect the main and develop branches against developers directly pushing commits to those branches, and instead require work to first be code reviewed then merged from a feature branch into one of the protected branches.</p><p>When enabling branch protection in GitHub these days it’s possible to create a rule which prevents unsigned commits being pushed to a branch within the repository. To do, navigate the repository’s <em>Settings</em> tab, select <em>Branches</em> and then in the <em>Branch protection rules</em> section, click the <em>Add rule</em> button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VjcpyYWKgHR6iPCvB65haA.png" /></figure><p>From this section it’s possible to select the branch (or branches using a regular expression) to be protected by the new rule. In order to protect the specified branches against unverified commits, simply check the box next to <em>Require signed commits</em> and then save the rule.</p><h4>Vigilant Mode</h4><p>This is a relatively new feature, still in beta at the time of writing. In my previous blog post I mentioned that once you have successfully enabled commit signing, GitHub will flag to you that your (and other developer’s) commits have been signed by displaying a green <em>Verified</em> badge next to the commit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JjgD2O6dxfb66C5QIX0gKg.png" /></figure><p>What Vigilant Mode does is that it flags any commits which have <em>not</em> been signed using a yellow <em>Unverified </em>badge — so long as either the claimed author of the commit or committer have enabled vigilant mode. Notice the distinction between the author and committer here — the author is the individual who is claimed to have authored the commit who isn’t necessarily the same person as the developer who actually made the commit itself (and potentially signed the commit).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1OYB5u6DqVbW-h6na4vyFA.png" /></figure><p>Why is this potentially useful? As an author, you could enable Vigilant mode on your account and then from that point on if another developer makes a commit which claims that you were the author, then that commit will be flagged as either <em>Unverified</em> or <em>Partially verified</em>.</p><p>Partially verified commits will also display a green badge next to the commit and represent a half-way house between a commit being verified and unverified. A partially verified commit is one which has been signed, but by someone other than the claimed author of the commit i.e. someone has made a commit and signed that commit (the committer) but that person does not match the person claimed to be the author of the commit (the author).</p><p>The following table illustrates the conditions under which commits will be badged as verified, partially verified or unverified.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9YMQzKpsSASXf3iaGEWPYg.png" /></figure><p>To enable Vigilant mode, click the account button in the top-right of GitHub which displays a menu (pictured) from which you should then select <em>Settings.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/356/1*weerA6ZM32-OUqfDMsF50w.png" /></figure><p>From the account settings menu, then select <em>SSH and GPG keys</em>. This will display firstly, any SSH key which have been added to your account and then below this section, any GPG keys which have been added.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/484/1*SRgTindCCA-tQHeX1Aeb5A.png" /></figure><p>At the bottom of this screen the <em>Vigilant mode</em> section can be found. To enabled it, check the box next to <em>Flag unsigned commits as unverified</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6CcpPc8-L1huG6unG7grPQ.png" /></figure><p>From now on, any commits which claim to have been authored in your name but have not been signed will display an <em>Unverified</em> badge next to them whilst any commits which claim to have been authored in your name and which have been signed but the author could not be verified to have been you (i.e. even though the commit was signed, it was not signed using your GPG keys therefore you may not have consented to being the author) will display a <em>Partially verified</em> badge indicating the possibility that they may have been forged.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FSmtaWb2OZy6imC2Yfw5AQ.png" /></figure><h4>References</h4><ul><li><a href="https://medium.com/@rwbutler/signing-commits-using-gpg-on-macos-7210362d15">Signing Commits Using GPG on macOS</a></li><li><a href="https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule\">Managing a branch protection rule</a></li><li><a href="https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits">Displaying verification statuses for all of your commits</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=84ed00a71f94" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Get More From Codable By Implementing a JSON Key Decoding Strategy]]></title>
            <link>https://medium.com/@rwbutler/supercharge-codable-by-implementing-a-json-key-decoding-strategy-a46fedacabc4?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/a46fedacabc4</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[codable]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Fri, 21 Aug 2020 23:27:03 GMT</pubDate>
            <atom:updated>2020-09-11T09:15:47.304Z</atom:updated>
            <content:encoded><![CDATA[<h4>If you’re not using these, you’re probably writing more code than necessary</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BzI7kEVusRnhIQnS0TCecA.jpeg" /></figure><p><a href="https://developer.apple.com/documentation/swift/codable">Codable</a> is a protocol that was introduced in Swift 4 to make the serialization and deserialization of data into and out of Swift structures a breeze. This post assumes some knowledge of how to use Codable so if you’re unfamiliar with the basics it’s worth reading <a href="https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types">Encoding and Decoding Custom Types</a> first as a primer. With that being said, let’s continue.</p><p>Imagine you have the following JSON you wish to deserialize:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bbb6d813a4ca164810c93c1075f82110/href">https://medium.com/media/bbb6d813a4ca164810c93c1075f82110/href</a></iframe><p>Here’s the series of Swift structures you might write to deserialize the data:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/72b0700a83412a4ca049a0a427445c1c/href">https://medium.com/media/72b0700a83412a4ca049a0a427445c1c/href</a></iframe><p>Then using JSONDecoder decode the data as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eaacca30ccaa6ea0f64dd5263895643e/href">https://medium.com/media/eaacca30ccaa6ea0f64dd5263895643e/href</a></iframe><p>Very quickly you’ll run into the following error message:</p><p><strong>keyNotFound(CodingKeys(stringValue: “travelsOn”, intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: “vehicles”, intValue: nil), _JSONKey(stringValue: “Index 0”, intValue: 0)], debugDescription: “No value associated with key CodingKeys(stringValue: \”travelsOn\”, intValue: nil) (\”travelsOn\”).”, underlyingError: nil))</strong></p><p>That’s because the JSONDecoder is looking for the the key travelsOn in your JSON file. Unfortunately, the actual name of the key in the JSON file is travels-on.</p><p>No problem, we can solve this by implementing CodingKeys and custom conformance to the Codable protocol as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bb321c708fbaad172605d8db5af5e24d/href">https://medium.com/media/bb321c708fbaad172605d8db5af5e24d/href</a></iframe><p>We’ve implemented our own initializer and encode function in order deserialize / serialize using the JSON keys provided by our CodingKeys enum. But that’s quite a bit of extra code - had our JSON keys been in snake case as follows then it would have made life much easier:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6c3b606670a03870a1f351c9d0175ebf/href">https://medium.com/media/6c3b606670a03870a1f351c9d0175ebf/href</a></iframe><p>This is because in this instance, we could have specified a JSON key decoding strategy and avoided have to write all of that extra code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7dd50bf441a06a5715824fdae548f81f/href">https://medium.com/media/7dd50bf441a06a5715824fdae548f81f/href</a></iframe><p>This one line of code specifying a keyDecodingStrategy saved us from having to implement an initalizer, encode function and CodingKeys enum.</p><p>Unfortunately our original JSON file uses <a href="https://en.wikipedia.org/wiki/Letter_case#Special_case_styles">kebab case</a> rather than snake case keys and Foundation doesn’t provide a key decoding strategy for kebab case.</p><p>However, it does provide the <a href="https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/custom">custom key decoding strategy</a>:</p><pre>case custom(([<a href="https://developer.apple.com/documentation/swift/codingkey">CodingKey</a>]) -&gt; <a href="https://developer.apple.com/documentation/swift/codingkey">CodingKey</a>)</pre><p>This allows us the ability to implement our own custom key decoding strategy for decoding keys in whatever format we wish. All we need to do is provide an implementation of the custom key decoding strategy returning an implementation of the <a href="https://developer.apple.com/documentation/swift/codingkey">CodingKey</a> protocol (we’ve named our implementation AnyKey following the example provided in the <a href="https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/custom">Apple documentation</a>):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d7b0d31b79467d70cc4c939f0e0e33cd/href">https://medium.com/media/d7b0d31b79467d70cc4c939f0e0e33cd/href</a></iframe><p>Note that the <a href="https://developer.apple.com/documentation/swift/codingkey">CodingKey</a> protocol defines two <a href="https://developer.apple.com/swift/blog/?id=17">failable initializers</a> which must be implemented:</p><p><a href="https://developer.apple.com/documentation/swift/codingkey/2892625-init">init?(intValue: Int)</a></p><p><a href="https://developer.apple.com/documentation/swift/codingkey/2893595-init">init?(stringValue: String)</a></p><p>However we’ve provided an additional non-failable intializer to make our implementation a little more straightforward.</p><p>When implementing a custom key decoding strategy, you must provide a closure which accepts an array of type CodingKey. An array is passed to the closure rather than a single key in order to provide all of the ancestors of the current key providing some context for the current key to be decoded in case this affects the decoding strategy in any way. The current key will always be the last key in the array.</p><p>For example, when decoding the key number-of-wheels in our original JSON example, the array passed to the closure would look as follows:</p><pre>▿ 3 elements</pre><pre>- 0 : CodingKeys(stringValue: &quot;vehicles&quot;, intValue: nil)</pre><pre>▿ 1 : _JSONKey(stringValue: &quot;Index 0&quot;, intValue: 0)</pre><pre>- stringValue : &quot;Index 0&quot;</pre><pre>▿ intValue : Optional&lt;Int&gt;</pre><pre>- some : 0</pre><pre>▿ 2 : _JSONKey(stringValue: &quot;number-of-wheels&quot;, intValue: nil)</pre><pre>- stringValue : &quot;number-of-wheels&quot;</pre><pre>- intValue : nil</pre><p>The last key is the actual key we are interested in however note that the previous key in the array is a JSON key with an int value of 0. This is because the number-of-wheels key belongs to a JSON object at position 0 in the array of vehicles objects.</p><p>You might have wondered why we needed to provided an implementation of the initializer <a href="https://developer.apple.com/documentation/swift/codingkey/2892625-init">init?(intValue: Int)</a> as part of the CodingKey protocol - this is so that we are able to index arrays which aren’t usually indexed by strings.</p><p>Using our new key decoding strategy, all we need write to decode our original JSON file is:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d623b32e4865b8c7f1f240717d3a653e/href">https://medium.com/media/d623b32e4865b8c7f1f240717d3a653e/href</a></iframe><p>The new key coding strategy can be applied every time we need to use the Codable protocol to deserialize from JSON into a Swift structure from now on making it worth the initial investment of writing a custom key decoding strategy.</p><p>However, if you’d rather make use of some pre-written implementations then <a href="https://github.com/rwbutler/LetterCase">LetterCase</a> provides a number of implementations for converting keys from a number of popular letter cases including kebab case, train case and macro case amongst others:</p><ul><li>convertFromCapitalized</li><li>convertFromDashCase</li><li>convertFromKebabCase</li><li>convertFromLispCase</li><li>convertFromLowerCase</li><li>convertFromLowerCamelCase</li><li>convertFromMacroCase</li><li>convertFromScreamingSnakeCase</li><li>convertFromTrainCase</li><li>convertFromUpperCase</li><li>convertFromUpperCamelCase</li></ul><p><a href="https://github.com/rwbutler/LetterCase">LetterCase</a> is an open-source framework available under MIT license compatible with Cocoapods, Carthage and Swift Package Manager. As well as providing implementation of JSONDecoder.KeyDecodingStrategy for decoding JSON it provides implementations of JSONEncoder.KeyEncodingStrategy for going the other way and encoding JSON keys.</p><p>It also provides an implementation for converting from one letter case to another therefore you if you wanted to decode the original JSON file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bbb6d813a4ca164810c93c1075f82110/href">https://medium.com/media/bbb6d813a4ca164810c93c1075f82110/href</a></iframe><p>Into variables of macro case e.g.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d840f6f5a8d619f7d0221a2ce084145/href">https://medium.com/media/1d840f6f5a8d619f7d0221a2ce084145/href</a></iframe><p>Then this could be achieved as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a4b0d43f21e2830796bb75a57c3f520d/href">https://medium.com/media/a4b0d43f21e2830796bb75a57c3f520d/href</a></iframe><p>Hopefully this article has shown that by spending a little bit of time upfront creating a custom JSON key decoding strategy, a lot of time can be saved further down the road by reusing the same strategies each and every time you need to decode JSON keys of a different letter case.</p><p><em>LetterCase can be found open-sourced on </em><a href="https://github.com/rwbutler/LetterCase"><em>GitHub</em></a><em> under MIT license and is compatible with both </em><a href="https://cocoapods.org/pods/LetterCase"><em>Cocoapods</em></a><em>, Carthage and Swift Package Manager.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a46fedacabc4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Debounce UIButton Taps]]></title>
            <link>https://medium.com/swlh/how-to-debounce-uibutton-taps-2884dce3cd3c?source=rss-48f7d614a0e6------2</link>
            <guid isPermaLink="false">https://medium.com/p/2884dce3cd3c</guid>
            <category><![CDATA[development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Ross Butler]]></dc:creator>
            <pubDate>Wed, 01 Apr 2020 23:27:46 GMT</pubDate>
            <atom:updated>2020-04-02T20:47:28.556Z</atom:updated>
            <content:encoded><![CDATA[<h4>Does your QA like to button bash? Here’s a solution.</h4><p><em>We’re all familiar with the situation — we’ve implemented the greatest new feature our app has ever seen. It gets to QA and the first thing they do is rapidly bash the button that pushes the next UIViewController five times in a row.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RGY-JKkXWKRl0KoM3ET_0Q.jpeg" /></figure><p>Mechanical switches typically operate using two metallic contacts which when the switch is depressed come into contact within one another thus completing a circuit. These contacts are usually made of a springy metal which may bounce apart from one another once or several times before coming into final contact with one another. This effect can result in several transitions from low to high power state occurring which in a logic circuit may potentially register as multiple button taps. In order to guard against this, the signal state can be sampled at a low rate until a state change can be reliably said to have occurred. This process is known as <em>debouncing</em>.</p><p>The term has been appropriated particularly in the JavaScript and reactive programming communities to refer to the process of observing a stream of successive events until a steady state is reached at which point an event representing that steady state (i.e. the final state) is dispatched. If this idea were to be applied to a stream of button taps then the button tap event would only be dispatched at some point after the last button tap had occurred. This would typically be implemented by introducing a delay in emitting the button tap event such that the event is only emitted if no subsequent button taps were made during the delay period.</p><p>It is frequently referenced in relation to <em>throttling</em> because the two operations are somewhat similar in nature. Both involve sampling a stream of events — with throttling we are interested in emitting a sampled version (the sample rate or delay is usually passed as a parameter to the throttling function) of the original event stream. This might be implemented by emitting the first or the last button tap events every delay period for example every 0.5 seconds. With debouncing we are interested in sampling the original event stream and only emitting an event once a steady state has been reached i.e. once the value no appears to be changing.</p><p>An example of where this could be useful is the implementation of search suggestions. We might generate excessive traffic on an API if we were to send a request to a search suggestions API each time the user types a new letter into a text field however if we were to throttle the stream events we might only dispatch the request to the API every couple of seconds rather than on each letter press. If we wanted to implement pre-loading of search results such that results appeared without having to hit a done button or equivalent we might debounce the stream of input events such that we dispatch a request to the search API at some delay time after we stop receiving new input.</p><p>In terms of debouncing a stream of button taps, it wouldn’t make sense to wait until after the last button tap to dispatch the button tapped event as this would make the UI appear unresponsive. In this case, accepting the first button tap and then ignoring subsequent button taps allows the UI to remain responsive without potentially causing incorrect program behaviour through the execution of the original button tap handler code multiple times.</p><p>We can implement such behaviour on a UIButton in Swift fairly simply though an extension on UIControl as follows:</p><pre><strong>public</strong> <strong>extension</strong> UIControl {</pre><pre><strong>    @objc</strong> <strong>static</strong> <strong>var</strong> debounceDelay: Double = 0.5<br></pre><pre><strong>    @objc</strong> <strong>func</strong> debounce(delay: Double = UIControl.debounceDelay, siblings: [UIControl] = []) {</pre><pre><strong>        let</strong> buttons = [<strong>self</strong>] + siblings</pre><pre>        buttons.forEach { $0.isEnabled = <strong>false</strong> }</pre><pre><strong>        let</strong> deadline = DispatchTime.now() + delay</pre><pre>        DispatchQueue.main.asyncAfter(deadline: deadline) {</pre><pre>            buttons.forEach { $0.isEnabled = <strong>true</strong> }</pre><pre>        }</pre><pre>     }</pre><pre>}</pre><p>In the above code sample, we simply set the button’s isEnabled property to false for a period of 0.5 seconds after the initial tap and then set it back to true after the delay period elapses. We put the extension on UIControl rather than UIButton as the <a href="https://developer.apple.com/documentation/uikit/uicontrol/1618217-isenabled">isEnabled</a> property is actually defined on UIControl rather than UIButton and allows the code to be used more generally. We also set a global delay of 0.5s such that if this function is invoked within a button’s handler function then all subsequent button taps will be ignored for this period by default — however, if we wish to override this delay on a per button basis then we may pass the overridden delay value to the debounce function as a parameter.</p><p>One last thing to note is we also allow an array of sibling UIControls to be passed as a parameter to the function in case we wish to temporarily debounce other controls at the same time as the original button. For example, say that we have two buttons A and B whereby tapping button A results in UIViewController A being pushed whilst tapping button B results in UIViewController B being pushed. If we rapidly tapped button A followed by button B (which might happen if a delay in pushing UIViewController A occurred due to a delay introduced by poor signal strength and a logic error) whilst only debouncing button A then at best we would end up in a situation whereby UIViewController A would be pushed followed by UIViewController B. At worst such a scenario could result in an application crash. Therefore we allow sibling controls to be simultaneously debounced in order to avoid scenarios whereby button handlers for distinct buttons being invoked simultaneously could result in unintended interactions.</p><p>Functional Reactive Programming (FRP) frameworks such as <a href="https://github.com/ReactiveX/RxSwift">RxSwift</a> frequently provide implementations of debouncing and throttling functions including Apple’s <a href="https://developer.apple.com/documentation/combine">Combine</a> framework introduced with iOS 13. The <a href="https://developer.apple.com/documentation/combine/anypublisher/3204205-debounce">Combine implementation of debounce</a>, emits an event after the input stream has reached a steady state which would be exactly what we would need in the example above involving making an API request for search results following data entry into a text field.</p><pre>func debounce&lt;S&gt;(for dueTime: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil) -&gt; <a href="https://developer.apple.com/documentation/combine/publishers">Publishers</a>.<a href="https://developer.apple.com/documentation/combine/publishers/debounce">Debounce</a>&lt;<a href="https://developer.apple.com/documentation/combine/anypublisher">AnyPublisher</a>&lt;Output, Failure&gt;, S&gt; where S : <a href="https://developer.apple.com/documentation/combine/scheduler">Scheduler</a></pre><p>In terms of debouncing button taps however the use of this function wouldn’t be ideally suited since as mentioned above, there would be a delay between tapping the button and the UI reacting to the tap.</p><p>The Swift extension above is much more in-line with <a href="https://developer.apple.com/documentation/combine/passthroughsubject/3204657-throttle">Combine’s throttle implementation</a> which can emit either the first or last event within the specified time interval. It accepts a parameter latest which if set to false will cause the first event within a time interval to be emitted which seems more in-line with our desired behaviour of allowing the button and the UI to remain responsive whilst still ignoring subsequent button bashing.</p><pre>func throttle&lt;S&gt;(for interval: S.SchedulerTimeType.Stride, scheduler: S, latest: <a href="https://developer.apple.com/documentation/swift/bool">Bool</a>) -&gt; <a href="https://developer.apple.com/documentation/combine/publishers">Publishers</a>.<a href="https://developer.apple.com/documentation/combine/publishers/throttle">Throttle</a>&lt;<a href="https://developer.apple.com/documentation/combine/passthroughsubject">PassthroughSubject</a>&lt;Output, Failure&gt;, S&gt; where S : <a href="https://developer.apple.com/documentation/combine/scheduler">Scheduler</a></pre><h3>Summary</h3><p>By applying a concept prevalent in web development (particularly JavaScript) and reactive programming using a simple Swift extension or FRP library function we can guard against unintended consequences in our application and hopefully see fewer tickets coming back from QA as a result. 😎</p><p><em>The extension on UIControl can be found open-sourced on </em><a href="https://github.com/rwbutler/TailorSwift/blob/master/TailorSwift/Classes/UIControlAdditions.swift"><em>GitHub</em></a><em> under MIT license along with a </em><a href="https://github.com/rwbutler/TailorSwift"><em>sample app</em></a><em> in order to allow you to try it out. Note how each button tap in the sample application does not result in ‘Button tapped!’ being printed. Instead due to button taps being debounced, the text is printed no more than once every 0.5s.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2884dce3cd3c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/how-to-debounce-uibutton-taps-2884dce3cd3c">How to Debounce UIButton Taps</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>