<?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 Andrea Stevanato on Medium]]></title>
        <description><![CDATA[Stories by Andrea Stevanato on Medium]]></description>
        <link>https://medium.com/@andr3a88?source=rss-520fb01db98d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*vwZhsED4Mmyk-FjwC9c9_w@2x.jpeg</url>
            <title>Stories by Andrea Stevanato on Medium</title>
            <link>https://medium.com/@andr3a88?source=rss-520fb01db98d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 10:31:09 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@andr3a88/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[FloatingMenuButton: a simple SwiftUI FAB that expands into actions]]></title>
            <link>https://medium.com/@andr3a88/floatingmenubutton-a-simple-swiftui-fab-that-expands-into-actions-9025e4f645e7?source=rss-520fb01db98d------2</link>
            <guid isPermaLink="false">https://medium.com/p/9025e4f645e7</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Andrea Stevanato]]></dc:creator>
            <pubDate>Fri, 27 Mar 2026 14:05:51 GMT</pubDate>
            <atom:updated>2026-03-27T14:05:51.340Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EYUjn3ZULG3zHvi9ysVkVQ.png" /></figure><p>SwiftUI is powerful, but it still lacks many native components that are common in other UI frameworks. A floating action button with a compact, expandable menu is one of those missing pieces. That gap is what motivated me to build <strong>FloatingMenuButton</strong>: a lightweight, SwiftUI‑native FAB that expands into a clean list of actions.</p><h3>Why FloatingMenuButton?</h3><p>A FAB is a strong UI pattern for “primary action.” But apps often need <strong>more than one action</strong>. FloatingMenuButton lets you present a small list of actions right where the user expects it — no extra sheets or menus.</p><p>It’s designed to be:</p><ul><li><strong>Simple to integrate</strong> (Swift Package Manager)</li><li><strong>Customizable</strong> (colors, fonts, icons)</li><li><strong>SwiftUI‑native</strong></li><li><strong>Flexible</strong> (supports both SF Symbols and xcassets)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/368/0*ObYKfQ2NamM0RSZS.gif" /></figure><h3>Installation (Swift Package Manager)</h3><p>In Xcode:</p><p>File &gt; Add Package Dependencies...</p><p>Repository:</p><p><a href="https://github.com/andr3a88/FloatingMenuButton">https://github.com/andr3a88/FloatingMenuButton</a></p><h3>Quick Usage Example</h3><p>Here’s a snippet:</p><pre>import SwiftUI<br>import FloatingMenuButton<br><br>struct ContentView: View {<br>    var body: some View {<br>        ZStack(alignment: .bottomTrailing) {<br>            Color(.systemGroupedBackground).ignoresSafeArea()<br><br>            FloatingMenuButton(<br>                items: [<br>                    FloatingMenuItem(<br>                        title: &quot;Video&quot;,<br>                        subtitle: &quot;Add a video&quot;,<br>                        systemImage: &quot;video&quot;,<br>                        color: .red<br>                    ) {<br>                        // handle video<br>                    },<br>                    FloatingMenuItem(<br>                        title: &quot;Photo&quot;,<br>                        subtitle: &quot;Add a photo&quot;,<br>                        systemImage: &quot;photo&quot;,<br>                        color: .green<br>                    ) {<br>                        // handle photo<br>                    }<br>                ],<br>                fabText: &quot;Add&quot;,<br>                fabTextColor: .black,<br>                fabColor: .orange,<br>                fabIcon: &quot;plus&quot;,<br>                fabIconExpanded: &quot;xmark&quot;<br>            )<br>        }<br>    }<br>}<br></pre><p><em>This snippet shows a basic FAB with two actions, custom colors, and SF Symbol icons .</em></p><h3>Use xcassets Icons Too</h3><p>If you prefer custom icons from your asset catalog:</p><pre>FloatingMenuItem(<br>    title: &quot;Profile&quot;,<br>    subtitle: &quot;From xcassets&quot;,<br>    assetImage: &quot;CustomIcon&quot;,<br>    color: .purple<br>) {<br>    // handle tap<br>}</pre><p>You can even swap the FAB icon with an asset:</p><pre>FloatingMenuButton(<br>    items: [...],<br>    fabColor: .orange,<br>    fabAssetIconExpanded: &quot;CustomIcon&quot;<br>)</pre><h3>Wrap‑Up</h3><p><strong><em>FloatingMenuButton</em></strong> keeps things small, SwiftUI‑native, and easy to customize — ideal when you want a FAB with multiple actions without the overhead of a full menu system.</p><p>If you want to try it or contribute:</p><p><a href="https://github.com/andr3a88/FloatingMenuButton">GitHub - andr3a88/FloatingMenuButton: A SwiftUI Floating Action Button that expands into a menu with multiple items when tapped.</a></p><p>An example app is avaiable in the repo:</p><p><a href="https://github.com/andr3a88/FloatingMenuButton/tree/main/Example">github.com/andr3a88/FloatingMenuButton/tree/main/Example</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/552/1*zlT2jMfTPe4vt-W2IVwUYQ.png" /></figure><p>You can connect me on:</p><p><strong>X (Twitter) — @</strong><a href="https://twitter.com/andrea_steva"><strong>andrea_steva</strong></a></p><p><strong>Linkedin — </strong><a href="https://www.linkedin.com/in/stevanato-andrea/">https://www.linkedin.com/in/stevanato-andrea/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9025e4f645e7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Decode ISO 8601 date format with fractional seconds in Swift]]></title>
            <link>https://medium.com/@andr3a88/decode-iso-8601-date-format-with-fractional-seconds-in-swift-3c6750491bdd?source=rss-520fb01db98d------2</link>
            <guid isPermaLink="false">https://medium.com/p/3c6750491bdd</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[iso8601dateformatter]]></category>
            <category><![CDATA[swift-programming]]></category>
            <category><![CDATA[jsondecoder]]></category>
            <category><![CDATA[json-parsing]]></category>
            <dc:creator><![CDATA[Andrea Stevanato]]></dc:creator>
            <pubDate>Wed, 24 Sep 2025 08:32:33 GMT</pubDate>
            <atom:updated>2025-09-24T08:32:33.501Z</atom:updated>
            <content:encoded><![CDATA[<h4>Use a cascade approach to manage date-time with fractional and non fractional seconds</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/1*31CkIVSSVrSnal7NZPew_w.png" /></figure><h3>The Problem</h3><p>Several time we have to deal with different representation of date and time, depending on the service or API we are integrating.</p><p>A special case occurs when the same API or service returns an array of objects with ISO 8601 dates that use different representations for the fractional seconds.</p><h4>ISO 8601</h4><blockquote><strong>ISO 8601</strong> is an <a href="https://en.wikipedia.org/wiki/International_standard">international standard</a> covering the worldwide exchange and communication of <a href="https://en.wikipedia.org/wiki/Calendar_date">date</a> and <a href="https://en.wikipedia.org/wiki/Time">time</a>-related data</blockquote><p>Below the different cases of fractional seconds in ISO 8601:</p><pre>2024–04–18T21:54:22Z // not fractional<br>2024–04–18T21:54:22.000Z // fractional milliseconds<br>2024–04–18T21:54:22.000000Z // fractional nanoseconds</pre><p>Using <em>JSONDecoder</em> with <em>dateDecodingStrategy = .iso8601</em> allows you to enable option <em>fractional seconds</em>, but decoding will fail with the error “Expected date string to be ISO8601-formatted” if the JSON contains <strong>both fractional and non-fractional dates</strong>.</p><pre>&quot;Decoding failed: dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: &quot;events&quot;, intValue: nil), _CodingKey(stringValue: &quot;Index 1&quot;, intValue: 1)], debugDescription: &quot;Expected date string to be ISO8601-formatted.&quot;, underlyingError: nil))</pre><h3>Solution — A cascade approach</h3><p>A first idea is to ask whoever supplies us the API to normalize the format of the seconds using a single approach to represents the seconds, but as we well know, most of the time we have to adapt our code and find a solution.</p><p>Our goal is to use a single <em>JSONDecoder </em>to handle a JSON response containing multiple representations for the “second” component of the date:</p><pre>{<br>    &quot;events&quot;: [<br>        {<br>            &quot;id&quot;: 1,<br>            &quot;timestamp&quot;: &quot;2025-09-05T13:25:41Z&quot;<br>        },<br>        {<br>            &quot;id&quot;: 2,<br>            &quot;timestamp&quot;: &quot;2025-09-01T09:53:38.2492Z&quot;<br>        },<br>        {<br>            &quot;id&quot;: 3,<br>            &quot;timestamp&quot;: &quot;2025-09-18T21:54:22.923456Z&quot;<br>        }<br>    ]<br>}</pre><p>The idea is to define a cascade decoding strategy, trying multiple <strong>ISO8601DateFormatter</strong> configurations until one matches.</p><p>In the extensions below, we define two <strong>ISO8601DateFormatters</strong>: one that supports fractional seconds and one that doesn’t. This allows us to handle multiple date formats consistently.</p><p>We then create a custom<strong> JSONDecoder.DateDecodingStrategy</strong> called <em>customISO8601Cascade</em>, which tries to parse a date string first with fractional seconds and, if that fails, without. If neither format matches, it throws a decoding error.</p><pre>extension Formatter {<br><br>    static let iso8601: ISO8601DateFormatter = {<br>        let formatter = ISO8601DateFormatter()<br>        // This options is the default (can be omitted but but I prefer to make it explicit)<br>        formatter.formatOptions = [.withInternetDateTime]<br>        return formatter<br>    }()<br><br>    static let iso8601withFractionalSeconds: ISO8601DateFormatter = {<br>        let formatter = ISO8601DateFormatter()<br>        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]<br>        return formatter<br>    }()<br>}<br><br>// Custom date decoding strategy<br>extension JSONDecoder.DateDecodingStrategy {<br>    static let customISO8601Cascade = custom {<br>        let container = try $0.singleValueContainer()<br>        let string = try container.decode(String.self)<br>        if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) {<br>            return date<br>        }<br>        throw DecodingError.dataCorruptedError(in: container, debugDescription: &quot;Invalid date: \(string)&quot;)<br>    }<br>}</pre><p>After that, we can create a <em>JSONDecoder </em>and use the newly created date decoding strategy, and decode our objects.</p><pre>let decoder = JSONDecoder()<br>decoder.dateDecodingStrategy = .customISO8601<br><br>let response = try decoder.decode(MyResponse.self, from: data)</pre><p>With this approach you can handle both fractional and non-fractional seconds using <strong>a single decoder, </strong>without encountering indecoding errors.</p><h3>Playground</h3><p>Want to try this out directly in Xcode?</p><p>You can find below <strong>Xcode Playground</strong> with a simple example (tested with Xcode 16.4)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/50e4df81252977379ded14f8b18198d5/href">https://medium.com/media/50e4df81252977379ded14f8b18198d5/href</a></iframe><h3>Conclusion</h3><p>When APIs don’t follow a consistent ISO 8601 format, instead of multiplying decoders or adding manual parsing, you can define a <strong>cascade strategy</strong>.</p><p>This makes your code more resilient to mixed formats while keeping it clean and reusable.</p><p>Let me know if you found the article interesting. Thanks for reading.</p><p>You can connect me on:</p><p><strong>X (Twitter) — @</strong><a href="https://twitter.com/andrea_steva"><strong>andrea_steva</strong></a></p><p><strong>Linkedin — </strong><a href="https://www.linkedin.com/in/stevanato-andrea/">https://www.linkedin.com/in/stevanato-andrea/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3c6750491bdd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GraphQL subscriptions with Apollo iOS and AWS AppSync]]></title>
            <link>https://medium.com/@andr3a88/graphql-subscriptions-with-apollo-ios-and-aws-appsync-22dc949c80c5?source=rss-520fb01db98d------2</link>
            <guid isPermaLink="false">https://medium.com/p/22dc949c80c5</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[grapqhql]]></category>
            <category><![CDATA[apollo-client]]></category>
            <category><![CDATA[aws-appsync]]></category>
            <category><![CDATA[apollo]]></category>
            <dc:creator><![CDATA[Andrea Stevanato]]></dc:creator>
            <pubDate>Tue, 10 Sep 2024 08:02:04 GMT</pubDate>
            <atom:updated>2024-09-10T08:02:04.909Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*14ZFTQMr_X1S2u6y" /><figcaption>Photo by <a href="https://unsplash.com/@roisolomon?utm_source=medium&amp;utm_medium=referral">Roi Solomon</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Setting up GraphQL subscriptions in an iOS app can be challenging, especially when dealing with WebSocket connections and authentication.</p><p>In this article, we’ll walkthrough how to configure <strong>Apollo iOS with an <em>AWS AppSync</em></strong> GraphQL server (<em>Amazon Cognito user pools authorization mode</em>) to handle subscriptions.</p><h3>Create the WebSocketTransport</h3><p>The first essential step is to set up a WebSocketTransport with proper authentication header. The code below demonstrates how to create a WebSocket client that connects to the AppSync endpoint.</p><pre>func getWebSocketTransport() -&gt; WebSocketTransport {<br>    let webSocketClient = WebSocket(url: URL(stringLiteral: &quot;graphQlRealTimeUrl&quot;), protocol: .graphql_ws)<br>    webSocketClient.request.setValue(&quot;accessToken&quot;, forHTTPHeaderField: &quot;Authorization&quot;)<br><br>    // The custom body creator is required for AppSync&#39;s protocol<br>    let requestBody = AppSyncRequestBodyCreator()<br>    let config = WebSocketTransport.Configuration(reconnectionInterval: 1.0,<br>                                                  requestBodyCreator: requestBody)<br>    let webSocketTransport = WebSocketTransport(websocket: webSocketClient, config: config)<br>    webSocketTransport.delegate = self<br>    return webSocketTransport<br>}</pre><p>The connection is initialized with the AppSync real-time endpoint, .graphql_ws protocol, and the Authentication header (if you need) is set on the request with the user access token.</p><p>The Authorization header is crucial and must be configured according to the AppSync API’s authorization mode, whether it’s API key, AWS IAM, Cognito User Pools, or OpenID Connect.</p><p>A custom request body creator, AppSyncRequestBodyCreator, is also required to generate the required payload based on AppSync’s protocol specifications, ensuring the messages are correctly authenticated.</p><blockquote>For additional details on configuring your specific case, you can find many useful insights in the official AWS AppSync <a href="https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#appsynclong-real-time-websocket-client-implementation-guide-for-graphql-subscriptions">documentation</a>, that provides in-depth instructions on managing real-time WebSocket operations, including details on endpoints, authentication, and the necessary request formats for correctly setting up the WebSocket client with AppSync.</blockquote><h3>Request body creator: <em>AppSyncRequestBodyCreator</em></h3><p>AppSync’s server protocol is denotated asgrapql-ws on Apollo, and the generic payload using <strong>Amazon Cognito user pools</strong> is the following:</p><pre>{<br>    &quot;id&quot;: &quot;subscriptionId&quot;,<br>    &quot;type&quot;: &quot;start&quot;,<br>    &quot;payload&quot;:<br>    {<br>        &quot;data&quot;:<br>        {<br>            &quot;query&quot;: &quot;query in string format&quot;,<br>            &quot;variables&quot;: &quot;variables&quot;<br>        },<br>        &quot;extensions&quot;:<br>        {<br>            &quot;authorization&quot;:<br>            {<br>                &quot;host&quot;: &quot;host&quot;,<br>                &quot;Authorization&quot;: &quot;accessToken&quot;<br>            }<br>        }<br>    }<br>}</pre><p>In details we can see:</p><ul><li>The id of the subscription. This id must be unique for each subscription, otherwise the server returns an error indicating that the subscription id is duplicated. <em>This field is managed by Apollo.</em></li><li>The type of operation send or received: can be start , stop , data , connection_init , connection_ackor start_ack. <em>This field is managed by Apollo.</em></li><li>The payload depending on the operation type, contains the information relevant to the subscription. <em>This field is managed by Apollo.</em></li><li>The data object is a stringified JSON object that contains a GraphQL query and variables . <em>This field is setted by our custom RequestBodyCreator.</em></li><li>The extensions object contains anauthorization dictionary which holds the fields designated for authentication: host where you have to specificy your GraphQL endpoint (without https:// and /graphql) and Authorization with your access token. The Authorization key may vary depending on your API authorization mode. <em>Also these fields aresetted by our custom RequestBodyCreator.</em></li></ul><blockquote>FYI: the type field is the only required in the message; the id and payload fields are optional.</blockquote><p>Here is an example of how you can create the AppSyncRequestBodyCreator:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/73feb07e67cc9cdeba6feb8595bc7c52/href">https://medium.com/media/73feb07e67cc9cdeba6feb8595bc7c52/href</a></iframe><p>Basically you have to subclass RequestBodyCreator and implement the method <em>requestBody(for:sendQueryDocument:autoPersistQuery:). </em>This method is called upon the ApolloClient creation and ensures that the request body conforms to <strong><em>AppSync</em></strong> protocol</p><p>To better understand the message body, I leave you below an example of the payload message sent by the WebSocket to <em>start</em> a subscription.</p><pre>{<br>  &quot;id&quot;: &quot;1&quot;,<br>  &quot;payload&quot;: {<br>    &quot;operationName&quot;: &quot;userOperation&quot;,<br>    &quot;data&quot;: &quot;{\&quot;query\&quot;: \&quot;subscription onUserChangedSubscription { onUserChanged { __typename userId firstName lastName }}\&quot; }&quot;,<br>    &quot;extensions&quot;: {<br>      &quot;authorization&quot;: {<br>        &quot;Authorization&quot;: &quot;&lt;accessToken&gt;&quot;,<br>        &quot;host&quot;: &quot;&lt;host&gt;&quot;<br>      }<br>    }<br>  },<br>  &quot;type&quot;: &quot;start&quot;<br>}</pre><h3>Setup an Apollo Client with SplitNetworkTransport</h3><p>This step is quite straightforward, basically you need to create an Apollo client using aSplitNetworkTransport. I leave you also the official Apollo iOS <a href="https://www.apollographql.com/docs/ios/networking/client-creation/#network-transport">documentation</a>.</p><p>A <strong>SplitNetworkTransport</strong> is a network transport layer that allows you to handle both HTTP and WebSocket connections simultaneously. It routes <em>queries</em> and <em>mutations</em> through the <em>HttpNetworkTransport</em> for standard requests, while subscriptions are managed through the <em>WebSocketNetworkTransport</em>.</p><p>To set it up, create a SplitNetworkTransport passing the WebSocketTransport configured earlier and a generic <em>HttpNetworkTransport</em> to handle the HTTP connections.</p><p>If you need to authenticate your requests for the <em>HttpNetworkTransport</em>, you can create a NetworkInterceptorProvider that conform to DefaultInterceptorProvider, an object that allows you to add an specific headers to each request, for more details follow this <a href="https://www.apollographql.com/docs/ios/tutorial/tutorial-authenticate-operations">steps</a>.</p><pre>...<br><br>func createApolloClient() -&gt; ApolloClient {<br>    let cache = InMemoryNormalizedCache()<br>    let store = ApolloStore(cache: cache)<br><br>    logger.debug(&quot;Create HTTP and WebSocket transports&quot;)<br>    let splitNetworkTransport = SplitNetworkTransport(uploadingNetworkTransport: getHttpNetworkTransport(store: store),<br>                                                      webSocketNetworkTransport: getWebSocketTransport())<br>    return ApolloClient(networkTransport: splitNetworkTransport, store: store)<br>}<br><br>func getHttpNetworkTransport(store: ApolloStore) -&gt; RequestChainNetworkTransport {<br>    let client = URLSessionClient()<br>    // Intercept the requests and attacch the Authorization header<br>    let provider = NetworkInterceptorProvider(client: client, store: store)<br><br>    let httpTransportUrl = URL(string: graphQlUrl)!<br>    let httpTransport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: httpTransportUrl)<br>    return httpTransport<br>}</pre><h3>Finally, create a subscription</h3><p>To create a subscription use ApolloClient <em>subscribe</em> method, passing in your subscription object and handling the result with a closure.</p><p>Below a code snippet that demonstrates how to initiate a subscription and process incoming data. You can cancel the subscription using subscription?.cancel() .</p><pre>import ApolloWebSocket<br><br>var subscription: Cancellable?<br><br>func startSubscription() {<br>    self.subscription = apolloClient.subscribe(subscription: YourSubscription()) { [weak self] result in<br>        switch result {<br>        case .success(let graphQLResult):<br>            print(&quot;Subscription data \(graphQLResult.data)<br>        case .failure(let error):<br>            print(&quot;Subscription error \(error)<br>        }<br>    }<br>}<br><br>deinit {<br>  self.subscription?.cancel()<br>}</pre><h3>Conclusion</h3><p>I hope you found this article both useful and insightful. For further details, please refer to the AWS AppSync documentation and the <a href="https://github.com/apollographql/apollo-ios">Apollo iOS</a> repository. Additionally, the Apollo iOS Discord channel is a great resource for support and community discussion. If you have any questions or need further assistance, don’t hesitate to reach out to these resources.</p><p>Feel free to leave a comment and share your thoughts or any alternative solutions you’ve discovered.</p><p>Happy coding!</p><h4>Resources:</h4><ul><li><a href="https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#real-time-websocket-operation">Building a real-time WebSocket client in AWS AppSync</a></li><li><a href="https://github.com/lingfengmarskey/NetworkSample">https://github.com/lingfengmarskey/NetworkSample</a></li><li><a href="https://www.apollographql.com/docs/ios">https://www.apollographql.com/docs/ios</a></li><li><a href="https://community.apollographql.com/">https://community.apollographql.com/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22dc949c80c5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JSON Encode an array of different types in Swift]]></title>
            <link>https://medium.com/@andr3a88/json-encode-an-array-of-different-types-in-swift-dd8510aa8cfc?source=rss-520fb01db98d------2</link>
            <guid isPermaLink="false">https://medium.com/p/dd8510aa8cfc</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift-programming]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[json]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Andrea Stevanato]]></dc:creator>
            <pubDate>Fri, 19 Apr 2024 04:17:11 GMT</pubDate>
            <atom:updated>2024-04-19T04:17:11.956Z</atom:updated>
            <content:encoded><![CDATA[<p>In the context of mobile development, often we deal with complex backend APIs that require an extra work than the typical serialisation and deserialisation usingJSONDecoder and JSONEncoder .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zzdp8sy3pXbLfHfRWkTUIA.jpeg" /></figure><p>In this article, we’ll tackle a specific scenario where we have content in the form of an array but with <strong>different types</strong> in it.</p><p>Let’s say we have to send to our backend an <em>http</em> <em>request</em> with the following json array as <strong>body, </strong>which represents a list of different types of vehicles:</p><pre>[<br>    {<br>        &quot;name&quot;: &quot;Petrol Car&quot;,<br>        &quot;manufacturer&quot;: &quot;Ford&quot;,<br>        &quot;petrolTankCapacity&quot;: 50<br>    },<br>    {<br>        &quot;name&quot;: &quot;Electric Car&quot;,<br>        &quot;manufacturer&quot;: &quot;Tesla&quot;,<br>        &quot;batteryCapacity&quot;: 75<br>    },<br>    {<br>        &quot;name&quot;: &quot;Gas Car&quot;,<br>        &quot;manufacturer&quot;: &quot;Renault&quot;,<br>        &quot;gasTankCapacity&quot;: 50<br>    },<br>    {<br>        &quot;manufacturer&quot;: &quot;Ferrari&quot;,<br>        &quot;name&quot;: &quot;SF-24&quot;,<br>        &quot;petrolTankCapacity&quot;: 90,<br>        &quot;batteryCapacity&quot;: 40,<br>        &quot;frontWing&quot;: true,<br>        &quot;rearWing&quot;: true,<br>    }<br>]</pre><p>As you notice each objects has different “<em>tank</em>” type or additional custom properties (<em>frontWing </em>and<em> rearWing</em>)<em> </em>. At first glance you might be tempted to create a single struct MixedVehicles containing properties name, manufacturer, and the other properties as optional. Then use JSONEncoder().encode(mixedVehiclesArray) to perform the encoding of the the array [MixedVehicles] .</p><pre>// AVOID THIS APPROACH<br>struct MixedVehicles: Encodable {<br>    let name: String<br>    let manufacturer: String<br>    let petrolTankCapacity: Int?<br>    let gasTankCapacity: Int?<br>    let batteryCapacity: Int?<br>    let frontWing: Bool?<br>    let rearWing: Bool?<br>    let anytherNotCommonProperties: String?<br>    ...<br>}</pre><p>This approach works, but it will lead to design problems as the number of objects to handle grows. In fact, each new object to manage, cause MixedVehicles structure tend to become more cumbersome, increasing the number of optional properties, and making impossible to manage the object in the long run.</p><p>Now let’s see how we can solve the problem.</p><h3>Type erase Encodable with <em>AnyEncodable</em></h3><p>The strategy to overcome MixedVehicles approach is to use an AnyEncodable helper structure that acts as a <em>type eraser</em> for our concrete (<em>Encodable</em>) objects.</p><p>For example defining AnyEncodable as below,</p><pre>/// A type eraser to erase the concrete (encodable) types<br>struct AnyEncodable: Encodable {<br>    private let encodeClosure: (Encoder) throws -&gt; Void<br><br>    init&lt;T: Encodable&gt;(_ value: T) {<br>        encodeClosure = { encoder in<br>            try value.encode(to: encoder)<br>        }<br>    }<br><br>    func encode(to encoder: Encoder) throws {<br>        try encodeClosure(encoder)<br>    }<br>}</pre><p>let us use an array [AnyEncodable] to wrap all our objects that conform toEncodable.</p><p>Let’s continue with our example. Now we have to define the different concrete objects for the <em>Vehicles:</em></p><pre>// Define a common protocol for all encodable objects<br>protocol Vehicle: Encodable {}<br><br>struct PetrolCar: Vehicle {<br>    let name: String = &quot;Petrol Car&quot;<br>    let manufacturer: String = &quot;Ford&quot;<br>    let petrolTankCapacity: Int = 50<br>}<br><br>struct ElectricCar: Vehicle {<br>    let name: String = &quot;Electric Car&quot;<br>    let manufacturer: String = &quot;Tesla&quot;<br>    let batteryCapacity: Int = 75<br>}<br><br>struct GasCar: Vehicle {<br>    let name: String = &quot;Gas Car&quot;<br>    let manufacturer: String = &quot;Renault&quot;<br>    let gasTankCapacity: Int = 55<br>}<br><br>struct F1Car: Vehicle {<br>    let name: String = &quot;SF-24&quot;<br>    let manufacturer: String = &quot;Ferrari&quot;<br>    let petrolTankCapacity: Int = 50<br>    let batteryCapacity: Int = 10<br>    let frontWing: Bool = true<br>    let rearWing: Bool = true<br>}</pre><p>The next step is to create an array of <em>type-erased</em> encodable objects:</p><pre>let vehicles: [AnyEncodable] = [<br>    AnyEncodable(PetrolCar()),<br>    AnyEncodable(ElectricCar()),<br>    AnyEncodable(GasCar()),<br>    AnyEncodable(F1Car())<br>]</pre><p>Now we can encode our data using JSONEncoder:</p><pre>do {<br>    let jsonData = try JSONEncoder().encode(objects)<br>    print(String(decoding: jsonData, as: UTF8.self))<br>} catch {<br>    print(&quot;Error encoding JSON: \(error)&quot;)<br>}<br><br>// OUTPUT:<br>// [{&quot;petrolTankCapacity&quot;:50,&quot;manufacturer&quot;:&quot;Ford&quot;,&quot;name&quot;:&quot;Petrol Car&quot;},{&quot;name&quot;:&quot;Electric Car&quot;,&quot;manufacturer&quot;:&quot;Tesla&quot;,&quot;batteryCapacity&quot;:75},{&quot;name&quot;:&quot;Gas Car&quot;,&quot;manufacturer&quot;:&quot;Renault&quot;,&quot;gasTankCapacity&quot;:55},{&quot;name&quot;:&quot;SF-24&quot;,&quot;manufacturer&quot;:&quot;Ferrari&quot;,&quot;batteryCapacity&quot;:10,&quot;petrolTankCapacity&quot;:50,&quot;frontWing&quot;:true,&quot;rearWing&quot;:true}]</pre><p>As you can see, as output we have our jsonData ready to be used on a URLRequest .</p><h3>Playground</h3><p>I leave below a playground to let you test the code in Xcode.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/060587ee8f005b113281eb358344dc6e/href">https://medium.com/media/060587ee8f005b113281eb358344dc6e/href</a></iframe><p>Let me know if you found the article interesting or if you know different approach.</p><p>Thanks for reading.</p><p>You can connect me on:</p><p><strong>X (Twitter)— @</strong><a href="https://twitter.com/andrea_steva"><strong>andrea_steva</strong></a></p><p><strong>Linkedin — </strong><a href="https://www.linkedin.com/in/stevanato-andrea/">https://www.linkedin.com/in/stevanato-andrea/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd8510aa8cfc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improve your code reviews using Danger Swift on Bitrise CI]]></title>
            <link>https://medium.com/@andr3a88/improve-your-code-reviews-using-danger-swift-on-bitrise-ci-e05c90a03244?source=rss-520fb01db98d------2</link>
            <guid isPermaLink="false">https://medium.com/p/e05c90a03244</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[bitrise]]></category>
            <category><![CDATA[danger]]></category>
            <dc:creator><![CDATA[Andrea Stevanato]]></dc:creator>
            <pubDate>Wed, 06 Nov 2019 16:55:26 GMT</pubDate>
            <atom:updated>2019-11-07T10:05:51.358Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/847/1*0XSEFaDAEnimPY5Di8Nk5g.png" /><figcaption>Danger Swift</figcaption></figure><p>This article is intended to help you configure <a href="https://danger.systems/swift/">Danger Swift</a> with <a href="https://www.bitrise.io/">Bitrise</a>. The main goal is to automating the simplest and most repetitive steps of a code review, allowing you and your team to focus on harder problems.</p><p>You can <a href="https://danger.systems/swift/guides/getting_started.html">integrate Danger</a> into your project using the most common CI services, for a complete list visit the official “<a href="https://danger.systems/swift/guides/getting_started.html#setting-up-danger-to-run-on-your-ci">Getting started</a>” section.</p><p>In my case I used <em>Bitrise</em>, a service I use daily to build and distribute iOS applications, the same procedure may be applied to any other CI service (Teamcity, Jenkins, Travis, etc.)</p><blockquote>Note: for simplicity I assume that a basic workflow on Bitrise has already been configured for the project in use.</blockquote><h3>What is Danger Swift?</h3><p><a href="https://danger.systems/swift/">Danger Swift</a> is an automation tool that runs after your CI to enforce conventions in code reviews. Danger leaving messages inside your PRs based on rules that you write in Swift.</p><p>For example you can write rules that:</p><ul><li>Enforce CHANGELOGs</li><li>Enforce links to JIRA in PRs bodies</li><li>Enforce using labels, reviewers and assignee and any other PR property</li><li>Look out for common anti-patterns</li><li>Highlight interesting build artefacts (additions, deletions, files changed)</li><li>Verify the presence of unit tests or any other specific pattern inside your source files</li></ul><p>The core of Danger is the Dangerfile, named by conventions Dangerfile.swift, that contains a collection of rules specific to your project written in Swift.</p><h3>Danger Installation</h3><p>Danger can be <a href="https://danger.systems/swift/guides/getting_started.html#installation">installed</a> via Swift Package Manager or via <a href="https://brew.sh">Brew</a>. In my case I used Brew:</p><pre>brew install danger/tap/danger-swift</pre><p>As mentioned earlier the rules must be specified in a file Dangerfile.swift, these two commands are useful during the development for creating and testing our rules. The first is:</p><pre>danger-swift edit</pre><p>that create a standalone Xcode project where you can edit your Dangerfile.swift. The second is:</p><pre>danger-swift pr <a href="https://github.com/DoveConviene/shopfully-engage-sdk-ios/pull/42">https://github.com/your_account/your_project/pull/1</a></pre><p>that command allows to test your rules directly from command line without necessarily having to push code into the repository. You should only have an existing PR to link.</p><h3>Create a personal access token on Github</h3><p>To allow Danger to work properly on Bitrise, i.e. add comments in your pull requests must be generated a <a href="https://github.com/settings/tokens"><em>personal access token</em></a> from your Github account.</p><blockquote>Optionally: you could create a bot account (a normal Github user account) used specifically for this purpose.</blockquote><p>In the creation form you need to select all the repo scopes like the image below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/765/1*AiA0kccC3ptFOFHTthY5qw.png" /><figcaption>Github -&gt; Profile-&gt; Settings -&gt; Developer settings -&gt; Personal access token</figcaption></figure><p>After creation keep the access token safe, if you lose it, you’ll have to recreate it again.</p><h3>Configure Bitrise CI</h3><p>If you are familiar with Bitrise you will certainly know the concept of workflow, that is the flow of operations that are carried out during a build.</p><p>First of all you need to add a new <em>script</em> step at the end of your build process:</p><figure><img alt="Script step" src="https://cdn-images-1.medium.com/max/804/1*z4iVOfZV6FlNObQnSWGYjg.png" /></figure><p>the script installs Danger Swift and executes it. A possible implementation is given below:</p><pre>#!/usr/bin/env bash<br># fail if any commands fails<br>set -e</pre><pre>echo &quot;################### DANGER ######################&quot;</pre><pre>echo &quot;Install Danger&quot;<br>brew install danger/tap/danger-swift</pre><pre>echo &quot;Run danger&quot;<br>danger-swift ci</pre><pre>echo &quot;#################################################&quot;</pre><p>The next step is to allow Danger access your PRs by setting the <em>Github personal token</em> you created earlier. In the workflow editor, select the tab <em>Secrets </em>and add a new environment variable named <em>DANGER_GITHUB_API_TOKEN </em>and valued with the <em>Github personal token</em>.</p><p>Remember to <strong>enable</strong> the options “<em>Expose for Pull Requests?” </em>like the image below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*ScQ1Q1vaJZEnQ1jqM_P3aA.png" /><figcaption>Bitrise workflow editor, “Secrets” tab</figcaption></figure><h3>Create your own rules</h3><p>Finally you are ready to import Danger into your project. Create Dangerfile.swift on the root of your Xcode project. Below is an example with some simple rules.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/062eb658d39dff67e125d5e476928e0d/href">https://medium.com/media/062eb658d39dff67e125d5e476928e0d/href</a></iframe><h3>Give it a try!</h3><p>It’s time to try out what’s been done and make sure it all works. First of all create a new branch and push your danger file. Make an empty PR that does not respect the rules you wrote, for example a short title or an empty description. When the build on Bitrise is finished you should have an additional check regarding Danger, like the image below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/761/1*JMRARG4dB1ceyR8BvhWFBA.png" /></figure><p>In addition, a message should be added to the PR with output generated by Danger:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/1*3o_Ksntscsj1-w4VEmKKXg.png" /><figcaption>Generated PR message from Danger Swift</figcaption></figure><h3>Next step: Plugins!</h3><p>Danger is designed to be extended through plugins.</p><p>An extension I would like to try and which is now fully integrated in Danger is <strong>Swiftlint. </strong>More information <a href="https://danger.systems/swift/tutorials/ios_app.html#swiftlint-for-the-greater-good">here</a>.</p><p>Another interesting plugin is <a href="https://github.com/f-meloni/danger-swift-coverage"><strong>danger-swift-coverage</strong></a> which helps to display Xcode’s code coverage files in a friendly way.</p><p><a href="https://github.com/f-meloni/danger-swift-coverage">f-meloni/danger-swift-coverage</a></p><h3>Conclusion</h3><p>I consider Danger an excellent tool to give fast feedback to the team and increase productivity by leaving repetitive task to automation. The integration of this tool should be gradual and the rules created in agreement with the members of the team, create positive rules and celebrate the achievements with 🎉 and 👍.</p><p>You can get more information reading the <a href="https://danger.systems/swift/">official documentation</a> and find other examples of <em>Dangerfile</em> at these links:</p><ul><li><a href="https://github.com/danger/awesome-danger">danger/awesome-danger</a></li><li><a href="https://github.com/artsy/eigen/blob/master/Dangerfile.swift">artsy/eigen</a></li><li><a href="https://github.com/Moya/Moya/blob/master/Dangerfile.swift">Moya/Moya</a></li></ul><p>Let me know if you found the article interesting. Thanks for reading.</p><p>You can connect me on:</p><p><strong>Twitter — @</strong><a href="https://twitter.com/andrea_steva"><strong>andrea_steva</strong></a></p><p><strong>Linkedin— </strong><a href="https://www.linkedin.com/in/stevanato-andrea/">https://www.linkedin.com/in/stevanato-andrea/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e05c90a03244" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>