<?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[AVIV Product &amp; Tech Blog - Medium]]></title>
        <description><![CDATA[Learn more about how tech, product and design people at AVIV build and operate the systems and products to unlock everyone’s perfect place. - Medium]]></description>
        <link>https://medium.com/aviv-product-tech-blog?source=rss----89805f38d8fb---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>AVIV Product &amp;amp; Tech Blog - Medium</title>
            <link>https://medium.com/aviv-product-tech-blog?source=rss----89805f38d8fb---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 16:26:48 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/aviv-product-tech-blog" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Modernize without breaking everything — the Strangler Fig pattern in action!]]></title>
            <link>https://medium.com/aviv-product-tech-blog/modernize-without-breaking-everything-the-strangler-fig-pattern-in-action-9d32a468b707?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/9d32a468b707</guid>
            <category><![CDATA[cloud-migration]]></category>
            <category><![CDATA[strangler-fig-pattern]]></category>
            <category><![CDATA[legacy]]></category>
            <dc:creator><![CDATA[Félix Vonthron]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 11:21:22 GMT</pubDate>
            <atom:updated>2025-11-24T11:21:05.972Z</atom:updated>
            <content:encoded><![CDATA[<h3>Modernize without breaking everything — the Strangler Fig pattern in action!</h3><h3>The Strangler Fig pattern 🧬</h3><h4>What is it? 🤔</h4><p>This pattern is kind of a <strong>proxy</strong>: <strong>it intercepts clients requests, map them and redirects them</strong> from a legacy dependency (application or service) to its newer version.</p><figure><img alt="Client application call the Strangler Fig Proxy which redirects requests either to the legacy dependency or to the new dependency" src="https://cdn-images-1.medium.com/max/361/1*C3lzvLVSC6d5SSsFoFyukg.jpeg" /><figcaption>Strangler Fig global view</figcaption></figure><h4>When should you use it? ⌚</h4><ol><li>You need to <strong>migrate a service</strong> from one version to another</li><li>You have a <strong>client application which is complicated to update</strong> (too complex, too big, knowledge lacking, etc.)</li><li>You want to <strong>avoid disruptions</strong> and <strong>reduce the risks</strong></li></ol><h4>When could it goes wrong? 😱</h4><ol><li>It’s <strong>impossible to map</strong> a Legacy request or a New response</li><li>It’s <strong>impossible to intercept</strong> all clients requests</li><li>The New dependency <strong>haven’t access to the same resources</strong> as the Legacy dependency (as different or not synchronized data stores for example)</li></ol><h3>Our use case at AVIV 🔍</h3><h4>Who is AVIV? 🤔</h4><p>The AVIV Group is <strong>one of the world’s largest digital real estate tech companies</strong>, present across our key markets in France, Belgium and Germany. It includes some leading brands, like <a href="https://www.seloger.com/"><strong>SeLoger</strong></a> 🇫🇷, <a href="https://www.immowelt.de/"><strong>Immowelt</strong></a> 🇩🇪, <a href="https://www.immoweb.be/"><strong>Immoweb</strong></a> 🇧🇪, <a href="https://www.meilleursagents.com/"><strong>Meilleurs</strong> <strong>Agents</strong></a> 🇫🇷 or also <a href="https://www.homeday.de/"><strong>Homeday</strong></a> 🇩🇪.</p><p><strong>Our company’s mission is to unlock everyone’s perfect place.</strong></p><h4>Our use case 🧐</h4><p>Our Data team developed multiple internal APIs, for example to expose market insights, valuation models, a geographic referential, etc. Some months ago, <strong>they had to upgrade theses APIs to a second version without backward compatibility</strong> and are now maintaining both versions, but it’s costly 💸</p><p>The problem was that a <strong>lot of our micro-services in Meilleurs Agents stack were using them</strong> and also that we hadn’t the workforce (one team) nor the time (one month) to update all usages: the work was way too big! 😱</p><p>Our use case was a perfect application for the Strangler Fig pattern ✅</p><h3>How we used it at AVIV 🧭</h3><h4>First step: a simple proxy 👮</h4><p>We started by building a very simple proxy application: it <strong>intercepts all client requests and redirects them</strong> to the legacy system.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*6kjq6a35amxbSzC6F0-0kA.jpeg" /></figure><p>It allowed us to:</p><ol><li>Check that <strong>all client requests are going through the proxy</strong>: none should be missed</li><li>Give visibility on <strong>load per endpoint</strong>: requests per second, response time, real usage, etc.</li><li>Ensure our proxy<strong> can handle the needed load</strong></li></ol><h4>Second step: an hidden mapper 🤫</h4><p>Then, we started to <strong>map the legacy request</strong>s (and obviously the newer responses) in the background: it has <strong>no impact</strong> on “visible” behavior!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/421/1*HyqXrgCKPpFb2kEqWEbFVQ.jpeg" /></figure><p>After we call the new dependency, we <strong>compare both responses</strong>: the Legacy one and the Mapped one. If they are not the same, we <strong>log the differences</strong>.</p><p>We need to implement the mapping for all used endpoints of the Legacy dependency.</p><p>It allowed us to:</p><ol><li>Develop <strong>without any risk</strong></li><li><strong>Test our mappers</strong> in real environment to ensure that the Mapped response is the same as the Legacy one</li><li>Ensure our New dependency is <strong>ready to handle all the new load</strong> coming with this migration</li></ol><h4>Third step: migrate endpoints one by one ⤵️</h4><p>Once an endpoint has been tested, we simply configure our Proxy to <strong>return the Mapped response instead of the Legacy one</strong>. We did it <strong>one by one</strong> in order to limit the risks. If something goes wrong, we can <strong>quickly rollback</strong> to return the Legacy response again.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/436/1*0mRDBwMwac5dGaRmGeahyw.jpeg" /></figure><p>We can optionally continue to call the Legacy dependency in the background for monitoring purposes, or simply stop it.</p><h4>Fourth step: cleanup 🧹</h4><p>Once all the endpoints are migrated, you can <strong>remove all dead code</strong> in your proxy: background tasks, calls to Legacy dependency, responses comparison, etc. The goal here is to have the lightest Proxy possible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/354/1*1erTN7UAXQkNB8lkZHGKHw.jpeg" /></figure><h4>Final step: legacy dependency removal 🗑️</h4><p>Then here we are, the culmination of all this work: you can finally <strong>decommission the Legacy dependency</strong> 🙌</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*sARQXwx83auLtZjg8hCOKw.jpeg" /></figure><h3>Final thoughts</h3><p>We were in a really hard situation, but we did it! 🏆And the Strangler Fig pattern was a game changer for us 🦸</p><p>We <strong>avoided to update a lot of applications</strong>, from front-end to the databases, and we were able to <strong>deploy it really smoothly</strong>, one endpoint after another. Plus, a great advantage of this pattern is that we have <strong>all mappings centralized in one place</strong>, helping checks and acting like an “how-to-migrate” documentation.</p><p>But we also encountered <strong>some difficulties</strong>:</p><ul><li>Some responses were <strong>difficult to map</strong> (outdated data, different geographic referential, different ordering, different default values, etc.), but without this pattern, it would have been the same.</li><li>The proxy is intensively used and we needed to <strong>focus on performances and availability on multiple levels</strong>: hardware resources, CI/CD, technologies selection, gunicorn/uvicorn configuration for FastAPI, etc.</li><li>And finally, one very specific case: the calls to the dependencies needed to be internal but are on a different cloud provider, forcing us to split the proxy in two parts.</li></ul><p>Even if the Strangler Fig pattern <strong>isn’t ideal as a long-term solution</strong> (for performance and cost reasons), we didn’t plan to remove it for now. But it enables us to <strong>upgrade client applications one by one</strong> if we want to.</p><h3>Resources</h3><ol><li><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig">Strangler Fig pattern description in Microsoft’s website</a></li><li><a href="https://martinfowler.com/bliki/StranglerFigApplication.html">Martin Fowler’s blog post about Strangler Fig pattern application</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d32a468b707" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/modernize-without-breaking-everything-the-strangler-fig-pattern-in-action-9d32a468b707">Modernize without breaking everything — the Strangler Fig pattern in action!</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unifying form validation using TCA]]></title>
            <link>https://medium.com/aviv-product-tech-blog/unifying-form-validation-using-tca-e582b919cc02?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/e582b919cc02</guid>
            <category><![CDATA[composable-architecture]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[forms]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[CHANLIAU MERLE Anthony]]></dc:creator>
            <pubDate>Wed, 21 May 2025 12:25:32 GMT</pubDate>
            <atom:updated>2025-05-21T12:25:31.999Z</atom:updated>
            <content:encoded><![CDATA[<p>As frontend developers, we’ve all had to define a form to present to the user at least once.<br>We all know how cumbersome it can be due to the different input types, edge cases, validation rules and so on.</p><p>This is fine to go through all of this by hand when you have to define a single form in our app, but what if you have multiple forms ? Not 2 or 3, but more like 5 or 10 ?</p><p><em>For sure, you don’t want to redo everything every time… right?</em></p><figure><img alt="Multiple form examples from our apps" src="https://cdn-images-1.medium.com/max/1024/1*nw3U3dEnHfC4Z6P-VZ2tDw.png" /><figcaption>Multiple form examples from our apps</figcaption></figure><p>In the iOS dev team at AVIV, we develop our features using <a href="https://github.com/pointfreeco/swift-composable-architecture">The Composable Architecture</a> (aka TCA). At the time I write this article, there are no tools to define a form in TCA, so we had to come up with a solution on our own.</p><p><strong>Note:</strong> We won’t dive into how TCA works. But if you’d like to know more about it, <a href="https://youtu.be/h0Agq-98RZ8?si=ADRowU5_g2qITpeQ">here is a good talk to present it</a>.</p><h3>By talking about unifying form validation, what am I talking about and what am I not talking about ?</h3><p>The tool we developed focuses on defining the model and the validation rules for a given form.<br><em>It doesn’t help to define the UI side.</em></p><p>TCA has a clear separation between the view and the logic, such that we allow any kind of view to represent the form we define on the logic side. That’s really helpful if you want to quickly iterate over the look of the form for example.</p><p>The main issues we faced while developing our forms without being unified were:</p><ul><li>validation trigger inconsistency<br>sometimes the validation was triggered every time the value changed (while in other forms it was only when the focus changed), or when submitting the whole form</li><li>code duplication<br>to validate the whole form after the user taps on the submit button, we always need to go through each field and validate it</li><li>basically reinventing the wheel on every form</li><li>no proper way of testing the validation behaviour (or it should have been, once again, duplicated for every form)</li></ul><p><strong>So how did we solve these problems?<br></strong> By creating what we called a <em>FormValidationReducer</em> and few other helping units.</p><h3>How to use the FormValidation</h3><p>Here is a code example of using the <em>FormValidationReducer</em>. <br>It looks heavy but don’t worry, I will explain each part after</p><pre>@Reducer<br>struct MyFormReducer {<br>  struct State {<br>    var username: ValidatableField&lt;String&gt; = &quot;&quot;<br>    var password: ValidatableField&lt;String&gt; = &quot;&quot;<br>  }<br> <br>  enum Action: BindableAction {<br>    case binding(BindingAction&lt;State&gt;)<br>    case sendFormButtonTap<br>    case formValidationSucceed<br>  }<br> <br>  var body: some ReducerOf&lt;Self&gt; {<br>    BindingReducer()<br> <br>    FormValidationReducer(<br>      userAction: \.sendFormButtonTap,<br>      onSucceed: .formValidationSucceed,<br>      fields: [<br>        FieldValidation(<br>          name: \.username,<br>          rules: [<br>            .nonEmpty(&quot;Username is required&quot;),<br>            .length(min: 5, &quot;Username is too short, at least 5 characters needed&quot;)<br>          ]<br>        ),<br>        FieldValidation(<br>          name: \.password,<br>          rules: [<br>            .nonEmpty(&quot;Password is required&quot;),<br>            .length(min: 8, &quot;Password needs to be at least 8 characters long&quot;),<br>            .containsAtLeast(1, .uppercase, &quot;Password should contain at least 1 uppercased character&quot;),<br>            .containsAtLeast(1, .digit, &quot;Password should contain at least 1 digit&quot;),<br>            .disallowed(.specialCharacters, &quot;Password can&#39;t contain special characters&quot;)<br>          ]<br>        )<br>      ]<br>    )<br> <br>    Reduce { state, action in<br>      switch action {<br>      case .formValidationSucceed:<br>        // do anything upon form being successfully validated<br>        return .none<br>      }<br>    }<br>  }<br>}</pre><p>As you can see, to define a form, we add the <em>FormValidationReducer</em> in the corresponding form’s reducer.</p><h4>FormValidationReducer parameters</h4><h4>User action</h4><p>The first parameter to send to the <em>FormValidationReducer</em> is <em>userAction</em>.</p><p>This is a <em>KeyPath</em> to the action that is sent by the View when the user initiate the form’s validation process. Usually by tapping a “Send” button at the bottom of the form.</p><h4>On Succeed</h4><p>The next parameter corresponds to the action that is expected to be sent by the <em>FormValidationReducer</em> when the form validation succeeds.</p><p>That is, to allow the feature to react to the form successful validation and do additional work. Usually sending the form’s information to a back-end server.</p><h4>Fields</h4><p>Last but not least, we need to pass the various field information to validate in the form.</p><p>Each field is represented by a <em>FieldValidation</em> value type that lets the <em>FormValidationReducer</em> to know:</p><p>- which data in the <em>State</em> to validate, by using a <em>KeyPath</em> to the <em>State</em>’s value<br>- which rules to run over the grabbed value, by using a list of <em>Rule</em> value types</p><p>In our example, we want to validate both the <em>username</em> and the <em>password</em>.</p><p><strong>A note on the fields type:<br></strong>In the above example, you may have noticed that the <em>username</em> and <em>password</em> are of type <em>ValidatableField&lt;String&gt;</em>.</p><p>This value type was added as a kind of “syntactic sugar”, to make both the <em>State</em> and the <em>FieldValidation</em> lighter to use.</p><p>To see how, let’s show both types as if we were not using the <em>ValidatableField</em> type:</p><pre>// MyFormReducer.State<br>struct State {<br>  var username = &quot;&quot;<br>  var password = &quot;&quot;<br> <br>  var usernameError: String?<br>  var passwordError: String?<br>}</pre><pre>// MyFormReducer.body<br>var body: some ReducerOf&lt;Self&gt; {<br>  BindingReducer()<br> <br>  FormValidationReducer(<br>    userAction: \.sendFormButtonTap,<br>    onSucceed: .formValidationSucceed,<br>    fields: [<br>      FieldValidation(<br>        name: \.username,<br>        error: \.usernameError,<br>        rules: [ … ]<br>      ),<br>      FieldValidation(<br>        name: \.password,<br>        error: \.passwordError,<br>        rules: [ … ]<br>      )<br>    ]<br>  )<br> <br>  Reducer { state, action in … }<br>}</pre><p>As you can see, by not using the <em>ValidatableField</em> type, we would have to write every time both the field’s value information (i.e. <em>var username</em>, <em>field: \.username</em>) and their error value counterpart (i.e. <em>var usernameError: String?</em>, <em>error: \.usernameError</em>).<br>That rang a bell to us as if the two values “field” and “fieldError” wanted to always be treated together. That’s exactly what the <em>ValidatableField&lt;T&gt;</em> does! Here is its basic declaration:</p><pre>struct ValidatableField&lt;Value&gt; {<br>  var value: Value<br>  var error: String?<br>}</pre><h4>Error handling</h4><p>As you may have already guessed from the example, the <em>FormValidationReducer</em> makes it easy to handle validation failures.</p><p>Because we require to have a <em>KeyPath</em> to the <em>fieldError</em> state, we make sure that no field is left with hidden validation failures. The <em>FormValidationReducer</em> is responsible for grabbing the error message from the failing <em>Rule</em>, and set it the corresponding <em>fieldError</em> state.</p><h4>Testing</h4><p>Because the <em>FormValidationReducer</em> is a reducer, it’s pretty easy to test its behaviour in unit tests.</p><p>All you have to do is to initialise a <em>TestStore</em> with your form’s <em>Reducer</em> and the state you want to check, send the <em>userAction</em> and assert that you either:</p><p>- receive the <em>onSuccess</em> action if you expect the validation to succeed<br>- receive the new state with the corresponding errors in expected failed fields</p><h3>Conclusions</h3><p>Thanks to that <em>FormValidationReducer</em>, we are able to:</p><p>- declaratively define a form and its fields to validate<br>- enforce a single validation behaviour, because the validation logic is defined in the <em>FormValidationReducer</em> itself</p><p>For a more in-depth talk on how we came up with this reducer and how it works under the hood, you can:</p><ul><li>watch a talk I made for the SwiftConnection on Youtube</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FRrBxRX_SEsQ%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DRrBxRX_SEsQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FRrBxRX_SEsQ%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/6fd974dd97dc22270eb31e618900f1cf/href">https://medium.com/media/6fd974dd97dc22270eb31e618900f1cf/href</a></iframe><ul><li>reach to our public repository, that open-sources the presented form validation code, and ships an example project you can play with.</li></ul><p><a href="https://github.com/Aviv-public/swift-form-validation">https://github.com/Aviv-public/swift-form-validation</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e582b919cc02" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/unifying-form-validation-using-tca-e582b919cc02">Unifying form validation using TCA</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Embracing accessibility]]></title>
            <link>https://medium.com/aviv-product-tech-blog/embracing-accessibility-d56dc27bf967?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/d56dc27bf967</guid>
            <category><![CDATA[inclusivity]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[mobile]]></category>
            <dc:creator><![CDATA[Kevin Hirsch]]></dc:creator>
            <pubDate>Tue, 14 Jan 2025 16:09:36 GMT</pubDate>
            <atom:updated>2025-01-14T16:09:36.132Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LZR9nSKsUADpL1yrWXIDsA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@amigomobility?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Amigo Mobility</a> on <a href="https://unsplash.com/photos/a-man-sitting-on-a-bench-with-two-children-HDwVsxnuUr0?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>A few years ago, my ex-CTO, <a href="https://medium.com/u/46a8fdf2b133">Erwan Ripoll</a>, made me discover the world of accessibility. It hit me hard. I always wanted to make great apps as an iOS developer — fine tune the user experience, design, fluidity and ease of use. However, I never gave much of thought to accessibility — or rather my definition of accessibility was flawed. Today, (like it happened to me) I’d like to reshape your perceptions about accessibility!</p><p>We often hear about accessibility nowadays. It has become a buzz word. The question is, what really lies behind it? What does it mean? <br>Why should we care? And is it really important for mobile apps?</p><p>These are questions I asked myself over the years — and here’s what I found out…</p><h3>What is (digital) accessibility?</h3><p>There’s a definition about accessibility I really like to give as it usually shatters — right of the bat — the conception people tend to have about it.</p><blockquote>Accessibility is the practice of ensuring that we create products that can be used by anyone regardless of their capabilities.</blockquote><p>You read it right — it’s a <em>practice</em>, a practice we must learn and get better at. But moreover it’s about creating products for <strong>everyone</strong>… and I’ll come back to that soon enough!</p><h3>“Disability” you said?</h3><p>When people think about accessibility, they tend to have another definition of it; one tied to disability. Fair enough! But what is a disability? Well… once again, there’s more to it than we could think.</p><p>We can define a disability as an illness, injury or condition that makes it difficult for someone to do the things that other people do.</p><p>And of course, first things that come to mind are the most obvious disabilities like being in a wheelchair, being blind, and being deaf, while there are many other kind of visible — and mostly <strong>invisible — </strong>disabilities, like being colorblind, having arthritis or having dyslexia, just to name a few.</p><h3>It impacts a lot of people</h3><p>Not only most disabilities are invisible, but most of them are not permanent. Coming back to our initial and most obvious examples when it comes to disability: wheelchairs, blindness, deafness — those are permanent disabilities which people cannot recover from.</p><p>What people are usually not aware of is that disabilities come in 3 forms of temporality:</p><ol><li><strong>Permanent </strong>(e.g. having an amputated arm)</li><li><strong>Temporary</strong> (e.g. having a broken arm in a cast)</li><li><strong>Situational </strong>(e.g. holding a child in one’s arms)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IaEYSU51P4LCjVjaksZV3g.png" /><figcaption>Example of permanent, temporary and situational disability</figcaption></figure><p>The context is different but the result is the same: <strong>a person is disabled</strong> (e.g. cannot use their arm) whether the handicap is permanent, temporary or depending on the situation.</p><p>According to the World Health Organization, about 16% of the global population experience significant disability (<a href="https://www.who.int/health-topics/disability#tab=tab_1">source</a>). And this is not factoring in all <em>temporary</em> and <em>situational</em> disabilities.</p><p>There are many many people <em>temporarily</em> disabled and even more <em>situationally</em> disabled. For instance, someone might struggle to read a screen under bright sunlight, type on a keyboard with a broken arm, or hear a phone call in a noisy crowd.</p><h3>Data, data, data!</h3><p>At AVIV Group, we decided to look into how many of our mobile users are actually using accessibility features on their phone and the result is staggering!</p><ul><li>1/3 of our users on iOS are not using the default font size</li><li>40% are using dark mode</li><li>Between 29% and 47% are using an accessibility feature</li></ul><p>You can find similar numbers over the internet. A good website I recommend with tons of stats and great info on accessibility is <a href="https://appt.org/en/stats">https://appt.org/en/stats</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ntwYr0CM4dK1LFygHFT4LA.png" /><figcaption>Statistics of iOS accessibility features enabled by users</figcaption></figure><h3>When you build for accessibility, everyone benefits</h3><p>Without delving into how to concretely make accessible products, I’d like to showcase how building for accessibility benefits everyone.</p><p><strong>At heart, accessibility is a key piece of usability and is an inherent part of it.</strong></p><p>Not caring about accessibility is degrading your product’s usability — and working on usability without thinking about accessibility means you are not taking into account how people might use your app in different situations/context (remember <em>situational disabilities</em>?).</p><p>I like to give a non-digital example of how a simple accessibility feature can benefit everyone: an entrance ramp.</p><p>Imagine standing in front of a grocery store. To enter, you have two options: take the steps leading to the entrance or use the <strong>gently sloping ramp</strong> nearby. While this is a feature made initially for disabled people in wheelchairs, it actually benefits many more people: elders, parents with their babies in a stroller, cyclists, delivery persons with heavy packages to carry.</p><p>Accessibility features tend to have far more reach than their original intention. The entrance ramp is a great example of that, but we can look at many more digital examples…</p><h4>Text size</h4><p>Making your interface adapt to the user’s preferred text size is key. While the obvious use case here is for users with low vision (or dyslexia, low attention span, etc.) to increase the text size, we can clearly see that 12% of users also take advantage of such feature to decrease the text size in order to see more on screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*ymMXFI8v0Hrv-2s_uYn0AQ.gif" /><figcaption>Example of interface adapting to user preferred text size</figcaption></figure><h4>Dark mode</h4><p>Dark mode quickly gained in popularity a few years ago although being at its core an accessibility feature. Lots of users are using it. Almost 40% on iOS. Sometimes full time and sometimes based on the time of day. Who likes to be dazzled by the screen brightness of their phone — in light mode — at night before going to sleep? No one. That’s why so many people are using dark mode.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*XGkrRAHIamrcweEWROqEQg.gif" /><figcaption>Example of interface adapting to dark mode</figcaption></figure><h4>Subtitles</h4><p>Subtitles are now everywhere, and people use them on a daily basis, whether they are deaf or not. They make it easy to follow some content in a noisy environment or follow your favorite Netflix show in its original language while having the subtitles in their own language. Most social media apps even autoplay the videos in silent with subtitles enabled.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*tYpoKiyKdxR9VK4w5AeBfg.gif" /></figure><p><strong>These are just a few examples that building for accessibility benefits everyone.</strong></p><h3>Conclusion</h3><p>Accessibility is not only about disability. It is mainly about making products usable by everyone. The features we must support may appear to serve only a small group of people, yet they often have a far-reaching impact, because many individuals live with permanent disabilities, and an even larger number experience temporary or situational limitations.</p><p>In the end, I strongly believe that:</p><blockquote>We are personally responsible for becoming more ethical than the<br>society we grew up in.<br> — Eliezer Yudkowsky</blockquote><p>We should stop seeing accessibility as a chore for a minority of our users. Accessibility is about tailoring your products to everyone. It’s about pushing the boundaries of what’s possible. It’s caring about your users. It’s wanting to deliver a great user experience. It’s about innovation.</p><p>Let’s start embracing accessibility today!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d56dc27bf967" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/embracing-accessibility-d56dc27bf967">Embracing accessibility</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[iOS 18 — Apple's Translation API]]></title>
            <link>https://medium.com/aviv-product-tech-blog/ios-18-apples-translation-api-ea9a5afc281f?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/ea9a5afc281f</guid>
            <category><![CDATA[translation]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[language]]></category>
            <dc:creator><![CDATA[activesludge]]></dc:creator>
            <pubDate>Tue, 19 Nov 2024 09:13:46 GMT</pubDate>
            <atom:updated>2024-11-19T09:13:46.496Z</atom:updated>
            <content:encoded><![CDATA[<h3>iOS 18 — Apple&#39;s Translation API</h3><p>Say goodbye to copy-pasting text between apps!</p><p>With iOS 18, Apple’s new <a href="https://developer.apple.com/documentation/translation">Translation</a> API brings translation seamlessly into the user experience.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/626/0*MQg4rQiZQzXgUVKi.jpg" /><figcaption>Illustration by <a href="https://www.freepik.com/author/vectorjuice">vectorjuice</a> at <a href="http://www.freepik.com">freepik</a></figcaption></figure><p>Users do not have to copy and paste a text from an app to a translation apps anymore. They will be able to translate a text directly within the app elegantly.</p><p>Moreover, they&#39;ll be able to replace any text in the app. This includes any field or label. Sequentially or all at once.</p><p>I’ll walk you through some experiments with this powerful API and showcase a demo app that illustrates how some made up real-world scenarios can benefit from these features.</p><p>We can demonstrate the APIs capabilities in three main categories:</p><p><strong>System UI</strong></p><p>Using Apple native tools to translate text in Translation in-app popup and optionally replace the original text in the app.</p><ul><li>Overlay Translation</li><li>Replace Translation</li></ul><p><strong>Custom UI</strong></p><p>Translate a text or a batch of those and tailor your UI as you see fit.</p><ul><li>Single String</li><li>Batch of Strings</li><li>Batch as a sequence</li></ul><p><strong>Availability</strong></p><p>Checks the availability of translations based on user preferences, device settings, and installed language packs.</p><ul><li>Language availability</li><li>Prepare for Translation</li></ul><p>Plus, I&#39;ll share with you what you should expect and what I encountered unexpectedly.</p><p>Let&#39;s jump in.</p><h3>Requirements</h3><ul><li>A real device (Translation API is not supported on simulated devices. Also not on Preview)</li><li>iOS 18.0 +</li><li>Xcode 16.0 +</li><li>Apple&#39;s Translate app has to be installed on the device. Since iOS 14, it&#39;s installed automatically.</li></ul><h3>Demo project</h3><p>Here you can download the demo app and run it on Xcode.</p><p>Features are developed using both just a view and with a view model to provide flexibility on implementation.</p><p><a href="https://github.com/Aviv-public/apples-translation-api-demo">https://github.com/Aviv-public/apples-translation-api-demo</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*_pOy2rm5cM2uEwSsKeFCWQ.gif" /><figcaption>Translation API Demo</figcaption></figure><h3>System UI</h3><h4>Overlay Translation</h4><ul><li>Opens an overlay of Apple’s Translate app within our app.</li><li>Just like copying and pasting a text to the Translate app.</li><li>User can pick any source and target language they want in the overlay sheet.</li></ul><p>In this example, we showcase a legal disclaimer page. Let&#39;s pretend that, to comply with local law, the particular disclaimer below must be displayed in the local language. However, to simplify the experience for our international users, we also offer a convenient button that provides a quick translation, allowing users to easily understand the content in their preferred language</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*bc--rgY4NCM5b3WKIeLiLQ.gif" /><figcaption>A legal disclaimer page that must be presented in German, due to legal reasons.</figcaption></figure><pre><br>struct LegalDisclaimerDemoView: View {<br>    let legalTextTitle = &quot;Nutzungsbedingungen&quot;<br>    let legalText = &quot;Diese App dient ausschließlich ...&quot;<br><br>    var textToBeTranslated: String {<br>        legalTextTitle + &quot;\n&quot; + legalText<br>    }<br><br>    @State var isTranslationOverlayPresented = false<br><br>    var body: some View {<br>        ...<br><br>        Text(verbatim: legalText)<br>            // Presents the sheet binding it to a boolean <br>            // just like presenting a sheet<br>            // Add the source String as the text argument.<br>            .translationPresentation(<br>                isPresented: $isTranslationOverlayPresented,<br>                text: textToBeTranslated<br>            )<br><br>        ...<br>        .overlay(alignment: .topTrailing) {<br>            Button {<br>                // Show translate overlay<br>                isTranslationOverlayPresented = true<br>            } label: {<br>                Image(systemName: &quot;translate&quot;)<br>            }<br>        }<br>        ...<br>    }<br>}</pre><h4>Replace Translation</h4><ul><li>Opens an overlay of Apple’s Translate app within our app. Same as the prior feature.</li><li>User can pick any source and target language they want in the overlay sheet.</li><li>Now presents an option called “Replace with Translation” to replace the text within the app.</li><li>Replacing the translated String is up to the developer.</li></ul><p>In this example, we present a real estate property detail page. The description was filled in French by the seller. We provide a button to see a translation of this description, and elegantly replace it inside of our app. Notice how the description turns into English.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*JfO5kS0a5XJ9RmneeXULkQ.gif" /><figcaption>A real estate property detail page with a description that has been filled in French.</figcaption></figure><pre>struct PropertyDetailDemoView: View {<br>    ...<br><br>    @State private var isTranslationOverlayPresented = false<br><br>    var body: some View {<br>        ...<br>        .translationPresentation(<br>            isPresented: $isTranslationOverlayPresented,<br>            text: viewModel.property.description<br>        ) { translatedText in<br>            ...<br>            // Replace the translatedText here<br>            viewModel.propertyDescription = translatedText<br>        }<br>        ... <br>        Button {<br>            isTranslationOverlayPresented = true<br>        } label: {<br>            Image(systemName: &quot;translate&quot;)<br>        }<br>    }<br>}</pre><h3>Custom UI</h3><p>Doesn’t present and overlay view from the system. User doesn’t have to leave the app. We have the ultimate freedom to display translations.</p><ul><li>It’s up to developer to display the translation.</li><li>It’s technically a Task. Which means it’s asynchronous. Has a delay, and can fail.</li></ul><p>The API works with a couple of extra objects:</p><p>TranslationSession</p><ul><li>A class that performs translations between a pair of languages.</li><li>It uses a configuration to define the source and target languages.</li><li>We obtain an instance of this class inside the closure by adding the view modifier .translationTask(_:action:) to a SwiftUI view.</li><li>It contains the methods we need to translate a source text.</li><li>All translations using the TranslationSession class are processed on the user’s device. Apple may collect API usage and performance metrics including the app bundle ID and the original and translated language, but this data does not include the original or translated content.</li></ul><p>TranslationSession.Configuration</p><ul><li>Specifies the source and target languages to use in a translation session. Default initialisation allows system to detect source and target language automatically.</li><li>When you pass this configuration into the .translationTask(_:action:) function, the framework uses the configuration you initialised for the translation.</li></ul><p>Locale.Language</p><ul><li>A type that represents a language, as used in a locale.</li></ul><h4>Single String</h4><ul><li>Pass the text and receive a response that contains translated text.</li></ul><p>Here, we have a contact form to reach out to a seller. The user wrote their message in English. We give them a kind reminder saying that the seller of the property is from Italy. Sending the message in Italian may help them in their effort to get the best deal. They see the translation in a sheet, if they use it, the app replaces the message with the translated one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*meAe6019VE4nqxplrrlThA.gif" /><figcaption>A contact form to reach out to a property seller. The page offers the user to translate their message to Italian.</figcaption></figure><pre>struct ContactSellerView: View {<br>    ...<br>    @State private var message: String =<br>    &quot;&quot;&quot;<br>    Hello, <br>    I&#39;m interested in your property. I like what you did with the interior design. I love the neighborhood and the location. But I&#39;m looking for an at least 10% discount on the deal. Is it acceptable to you? Please let me know. <br>    Thanks!<br>    &quot;&quot;&quot;<br>    ...<br><br>    @State private var configuration: TranslationSession.Configuration?<br>    @State private var translatedMessage: String = &quot;&quot;<br>    @State private var isTranslationSheetPresent: Bool = false<br><br>    var body: some View {<br>        ...<br><br>        Button {<br>            triggerTranslation()<br>        } label: {<br>            HStack {<br>                Text(&quot;See how it looks in 🇮🇹&quot;)<br>            }<br>        }<br>        // Once the configuration property detects a change, <br>        // it triggers a new session. <br>        .translationTask(configuration) { session in<br>            do {<br>                // Use the session the task provides to translate the text.<br>                let response = try await session.translate(message)<br>                // Update the view with the translated result.<br>                translatedMessage = response.targetText<br>                isTranslationSheetPresent = true<br>            } catch {<br>                // Handle any errors.<br>            }<br>        }<br>        ...<br>    }<br><br>    private func triggerTranslation() {<br>        guard configuration == nil else {<br>            // Call .invalidate() method to trigger the translation again <br>            // with the same configuration instance<br>            configuration?.invalidate()<br>            return<br>        }<br><br>        // Create a new configuration for the translation session.<br>        // This configuration will target Italian as the target language.<br>        configuration = .init(target: .init(identifier: &quot;it&quot;))<br>    }<br>}</pre><h4>Batch of Strings</h4><ul><li>Send an array of String in order, receive an array of translated String in the same order.</li></ul><p>Here&#39;s a survey for a property detail page. The real estate agency is curious about how satisfied was the interested party about the details. The survey statements were provided by the agent, so they are in Dutch. We provide them a way to translate all those statements. Once the user taps on the translate button, all statements turn into the language of users location, in this case German.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*mY8SsRdsIR1wtNTD2Ozcxg.gif" /><figcaption>Property detail survey in Dutch.</figcaption></figure><pre>import SwiftUI<br>import Translation<br><br>struct SurveyPageView: View {<br>    // Statement about the satisfaction on the property detail page in Dutch<br>    @State var statements = [<br>        &quot;De details van het eigendom waren duidelijk.&quot;,<br>        &quot;De foto’s van het eigendom waren van goede kwaliteit.&quot;,<br>        ...<br>    ]<br><br>    @State private var configuration: TranslationSession.Configuration?<br><br>    var body: some View {<br>            ...<br><br>            Button {<br>                triggerTranslation()<br>            } label: {<br>                Image(systemName: &quot;translate&quot;)<br>            }<br><br>            ...<br><br>            .translationTask(configuration) { session in<br>                await translateAllAtOnce(using: session)<br>            }<br><br>            ...<br>        }<br>    }<br><br>    ...<br><br>    private func triggerTranslation() {<br>        guard configuration == nil else {<br>            configuration?.invalidate()<br>            return<br>        }<br><br>        // Default initializer allows device to detect the source language <br>        // and the target language.<br>        // Target language is based on user&#39;s region by default.<br>        configuration = .init()<br>    }<br><br>    private func translateAllAtOnce(using session: TranslationSession) async {<br>        // Translate all statements at once<br>        Task { @MainActor in<br>            let requests: [TranslationSession.Request] = statements.map {<br>                // Map each item into a request.<br>                TranslationSession.Request(sourceText: $0)<br>            }<br><br>            do {<br>                let responses = try await session.translations(from: requests)<br>                withAnimation {<br>                    statements = responses.map {<br>                        // Update each item with the translated result.<br>                        $0.targetText<br>                    }<br><br>                    ...<br>                }<br>            } catch {<br>                // Handle any errors.<br>            }<br>        }<br>    }<br>}<br></pre><h4>Batch as a sequence</h4><ul><li>An array of “translation requests” can be send sequentially. Then the result can be processed as they are being received.</li><li>Order of receiving is not guaranteed. But can be tracked with index using clientIdentifier property.</li><li>Can be useful for translating a set of very large texts.</li></ul><p>Here we have an agents page and reviews given by customers. They&#39;re in French. When the blue button is tapped, we translate every review one by one to the primary language, which is German. When it&#39;s tapped again they&#39;re translated into English. which is the second preferred language of user&#39;s device.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*0n5FdkREMsQQ0eMufXGTuw.gif" /><figcaption>An agents reviews in French. They’re being translated into German, then English.</figcaption></figure><pre>struct RealEstateAgentProfileView: View {<br>    ...<br>    // Customer reviews with dates and reviewer names<br>    @State var reviews: [Review] = .reviews<br><br>    ...<br><br>    @State private var configuration: TranslationSession.Configuration?<br><br>    var body: some View {<br>        ...<br>        Button {<br>            triggerTranslation()<br>        } label: {<br>            Image(systemName: &quot;translate&quot;)<br>        }<br>        ...<br>        .translationTask(configuration) { session in<br>            await translateSequence(session)<br>        }<br>        ...<br>    }<br><br>    private func triggerTranslation() {<br>        guard configuration == nil else {<br>            configuration?.invalidate()<br>            return<br>        }<br><br>        // In default, source language will be detected as French<br>        // since the review texts are in French.<br>        // Target language is user&#39;s region, in this case German.<br>        // When same configuration is used, then the source language is German,<br>        // and the target language will the user&#39;s prefered language, <br>        // in this case English.<br>        configuration = .init()<br>    }<br><br>    private func translateSequence(_ session: TranslationSession) async {<br>        Task { @MainActor in<br>            // Create an array of requests. Use the index of the review array <br>            // as the client identifier.<br>            let requests: [TranslationSession.Request] =  reviews.enumerated().map { (index, review) in<br>                .init(sourceText: review.text, clientIdentifier: &quot;\(index)&quot;)<br>            }<br><br>            do {<br>                // Translate the batch of requests.<br>                // For each response received, update the corresponding review in the array.<br>                for try await response in session.translate(batch: requests) {<br>                    guard let index = Int(response.clientIdentifier ?? &quot;&quot;) else { continue }<br>                    withAnimation {<br>                        // The client identifier is used to match the response to the original reviews<br>                        reviews[index].text = response.targetText<br>                    }<br>                }<br>            } catch {<br>                // Handle error<br>            }<br>        }<br>    }<br>}</pre><h3>Availability</h3><h4>Available languages</h4><p>You can see the availability of languages the API offers. LanguageAvailability().status(_ from: _to:) checks to see if a specific language pairing is installed and ready for translation.</p><ul><li>.installed supports, has been downloaded and made ready for use in a translation.</li><li>.supported supports but can’t yet use. Only after it downloads and installs.</li><li>.unsupported doesn’t support.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*mT4i8HlVGz0WsP0WBIt2qA.gif" /><figcaption>A page that displays the availability of a language pairing.</figcaption></figure><pre>@Observable<br>class LanguageAvailabilityViewModel {<br>    var isTranslationSupported: Bool?<br>    ...<br><br>    func checkLanguageSupport(from source: Locale.Language, to target: Locale.Language) async {<br>        let availability = LanguageAvailability()<br>        let status = await availability.status(from: source, to: target)<br><br>        switch status {<br>        case .installed, .supported:<br>            isTranslationSupported = true<br>        case .unsupported:<br>            isTranslationSupported = false<br>        @unknown default:<br>            print(&quot;Not supported&quot;)<br>        }<br>    }<br>}</pre><h4>Prepare languages</h4><ul><li>Displays a sheet asking the user’s permission to start downloading the language pairing. So that the above translation features can be ready from the get going.</li><li>Useful to prepare the device for translations.</li><li>If user is faced with any of above features before their device has the necessary language packs, the device will automatically ask to download languages.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*CZ2ixzSmrSYa1KUyJ9k8-A.gif" /><figcaption>A page where user can prepare the device for In-App translations.</figcaption></figure><pre>struct PrepareTranslationView: View {<br>    @State private var configuration = TranslationSession.Configuration(<br>        source: Locale.Language(identifier: &quot;pt_BR&quot;),<br>        target: Locale.Language(identifier: &quot;ko_KR&quot;)<br>    )<br><br>    @State private var buttonTapped = false<br><br>    var body: some View {<br>        ...<br>        Button(<br>            action: {<br>                configuration.invalidate()<br>                buttonTapped = true<br>            }<br>        ) {<br>            Text(&quot;Download language packs&quot;)<br>        ...<br>        }<br>        ...<br>        .translationTask(configuration) { session in<br>            if buttonTapped {<br>                do {<br>                    // This will present the sheet to <br>                    // download the required language packs.<br>                    try await session.prepareTranslation()<br>                } catch {<br>                    // Handle any errors.<br>                }<br>            }<br>        }<br>        ..<br>    }<br>    ...<br>}</pre><h3>In default configuration, …</h3><ul><li>If the language of the source text is detected to be the user&#39;s <strong>region</strong>, then the target defaults to <strong>device’s preferred language</strong>.</li><li>If the source is not the same with the region, the target defaults to the <strong>region’s language</strong>.</li><li>If the source and target language pairing isn’t available on the device, user will be prompted with an overlay asking to download it.</li><li>If the translation intend is unclear from the APIs perspective, user will be prompted with an overlay asking to pick a source language from the list.</li></ul><h3>It was unexpected when I saw, that …</h3><ul><li>When the Translate app isn&#39;t installed on the device, features do not work as expected. For instance, when you try to use Custom UI features, the API throws error. But System UI features work just fine. So, Translate app is better be installed on user&#39;s device, or you may face with unexpected behaviours.</li><li>There is no way to check beforehand, whether the user has the Translation app on their device or not.</li><li>In default, if the target isn&#39;t specified in the configuration, target language is set to the device&#39;s region. I would expect it to be the devices preferred language, which wasn&#39;t the case.</li></ul><h3>User&#39;s Permission …</h3><p>Is required by Apple’s Translate app (not the app utilising the Translation API) for the first time:</p><ul><li>to read user input for translations, and</li><li>to download source and target language when translation is being used.</li></ul><h3>Advantages</h3><ul><li>Translates any text in the app.</li><li>Very easy to use and implement.</li><li>Translation are done on device. So, it&#39;s more secure and apart from downloading language pairings does not require constant internet connection.</li><li>You can expand and limit available languages in your app.</li><li>A large set of language availability.</li><li>Emojis do not distract translations.</li><li>Apple’s own API. Support in future is pretty much guaranteed.</li><li>Already downloaded languages can be present in the device and can be utilised by the API.</li><li>The API is smart enough to determine source and target languages, if a specific configuration isn’t passed as an argument.</li></ul><h3>Disadvantages</h3><ul><li>Can redirect user out of your apps.</li><li>Translations are provided by Apple. No any other translation API can be injected.</li><li>The algorithm of the translation is a black box. You will have no control over the translated text. They can get wrong.</li><li>Only available after iOS 18. No backward availability.</li><li>Internet connection is required for the first usage. Language pairings have to be downloaded.</li><li>Custom UI features do not work without the Translate app.</li></ul><h3>Conclusion</h3><p>Easy to implement. Definitely an easy win for any text in your apps that get translated frequently by users. This can be a great addition to any app.</p><p>I hope that this article will be useful to you.</p><p>Have a great one, cheers! ✌️</p><h4>References:</h4><ul><li><a href="https://developer.apple.com/videos/play/wwdc2024/10117/">Meet the Translation API — WWDC24 — Videos — Apple Developer</a></li><li><a href="https://developer.apple.com/documentation/translation/translating-text-within-your-app">Translating text within your app | Apple Developer Documentation</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea9a5afc281f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/ios-18-apples-translation-api-ea9a5afc281f">iOS 18 — Apple&#39;s Translation API</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Enhancing User Experience with OpenAI’s Realtime API and Function Calling: A Seamless…]]></title>
            <link>https://medium.com/aviv-product-tech-blog/enhancing-user-experience-with-openais-realtime-api-and-function-calling-a-seamless-a21f8ee5a93b?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/a21f8ee5a93b</guid>
            <category><![CDATA[functioncall]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[speech-to-speech]]></category>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[tech]]></category>
            <dc:creator><![CDATA[Laurent HUSSON]]></dc:creator>
            <pubDate>Thu, 17 Oct 2024 11:44:50 GMT</pubDate>
            <atom:updated>2024-10-17T11:44:50.598Z</atom:updated>
            <content:encoded><![CDATA[<h3>Enhancing User Experience with OpenAI’s Realtime API and Function Calling: A Seamless Speech-to-Speech Integration</h3><p>As AI technology continues to evolve, the way we interact with machines is undergoing significant changes. At latest <a href="https://openai.com/devday/content/">OpenAI’s DevDay</a>, the company behind the well-known ChatGPT and GPT models announced a public beta of their new<a href="https://openai.com/index/introducing-the-realtime-api/"> Realtime API</a>, alongside several other updates.</p><p>This release introduces a new approach to voice-powered experiences by enabling low-latency, speech-to-speech interactions. A key advantage comes from combining this with the existing Function Calling feature, which enhances real-time, responsive user experiences.</p><p>In this blog post, we’ll discuss how this combination improves both developer capabilities and user interactions, offering new opportunities for voice-first applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*tgdPmhiZ5BVasxtXTSbN7A.png" /></figure><h3>The Power of Realtime Speech-to-Speech: What’s New?</h3><p>Previously, developers had to use multiple models to achieve speech-to-speech functionality: transcribing audio with tools like Whisper, sending the transcription to a GPT model, and then using text-to-speech tools for playback. This process often introduced delays and impacted the fluidity of conversations.</p><p>The new Realtime API addresses this issue by streaming audio inputs and outputs with near-human response times. This allows for more natural conversations, where users can engage without noticeable interruptions. Built on the GPT-4o model, the API supports real-time conversations while maintaining the expressive qualities of speech.</p><h3>Enter Function Calling: Structured, Dynamic Intelligence</h3><p>Now, consider combining the real-time capabilities of the Realtime API with OpenAI’s Function Calling feature. This feature allows developers to define APIs or functions that GPT can trigger based on the context of a conversation. Tasks like booking appointments, retrieving data, or interacting with external services can be completed seamlessly in real time.</p><p>This combination transforms voice assistants from simple conversational bots into more capable virtual agents, offering hands-free, task-driven experiences.</p><h3><strong>How Function Calling and Realtime API Improve User Experience</strong></h3><p>By integrating both the Realtime API and function calling, developers can create voice-first applications that engage users more effectively. Here are some key benefits:</p><ul><li><strong>Conversational and Task-Oriented:</strong> Users can perform tasks like booking appointments or checking schedules within the same conversation, without switching between different interactions.</li><li><strong>Hands-Free Convenience:</strong> Tasks like controlling smart devices or checking information can be completed without interrupting the conversation flow.</li><li><strong>Real-Time Personalization:</strong> Function calling can trigger APIs to fetch user-specific data, providing real-time, personalized responses.</li><li><strong>Latency-Free Integration:</strong> The Realtime API minimizes delays, enhancing interactions in time-sensitive situations, such as customer support or telemedicine.</li><li><strong>Natural Conversations:</strong> The API allows for more human-like conversations by managing interruptions and maintaining a natural flow, improving the overall user experience.</li></ul><h3>My Experiment: Setting Up a Speech-to-Speech Assistant</h3><p>To test the combination of OpenAI’s Realtime API and Function Calling, I used the OpenAI Realtime Console as a foundation by forking it for the proof of concept.</p><p>Idea was to build something that lools like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/855/1*h0R6mV7GVhUOCT5kj29VbQ.png" /></figure><h4>Setting the Stage: Getting Started with the OpenAI Realtime Console</h4><p>The <a href="https://github.com/openai/openai-realtime-console">OpenAI Realtime Console</a> is a powerful interface for experimenting with the Realtime API. It allows developers to interact with speech input, trigger custom functions, and manage API sessions effortlessly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hVHkMHn08F7rNz9ym2oMCA.png" /></figure><p><strong>Setting up the Console</strong><br>The console is built as a React project. Simply clone the github <a href="https://github.com/openai/openai-realtime-console">repository </a>and install dependencies using npm install.</p><p>Additionaly you may want to use the provided relay server, mostly if you want to :</p><ul><li>Hide API credentials if you would like to ship an app to play with online</li><li>Handle certain calls you would like to keep secret (e.g. instructions) on the server directly</li><li>Restrict what types of events the client can receive and send</li></ul><p>In that case, you’ll need to add an .env file with you OpenAI api key and path to your relay server url at the root of the project.</p><p><strong>Adding your custom “function calling” tool</strong></p><p>This process is simple. In the ConsolePage.tsx file under src/pages, look for the <a href="https://github.com/openai/openai-realtime-console/blob/971323d7f81b42f14177741e4c8666bbe4591c1c/src/pages/ConsolePage.tsx#L385">client.addTool</a> method. Remove the existing set_memory and get_weather functions, and add your own by providing a JSON OpenAPI definition for your custom function and available parameters as the first argument, followed by your function code as the second argument.</p><p>This is for instance one of the example tool provided with the codebase.</p><pre>client.addTool(<br>      {<br>        name: &#39;get_weather&#39;,<br>        description:<br>          &#39;Retrieves the weather for a given lat, lng coordinate pair. Specify a label for the location.&#39;,<br>        parameters: {<br>          type: &#39;object&#39;,<br>          properties: {<br>            lat: {<br>              type: &#39;number&#39;,<br>              description: &#39;Latitude&#39;,<br>            },<br>            lng: {<br>              type: &#39;number&#39;,<br>              description: &#39;Longitude&#39;,<br>            },<br>            location: {<br>              type: &#39;string&#39;,<br>              description: &#39;Name of the location&#39;,<br>            },<br>          },<br>          required: [&#39;lat&#39;, &#39;lng&#39;, &#39;location&#39;],<br>        },<br>      },<br>      async ({ lat, lng, location }: { [key: string]: any }) =&gt; {<br>        setMarker({ lat, lng, location });<br>        setCoords({ lat, lng, location });<br>        const result = await fetch(<br>          `https://api.open-meteo.com/v1/forecast?latitude=${lat}&amp;longitude=${lng}&amp;current=temperature_2m,wind_speed_10m`<br>        );<br>        const json = await result.json();<br>        const temperature = {<br>          value: json.current.temperature_2m as number,<br>          units: json.current_units.temperature_2m as string,<br>        };<br>        const wind_speed = {<br>          value: json.current.wind_speed_10m as number,<br>          units: json.current_units.wind_speed_10m as string,<br>        };<br>        setMarker({ lat, lng, location, temperature, wind_speed });<br>        return json;<br>      }<br>    );</pre><p>Make sure your function returns a JSON object so the assistant can provide accurate answers based on the user’s request.</p><p><strong>Providing the assistant with instructions</strong></p><p>In the <a href="https://github.com/openai/openai-realtime-console/blob/main/src/utils/conversation_config.js">conversation_config.js</a>file, you can customize the system settings for your chatbot, including details about its personality and behavior.</p><pre>export const instructions = `System settings:<br>Tool use: enabled.<br><br>Instructions:<br>- You are an artificial intelligence agent responsible for helping test realtime voice capabilities<br>- Please make sure to respond with a helpful voice via audio<br>- Be kind, helpful, and curteous<br>- It is okay to ask the user questions<br>- Use tools and functions you have available liberally, it is part of the training apparatus<br>- Be open to exploration and conversation<br>- Remember: this is just for fun and testing!<br><br>Personality:<br>- Be upbeat and genuine<br>- Try speaking quickly as if excited<br>`;</pre><p><strong>Running the app</strong></p><p>If you decided to use the relay server, start by running it using the following npm run relay command and then run the app with npm start . And that’s it!</p><h3>The caveats</h3><p>While the tool is easy to set up, keep in mind that the API is still in beta, and the console has a few bugs. For example, at the time of writing, if you use the relay server, you’ll need to apply a patch (as outlined in a <a href="https://github.com/openai/openai-realtime-api-beta/issues/14#issuecomment-2395041263">GitHub issue</a>) to ensure function calls work correctly.</p><p>Another consideration is the current cost of audio input and output. Currently, audio input is estimated at 6¢ per minute, and output at 24¢ per minute. And this really quickly adds up, so make sure you define limits accordingly. Hopefully, these costs will decrease over time, similar to other OpenAI APIs.</p><h3>Conclusion: Elevating the Future of Voice Applications</h3><p>OpenAI’s Realtime API and Function Calling provide new tools for designing voice-first applications. With these features, developers can create more interactive, task-oriented agents that facilitate seamless, natural conversations. As more developers adopt these tools, we can expect further advancements in voice-powered applications that offer more intuitive and personalized experiences.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a21f8ee5a93b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/enhancing-user-experience-with-openais-realtime-api-and-function-calling-a-seamless-a21f8ee5a93b">Enhancing User Experience with OpenAI’s Realtime API and Function Calling: A Seamless…</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Higher-Order Reducers with KeyPath and CaseKeyPath in the Composable Architecture]]></title>
            <link>https://medium.com/aviv-product-tech-blog/higher-order-reducers-with-keypath-and-casekeypath-in-the-composable-architecture-2c0ddfd4239d?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/2c0ddfd4239d</guid>
            <category><![CDATA[keypath]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[higher-order-function]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[composable-architecture]]></category>
            <dc:creator><![CDATA[activesludge]]></dc:creator>
            <pubDate>Tue, 21 May 2024 08:01:38 GMT</pubDate>
            <atom:updated>2024-05-27T14:17:26.971Z</atom:updated>
            <content:encoded><![CDATA[<p>Expand your arsenal with this technique to take your app&#39;s capabilities one step further</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*E7tCQRxQ5zHLmi16.jpg" /><figcaption>A scene from the iconic movie Matrix (1999) where Neo was finally starts to feel he&#39;s the chosen one. This image visualises how I personally felt when I grasped the concept of KeyPath in Swift language for the first time.</figcaption></figure><p>Before we dive into the intricacies of reusable reducers, it’s important to note that this article is tailored for readers who already have a good understanding of <a href="https://github.com/pointfreeco/swift-composable-architecture">the Composable Architecture (TCA)</a>. If you’re new to this framework/architecture, you may find it beneficial to familiarise yourself with it before proceeding.</p><h3>When do we use the Higher-Order Reducers?</h3><p>When we want to extract a common logic across actions, we create a private function and use that method. But, what about common logic across features?</p><p>My first intuition would be to create a dependency that has a name like “SomethingManager”, or “SomethingDoer” whose method takes the necessary arguments, then achives the desired objective, possibly returns a type. And that seems perfectly fine. But, I don&#39;t ever see myself overriding this dependency in testing, preview or any other environment, so a dependency feels like an overkill here. So, is there any other alternative?</p><p>By taking the inspiration from BindingReducer in TCA framework, I want to show you how you can create a <strong>Higher-Order Reducer</strong> that is sharable across any domain. This will take our reducer&#39;s capabilities one step further.</p><h3>A real world case</h3><p>An actual requirement urged us to find a solution with a custom Higher-Order Reducer. One of our form pages in our app has many fields where we need to validate each field with each keystroke and also with the submit action. We find the most optimal solution by intercepting the binding actions and a submit action that parent reducer uses, then finally do the validation and return error messages if needed. Now we are able to use this validation reducer in any other form that needs it!</p><h3>What&#39;s the objective?</h3><p>We are going to:</p><ul><li>Create a reducer that can intercept certain actions, and mutate the parent&#39;s state,</li><li>make it so that the reducer is reusable in any other feature,</li><li>move any logic from the parent reducer into the Higher-Order Reducer that was otherwise bound to the feature.</li></ul><p>In this article, we are also going to utilise KeyPaths from Swift and CaseKeyPaths from TCA.</p><p>In the end, the body inside our feature&#39;s reducer will look like this:</p><pre>@Reducer<br>struct FeatureReducer {<br>  ...<br><br>    var body: some ReducerOf&lt;Self&gt; {<br>      ...<br>  <br>      HigherOrderReducer(<br>        input: \.someProperty,<br>        output: \.someOtherProperty,<br>        triggerAction: \.someAction<br>      )<br>      <br>      ...<br>    }<br>  <br>  ...<br>}</pre><p>If that&#39;s something you&#39;re interested, let&#39;s refresh our memory by covering some bases first.</p><h3>KeyPaths</h3><p>KeyPaths offer a flexible and type-safe mechanism for working with <strong>properties</strong>, whether accessing them directly, using them as parameters in functions.</p><p>To put it in simpler words, you define KeyPaths by saying “Here’s a key where I expect a property, from a certain type, that returns a certain type”. You can write onto or read from these properties.</p><p>\Root.someProperty, \.id , \.self , etc.</p><p>KeyPaths are already being used extensively by Swift and SwiftUI. You may remember them from ForEach view where you provide a KeyPath for id argument.</p><pre>ForEach(<br>  _ data: Data, <br>  id: KeyPath&lt;Data.Element, ID&gt;, <br>  content: @escaping (Data.Element) -&gt; Content<br>)</pre><p>Plus, this concept is gaining more popularity. It’s definitely worth mastering in order to advance in Swift language.</p><p>More information on that can be found below links:</p><ul><li><a href="https://developer.apple.com/documentation/swift/keypath">KeyPath</a> by Apple</li><li><a href="https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift">The power of KeyPaths in Swift</a> by Sundell</li></ul><h3>CaseKeyPaths</h3><p>Unfortunately, a structure like KeyPath did not exist for enum cases where we want pass actions which are exclusively enums. Not until, a new concept is introduced by TCA developers to address the missing part. It&#39;s called CaseKeyPath from <strong>CasePaths</strong> library by <strong>PointFree</strong>. It allows us to pass enum cases just like KeyPaths. It looks like this:</p><p>\.view.didTapButton</p><p>Delicious!</p><p>More information on <strong>CaseKeyPaths</strong> can be found below:</p><ul><li><a href="https://github.com/pointfreeco/swift-case-paths">SwiftCasePaths</a> by PointFree</li></ul><h4>Fun fact</h4><p>Here&#39;s what a <strong>CasePath</strong> looked like before the introduction of <strong>CaseKeyPath</strong>.</p><p>/Action.view.didTapButton</p><p>In order to pass a CasePath like the above the initialiser had to look like this:</p><pre>init(toViewAction: @escaping (Action) -&gt; ViewAction?) {<br>  self.toViewAction = toViewAction<br>}</pre><p>It was essentially a closure where it takes parent action as argument and expects to return another action. As of today, this is how the framework still works behind the counter. But it seems this usage is bound to be deprecated.</p><h3>Higher-Order Reducer</h3><p>In Swift, “higher order” typically refers to functions or closures that either accept other functions/closures as parameters, or return functions/closures as output. These higher-order functions allow for more flexibility and composability in code, as they can manipulate or encapsulate behaviour.</p><p>In earlier days (mid-2019), that was exactly what it looked like in TCA as well. It meant a reducer function that returns a reducer function.</p><pre>func higherOrderReducer(<br>  _ reducer: @escaping (inout AppState, AppAction) -&gt; Void<br>) -&gt; (inout AppState, AppAction) -&gt; Void {<br><br>  return { state, action in<br>    // do some computations with state and action<br>    reducer(&amp;state, action)<br>    // inspect what happened to state?<br>  }<br>}</pre><p>Over years of transformation, in TCA context, it refers to reducers that are shareable, reusable, can handle an objective, and can be built on top of our apps. One of the most important example is the BindingReducer().</p><h3>How would Higher-Order Reducers help us?</h3><h4>Managing complexity</h4><p>In large applications, managing state and actions can get really complex. They help address this problem by allowing you to break down the state management logic into smaller, more manageable units. This makes it easier to understand, maintain, and modify the codebase as your application grows.</p><h4>Reusability</h4><p>They enable you to encapsulate common patterns or behaviours within reusable bodies. This promotes code reuse across different parts of your application, reducing duplication and ensuring consistency in how state is managed.</p><h4>Testing</h4><p>You can write unit tests for individual reducers. By this way we can ensure that each piece of state management logic behaves as expected in isolation.</p><h3>Tutorial</h3><h4>Project Setup</h4><p>Create a new project using Xcode. Minimum requirements for this project is as below:</p><ul><li>Xcode 15.3</li><li>Swift 5.9</li><li>Minimum deployment target: iOS 17.0</li><li><a href="https://github.com/pointfreeco/swift-composable-architecture">TCA</a>: 1.10.0</li></ul><p>Once packages are resolved. Give it a run. You may be asked to enable macros by Xcode. Go ahead and enable them. Then we&#39;re good to go.</p><h4>User story</h4><p>I have come up with a user story for us to start exercising. It’s just a fictional one to keep things as simple as possible for us to step into the concept.</p><blockquote>I want to be able to enter a text into a field. When I tap onto the button, I want to see that same text in uppercased form below. I want to be able to long press and copy the uppercased text. This logic has to be reusable. We’ll call this feature <strong>Shouting text</strong>.</blockquote><p>The logic is to take a <strong>string as input </strong>and return an <strong>uppercased string as output </strong>after a certain <strong>trigger action</strong>.</p><h4>Shouting text</h4><p>First, create a reducer for our feature. Name it ShoutingText.swift . Copy and paste below code to this file.</p><pre>import ComposableArchitecture<br><br>@Reducer<br>struct ShoutingText {<br>    @ObservableState<br>    struct State {<br>        var text: String = &quot;&quot;<br>        var uppercasedText: String = &quot;&quot;<br>    }<br>    <br>    enum Action: BindableAction {<br>        case didTapShoutButton<br>        case binding(BindingAction&lt;State&gt;)<br>    }<br>    <br>    var body: some ReducerOf&lt;Self&gt; {<br>        BindingReducer()<br><br>        Reduce { state, action in<br>            switch action {<br>            case .didTapShoutButton:<br>                // Here&#39;s the state mutating logic that<br>                // we want to move to the new reducer.<br>                state.uppercasedText = state.text.uppercased()<br>                return .none<br>            case .binding:<br>                return .none<br>            }<br>        }<br>    }<br>}</pre><p>Second, create a new file called ShoutingTextView.swift then copy and paste below code inside.</p><pre>import ComposableArchitecture<br>import SwiftUI<br><br>struct ShoutingTextView: View {<br>    @Bindable var store: StoreOf&lt;ShoutingText&gt;<br>    <br>    var body: some View {<br>        Form {<br>            Section {<br>                TextField(<br>                    &quot;what do you mean&quot;,<br>                    text: $store.text<br>                )<br>                .textFieldStyle(.plain)<br><br>                Button {<br>                    store.send(.didTapShoutButton)<br>                } label: {<br>                    Image(systemName: &quot;person.wave.2.fill&quot;)<br>                }<br>            }<br><br>            Text(store.uppercasedText)<br>                .textSelection(.enabled)<br>        }<br>        .navigationTitle(&quot;Shout it!&quot;)<br>    }<br>}<br><br>#Preview {<br>    NavigationStack {<br>        ShoutingTextView(<br>            store: Store(<br>                initialState: ShoutingText.State(),<br>                reducer: ShoutingText.init<br>            )<br>        )<br>    }<br>}</pre><p>When you run the preview you should see a screen like below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/1*zxPvl3gui90DDhde1FLmIA.png" /><figcaption>&quot;Shout it!&quot; preview on canvas</figcaption></figure><p>Try and type something to the text field and tap onto the button below. See the text being uppercased.</p><p>If everything is in order, it means our base code is ready.</p><h3>Creating a Higher-Order Reducer</h3><p>These are not so different from typical feature reducers. They just don&#39;t have to possess a <em>State</em> struct and an <em>Action</em> enum.</p><p>Create a new file called UppercasedReducer.swift, then copy and paste below code inside.</p><pre>import ComposableArchitecture<br><br>// 1<br>@Reducer<br>struct UppercasedReducer&lt;State, Action, UppercaseAction&gt; where Action: CasePathable {<br>    // 2<br>    private let keyPathToText: KeyPath&lt;State, String&gt;<br>    private let keyPathToUppercasedText: WritableKeyPath&lt;State, String&gt;<br>    private let caseKeyPathToAction: CaseKeyPath&lt;Action, UppercaseAction&gt;<br><br>    init(<br>        input keyPathToText: KeyPath&lt;State, String&gt;,<br>        output keyPathToUppercasedText: WritableKeyPath&lt;State, String&gt;,<br>        triggerAction caseKeyPathToAction: CaseKeyPath&lt;Action, UppercaseAction&gt;<br>    ) {<br>        self.keyPathToText = keyPathToText<br>        self.keyPathToUppercasedText = keyPathToUppercasedText<br>        self.caseKeyPathToAction = caseKeyPathToAction<br>    }<br><br>    // 3<br>    func reduce(into state: inout State, action: Action) -&gt; Effect&lt;Action&gt; {<br>        if action[case: caseKeyPathToAction] != nil {<br>            state[keyPath: keyPathToUppercasedText] = state[keyPath: keyPathToText].uppercased()<br>        }<br>        return .none<br>    }<br>}</pre><p>Let&#39;s see what&#39;s happening here:</p><ol><li>UppercasedReducer is the Reducer that will do the job. Thanks to @Reducer macro, we&#39;re free from filling in some boilerplate code, such as conformances. <br>Plus, it&#39;s generic. Which mean its properties will be inferred by the types of properties from whichever Reducer uses it when initialised.<br>State is the parent state. Action is the parent action. UppercaseAction is the sub action of the parent action we want to intercept and mutate the state with.<br>In order to take advantage of CaseKeyPaths, the Action generic parameter has to conform to CasePathable protocol.</li><li>keyPathToText is a <strong>KeyPath</strong> to the text we want to turn into uppercase. It&#39;s read only. The root is the parent state and the value is a String.<br>keyPathToUppercasedText is a <strong>WritableKeyPath</strong> to the final text we want to display on the screen. It&#39;s needs to be writable because we have to replace what&#39;s there after mutation. The root is the parent state and the value is a String.<br>caseKeyPathToAcion is a <strong>CaseKeyPath</strong> to the action we want to intercept and make the state mutation. The root is the parent action and the value is an enum case of that action.</li><li>Here&#39;s the reduce function that will do the mutation. We employ an if statement to determine if the action that is subscripted with the caseKeyPathToAction is not nil. This serves as our interception mechanism for actions. <br>If the condition holds true, we proceed to replace the property referenced by keyPathToUppercasedText in the parent state with the property referenced by keyPathToText within the same parent, right after applying the .uppercased() method. Subsequently, we conclude the operation by returning the .none effect.</li></ol><h3>Usage of the Higher-Order Reducer</h3><p>Return to ShoutingText.swift file and initialise the our new UppercasedReducer inside the body. Since we have no particular side effect to run, go ahead and remove the other Reduce initialiser.</p><pre>import ComposableArchitecture<br><br>@Reducer<br>struct ShoutingText {<br>    ...<br>    <br>    var body: some ReducerOf&lt;Self&gt; {<br>        BindingReducer()<br><br>        UppercasedReducer(<br>            input: \.text,<br>            output: \.uppercasedText,<br>            triggerAction: \.didTapShoutButton<br>        )<br>    }<br>}</pre><p>Thanks to @Reducer macro right above ShoutingText reducer, we don&#39;t have to explicitly conform the Action enum of the feature to CasePathable protocol. It&#39;s already covered! You can expand the macro to see conformances above there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lioe5uvkYPg5Gzy4EyZtDg.png" /><figcaption>@CasePathable macro above Action enum provided by @Reducer macro.</figcaption></figure><p>And that’s it. Run the app and see the reducer is doing its job!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/295/1*gTK0IVuw4b7oQ2M826OX8A.gif" /></figure><p>The end product hasn&#39;t changed a bit, but the underlying difference is huge. Now we have a reducer that is easily sharable. We won&#39;t have to repeat this logic anywhere in our code base. Lovely.</p><p>Congratulations on completing the above journey through the exercise using KeyPaths and CaseKeyPaths in the Composable Architecture!</p><p>By following along with this article, you’ve taken a solid step towards developing reusable reducers in your app.</p><p>On top of my mind, here are some examples where Higher-Order Reducer can be useful:</p><ul><li>Logging</li><li>Tracking events</li><li>State property validations, filters, etc.</li></ul><p>Let me know if you can think of any other use case!</p><p>Keep exploring, experimenting, and pushing the boundaries of what’s possible with the Composable Architecture.</p><p>Cheers!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2c0ddfd4239d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/higher-order-reducers-with-keypath-and-casekeypath-in-the-composable-architecture-2c0ddfd4239d">Higher-Order Reducers with KeyPath and CaseKeyPath in the Composable Architecture</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How did we enhance our app’s compile time?]]></title>
            <link>https://medium.com/aviv-product-tech-blog/how-did-we-enhance-our-apps-compile-time-fe35cf032991?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/fe35cf032991</guid>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[composable-architecture]]></category>
            <category><![CDATA[performance-analysis]]></category>
            <category><![CDATA[compile-times]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Romain Brunie]]></dc:creator>
            <pubDate>Wed, 17 Jan 2024 08:31:00 GMT</pubDate>
            <atom:updated>2024-01-17T09:24:59.310Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g5GiH80PVyhm884I8n1lkA.png" /></figure><p>At AVIV, we consistently seek ways to enhance our developers’ experience working on our apps (SeLoger, Immoweb, Immowelt, and Immonet). Compilation time is no exception. Our latest efforts were concentrated on optimizing the compilation time for the white-label features/packages that our apps currently use.</p><p>Before attempting to improve compile time, we initiated the process by assessing areas for potential improvement. We employed various methods to accomplish this.</p><h3>Xcode Build Timeline</h3><p>We first used Xcode Build Timeline to identify potential bottlenecks that were taking too long to compile. Cleaning our build folder in between builds is crucial so that you can compare them with the performance optimizations you have implemented.</p><h4>Assets</h4><p>We identified that on a fresh build, our asset catalog was taking 19 seconds to build and was blocking compilation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/686/1*6lC9alZyOSX1hLiQ-SQ5cQ.png" /></figure><p>We have a large number of icons imported from Figma, and currently, not all of them are used as not all white-label features have been developed yet.</p><p>This issue is still in progress, and we are considering the following alternatives:</p><ul><li>Should we only import assets that are currently needed?</li><li>Should we create a catalog per theme so that each module imports only the necessary assets?</li></ul><p>It’s worth noting that this issue only affects fresh builds, so is it a genuine concern? Maybe not.</p><h4>SwiftLint</h4><p>In our effort to make our build times faster, we discovered that SwiftLint was running twice for every dependency when building our project.</p><p>Firstly, we observed its presence in the build phases of our project, and then we identified it as a SwiftLint plugin in the manifest file (Package.swift) for each package:</p><pre>for target in package.targets {<br>    target.plugins = target.plugins ?? []<br>    target.plugins!.append(.swiftLint)<br>}</pre><p>This redundancy meant that SwiftLint was executed once by the target and once more for each dependency, doubling its impact on our build times.</p><p>This issue is still under consideration, and we are exploring two alternatives:</p><ul><li>Should we only run SwiftLint on commit?</li><li>Should we remove SwiftLint from our dependencies?</li></ul><h3>Build Timing Summary</h3><p>We used Build Timing Summary, but we did not identify any areas for improvement here.</p><h3>Build time compiler flags</h3><p>We used -Xfrontend -warn-long-function-bodies=200 and -Xfrontend -warn-long-expression-type-checking=200 to gather warnings from the compiler whenever a function or type check takes more than 200 ms.</p><p>To set this up, we updated the manifest file (Package.swift) for each package. We enabled our flags in the Swift build settings of each target within a package.</p><pre>for target in package.targets {<br>    target.swiftSettings = target.swiftSettings ?? []<br>    target.swiftSettings!.append(<br>        .unsafeFlags([<br>            &quot;-Xfrontend&quot;, &quot;-warn-long-function-bodies=200&quot;,<br>            &quot;-Xfrontend&quot;, &quot;-warn-long-expression-type-checking=200&quot;<br>        ])<br>    )<br>}</pre><p>When we use the -Xfrontend flag with unsafeFlags in a Swift Package Manager configuration, we are providing additional flags that will be passed to the Swift compiler frontend during the build process. This is useful for making certain adjustments or optimizations specific to the compiler&#39;s behavior.</p><p>Compiling our packages enabled us to pinpoint significant areas for improvement. Our UI white-label features, which are powered by TCA, presented an opportunity to optimize our build time. First, it’s important to note that we follow the <a href="https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance">performance guidelines</a> outlined by Point-Free.</p><h4>Type inference with EmptyReducer</h4><p>Having a view that displays multiple steps could result in the following reducer:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OTD_lyWATg760R03" /></figure><p>It took 19 seconds for the code to compile from a cleaned build folder. At some point, the build failed because the compiler was unable to type-check this expression in reasonable amount of time.</p><p>We updated the code to provide explicit types to the EmptyReducer, which includes some generics:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WobXS28ojC1fgPgb" /></figure><p>It took 100ms for the code to compile from a cleaned build folder.</p><h4>Type inference with XCTestDynamicOverlay unimplemented method</h4><p>Before TCA 1.5, adding a test value to a dependency could result in the use of the unimplemented method.</p><pre>static let test: Self = .init(<br>    load: unimplemented(),<br>    loadProjectType: unimplemented(),<br>    store: unimplemented(),<br>    delete: unimplemented(),<br>    userType: unimplemented(),<br>    setNonOwner: unimplemented()<br>)</pre><p>With this test value, it took 3 minutes for the code to compile from a cleaned build folder.</p><p>We updated the code to provide an explicit type for the test value:</p><pre>static let test: Self = {<br>    let load: @Sendable () -&gt; OwnerProject? = unimplemented(placeholder: nil)<br>    let loadProjectType: @Sendable () -&gt; OwnerProject.ProjectType? = unimplemented(placeholder: nil)<br>    let store: @Sendable (OwnerProject) -&gt; Void = unimplemented()<br>    let delete: @Sendable () -&gt; Void = unimplemented()<br>    let userType: @Sendable () -&gt; UserType = unimplemented()<br>    let setNonOwner: @Sendable () -&gt; Void = unimplemented()<br><br>    return .init(<br>            load: load,<br>            loadProjectType: loadProjectType,<br>            store: store,<br>            delete: delete,<br>            userType: userType,<br>            setNonOwner: setNonOwner<br>    )<br>}()</pre><p>With this test value, it took less than 10ms for the package to compile from a cleaned build folder.</p><p>Since TCA 1.5, the @DependencyClientmacro automatically provides a public initializer for all endpoints along with a default ‘unimplemented’ value for each endpoint.</p><pre>static let test = OwnerProjectStore()</pre><p>With this test value, compilation time also reduced to less than 10 ms from a cleaned build folder.</p><h3>Conclusion</h3><p>Our exploration revealed several areas where compile times could be improved.</p><p>Generics can sometimes contribute to longer compile times, especially when used extensively or in complex ways, as TCA uses them in its stores and reducers.</p><p>Updating TCA to the latest version and leveraging its new features improved our build compile time in certain areas without requiring additional code.</p><p>As we continue to refine our build process, we remain vigilant in our efforts to find the best approaches for compilation efficiency and deliver a smoother development experience.</p><p>We conducted our optimizations with valuable insights gained from following the guidance provided in this informative <a href="https://www.avanderlee.com/optimization/analysing-build-performance-xcode">article</a>.</p><p>Kudos to Adrien Simon, Omar Belhaouss, and Daniel Ahlborn, who also contributed to these enhancements.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe35cf032991" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/how-did-we-enhance-our-apps-compile-time-fe35cf032991">How did we enhance our app’s compile time?</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elevating Code Quality: The Power of Static Code Analysis in Modern Software Development]]></title>
            <link>https://medium.com/aviv-product-tech-blog/elevating-code-quality-the-power-of-static-code-analysis-in-modern-software-development-e0316e303afb?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/e0316e303afb</guid>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[static-code-analysis]]></category>
            <category><![CDATA[sonarcloud]]></category>
            <category><![CDATA[cicd]]></category>
            <category><![CDATA[tech]]></category>
            <dc:creator><![CDATA[Moataz Nabil]]></dc:creator>
            <pubDate>Wed, 25 Oct 2023 10:01:41 GMT</pubDate>
            <atom:updated>2023-11-02T21:33:39.305Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w2MC2GiNtSCht_cwvkR2Vw.png" /></figure><p>In today’s ever-changing software development landscape, code quality is a must-have requirement. At <a href="https://www.aviv-group.com/">AVIV Group</a>, we are committed to delivering exceptional applications to our customers by ensuring a shared responsibility for quality. To achieve agility, speed, and reliability: static code analysis is essential.</p><p>Integrating code analysis tools into your Continuous Integration and Continuous Delivery/Deployment (CI/CD) pipeline is an effective way to catch code issues early and ensure the health of your codebase.</p><p>In this blog, we will explore together how to seamlessly integrate <a href="https://sonarcloud.io/">SonarCloud</a>, a popular cloud-based code analysis platform, with one of the leading CI/CD tools, <a href="https://circleci.com/">CircleCI</a>.</p><p>Sound interesting? Let’s get started!</p><h3>What is Static Code Analysis?</h3><p>Static code analysis is a technique that involves examining the source code of a software application without executing it. It aims to identify issues, potential problems, code smells, and violations of coding standards by analyzing the code’s structure, syntax, and dependencies.</p><h3>Importance of Static Code Analysis</h3><p>One of the most crucial aspects of static code analysis is the ability to catch issues early in development. This early detection reduces the cost and effort required for fixing issues later in the development cycle, which can be more challenging to address.</p><p>Static code analysis consistently enforces coding standards and best practices across the entire codebase. It checks for issues like <strong>code complexity</strong>, <strong>naming conventions</strong>, <strong>code duplication</strong>, and adherence to <strong>coding guidelines</strong>.</p><p>By maintaining a high level of code quality, development teams can produce more readable, maintainable, and reliable software.</p><p>By automating code analysis, development teams can focus on writing code and delivering features rather than manually reviewing every line of code. This automation streamlines the development process, increases productivity, and allows developers to spend more time on creative problem-solving.</p><p>After learning about static code analysis, let’s explore <a href="https://sonarcloud.io/">SonarCloud</a>, the static analysis tool, and how it fits into the development lifecycle.</p><h3>How SonarCloud fits into agile development</h3><p><a href="https://sonarcloud.io/">SonarCloud</a>’s static code analysis capabilities enable Agile teams to detect code issues, bugs and smells early in development. This early detection helps teams address problems before they accumulate; reducing technical debt.</p><p>SonarCloud allows teams to set custom quality gates based on their specific project requirements. This flexibility ensures that code meets predefined quality criteria before it can be considered “done”.</p><p>SonarCloud supports a wide range of programming languages, including but not limited to <strong>Java, JavaScript, TypeScript, Python, and C#.</strong> SonarCloud also supports IaC, such as <a href="https://www.sonarsource.com/knowledge/languages/terraform"><strong>Terraform code</strong></a>, making it suitable for diverse software development environments.</p><p>SonarCloud seamlessly integrates with CI/CD pipelines, automatically analysing code changes as they are committed. This automation ensures that code quality checks are integral to every build, preventing the introduction of low-quality code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mExK0AYxiBJVtzKAHcYb_A.png" /><figcaption>Sonarcloud integration with CircleCI</figcaption></figure><p>As a cloud-based service, SonarCloud does not require teams to set up and maintain on-premises infrastructure for code analysis. This simplifies adoption and scalability.</p><h3>Integrate SonarCloud with the CI/CD Pipeline</h3><p>Let’s look at how to integrate SonarCloud with CircleCI, step-by-step seamlessly.</p><h3>Prerequisites</h3><p>Before we proceed, make sure you have the following prerequisites in place:</p><ol><li>A GitHub repository with your codebase.</li><li>An account on SonarCloud (<a href="https://sonarcloud.io">https://sonarcloud.io</a>).</li><li>A CircleCI account (<a href="https://circleci.com/">https://circleci.com</a>).</li></ol><p>Step 1. Log in to your SonarCloud account. Create a new project and follow the setup instructions to obtain a unique project key and token as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IiSOqgypQ0T7v8tWWcDK6A.png" /></figure><p>Configure the project with the code changes settings e.g. the previous version of the codebase, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GXgdYyOgSmHom5JE3HSFGg.png" /></figure><p>To ensure the effective use of SonarCloud, 5 essential things should be configured:</p><ol><li><strong>Organization: e</strong>ach SonarCloud organization represents a corresponding organization on the repository platform side.</li><li><strong>Projects: </strong>each imported repository becomes a SonarCloud project.</li><li><strong>Rules</strong>: rules are executed on source code to generate issues. There are four types of rules: Code smell, Bug, Vulnerability, and Security hotspot.</li></ol><p>4. <strong>Quality Profiles: </strong>SonarCloud provides a default quality profile called the Sonar Way profile for each language, activating rules that should apply to most projects. This profile is marked with the “built-in” tag in the interface. If no other profile is explicitly defined at the project level, the default profile for the language will be used.</p><p>New profiles can be created in two ways:</p><ul><li>Copying an existing profile and adjusting the copy.</li><li>Extending an existing profile.</li></ul><p>5. <strong>Quality Gates: </strong>These are defined at the organization level and applied at the project level. Each project has one quality gate assigned from among those defined in the organization.</p><p>After configuring the project, you will be redirected to the project page, including the analysis results, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*npHCHsnsVBOaw_PhtoBPMw.png" /></figure><p>After running the first scan, you can find the Quality Gate status, but it will display the results after the following scan, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S_OdIYlHe3ozJp0ew69cmg.png" /></figure><p>You can see the result if you create a new PR (Pull Request), as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IwGWHiLbLz73Oda1t9BmKw.png" /></figure><p>Then, the status will be updated with details such as Reliability, Maintainability, and other statistics, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*soIR7wQepgRM0YC_NL27VA.png" /></figure><p>You can also find the recent analysis activity on the project, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JdhPb1Kmv_pfxnQ_ZHxwyQ.png" /></figure><p>The SonarCloud bot will also be enabled in the PR view to get the results on every code change, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nXeNojftbwy-_phH5RIfJA.png" /></figure><p>You can also click on the details to redirect to the page, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_LHP2eDwJPvAB4VysstDUQ.png" /></figure><p>There is another way to enable SonarCloud with your GitHub project by enabling <a href="https://github.com/apps/sonarcloud">SonarCloud GitHub App</a>, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hmZTKYqQ_zQeMC_1yB3fmg.png" /></figure><p>Select your GitHub organization or account, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T-7TAsBBh6F7RSGy6kXJ_g.png" /></figure><p>Select the repositories you want to scan, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1ukoAAedFR1RZELE_2EWjQ.png" /></figure><p>Save the changes to enable SonarCloud on the project.</p><p>Now, let’s configure CircleCI to run SonarCloud as a part of the CI/CD pipeline.</p><p>Disabling <strong>automatic analysis</strong> on the project is recommended because it will be run via the CircleCI from the Project Administration; select Analysis Method, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*BNzcF59G9v6vXBbZU-DRPQ.png" /></figure><p>And disable the Automatic Analysis, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UqP80Fa_wvL-JGC-T-WK7A.png" /></figure><p>Set up CircleCI environment variables for your <strong>SonarCloud token</strong> and any other sensitive information you may have; you can find the steps in your SonarCloud project, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3nfqab6De6HBWWbEkuMh7g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YTv0JbdM2Qi_mQ6lwjVIUw.png" /></figure><p>Create a CircleCI context, and name it SonarCloud, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YTv0JbdM2Qi_mQ6lwjVIUw.png" /></figure><p>Add an Environment Variable, and name it SONAR_TOKEN as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UhnRqyoOgdVccPWnwAOgtQ.png" /></figure><p>Create a configuration file in the project&#39;s root directory and name it <strong>sonar-project.properties, </strong>which will include the <strong>sonar.projectKey</strong> and <strong>sonar.organization</strong>, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gtyj2ZUbmpGqQlzdEOfziQ.png" /></figure><p>In your project’s <strong>.circleci/config.yml</strong> file, you’ll need to add the necessary steps to integrate SonarCloud.</p><p>Here’s an example configuration:</p><pre>version: 2.1<br>jobs:<br>  build:<br>    docker:<br>      - image: circleci/node:14<br>    steps:<br>      - checkout<br>      - run:<br>          name: Install dependencies and build project<br>          command: npm install<br>      - sonarcloud/scan<br>orbs:<br>  sonarcloud: sonarsource/sonarcloud@1.0.3<br>workflows:<br>  main:<br>    jobs:<br>      - build:<br>          context: SonarCloud</pre><p>Commit and push the updated <strong>.circleci/config.yml</strong> file to your GitHub repository, triggering your CircleCI pipeline automatically, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ocq-3OR_brZkbARqs2t-Mg.png" /></figure><p>After completing the CI/CD pipeline, visit your SonarCloud project dashboard to view code analysis results, including code quality metrics, security vulnerabilities, and code smells, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GeKI7lNfFf-D7wnI-yGG-g.png" /></figure><p>Congratulations, you configured your first project to run static analysis with the CircleCI CI/CD pipeline.</p><h3>Extra point: Using SonarLint for real-time code analysis within the IDEs</h3><p><a href="https://www.sonarsource.com/products/sonarlint/">SonarLint</a> is a lightweight code analysis tool that provides real-time code analysis directly within integrated development environments (IDEs).</p><p>SonarLint is integrated into popular IDEs such as<a href="https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode"> Visual Studio Code</a>, <a href="https://marketplace.visualstudio.com/items?itemName=SonarSource.SonarLintforVisualStudio2022">Visual Studio</a>, <a href="https://plugins.jetbrains.com/plugin/7973-sonarlint">IntelliJ IDEA</a>, and more. Developers can install and configure the SonarLint plugin specific to their IDE.</p><p>Once installed, SonarLint performs<strong> real-time code analysis</strong> as developers write or modify code. It highlights code issues, bugs, code smells, and security vulnerabilities directly within the IDE, offering instant feedback, as shown in the following image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cTABSO2H7IbA_nl1bH7Zxw.png" /></figure><h3>Conclusion</h3><p>Static code analysis plays a vital role in achieving these objectives. By automating the analysis of code for issues, enforcing coding standards, and detecting security vulnerabilities, static code analysis tools enable development teams to produce higher-quality, more secure, and maintainable software.</p><p>Integrating SonarCloud with your CircleCI CI/CD pipeline is a powerful way to automate code analysis and ensure consistent code quality and security in your software projects.</p><h4>Resources</h4><ul><li><a href="https://www.sonarsource.com/products/sonarcloud/">Online Code Review as a Service Tool, SonarQube Cloud (Formerly SonarCloud)</a></li><li><a href="https://circleci.com/developer/orbs/orb/sonarsource/sonarcloud">CircleCI Developer Hub - sonarsource/sonarcloud</a></li></ul><p>Thank you for reading!</p><p>If you’re interested in joining our Aviv family, you can always find exciting opportunities here: <a href="https://careers.smartrecruiters.com/avivgroup">https://careers.smartrecruiters.com/avivgroup</a></p><p>Good luck and happy testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e0316e303afb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/elevating-code-quality-the-power-of-static-code-analysis-in-modern-software-development-e0316e303afb">Elevating Code Quality: The Power of Static Code Analysis in Modern Software Development</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ultimate guide to the AWS re:Invent]]></title>
            <link>https://medium.com/aviv-product-tech-blog/the-ultimate-guide-to-the-aws-re-invent-59dbc36c61a4?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/59dbc36c61a4</guid>
            <category><![CDATA[reinvent]]></category>
            <category><![CDATA[gui̇de]]></category>
            <category><![CDATA[culture]]></category>
            <category><![CDATA[events]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Ebattisti]]></dc:creator>
            <pubDate>Thu, 19 Oct 2023 12:06:41 GMT</pubDate>
            <atom:updated>2023-10-20T07:30:56.695Z</atom:updated>
            <content:encoded><![CDATA[<p>So, you’ve finally made your mind up? You’ll be part of <a href="https://reinvent.awsevents.com/">re:Invent</a>, the biggest worldwide AWS event which will be held in Las Vegas, from November 27th to December 1st : good for you! Whether it’s your first time or not, it’s always useful to have a checklist to make the most of the event.</p><p>Even though it may seem like a long event, the truth is that time there will fly by!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f0hp8wiAwRVOOW3g9e9jQA.jpeg" /><figcaption>A bunch of happy people</figcaption></figure><p><strong>First, let me explain the genesis of our travel:</strong></p><p>Last year, <a href="https://www.aviv-group.com/">AVIV</a>, (the company I work for as a frontend software developer) brought together a small team of colleagues to participate in re:Invent. Fun fact : AVIV is a younger company that gathered leading European brands in the real estate space. Now, we form a new big family, and that’s a bit what our team looked like at the start of our travels! Yann, backend engineer from Seloger, Jean-Christophe, product manager from Immoweb, Dennis, devops, Cemal, head of devops &amp; Micha, software architect from Immowelt, Laura, data engineering manager, David, head of geo data engineering and myself, from Meilleurs Agents, as all new AVIVers going to rock Las Vegas together.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/831/1*74HVslhnc_F0T5L_SXVOjA.png" /></figure><p>Most of us didn’t know each other when we started, and none of us had ever participated in re:Invent. We didn’t speak the same language, we didn’t come from the same companies and we didn’t do the same job. So we faced some challenges right from the start… Yet, magic happened! Not only did we all have an incredible experience, attended plenty of talks, but we came back together as #<em>oneteam</em> friends.</p><p><strong>Long story short:</strong></p><p>We might have found a little recipe that works, and it seemed like a good idea to share it (because a year ago, we looked on the internet for good advice to organize our trip, just like you).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tGoeeLr7tDxlOh1-iBNxEQ.jpeg" /></figure><p><strong>Our four main topics :</strong></p><p>1. Travel &amp; accommodation</p><p>2. AWS, the event</p><p>3. Fun &amp; Tourism</p><p>4. People</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*plxeQqPprRgA5hD_enTu-A.png" /></figure><p><strong>1. Travel &amp; accommodations :</strong></p><p>We will start with the most urgen…</p><blockquote>Take a day off before it starts to check out the place</blockquote><p>If possible, join the venue two days before the start of re:Invent, this allows you to check in at the hotel with peace of mind, and to have a full day to get your bearings and prepare for the week, including :</p><blockquote>Keep some time off to visit the city</blockquote><p>There are a few must-sees in Las Vegas, and don’t count too much on the downtime between talks to do them, nor in your evenings which will also be busy. A first day there allows you to visit, and spot from afar, the hotels hosting the conferences, which you will have plenty of time to see afterwards. So say hello to them from afar, and save your time for the rest of the city.</p><blockquote>Pick up your badge the day before it starts</blockquote><p>It worth it for two reasons :</p><p>A/ There are fewer people the day before, so you’ll waste less time waiting in line.</p><p>B/ You’ll save yourself a significant amount of stress for the first day of the event.</p><blockquote>Plan some extra room in your luggage</blockquote><p>This one was a really good tip that I was given: you will receive an unimaginable quantity of goodies (despite yourself). So to avoid having to wear 1 week of clothes on you on the return plane, plan some space for goodies right away.</p><blockquote>Take some painkillers</blockquote><p>Carry everything that can save your life, or at least your day! (anti-migraine, throat lozenges, eye drops) As if you were going into the desert. Oh well, actually…</p><blockquote>Pick your hotel to not be too far away from attending places</blockquote><p>You will spend a considerable amount of time commuting from point A to point B. Whether walking, taxi or shuttle, take this into consideration, it feels good, especially at the end of the day, to be able to return easily into your cosy bed.</p><blockquote>You’ll walk a lot, pick your shoes wisely and take a light backpack to carry your daily stuff</blockquote><p>There’s really no point in taking out your fancy clothes even if we’re thinking of palaces and casinos. The idea is to survive a week of alternating time between walking in a crowd, or sitting still. And believe me, it is a sport! Comfortable and practical clothes, sneakers, t-shirts, a sweatshirt for when it’s cold, a backpack (but no need to bring a water bottle, you will find one quickly — for free).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/642/1*g6OSq7hpwfuI9rVOLABNyA.png" /></figure><p><strong>2. AWS, the event</strong></p><blockquote>Schedule is key !</blockquote><p>Start with picking the talks you want to attend and add them to your personal agenda (there are several instances of each. Try to gather talks in the same place as much as possible, distances become painfull across a few days). You’ll also attend labs, workshops - for those: take your laptop. But honestly, for other talks, just don’t take it, it will be useless (and heavy).</p><blockquote>Download the mobile app</blockquote><p>…And charge your phone to 100% everyday, you’ll need it all day long. Take your charger in the backpack or an external battery, you will find sockets everywhere. So then there’s no need to save your battery, you can take photos as much as you want :-).</p><blockquote>Do not miss keynotes and be early to attend them</blockquote><p>Honestly, these are pretty incredible moments to experience. First of all, if you arrive early you will be well seated, and you will enjoy the concert which precedes the talk. I advise you participate in as many keynotes as possible, even if you like a topic less. It is the speaker who makes the difference. My preference was the one of CTO Werner Vogels, I watched the replay twice from home after the event. This guy is really something !</p><p>About keynotes: if you’re not super comfortable with English, you can grab a translation headphone when entering the room.</p><blockquote>Visit (several times) the Partners Village</blockquote><p>It’s fun and interesting : you’ll meet 1:1 with people working from companies you’re probably using the services/tools of (or want to). It’s a perfect time to ask technical or commercial questions and have a valuable conversation. That’s also the place you’ll get all the goodies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2K9uamdxqrI1-XB8KTWdvg.jpeg" /></figure><blockquote>Food won’t be an issue, you’ll get more than you can dream of !</blockquote><p>There will be food and drink everywhere and all the time. Don’t bother with takeaway food, and don’t waste time looking for somewhere to eat outside. The buffets are good and generous, it’s very well organized, no waiting time and you can take a lunchbox if you are in a hurry. AND the menu changes every day (vegetarian, kosher options, etc).</p><p>But if you really want to eat an American-style burger or pizza… the city is fast food heaven for your late evenings.</p><blockquote>Are you lost? No, you’re not.</blockquote><p>There are hundreds of volunteers who know the hotels by heart to show you the way, they are very recognizable, and installed at strategic points. Go see them, it gives them sincere pleasure to repeat for the 1000th time where BALLROOM C is, even if it is right behind you… There are maps, signs, screens, and even old-fashioned arrows everywhere too. We tried to get lost for fun, we NEVER got there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/597/1*t5HE5dXRYYrz8RbMYKme7A.png" /></figure><p><strong>3. Fun &amp; Tourism</strong></p><p>Our very special bullet-list of stuff you don’t want to miss :</p><ul><li>The Bellagio fountain runs a light show several times during the evening, it’s very pretty!</li><li>The Golden Nugget, aka the oldest casino in the city, for a jazz evening and an old fashioned cocktail.</li><li>Wedding chapel: what can sound more like Vegas than being married by Elvis? On the other hand, for your information, if you are going to get married for a laugh, don’t forget… you will indeed be married (legally, and officially, in several states of America).</li><li>Fremont Street, in old Vegas, and its psychedelic LED ceiling.</li><li>The famous Big Roller Coaster at the New York Hotel.</li><li>The second floor of the Venetian Hotel, with its indoor reproduction of Venice and its canals (yes, there are gondolas on the water, and you can even ride them).</li><li>Residencies shows of artists like Ru Paul or… Céline Dion. (it might be too late for the tickets there though).</li><li>There’ll be corporate events organised, try to get invitations (that’s how we played golf on a rooftop at the MGM hotel).</li><li>AWS organises a race - if you’re a runner, take your running shoes.</li><li>You’ll be in Vegas, try just a little bit of gambling!</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K8AEqsOEBu6k40nU6jTzwQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UIzlRQV-TSYPQuut8do8mg.jpeg" /></figure><p><strong>4</strong>.<strong>People</strong></p><blockquote>Pick your travel friends wisely ;-)</blockquote><p>For this advice, we played it “do as I say, not as I do”, but guilty in charge, I have great colleagues (are you looking for work? We’re hiring!). No kidding, it’s all about planning, talking about organisation with your travel mates before your departure can help a lot.</p><blockquote>Network!</blockquote><p>You’ll have 1000 opportunities to meet people, go ahead, go say hi, sit with strangers at lunchtime, don’t be shy if you need some info/help. My biggest laugh was in the shuttle that took us back after the evening party, it felt like a school bus, with people singing and shouting jokes at the assembly, those moments are precious. The volunteers, employees, partners as well as the locals are very welcoming and friendly, don’t miss an opportunity to chat.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/724/1*WIrJQYGO4Z8zC3Gx4UgiRw.png" /></figure><p>I think that with all of this, you are ready to succeed in your journey and experience an incredible re:Invent. We wish you make the most of it. As we mentioned previously, it goes by very quickly but it’s an experience you won’t forget!</p><p>And let’s share your personal best tips to the tech world when you’re back :-).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=59dbc36c61a4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/the-ultimate-guide-to-the-aws-re-invent-59dbc36c61a4">The ultimate guide to the AWS re:Invent</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What I learned implementing UX Writing in a Design System]]></title>
            <link>https://medium.com/aviv-product-tech-blog/what-i-learned-implementing-ux-writing-in-a-design-system-76229f9a575c?source=rss----89805f38d8fb---4</link>
            <guid isPermaLink="false">https://medium.com/p/76229f9a575c</guid>
            <category><![CDATA[content-design]]></category>
            <category><![CDATA[design-system-tips]]></category>
            <category><![CDATA[ux-writing]]></category>
            <category><![CDATA[content-design-system]]></category>
            <category><![CDATA[design]]></category>
            <dc:creator><![CDATA[Mélanie Michou]]></dc:creator>
            <pubDate>Mon, 09 Oct 2023 09:56:43 GMT</pubDate>
            <atom:updated>2023-10-09T09:56:43.053Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nWtWdyjl4uJK8gwtEPGclA.jpeg" /><figcaption>Photo <a href="https://unsplash.com/fr/@balazsketyi?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Balázs Kétyi</a>, <a href="https://unsplash.com/fr/photos/_x335IZXxfc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>It has been proven many times: a design system is a powerful asset that facilitates the seamless scaling of product design and development.</p><p>But imagine taking it a step further, by <strong>integrating UX content guidelines and fixed UI content</strong> into its components.</p><p>With a well-rounded design system that incorporates content, the product team can <strong>realise efficiency gains, and designers can concentrate on their core challenges</strong>. Simultaneously, UX Writers can maintain design consistency without the burden of repetitive microcopy tasks.</p><p>In this article, I’ll share my personal journey, spanning a year and a half at AVIV Group. I’ll take you through the <strong>process of creating a Content Design System from scratch, all while operating as a solo UX Writer</strong>. I’ll also recount the experience of expanding our team to a skilled group of UX Writers, allowing us to <strong>scale our Content Design System, in close collaboration with the Design System team.</strong></p><p><strong>Let’s explore the impact of content within design systems, grounded in real-world experiences!</strong></p><h3>Unlocking the power of a Content Design System</h3><p>Design will never exist without words, and words will never exist without design.</p><p>Think of them as a dynamic duo that makes our digital products tick. But with words scattered everywhere, it’s crucial to have some rules in place to use them consistently and efficiently. This is where a Content Design System comes into play.</p><h4><strong>So, what’s a Content Design System?</strong></h4><p>It’s like a helpful guide, not just for UX writers but for everyone involved in creating content — designers, writers, Product Managers, you name it. It gives us patterns for content, a style guide, and resources to ensure our content is accessible and inclusive.</p><h4><strong>Why is this important?</strong></h4><p>Well, it keeps things consistent, saves us time, and lets us focus on the more complex stuff.</p><p>As I mentioned earlier, it’s a smart move for UX writers to invest time in building these guidelines. It might take a <strong>bit of effort upfront, but it pays off big time down the road</strong>. It makes integrating simple text (like buttons) a breeze and keeps everything looking and sounding the same.</p><p>But creating a content design system isn’t a walk in the park. It takes time, experience, and a good amount of data about the content you’re dealing with. <strong>This ensures that the guidelines and examples you end up with, not only work well but also deliver a fantastic user experience.</strong></p><h3>Getting Started with integrating Content into a Design System</h3><p>At AVIV, (a prop-tech company with a European presence in Real Estate) our focus largely revolves around white label products. This means we create one design in English and then adapt and localise it for various markets and brands. With this context in mind, our attention naturally turned toward developing guidelines for addressing users in the realm of white label products.</p><p>Starting from scratch, <strong>the task of building a Content Design System can seem quite daunting, especially as a solo UX Writer within a team of over 300 product professionals!</strong></p><h4>Prioritisation became key.</h4><p>My approach was to begin somewhere tangible, and that’s where the content scorecard came into play. <strong>I stumbled upon this practical tool while reading ‘Strategic Writing for UX’ by Torrey Podmajersky.</strong> It provided a simple yet effective means of measuring content effectiveness and improving the user experience. It proved valuable for conducting content audits and assessing the quality of flows during UX Writing reviews with designers and product managers.</p><h4>In fact, I even created a <a href="https://docs.google.com/spreadsheets/d/16cecpmt9OPPJpAkkwlXCAT9nwnA7_sKL/edit?usp=sharing&amp;ouid=107525320505658165637&amp;rtpof=true&amp;sd=true">Content scorecard template</a> for broader use!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*U6LsYdgilo7S6hLlaLLcnQ.png" /></figure><p>With the content scorecard, I had established initial guidelines related to usability and tone of voice. <strong>It served as a clear and efficient resource for crafting UX content that was accessible, concise, and clear</strong>. It also delved into the nuts and bolts of writing, covering concepts, vocabulary, syntax, punctuation, and capitalisation.</p><p>After putting it to the test, I was ready to take the next leap — integrating content guidelines into our Design System. <strong>This required collaboration with the Design System team to find the best way forward</strong>. We decided to create dedicated content documentation space within Zeroheight as a central tool.</p><p>Our goal extended beyond documentation; we aimed to gain insights into how designers utilised the Design System and what content-related support they required. <strong>We reached a consensus to provide content guidelines for components that needed them the most</strong>, including buttons, dropdowns, and info states — elements commonly used by designers.</p><p>In the world of a truly integrated Design System, <strong>content becomes an integral part of the components themselves</strong>. The idea was to seamlessly link documentation, components, and prioritise tasks based on the specific needs of designers.</p><h3>Defining Content Guidelines: a closer look</h3><p>To ensure everyone grasped the essence of our Content Design System, I kicked off by introducing its core concepts, delving into UX Writing, and <strong>providing a clear roadmap for effectively utilising the system</strong>.</p><p>The journey began with a section dedicated to UX Writing principles. Here, I emphasised the fundamental aspects of being clear, concise, and conversational in our content. To make these principles crystal clear, <strong>I peppered the text with numerous concrete and illustrated examples of the content actually present in our products</strong>. These real-life ‘Do and Don’t’ examples helped drive home the importance of these principles.</p><h4>In the pursuit of consistency, I crafted two crucial sections:</h4><ul><li><strong>Content Patterns</strong>: as my time at AVIV progressed, I began to collect questions from designers about content. Many of these revealed opportunities to establish content patterns — <strong>golden formulas for certain types of messages and concrete examples</strong> for maintaining consistency, particularly in areas like error messages, success notifications, numbers, push notifications, and feedback messages.</li><li><strong>UX Glossary</strong>: this section was particularly challenging. I linked our Content Design System to a Google Sheet that we update monthly to keep pace with our progress. This sheet houses a comprehensive list of words, <strong>localised in our four main languages and linked to our localisation tool</strong>. This dynamic approach ensures cross-team consistency and efficiency.</li></ul><p>In addition to these core sections, I <strong>worked closely with our Accessibility expert</strong> to create a dedicated section on accessibility and inclusion. Here, I curated a wealth of resources to ensure readability, accessibility for screen readers, inclusivity (covering antiracist and gender-inclusive language), and other resources that contribute to a more inclusive and user-friendly experience.</p><h3>Scaling up a Content Design System</h3><p>With an initial version of our Content Design System in place, the natural question was how to continually enhance it. <strong>Simultaneously, I expanded our UX Writing team by bringing in two new members</strong>, and together, we embarked on a journey to scale up the Content Design System.</p><h4>Here’s how we did it:</h4><ol><li><strong>Building a backlog for continuous improvement</strong>: we initiated the process by creating a backlog of page ideas to further develop the Content Design System. To ensure steady progress, we set an ambitious goal of creating one new page or documentation per UX Writer each month, aiming to elevate our system to the next level.</li><li><strong>Collaborative monthly reviews</strong>: each month, our team comes together to brainstorm and decide what to add to the backlog and what needs prioritisation based on the specific needs of our designers. This collaborative approach keeps us agile and aligned with evolving requirements.</li><li><strong>Alignment with branding</strong>: we maintain close collaboration with the brand department to establish a unified voice for AVIV and its various brands. Ensuring alignment in voice and tone is critical for a seamless user experience.</li><li><strong>Communication and awareness</strong>: we’ve put significant effort into ensuring that everyone within the organisation is not only aware of the Content Design System but also knows how to use it effectively. We utilise a dedicated Slack channel for UX Writing to keep everyone informed about updates. Additionally, we share updates in the Design System Slack channel. We also take advantage of opportunities like design and product all-hands meetings and reviews to showcase our work and solicit feedback. Lastly, the UX Writing team has been integrated of every design critiques, a great opportunity to give our insights and remind designers about the existance of the Content Design System!</li><li><strong>Training sessions</strong>: to foster understanding and competence in UX Writing, we conduct training sessions for designers and product managers interested in enhancing their skills in this domain. These sessions serve as a valuable platform to introduce the Content Design System and the wealth of resources it offers.</li><li><strong>UX Writing Checklist</strong>: to streamline the integration of UX Writing principles into the design process, I conceived a concise UX Writing checklist. This checklist serves as an accessible entry point to the Content Design System. Designers can conveniently use it as a component directly within Figma, facilitating seamless integration into their design workflows.</li></ol><h4>If you’re interested in having your own customisable <a href="https://www.figma.com/community/file/1271402001555875835/UX-Writing-Checklist-(Community)">UX Writing checklist</a>, you can find it!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RzhiQNHYvKCtcT543STspw.jpeg" /></figure><h3>Next Steps and closing thoughts</h3><p>As we look ahead, our primary focus is on scaling our Content Design System to greater heights. We aim to provide crystal-clear guidelines not only for white-label products but also for each distinct brand, <strong>integrating more UI content directly into components in multiple languages, and crafting templates to streamline localisation efforts.</strong></p><p>Moreover, we’re eager to gauge the <strong>success of our Content Design System through key performance indicators</strong>. While we currently rely on qualitative feedback and insights, we’re keen to quantify the impact by assessing consistency across products and measuring the efficiency gains for our designers.</p><p>In the grander scheme of things, dedicating time to implement a Content Design System has proven to be a <strong>highly rewarding experience and a strategic investment for our UX Writers</strong>. It champions consistency, elevates the overall quality of the user experience, and provides an invaluable opportunity to step back from day-to-day tasks to document essential guidelines. It’s also a great asset when onboarding new UX Writers, enabling them to quickly adapt to the company’s unique voice and style.</p><p>Moreover,<strong> it fosters cross-team alignment and strengthens collaboration with stakeholders</strong>, including designers, product managers, accessibility experts, and, naturally, the Design System team.</p><p>If you’re looking to elevate your design team’s capabilities, I strongly recommend <strong>starting the process of setting up a Content Design System sooner rather than later</strong>.</p><p><strong>Finally, if you want to embark on the journey of crafting a robust Content Design System for your organisation, don’t hesitate to reach out!</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/874/1*gHQwlRbkd5HLpsu-LyX53A.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=76229f9a575c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/aviv-product-tech-blog/what-i-learned-implementing-ux-writing-in-a-design-system-76229f9a575c">What I learned implementing UX Writing in a Design System</a> was originally published in <a href="https://medium.com/aviv-product-tech-blog">AVIV Product &amp; Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>