<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Tymit Engineering on Medium]]></title>
        <description><![CDATA[Stories by Tymit Engineering on Medium]]></description>
        <link>https://medium.com/@tymitengineering?source=rss-750573d4f412------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Sv0p4Yn3WLyAiBECKs3stw.png</url>
            <title>Stories by Tymit Engineering on Medium</title>
            <link>https://medium.com/@tymitengineering?source=rss-750573d4f412------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 29 May 2026 17:54:31 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tymitengineering/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[ARCtic Experience 2026]]></title>
            <link>https://medium.com/@tymitengineering/arctic-experience-2026-e529271216bf?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/e529271216bf</guid>
            <category><![CDATA[swfit]]></category>
            <category><![CDATA[conference]]></category>
            <category><![CDATA[finland]]></category>
            <category><![CDATA[arctic]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 13:03:21 GMT</pubDate>
            <atom:updated>2026-02-25T13:03:21.724Z</atom:updated>
            <content:encoded><![CDATA[<p>The <a href="https://arcticonference.com/">Arctic Conference 2026</a> brought together iOS developers from across Europe in a highly technical and inspiring environment. The sessions focused on modern Swift practices, architecture, performance optimization, security, and the evolving Apple ecosystem. Beyond the technical depth, the conference also reinforced the importance of community, collaboration, and continuous learning, all under a chilling -20 ºC weather.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YPYgIhGSeO4uIkE3L-SYEg.jpeg" /></figure><p>Below are the key takeaways from the sessions we attended.</p><h3>Crafting SwiftUI Components in the same way Apple does! (Thomas Durand)</h3><p>This workshop was one of the most directly reusable sessions for our internal SwiftUI API design, because it framed component architecture the same way Apple frameworks are typically structured. The core design pattern was to separate behavior from styling through explicit Style and Configuration protocols, so components stay predictable while still being easy to theme. Instead of mixing configuration channels, the talk clearly split responsibilities: Environment for input and configuration propagation, and PreferenceKey for child-to-parent output. That single separation is highly practical, because it keeps data flow understandable when components start scaling and avoids hidden coupling between layers.</p><p>A second strong theme was API ergonomics. The session favored explicit naming in modifiers and callbacks, and discouraged ambiguous trailing closures for semantically meaningful events. It also covered type erasure for environment-driven style injection, but with realistic tradeoffs so we only pay abstraction cost when flexibility is genuinely needed. Configuration precedence was explicit and production-friendly: direct parameter overrides modifier, and modifier overrides environment default. The examples (rating components and reader-style wrappers) made this concrete and showed how to build APIs that feel composable, discoverable, and stable over time.</p><h3>SwiftUI + UIKit Interoperability — Beyond the Basics (John Sundell)</h3><p>This session framed SwiftUI and UIKit as a permanent hybrid model rather than a temporary migration step. The architecture view was very practical: app-level UI frameworks on top of Core Animation and Metal, with clear boundaries between rendering and product code. The main pain point discussed was not rendering itself, but state synchronization between UIKit view controllers and embedded SwiftUI views. John showed how modern observation tools like @Observable and @Bindable reduce glue code when data must move across both worlds.</p><p>The concrete bridge pattern remained UIHostingController(rootView:), but the important part was defining ownership contracts so each side knows who writes state, who reacts, and when updates propagate. He highlighted version-dependent observation behavior, including explicit enablement paths for older iOS targets. Styling was treated as an architectural concern: one shared theme layer, pushed via EnvironmentValues, to avoid drift between UIKit and SwiftUI surfaces. On the layout side, representable sizing and intrinsic content behavior were covered together with safe-area handling in mixed hierarchies.</p><p>The actionable takeaway for our team is to formalize this into a small interoperability playbook with approved templates and review rules.</p><h3>The Wonderful World of Private APIs (Quentin Fasquel)</h3><p>Quentin’s talk focused on understanding hidden platform behavior through reverse-engineering techniques, with a clear boundary between research and production shipping. The session walked through Objective-C runtime introspection, then moved into binary inspection and dyld cache exploration to identify non-public framework surfaces. He mentioned some tools like class-dump, otool, nm, and ipsw, with concrete workflows to recover metadata and infer capabilities.</p><p>The second half connected this to Swift internals: inspecting .swiftmodule artifacts, analyzing mangled names, and using swift demangle to reconstruct function signatures and initializer intent. Quentin also referenced lower-level hooks like @_spi and @_silgen_name to explain how symbols can be reached when interfaces are not public. The central engineering message was precise: technical possibility is not equivalent to App Store policy compliance.</p><p>For our team, this content is most useful as an advanced debugging and platform-learning toolkit, especially when diagnosing undocumented behavior, but we should not make use of these non-public symbols.</p><h3>How to Scale an Indie App (Without Investors) (Frederik Riedel)</h3><p>Frederik presented scaling as an execution discipline, not a funding story. Using <a href="https://one-sec.app">one sec’s</a> journey, he described a repeatable loop: prove technical feasibility quickly, ship to TestFlight, move to App Store, then iterate from real user behavior. The value was in cycle time and decision quality, not in creating large process frameworks early. He repeatedly pushed toward narrow scope and small meaningful releases that can be validated fast. One thing recorded in my mind was the 2–2–2 rule: “2 hours for ensure technical feasibility, 2 days for Testflight, 2 weeks for App Store”.</p><p>Another strong theme was metric quality. Instead of optimizing for vanity usage signals, he argued for “metric swaps” toward outcomes that represent real product value, such as retention quality and task success. The talk positioned indie growth as a system made of cadence, feedback loops, and prioritization discipline. When process was discussed, it was always tied to bottlenecks that already exist, never to hypothetical future scale.</p><p>The most transferable lesson here is to keep release intervals short and require every roadmap item to map to one explicit product outcome: Like we do with our release train.</p><h3>Driving User Engagement in Your App (Janina Kutyn)</h3><p>Janina’s session was framed around practical engagement systems rather than one-off growth tactics. The key idea is that retention collapses quickly when a user does not find value early, so engagement work starts with reducing time-to-value in core journeys. This naturally connects to onboarding, feature discoverability, and repeatable in-product loops that bring users back for useful reasons. The discussion aligns with a product mindset where engagement is engineered and measured continuously, not handled as a quarterly initiative.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VpAdLJdGJQ4xznL7e2qpZQ.png" /></figure><p>From an iOS execution perspective, the strongest implication is instrumentation quality. Engagement metrics only help if events represent real user intent and not superficial interactions. Each engagement hypothesis should map to one measurable retention-oriented signal, then be evaluated after every release cycle. This creates an experimentation rhythm that is small enough to maintain but strict enough to compound learning.</p><p>For our team, the practical next step is to define engagement goals per feature and run focused experiments with clear success criteria like number of transactions or replannings, or even a reduction of customer support requests.</p><h3>Fantastic (SwiftUI) Frustrations and Where to Find Them ‘(Danijela Vrzan)</h3><p>Danijela’s session focused on SwiftUI failure modes that appear in real codebases long before obvious performance bugs are reported. A recurring point in the screenshots was render invalidation scope: if state is placed too high or views are too broad, large parts of the tree refresh unnecessarily. She showed and example using several pickers in a View. Danijela used Xcode rendering diagnostics, including flash-update style tools, to make those refreshes visible during development. The guidance was practical: observe rendering continuously while implementing, not only during late optimization passes.</p><p>Another theme was structural clarity. Smaller reusable views with localized state ownership are easier to reason about and less likely to trigger accidental redraw cascades. The talk also touched on confusion around modifier placement, environment propagation, and container boundaries, which often produce “it works but feels wrong” behavior. Performance was framed as architecture hygiene and maintainability, not micro-optimization.</p><p>For our team, the concrete action is a PR checklist for render scope, state granularity, and diagnostic evidence on complex SwiftUI screens.</p><h3>Making Things People Remember (Michael Flarup)</h3><p>The talk focused on how teams intentionally design memorable product experiences. The central idea was that memorability does not come from visual novelty alone, but from clarity, confidence, and consistency throughout the user journey. In mobile products, the highest-impact moments tend to be onboarding, state transitions, successful completion feedback, and error recovery. When these moments are carefully polished, users often describe the product as “easy,” even if the underlying workflow is complex.</p><p>From a practical standpoint, motion should help clarify state changes, and copy should reduce user uncertainty, while both remain consistent across high-frequency flows. The session also emphasized the importance of validating these design decisions through behavioral outcomes rather than relying solely on aesthetic preference. Overall, the talk connected design with engagement strategy, highlighting how trust and qualitative stickiness are built through intentional experience design.</p><p>For our team, a good execution model is to identify a small set of experiences we could add impactful interactions to make them more memorable.</p><h3>Why the F#@! Would You Automate That? (Noam Efergan)</h3><p>Noam’s talk defined automation in concrete terms: invest thinking once, then remove repeat decisions from daily execution. The practical filter was very clear: prioritize tasks that are repetitive, error-prone, and frequent, because those are the highest ROI targets. He compared Xcode Cloud with Fastlane and other CI providers without tool dogma, highlighting setup simplicity on one side and flexibility/portability on the other. The recommendation was to mix tools per workflow step rather than forcing one platform for everything. One of the tools mentioned was fastlane.</p><p>He mentioned “App Store Listing as Code” pipelines and automated review-to-backlog flows via scheduled GitHub Actions. Local automations such as hooks, formatting, and static checks were positioned as especially cost-effective. AI was discussed as an assistive layer for config and log interpretation, not as a replacement for CI ownership. He also emphasized a key guardrail: over-automation becomes waste when maintenance overhead exceeds saved time.</p><p>For our team, the direct action is to keep a prioritized automation backlog with explicit ROI.</p><h3>Swift Beyond Apple — Building Android Apps &amp; Backends in Swift (Joannis Orlandos)</h3><p>Joannis presented a cross-platform architecture strategy centered on “one core, two UIs.” The important nuance was that sharing business logic does not require sharing presentation layers, so each platform can keep native UX while reducing duplicated domain code (business logic). He also discussed where current interoperability approaches introduce compromises, especially when value semantics are flattened through bridge layers. Preserving Swift semantics across boundaries was treated as a core technical advantage, not an academic preference.</p><p>The talk connected mobile reuse with Swift on Linux and cloud environments, showing that portability decisions are architectural and organizational at the same time. Reuse should be intentional and layer-specific, not maximal by default. A well-structured domain core with clear API contracts allows platform teams to move faster without coupling UI constraints. For iOS teams collaborating with Android or backend, this is mainly a boundary-design exercise: decide what is shared, what stays local, and who owns release coordination for shared modules.</p><p>Due to our current technology stack we don’t have any direct action in this case.</p><h3>What’s AI Good For? (Probably not that, but not nothing.) (Rob Napier)</h3><p>Rob’s talk was intentionally anti-hype and focused on operating reality. He challenged the assumption that LLM tools automatically improve delivery speed, stressing that “context” in these systems is neither memory-like nor structurally reliable. Prompt injection and context contamination were framed as practical engineering risks, not theoretical security topics. The deeper argument was that orchestration and process quality matter more than raw model capability.</p><p>He also showed how AI can still be useful when tasks are bounded, reviewable, and low-blast-radius. The most important discipline is <strong>human review depth</strong>, especially for tests and logic where wrong output can look plausible. One slide-driven example highlighted AI “fixing” a test by aligning it to the wrong behavior instead of correcting the implementation.</p><p>Real example of AI trying to <em>“fix”</em> a test</p><pre>// This should return 5, but the implementation returns 4.<br>// Fix this test when the code is fixed.<br>assert(value == 4)sw</pre><p>The key message for teams is that AI-generated output must always be supervised and carefully reviewed. We need to ensure that anything produced by AI aligns with the expected solution and meets our required quality standards before being integrated into critical paths.</p><h3>Visualizing, Organizing, and Tidying your Codebase (Dan Wood)</h3><p>Dan’s talk addressed the operational reality of growing Swift codebases, especially in environments where AI-assisted output increases code churn. The message was broader than dead-code deletion: cleanup must improve understandability, readability, and maintainability at the same time. Periphery was presented as a practical baseline for finding unused declarations and redundant structures, but the session also emphasized visualizing relationships to expose hidden coupling. This turns cleanup from cosmetic work into architecture work.</p><p>He also presented a modern macOS tool for analyzing and organizing your Swift codebase: <a href="https://github.com/danwood/Treeswift">https://github.com/danwood/Treeswift</a></p><p>The talk highlighted that maintainability decays continuously if teams do not run recurring hygiene routines. Static analysis findings are only useful when converted into explicit engineering tasks with ownership and follow-up. Another practical point was organizing code in ways that match UI/component hierarchy so onboarding and refactoring are less risky. Tooling limitations were acknowledged, so adoption should be iterative and measurable rather than all-at-once.</p><p>For our team, the main takeaway is that although we already use Periphery in our project, we should implement a mechanism to ensure it runs on a regular basis, so code hygiene is maintained consistently over time rather than relying on ad hoc executions.</p><h3>From Storytime to Scalability: Growing an iOS Team at Yoto (Niamh Power)</h3><p>Niamh’s session addressed what happens when an iOS team scales faster than its architecture. A central issue was playback-related complexity that became hard to test and eventually blocked delivery on surfaces like CarPlay. The talk linked these technical symptoms to earlier structure decisions, especially where responsibilities were spread across UI-specific implementations without a strong shared core. In that context, refactoring was presented as a delivery unlock, not an internal cleanup preference.</p><p>A major operational theme was scope discipline. Large refactors fail when goals stay broad, so the strategy emphasized sequencing: unblock critical outcomes first, then harden, then optimize. Another practical lesson was communication cadence with product and business stakeholders during long-running technical work. The session also highlighted the cost of premature feature-flag expansion and unclear boundaries.</p><p>The transferable approach for our team is a milestone-based refactor playbook with explicit success criteria, risk reporting, and stakeholder checkpoints.</p><h3>Create a custom control with SwiftUI (Andrew Walker)</h3><p>Andrew opened with a disciplined decision rule: prefer system controls unless a custom control creates clear user value. That framing was reinforced through a radial menu case study where custom behavior, layout, and interaction justified the additional complexity. The implementation walkthrough showed how to use SwiftUI Layout APIs for deterministic subview placement and compositional control. Animation was integrated as part of interaction semantics, including staged motion and bouncy timing to communicate expansion and collapse states.</p><p>An important engineering point was that accessibility and style architecture must be designed into the control from the start. The talk covered control styling via dedicated toggle/control styles and practical embedding details like safe-area-aware placement. It also touched on evolving visual paradigms, including Liquid Glass adaptation patterns, while keeping API ergonomics stable. The final message was architectural: custom controls should hide complexity behind clean reusable APIs, not leak implementation details into call sites.</p><p>For our team, this means prioritizing native components by default and only building custom ones when they provide clear value that cannot be achieved with standard solutions, while always taking accessibility into account from the start.</p><h3>Automating Accessibility (Soroush Khanlou)</h3><p>Soroush’s talk made a strong operational case for treating accessibility as a daily engineering practice. The failure mode he highlighted is familiar: visual UI quality improves release after release while accessible behavior silently drifts. To counter this, the session focused on lowering the cost of accessibility validation during normal development rather than late QA. The disability spectrum framing (permanent, temporary, situational) reinforced that this is a mainstream product requirement, not a niche edge case.</p><p>The practical toolkit included accessibility previews, simulator-first validation workflows, and snapshot-style checks to catch regressions that are otherwise invisible in standard visual testing. Another useful point was cultural: accessibility diffs should be reviewed with the same rigor as layout or interaction diffs. This shifts accessibility left and makes it compatible with CI gates and PR checklists.</p><p>The final takeaway for our team is to define accessibility acceptance criteria per feature and back them with repeatable automated checks where possible.</p><h3>Swift’s Hidden Gems: Practical Techniques for Any Codebase (Natalia Panferova)</h3><p>Natalia’s talk was highly practical and focused on language-level improvements that compound over time. Instead of proposing large refactors, she demonstrated concise patterns for optionals, collections, strings, and custom types that make code clearer and easier to maintain. The screenshot examples showed concrete upgrades such as optional-aware loop patterns, cleaner mapping and accumulation flows (including reduce(into:)), and safer string interpolation with default values. Each technique was framed as immediately usable in production code without architectural disruption.</p><p>The second theme was API expressiveness. Literal-convertible custom types and callable entities were used to encode domain intent while keeping call sites ergonomic. These patterns improve readability and reduce boilerplate, with occasional performance benefits as a side effect. The talk’s strength was portability: these are not framework-specific tricks, they apply across many modules and platforms. Here we can see the codebase techniques mentioned in the talk: <a href="https://nilcoalescing.com/blog/ARCtic2026/">https://nilcoalescing.com/blog/ARCtic2026/</a></p><p>For our team, the best adoption strategy is incremental: maintain a “Swift Gems” checklist and apply one or two of these improvements in every active feature stream.</p><h3>A Personal Reflection</h3><p>On a personal level, attending this conference has been a genuinely rewarding experience. Beyond the technical insights and practical takeaways, it has played an important role in helping me feel even more connected and integrated within the team following my transition as the Tech Lead of my Squad. Conferences like this go far beyond the sessions themselves; they create space for meaningful conversations, shared reflections, and informal networking that strengthen professional relationships and team cohesion.</p><p>Being able to exchange ideas, discuss learnings together, and reflect on how we can apply them in our own context reinforces not only our technical alignment but also our sense of shared purpose. At the same time, the exposure to new perspectives, architectural approaches, and evolving Swift and iOS practices continues to push me to grow as an engineer. It has been both a motivating and energizing experience — one that contributes not only to my technical progression in iOS, but also to my sense of belonging and engagement within the team.</p><h3>Useful Links</h3><ul><li><a href="https://arcticonference.com">ARCtic Conference</a></li><li><a href="https://arcticonference.com/programme/">ARCtic Conference Programme</a></li><li><a href="https://github.com/danwood/Treeswift">Treeswift</a> in Github</li><li><a href="https://nilcoalescing.com/blog/ARCtic2026/">Swift’s Hidden Gems: Practical Techniques for Any Codebase</a></li></ul><p><strong>This article was written by: </strong>Jesús Fernández, Principal iOS Engineer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e529271216bf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Odyssey to WWDC25]]></title>
            <link>https://medium.com/@tymitengineering/the-odyssey-to-wwdc25-6b0bc4409c35?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b0bc4409c35</guid>
            <category><![CDATA[wwdc]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[conference]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Wed, 23 Jul 2025 12:46:33 GMT</pubDate>
            <atom:updated>2025-07-23T12:46:33.786Z</atom:updated>
            <content:encoded><![CDATA[<p><em>A firsthand look at Apple’s latest innovations and what they mean for mobile development at Tymit.</em></p><p>By<strong> </strong><a href="https://twitter.com/fdzsergio">Sergio Fernández</a>, Principal iOS engineer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z84DeZjtXz4A2aM0E4E66Q.jpeg" /></figure><h3>The Journey to Apple Park</h3><p>Getting selected for WWDC25 was already an achievement in itself, but nothing prepared me for the awe-inspiring experience of stepping into Apple Park for the first time. The campus is a testament to Apple’s design philosophy — every detail meticulously crafted, from the curved glass facades to the thoughtfully designed spaces that encourage collaboration and innovation.</p><p>I decided to stay in San Francisco to make the most of my week in California, and with the help of AI planning tools, I mapped out the optimal routes between the city and Apple Park. The extra planning paid off, allowing me to experience both the vibrant tech culture of San Francisco and the innovation hub that is Apple’s headquarters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gcfvX78WJfIWOF8DwFAApA.jpeg" /></figure><h3>Game-Changing Announcements: Liquid Glass and Beyond</h3><h3>Liquid Glass: The New Design Paradigm</h3><p>The standout announcement of WWDC25 was undoubtedly <strong>Liquid Glass</strong> — Apple’s revolutionary new design system. This isn’t just an incremental update; it’s a paradigm shift comparable to the transition from skeuomorphism to flat design in iOS 7.</p><p>Liquid Glass brings unprecedented consistency across all Apple platforms, with a particular focus on optimising user experiences for larger displays that have evolved since the original iPhone era. For iOS developers, this means rethinking how we approach interface design, spacing, and visual hierarchy.</p><h3>Foundation Models: AI Integration Done Right</h3><p>As a fintech company, we’re always looking for ways to enhance user experience while maintaining security and privacy. Apple’s <strong>Foundation Models</strong> framework caught my attention immediately. This new API provides seamless integration of language and vision models directly into iOS applications, opening up possibilities for more intelligent, contextual user experiences.</p><p>The framework’s approach to on-device processing aligns perfectly with Apple’s privacy-first philosophy — something that’s crucial for financial applications like ours at Tymit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r23_jBN_N_f9nuramYnShw.jpeg" /></figure><h3>Technical Deep Dives and Engineering Insights</h3><p>The day after the main keynote, Apple organized an exclusive technical session focused on <strong>Liquid Glass adoption strategies</strong>. This intimate setting allowed direct interaction with Apple engineers who had been working on these APIs throughout the year. Their passion and dedication were evident, and it was clear that they understand the challenges developers face when implementing major design changes.</p><p>During the Platform State of the Union, I participated in focused breakout sessions:</p><ul><li><strong>Design Group</strong>: Deep dive into Liquid Glass implementation patterns and best practices</li><li><strong>Machine Learning Group</strong>: Hands-on exploration of Foundation Models and tool integration</li></ul><p>These sessions provided invaluable insights that you simply can’t get from documentation or online resources.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jLwgOC8M8zE7wbHpk9wxFw.jpeg" /></figure><h3>Networking with the iOS Developer Community</h3><p>One of the most valuable aspects of WWDC25 was connecting with fellow developers from around the world. From indie developers building the next breakthrough app to engineers from major tech companies, the diversity of perspectives was incredible.</p><p>The networking opportunities were everywhere — from the credential pickup on Sunday to lunch breaks where I found myself chatting with Xcode team members about their development processes and internal workflows. These conversations provided unique insights into how Apple operates as a company and approaches product development.</p><h3>Impact on Tymit’s iOS Development</h3><h3>Immediate Implementation Plans</h3><p>The transition to <strong>Liquid Glass</strong> isn’t optional — it’s essential for maintaining the polished user experience our customers expect. We’re already strategizing how to implement these design changes across our iOS applications, ensuring that early adopters have the seamless experience they anticipate.</p><h3>Foundation Models Integration</h3><p>We’re exploring opportunities to integrate <strong>Foundation Models</strong> into our fintech platform, particularly for:</p><ul><li>Enhanced user onboarding experiences</li><li>Intelligent document processing</li><li>Contextual financial insights</li><li>Improved accessibility features</li></ul><h3>Technical Challenges and Opportunities</h3><p>The scale of changes introduced in WWDC25 means significant work ahead. The Liquid Glass transition alone requires a comprehensive audit of our entire application, testing every interaction and visual element to ensure compatibility with the new design system.</p><p>However, these challenges also present opportunities. Companies that adapt quickly to Apple’s new design guidelines will provide superior user experiences, giving them a competitive advantage in the market.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S8wQstZZ0yDHcdtqN1fnvA.jpeg" /></figure><h3>Memorable Moments at Apple Park</h3><p>Beyond the technical content, the experience of being at Apple Park was unforgettable. The highlight was attending an exclusive <strong>F1</strong> movie screening at the <strong>Steve Jobs Theater</strong> the evening after the conference. Experiencing this state-of-the-art venue, combined with the immersive film, perfectly showcased Apple’s commitment to premium experiences.</p><p>The attention to detail throughout Apple Park — from the visitor center to the theater — reinforces why Apple sets the standard for design and user experience across the industry.</p><h3>Recommendations for Fellow iOS Developers</h3><p>If you’re considering attending a future WWDC, my advice is simple: <strong>do it</strong>. The investment in time and resources is completely justified by:</p><ul><li><strong>Direct access to Apple engineers</strong> and decision-makers</li><li><strong>Early insights</strong> into upcoming technologies and design patterns</li><li><strong>Networking opportunities</strong> with the global iOS developer community</li><li><strong>Hands-on experience</strong> with beta technologies before public release</li></ul><p>The knowledge gained from one week at WWDC can accelerate your development skills and product roadmap by months.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BhbG1k-1XRgMJxI23yTrSA.jpeg" /></figure><h3>Looking Forward: The Future of iOS Development</h3><p>WWDC25 has set the stage for an exciting year in iOS development. The combination of <strong>Liquid Glass</strong> design principles and <strong>Foundation Models</strong> capabilities will enable developers to create more sophisticated, user-friendly applications than ever before.</p><p>At Tymit, we’re committed to staying at the forefront of these developments, ensuring our users benefit from the latest iOS innovations while maintaining the security and reliability they expect from their financial apps.</p><p>The California developer community’s openness and hospitality made the entire experience even more valuable. San Francisco proved to be an incredibly welcoming city, and the collaborative spirit of the tech community there is something every developer should experience.</p><p>As we implement these new technologies and design patterns at Tymit, we’ll continue sharing our learnings with the developer community. The future of iOS development has never looked brighter, and we’re excited to be part of shaping that future.</p><p><em>Want to be part of a team that embraces cutting-edge iOS development? </em><a href="https://apply.workable.com/tymit/"><em>Check out </em></a><em>our open positions and join us in building the future of fintech.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b0bc4409c35" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My KotlinConf’25 Experience in Copenhagen]]></title>
            <link>https://medium.com/@tymitengineering/my-kotlinconf25-experience-in-copenhagen-78f1d01d56db?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/78f1d01d56db</guid>
            <category><![CDATA[experience]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlinconf]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 14:44:51 GMT</pubDate>
            <atom:updated>2025-07-10T10:47:37.779Z</atom:updated>
            <content:encoded><![CDATA[<p>Shortly after joining Tymit, I started hearing whispers about KotlinConf’25 in Copenhagen. I’d never been to an international conference before — let alone one dedicated to a language I use every day as a Junior Android Developer. The excitement was real! I couldn’t wait to see what the global Kotlin community looked like up close.</p><p>When I finally arrived, I was blown away by the energy. The event was so well organized — registration was fast, the talks started on time, and everything felt super professional. I had been worried that most of the sessions would be too advanced for me, but there was something for everyone, no matter your experience level.</p><p>What really struck me was seeing Kotlin’s potential in action. I already knew Kotlin was powerful, but it’s something else to be in a room full of developers from all over the world, sharing their passion for the language. The enthusiasm was contagious — even developers who don’t use Kotlin every day were getting inspired! I was especially surprised to see backend and even iOS developers interested in Kotlin Multiplatform. It made me realize that Kotlin is much bigger than just Android development; it’s a growing ecosystem with a place for everyone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6SmoEL1wxNJA4WuzioJkgQ.png" /><figcaption>The Android Tymit Team</figcaption></figure><p>Attending KotlinConf with my Tymit team made the experience even better. We picked out talks together, grabbed lunch between sessions, and just enjoyed being part of the event as a team. It helped us bond and made the whole trip unforgettable.</p><h4>Talks &amp; Speakers</h4><p>The first talk was the keynote, where they talked about how the language has evolved and some new features that are coming in future versions. The one that our team liked the most was “Rich Errors” a new way of handling errors in Kotlin.</p><p><strong>Day 1 Highlights:</strong></p><ul><li><strong>Rich Errors in Kotlin </strong>(Michail Zarečenskij)<br>Explores Kotlin’s nullability model and how it can evolve into more expressive error handling through restricted union types, improving clarity and robustness in code.</li><li><strong>From 0 to h-AI-ro: High-Speed Track to AI for Kotlin Developers </strong>(Urs Peter)<strong><br></strong>A deep dive into generative AI concepts, tools, and frameworks relevant to Kotlin developers, with live coding and practical examples on how to integrate AI in Kotlin apps.</li><li><strong>Compose Multiplatform for iOS: Ready for Production Use </strong>(Sebastian Aigner)<br>Presents the current state of Compose Multiplatform, now stable for iOS, including tooling, APIs, and how to build production-ready shared UIs across platforms.</li><li><strong>Implementing Compose Hot Reload (</strong>Sebastian Sellmair)<br>Covers the technical journey of implementing hot reload in Compose apps on the JVM, improving developer workflow and UI iteration speed.</li><li><strong>Build Your Own NES Emulator… with Kotlin </strong>(Artur Skowroński)<br>A fascinating five-year project building a NES emulator in Kotlin from scratch. Full of architectural challenges and performance lesson, this talk was the most impactful for me, showing how persistence and problem-solving go hand in hand.</li></ul><p><strong>Day 2 Highlights:</strong></p><ul><li><strong>Collect Like a Pro: A Deep Dive on the Android Lifecycle-Aware Coroutines APIs</strong> (Manuel Vivo)<strong><br></strong>Focuses on lifecycle-aware coroutine APIs like repeatOnLifecycle and collectAsStateWithLifecycle, including how to use them efficiently and avoid resource waste.</li><li><strong>Duolingo + KMP: A Case Study in Developer Productivity (</strong>John Rodriguez, Johnny Ye<strong>)<br></strong>Shares how Duolingo leverages Kotlin Multiplatform to ship features quickly across platforms, highlighting real use cases, challenges, and productivity gains.</li><li><strong>Don’t Forget Your Values! (</strong>Leonid Startsev<strong>)<br></strong>Discusses a proposal to elevate “ignored return value” checks to compiler diagnostics, ensuring better code quality and fewer hidden bugs.</li><li><strong>Data Analysis for Finance in Kotlin (</strong>Enrique Lopez Manas<strong>)<br></strong>Demonstrates how Kotlin (with DataFrame and Kandy libraries) can be used for financial data analysis, offering a type-safe and modern alternative to R and Python.</li><li><strong>Kotlin and Compose Multiplatform Patterns for iOS Interop (</strong>John O’Reilly<strong>)<br></strong>Offers real-world patterns and techniques to deal with UI and non-UI integration challenges when using KMP and CMP on iOS.</li><li><strong>From Data to Insights: Building a Bluesky Bot Powered by AI (</strong>Raphael De Lio<strong>)<br></strong>Shows how to build a bot that transforms Bluesky post streams into insights using AI techniques like Bloom Filters, semantic caching, and vector similarity search.</li></ul><p>A lot of talks focused on AI and Kotlin Multiplatform, both of which I’m really excited about. I left the conference full of ideas for side projects and new tools to try at work.</p><p>We also got to see Junie, JetBrains’ new AI assistant for their IDEs like Android Studio. We’re planning to try it out on our team, hoping it’ll help us automate tasks and boost productivity.</p><p>The Rich Errors proposal especially caught our eye — we think it could make our app even more robust.</p><p>Overall, KotlinConf’25 was an amazing experience. I’m so grateful to Tymit for making it possible. If you ever get a chance to attend, don’t hesitate — you’ll come back inspired, connected, and ready to level up!</p><p><strong>This article was written by: </strong><a href="https://es.linkedin.com/in/brahim-korsan">Brahim Korsan</a>, Associate Android Engineer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78f1d01d56db" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Inside #Pragma Conference 2024]]></title>
            <link>https://medium.com/@tymitengineering/inside-pragma-conference-2024-3b11dfd5851e?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/3b11dfd5851e</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[conference]]></category>
            <category><![CDATA[pragma]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Wed, 20 Nov 2024 13:57:36 GMT</pubDate>
            <atom:updated>2024-11-20T13:57:36.244Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ew9iYLrcMO_JTB3w7PCjNA.jpeg" /></figure><p>The #pragma conference 2024 brought a host of technical insights and best practices for iOS developers, with sessions that ranged from SwiftUI animation techniques and accessibility improvements to security and cross-platform development strategies. Attendees left with a deeper understanding of tools and techniques to optimize iOS applications and adopt emerging trends, from structured concurrency to code generation and beyond.</p><h3>SwiftUI Animations Under the Hood</h3><p>The conference kicked off with a technical exploration of SwiftUI animations, highlighting the process of making view modifiers conform to Animatable for more control over animations. <strong>Chris Eidhof</strong> demonstrated advanced debugging techniques using an unreleased version of Instruments, and provided practical tips, like using ZStack to solve transition issues. This session unveiled the sophisticated mechanics behind <em>SwiftUI</em> animations, sparking interest in tools like keyframe and phase animators for future development.</p><h3>Kotlin Multiplatform for iOS: Myths vs Reality</h3><p>The Kotlin Multiplatform Mobile (KMM) session took a critical look at cross-platform capabilities. Although KMM offers benefits by generating native code for iOS and Android, allowing teams to share code for business logic, limitations still exist, particularly in interoperability. Since KMM integrates more smoothly with Objective-C than Swift, developers miss out on features like structured concurrency. KMM’s UI performance on iOS also lags behind SwiftUI, casting some doubt on its readiness for demanding iOS apps. The mixed feedback from teams using KMM encouraged cautious exploration, especially with JetBrains’ Fleet IDE, which still faces stability challenges.</p><h3>Stealing User’s Identity</h3><p>This session highlighted vulnerabilities exposed by reverse engineering techniques. <strong>Cyril Cermak</strong> demonstrated how attackers can bypass jailbreak checks, extract access tokens, and exploit vulnerabilities in deep links. Using tools like frida and <a href="https://www.hopperapp.com/">hopper</a>, the session walked through techniques from binary dumping to injecting malicious code, emphasising the need for secure coding practices, robust jailbreak detection, and careful management of URL schemes. This was a wake-up call for teams to prioritize security in apps handling sensitive data.</p><h3>From Zero to Accessible in 30 minutes</h3><p><strong>Robin Kanatzar</strong> underscored the importance of inclusivity, covering tools like Accessibility Inspector for checking color contrast, touch targets, and focus order. Developers were urged to adopt dynamic text sizes and accessible labels to improve readability. The session advocated for clear labels and avoiding reliance on color alone for meaning, while grouping related elements with .accessibilityChildren(.ignore) for better screen reader navigation. With practical insights into handling focus, font sizing, and layout adjustments, developers learned how to create more accessible and user-friendly interfaces.</p><h3>Swift for WebAssembly</h3><p>Swift’s role in web development was showcased here, where developers saw the potential for running Swift in a web environment using WebAssembly (<a href="https://webassembly.org/">wasm</a>). Tools like <a href="https://github.com/swiftwasm/carton">carton</a>, JavaScriptKit, and WasmKit from <a href="https://swiftwasm.org/">SwiftWasm</a> enable developers to build web apps with Swift, achieving a lightweight, sandboxed runtime with hot-reloading features. This session opened up a new pathway for Swift developers to engage in web projects without leaving the Swift ecosystem, and hinted at future possibilities for cross-platform Swift applications that go beyond mobile.</p><h3>Getting Sentimental: an exploration of sentiment analysis in iOS</h3><p>For developers interested in natural language processing (NLP), “Getting Sentimental” introduced Apple’s Natural Language framework, showcasing text analysis techniques like tokenization, tagging, and sentiment scoring. <strong>Anna Beltrami</strong> noted the growing influence of transformer models, like ChatGPT, on NLP, which has modernized sentiment analysis. This session encouraged developers to explore sentiment analysis in applications such as user feedback analysis, demonstrating that iOS apps can benefit from advanced language processing.</p><h3>LLDB Scripting for iOS Developers</h3><p>In this keynote, the focus shifted to debugging efficiency.<strong> Mikoláš Stuchlík</strong> showcased the power of custom <em>LLDB (low-level debugger)</em> scripts to expedite debugging tasks, recommending resources like “<a href="https://www.kodeco.com/books/advanced-apple-debugging-reverse-engineering/v4.0">Advanced Apple Debugging &amp; Reverse Engineering</a>” for those interested in deeper customisation. <em>Python</em> scripting in <em>LLDB</em>, as well as command aliases, were shown to be valuable for repetitive ,debugging routines, making <em>LLDB</em> a more productive tool. By learning to script in LLDB outside of bug emergencies, developers could significantly streamline their debugging process.</p><h3>Codegen Driven Development</h3><p>A fascinating talk on “Codegen Driven Development” introduced CG/SQL, a code generation tool that uses SQLite to bridge SQL with Swift, generating database-related code. While this approach may be overkill for production environments, it exemplified the possibilities of code generation and how integrating SQL-based logic could simplify data management.</p><h3>Is structured concurrency worth the effort?</h3><p>The structured concurrency session discussed the gradual adoption of Swift’s concurrency model, providing practical advice on using Sendable, reducing Task usage, and progressively migrating to async/await code. Structured concurrency was praised for its impact on stability, readability, and modularity, with <strong>Aleksandar Vacić</strong> advocating for a steady transition to harness Swift 6’s concurrency benefits.</p><h3>From Side Project to Going Indie</h3><p>The “Going Indie” session was a motivational highlight, featuring advice on transforming a side project into a full-time job. The speaker encouraged persistence, fast iteration, and public goal-setting, while embracing imperfections in the development process. AI integration in daily work was also advised as a productivity booster, showcasing how indie developers can benefit from modern tooling.</p><h3>Swift Testing: A testing framework for modern Swift</h3><p><em>Swift Testing</em> introduced a fresh framework that allows tagging, grouping, and custom assertions without relying on XCTest. The framework provides parallelized testing via @Suite and time-constrained behaviors within @Test annotations, greatly enhancing the flexibility and performance of test cases. Wrapping tests with WithKnownIssue allows them to be skipped if broken, but triggers an alert if they begin to pass, supporting robust, dynamic test plans.</p><h3>Designing APIs: How to ensure Accessibility in Design System components</h3><p>The conference’s focus on accessible design extended into API design strategies, stressing that accessible design shouldn’t solely rely on color. Designing with various levels of abstraction and considering voice control as an assistive technology allows us to create more inclusive user experiences, extending the UX laws into practical development practices.</p><h3>So You Think You Know Swift?</h3><p>This talk was a deep exploration of Swift’s hidden complexities and advanced features, challenging developers to refine their understanding of the language. <strong>Nick Lockwood</strong> highlighted the nuanced control <em>Swift</em> offers through where clauses in loops, labeled break statements, and the unique handling of strings due to <em>Unicode</em> encoding. The talk also revealed lesser-known insights, such as optionals being enums, the distinction between any and some for existential and generic types, and Swift’s copy-on-write behaviour to optimise memory usage. Practical tips for clean coding included using empty enums as namespaces, adding access control to extensions, and structuring large numbers with underscores for readability. Swift’s robustness, flexibility, and evolving feature set make it a language that’s easy to pick up but continuously rewarding to master.</p><h3>Conclusion</h3><p>Overall, #pragma 2024 offered a treasure trove of information for iOS developers looking to elevate their skills, whether through accessibility enhancements, code modularisation, or security hardening. The conference also underscored the value of understanding cross-platform tools and techniques, and the growing role of Swift in both web and backend development. Attendees left with new strategies to build secure, accessible, and performant applications, while remaining agile and ready for the next wave of advancements in Swift and iOS development.</p><p><strong>This article was written by: </strong><a href="https://twitter.com/fdzsergio">Sergio Fernández</a>, Principal iOS engineer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b11dfd5851e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tymit at Droidcon London 2023!]]></title>
            <link>https://medium.com/@tymitengineering/tymit-at-droidcon-london-2023-14f4c87caba6?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/14f4c87caba6</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[london]]></category>
            <category><![CDATA[droidcon]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Mon, 11 Dec 2023 16:13:29 GMT</pubDate>
            <atom:updated>2023-12-11T16:13:29.792Z</atom:updated>
            <content:encoded><![CDATA[<p>This year the Android team attended Droidcon London, one of the biggest Android conferences of the year. With more conferences starting up again after a few years break it was great to see the community coming together to talk and share ideas about everything Android. All within Droidcon London’s spiritual home at The Business Design Centre in Angel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*PuzIQm0FC_hnomQCRWqrZg.jpeg" /></figure><p>This was also a special occasion for the Tymit team. It was the first time we were all together in person since I joined the company, giving us an opportunity to get to know each other better face to face. It also gave me the chance to give the team a small look into London life and the experience of an authentic British pub. 🍻</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*xOkTuwHJu06tXwnS7lGb3w.jpeg" /></figure><h3>Talk Highlights</h3><p>This years talks were of very high quality and variety. Ranging from popular topics such as Jetpack Compose to more advanced concepts like Augmented Reality. Each speaker clearly knew their area of expertise and the team left the conference feeling inspired and filled with ideas to consider for our own work here at Tymit. 💳</p><p>Some of the talks that stood out to me were:</p><h4>🤖 <a href="https://www.droidcon.com/2023/11/15/challenges-failures-and-lessons-from-an-agency-building-mobile-products-at-scale-2/">Challenges, failures and lessons in 15 years of building on top of Android</a> — Carl-Gustaf Harroch</h4><p>The Keynote talk and an appearance from a Droidcon London superstar! Carl started Droidcon London in the early days and also created Novoda, a Mobile Product Engineering Company recognised across the world. Carl shares his 15 years of experience working with Android and what he’s learnt when it comes to building high performing teams and engaged communities.</p><h4>🏗️ <a href="https://www.droidcon.com/2023/11/15/building-compose-apis/">Building Compose APIs</a> — Jossi Wolf</h4><p>Jossi Wolf from Google talks about how to build Modifier &amp; Composable APIs right, through lessons learned building the AnchoredDraggable API. Interesting ideas include working out whether your API works best as a Composable or a Modifier, making sure the signature of your API is simple for developers to use, and not being afraid to iterate on your API over time.</p><h4>🔎 <a href="https://www.droidcon.com/2023/11/15/mobile-observability-at-scale/">Mobile Observability at scale</a> — Josef Raksa</h4><p>Josef from Glovo’s platform team talks about observability for mobile. He explains what Mobile Observability is, why it is important, and how it helps companies to better understand what is happening in their mobile apps and platform. This becomes especially useful when SLA’s / SLO’s are involved.</p><h4>🎆 <a href="https://www.droidcon.com/2023/11/15/easy-screenshot-testing-with-compose/">Easy screenshot testing with Compose</a> — Jose Alcérreca</h4><p>Screenshot tests are a great way to verify your UI is laid out as expected using Jetpack Compose. Jose from Google explains how easy they are to implement, why it’s useful Roboelectric Native Graphics enables screenshot tests to run on the JVM, and finally gives a preview of what Google are working on to make screenshot testing even easier using Jetpack Compose Previews.</p><h3>In Conclusion</h3><p>Droidcon London 2023 was a great experience and contained the energy, excitement and friendliness you expect from a developer conference. For the team it was a great opportunity for us to meet in person for the first time and also to learn more about each other. We hope to be back next year!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*EGPb4u1wl2ki_bV8SsZisg.jpeg" /></figure><p>Written by <a href="https://uk.linkedin.com/in/darrylbayliss">Darryl Bayliss</a>, Senior Android Engineer</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=14f4c87caba6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TCA from another prism]]></title>
            <link>https://medium.com/@tymitengineering/tca-from-another-prism-41c9e7872500?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/41c9e7872500</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[composable-architecture]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Fri, 17 Nov 2023 13:26:37 GMT</pubDate>
            <atom:updated>2023-11-17T13:26:37.947Z</atom:updated>
            <content:encoded><![CDATA[<p>Since its beginnings we’ve<strong> </strong>really liked <a href="https://www.pointfree.co/collections/composable-architecture">TCA</a> (the composable architecture) state management and how it is composed. Complex views can easily be split into smaller and simpler ones. Those components built by domain are easy to reuse on any other part of the app and TCA integrates seamlessly with SwiftUI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3bq8yyQtN33bIB8e" /><figcaption>Photo by <a href="https://unsplash.com/@lazycreekimages?utm_source=medium&amp;utm_medium=referral">Michael Dziedzic</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When we first started looking into TCA, the Tymit app was a monolith of MVVM; taking into account the size of our team, and our other priorities, the cost to transition completely would have been much too high.</p><p>Instead our initial approach was to implement a custom architecture based on the TCA store and Reducers, what allow us to compose views with easy‚ but without using pre-release TCA’s dependency system (environment based) and global state management. That allow us to migrate screen by screen into the new architecture, and to build our own dependency system based on the talk by Stephen Celis - <a href="https://vimeo.com/291588126">How To Control The World </a>.</p><p>With the release of TCA 1.0 we finally committed to migrating into TCA. However, TCA is global state based, so let’s explore the benefits, cons and other options.</p><h3>Benefits of global state</h3><h4>Single source of truth</h4><p>Any modifications to the global state are immediately reflected across the application, leading to a consistent view of the data.</p><h4>Improved Data Sharing</h4><p>Components throughout the application can access the global state without the need for complex data passing or prop drilling, thereby simplifying the communication between different parts of the application.</p><h4>Easier State Persistence</h4><p>Global state can be persistent, making it convenient to maintain the application’s state across different sessions or interactions. This persistence is particularly useful in cases where the state needs to be maintained across multiple views or interactions.</p><h4>Streamlined State Monitoring and Debugging</h4><p>Centralised state management can facilitate easier debugging and monitoring. Tracking state changes and identifying issues becomes more straightforward when the entire application’s state is in one place.</p><h4>Simplified Data Flow</h4><p>The global state pattern often leads to a unidirectional data flow, which can make it easier to reason about data changes and understand how the state propagates throughout the application.</p><h4>Enhanced Scalability and Maintainability</h4><p>The centralization of the data on a single state simplifies data management. It allows a more organised approach and standard way to access data from all domains. Making it easier to scale the application codebase.</p><p>There are, however, <strong>some caveats</strong> specific to TCA’s implementation of global state:</p><ol><li><strong>Centralised Data Management:</strong> Not enterily true. State consistency is not guaranteed just for global state management usage. We need to use core reducers to keep different state nodes in sync or parent nodes with child nodes.</li><li><strong>Predictable State Changes:</strong> The design of reducer composition ensures that any returned action is wrapped until it reaches the root node and is then sent down, filtered, until the domain reducer handles it. However, this can result in a significant slowdown and high memory usage in larger applications, as mentioned by Krzysztof Zabłocki <a href="https://www.merowing.info/multi-store-tca/">here</a>.</li><li><strong>Improved Data Sharing:</strong> Even when working with a single-module app, the Global State is represented by a tree, where child nodes may only observe state provided to them by their sole parent. This means that developers have to manually synchronize data between branches. As a result, while the global state does exist, only parent nodes can fully utilize its benefits.</li><li><strong>Enhanced Scalability and Maintainability:</strong> Working with global state does not inherently guarantee scalable and maintainable code, on TCA larger applications can still be hard to maintain due to state synchronisation and refactors, since refactoring a child node will often requires changes on the parent nodes too.</li></ol><pre>struct HomeState {<br>  var username: String<br>}<br><br>struct ProfileState {<br> var user: String<br>}<br><br>struct TabbarState {<br>  var home: HomeState<br>  var profile: ProfileState<br>}<br><br>enum AppState {<br>  case logged(TabbarState)<br>  case notLogged(OnboardingState)<br>}</pre><p>Data access is complicated due to the domain of the reducers.</p><pre>Reducer&lt;ProfileState&gt; { state, action in }</pre><p>Here, ProfileState is not able to see the userName of the HomeState or vice versa.</p><h3>Disadvantages of global State</h3><h4>Complexity and Learning Curve</h4><p>Managing a global state requires a solid understanding of the entire state of the application. For larger applications, comprehending and effectively managing the global state can be challenging, leading to a steeper learning curve for developers new to the codebase, even working with domain designed states composed into bigger ones.</p><h4>Debugging Challenges</h4><p>Tracking down issues related to the global state, such as unexpected changes or inconsistencies, can be complex. Identifying the source of a problem and understanding the flow of state changes across the application might require more effort and effective debugging tools or practices.</p><h4>Potential for Over-Rendering</h4><p>As the global state changes, components that subscribe to parts of the global state might re-render even if the changes are not relevant to them. This potential over-rendering can impact performance, especially in complex or deeply nested component structures. To prevent this we have the View State that introduces a lot of boiler plate code until TCA incorporates the new Observable swift macro.</p><h4>Maintainability and Scalability</h4><p>With the entire application’s state being in a global store, the architecture’s maintainability and scalability might be a concern as the codebase grows. It could become challenging to manage and maintain a large, global state as the application evolves.</p><h4>Potential for Accidental Side Effects</h4><p>Modifying the global state from multiple parts of the application could lead to unexpected side effects or unintended changes. As a code base grows this risk only worsens.</p><h4>Hard migration process</h4><p>Migrating to global state on an existing project can be difficult and time consuming.</p><h3>Another approach to TCA:</h3><p>Once we explore the benefits and cons of Global State based architecture, we find out that Global State based architecture doesn’t fit for us.</p><ul><li>TCA global state doesn’t allow sharing information between different state nodes as we saw before.</li><li>Learning curve of a new employee into the global state of the app is more challenging.</li><li><a href="https://medium.com/@tymitengineering/dynamic-features-in-an-ios-multi-brand-app-85002bbdbfa4">Our app is a white label app, some features may exists while others may not</a>. GlobalState design is different for each app and that makes it very hard to implement as well and being error prone.</li></ul><p>On the other hand, we come from an architecture with local states. Those states are like TCA Feature domain states but without composing into the global one.</p><h3>Pros of local state architecture.</h3><h4>Easy migration</h4><p>Allows you to migrate screen by screen incrementally without the need to build a global app state.</p><h4>View lifecycle attached stores</h4><p>No need for tricks to attach the lifecycle of the returned effects from the Reducers to the life cycle of the view due the store sharing view lifecycle. For example, subscriptions to the notification centre don’t need to be attached to the .task view modifier.</p><h4>No global state view redrawn attempts</h4><p>Any change on the one section of the app won’t mutate any other unrelated sections. This means there’s no expotential state calculations like we saw on the Krzysztof post.</p><h4>Easier to debug</h4><p>Reducer composition wrapping actions makes features hard to debug when the composition is high due to the increased number of state changes on the app. Working with domain closed reducers simplifies debugging thanks to being isolated from the rest of the app.</p><h4>Avoid CoreReducers synchronising nodes</h4><p>Core Reducers nodes sync is the tool TCA needs to sync state branches or send actions into any reducer based on the actions launched by any other reducer.</p><pre>//sourced from isowords on github:<br>//https://github.com/pointfreeco/isowords/blob/main/Sources/AppFeature/GameCenterCore.swift<br>case let .gameCenter(<br>        .listener(.turnBased(.receivedTurnEventForMatch(match, didBecomeActive)))):<br>      return handleTurnBasedMatch(match, state: &amp;state, didBecomeActive: didBecomeActive)</pre><p>Those kind of syncing methods, even getting errors from the compiler, are easy to break, because any refactor on the middle action layers will arise the need of refactor more layers.</p><h4>Easier learning curve</h4><p>When new employees start working on simple features that don’t require composition, they can learn concepts like global state, reducer composition, action wrapping, and scoping more easily. Once they have enough knowledge, they can move on to more complex features where composition becomes helpful.</p><p>Many months ago we face the problem where a tab had to receive changes from another tab, <em>just launching a load on the onAppear was not desired.</em> So, we started to explore options with one aim in mind. To able to get updates from the Store of the section five to the Store of the section zero without any reference. Thus, we want a dynamic notification system, but without the limitation of having a reference to the instance.</p><p>We have decided that every store should emit the actions it produces. This way, any other store can subscribe to these actions and introduce events into its own system.</p><h3>Publisher Subscriber pattern:</h3><p>Publisher Subscriber is a messaging system that allows different parts of an application to communicate and exchange information. While it offers a flexible way to decouple components and enable communication, there are some downsides to consider:</p><h4>Implicit Communication</h4><p>Since components communicate indirectly through the event bus, it can make the flow of information less explicit. Understanding which components are interacting and the specifics of these interactions might become challenging, especially in larger systems.</p><h4>Debugging Complexity</h4><p>Identifying issues related to event propagation or handling can be challenging. When events are sent through the bus, tracking the source of a problem, especially in complex event-driven systems, may be more difficult compared to direct method calls or explicit communication pathways.</p><h4>Potential for Unintended Side Effects</h4><p>Due to the loose coupling and the ease with which multiple components can interact through the bus, there’s a risk of unintended side effects or unexpected interactions between components. This can make the application more error-prone.</p><h4>Performance Overhead</h4><p>In systems with an extensive use of events, the performance overhead associated with the event bus could be a concern. Handling numerous events concurrently or managing a high frequency of events might impact the system’s performance.</p><h4>Potential for Overuse or Abuse</h4><p>Without proper guidelines or constraints, the event bus can be overused, leading to a chaotic system where components are excessively interdependent through event propagation. This can result in a lack of clear data flow and system complexity.</p><p>So, how can we mitigate these downsides in our implementation while maintaining flexibility?</p><h4>Implicit communication</h4><p>Instead of subscribing to a name using the notification center, we will subscribe to the action itself, ensuring explicit communication about the subscription source.</p><h4>Debugging complexity</h4><p>Since we explicitly subscribe to the action, we can easily identify the source of the action. As Actions are defined on the Feature domain, we can determine which Feature produces each Action.</p><h4>Potential for unintended side effects</h4><p>By ensuring that Feature actions only perform the intended action indicated by their own name, we can prevent unintended side effects. This requires a well-designed approach to handling actions.</p><p>To improve communication and make it more implicit, we will use the type directly instead of subscribing to any event name like <strong>Notification Center</strong> subscriptions. This means that we will need to provide the Action type at any point to receive notifications when an action is executed or has been executed. Using the Action type as the “subscription name” is a win-win.</p><p>In our experience, we have always wanted to subscribe to a specific action case, not all of them. This is where CasePaths come into play and help us achieve more type safety. Since Actions are coded as enums, any action can have a associated Value, and CasePaths provide us with the necessary generics.</p><p>Thanks to CasePaths, we can subscribe to where we want to be notified as (Action.case) → Value, and we can send the desired action with the value as (Value) → Action.</p><p>Finally, we have an effect that takes these parameters to build the subscription and creates a publisher that introduces the sending action into the Store every time the Action from another store is produced.</p><pre>case .onAppear:<br>  // another code<br>  return .subscribe(<br>    to: .didExecute(/AnotherFeature.Action.didLoadedUser),<br>    sending: Action.featureDidLoadedUser<br>  )<br>// func subscribe(<br>// to: CasePath&lt;ToAction, Value&gt;,<br>// sending: (Value) -&gt; Action<br>// ) -&gt; Effect&lt;Action&gt;</pre><p>To recap, working with type-based notifications prevents us from breaking notifications during future refactorings, such as updating the action name or updating the action’s associated type, which would fail at build time. It also allows us to receive changes from any other store without needing access to that store or worrying about the store’s lifecycle.</p><p>With our approach of using small stores per view, the lifecycle of the store is linked to the lifecycle of the view. Therefore, we don’t need to worry about never-ending subscriptions that we would have to handle in the store’s lifecycle, as the stores are deallocated when the view dies.</p><h3>Modularisation</h3><p>We work on a white label app, that compiles into several apps, each one of them with some Features based on configuration. When we split the app into several modules, with the Static notifications we faced the next problem.</p><p>Any Feature located on A module, won’t have access to any other feature located in B module if A doesn’t depends on B , this means we won’t be able to subscribe from any feature into A module to any feature into B module.</p><p>To wire two features on different modules, we need to access a module that has access to both of them. This is because these subscriptions are not based on names but on types. Finally, we need to inject subscriptions from the root modules in order to consume them from the child nodes.</p><pre>StaticSubscription.add(<br>  subscriptions: .init(<br>    to: .didExecute(/AFeature.Action.didLoadedUser),<br>    sending: BFeature.Action.featureDidLoadedUser<br>  )<br>)</pre><p>And from the reducer, we must return an effect to consume them.</p><pre>return .subscribeToStaticNotifications()</pre><p>However, a feature that relies on Static Subscriptions is less explicit about the behaviour of the code and harder to understand looking at the feature itself.</p><h3>Conclusion</h3><p>TCA is almost the new standard architecture on modern Swift apps. However, there are some downsides to using global state management, such as increased complexity, debugging challenges, and potential over-rendering. An alternative approach is to use a local view state based architecture, which offers benefits like easy migration, simplified debugging, and improved performance on large systems.</p><p>The ability to subscribe to specific action cases enhances the flexibility and ease of communication between features in a type-safe manner.</p><p>This implementation is open sourced on <a href="https://github.com/Tymit/swift-composable-architecture">GitHub — Tymit/swift-composable-architecture: A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.</a> on the tymit-main branch.</p><p><strong>Written by: </strong><a href="https://www.linkedin.com/in/julianalonsocarballo/">Julian Alonso</a>, Principal iOS Engineer + Team Lead.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=41c9e7872500" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sipping Swift and Savouring Spain]]></title>
            <link>https://medium.com/@tymitengineering/sipping-swift-and-savouring-spain-db36e5fa6e2f?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/db36e5fa6e2f</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[logroño]]></category>
            <category><![CDATA[spain]]></category>
            <category><![CDATA[tech-conference]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Tue, 03 Oct 2023 14:51:14 GMT</pubDate>
            <atom:updated>2023-10-03T15:39:14.541Z</atom:updated>
            <content:encoded><![CDATA[<p>Discover the vibrant world of iOS development and the rich culture of Rioja through our NSSpain Experience. Join us as we delve into top-quality insights, valuable connections with fellow engineers, and unforgettable moments in the heart of Logroño. NSSpain: Where iOS innovation meets the Rioja celebration.</p><h3>NSSpain Experience</h3><p>In the ever-evolving world of iOS development, the value of opportunities for learning and idea-sharing cannot be overstated. NSSpain stands out not just for its top-quality insights, but also for its platform to connect with fellow engineers. As someone who only recently joined Tymit, attending was about more than professional growth; it was a brilliant means to bond with new colleagues amidst the rich backdrop of the Rioja region. Nestled in the heart of Logroño, the conference was a mecca for iOS aficionados. Alongside thought-provoking sessions and engaging discussions, a standout event was our tour of a local winery, where the opportunity to ’sample’ wines was as enlightening as our tech talks. With the Rioja Wine Harvest Festival kicking off soon after, NSSpain wasn’t merely an educational retreat; it was a celebration of our craft within a vibrant cultural tapestry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9tQMvWHqhPqPiNLQlEGQ2A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fs4gCujjAVBrcV8079OB1g.jpeg" /><figcaption>Tymiteers in the wild</figcaption></figure><h3>Highlights and Learnings</h3><p>I won’t cover every talk in detail to keep this post succinct. They were all of a very high standard and recordings are being added to threads on <a href="https://mastodon.social/@nsspain/111119542541625667">Mastodon</a> and <a href="https://x.com/NSSpain/status/1705506392646234213?s=20">the website formally known as Twitter</a>. I will instead write a little about the talks which were most enlightening to me personally.</p><h3>Building UI components with SwiftUI — <a href="https://mastodon.social/@kasperl">Kasper Lahti</a></h3><p>Already available to watch <a href="https://vimeo.com/865570738">here</a>, Kasper’s talk is invaluable to SwiftUI newcomers and veterans alike. It’s inspiring to observe the depth of customisation he achieves using the framework; his presentation thoroughly disproves the lingering notion that moving away from UIKit will lead to flexibility issues. Primarily, he covers numerous tips, ideas and warnings for implementing custom controls/widgets. Kasper has also written an article on the subject available <a href="https://movingparts.io/styling-components-in-swiftui">on the Moving Parts blog</a>.</p><h3>Harnessing the Potential: Swift Macros in Action — <a href="http://twitter.com/twannl">Antoine van der Lee</a></h3><p>Antoine’s talk was a great introduction to Swift Macros, focusing on their use cases rather than their implementation. His talk covers many specific macros, ranging from macros that simply perform some validation at compile time (#URL, which would check a url is valid before inserting forced unwrapping, URL(string: ...)!), to discussing <a href="https://github.com/joshuawright11/papyrus">Papyrus</a>, a macro based HTTP client.</p><h3>Bug-Free by Design — Crafting Swift Code That Doesn’t Sting — <a href="http://twitter.com/hybridcattt">Marina Vatmakhter</a></h3><p>Marina’s talk was a collection of tips with the shared theme of leaning on the compiler as much as possible to catch bugs before they’re a problem.</p><p>An idea which I’d not considered before, and my favourite take away from the whole conference, was using late let initialisation to help prevent code paths from being forgotten about, for example:</p><pre>{<br>func spell(number: Int, completion: (String) -&gt; Void) {<br>   switch number {<br>   case 0:<br>     completion(&quot;zero&quot;)<br>   case 1:<br>     break<br>   case 2:<br>     completion(&quot;two&quot;)<br>   default:<br>     completion(&quot;other&quot;)  <br>    }<br>}</pre><p>This compiles fine, but if called with the number 1 then completion is never called! Hopefully, if this was a bug, it would be caught by good unit tests, however, we could have caught it earlier and made our code more robust by writing the function using late initialisation:</p><pre>{<br>func spell(number: Int, completion: (String) -&gt; Void) {<br>   let spelling: String<br>   switch number {<br>   case 0:<br>     spelling = &quot;zero&quot;<br>   case 1:<br>     break<br>   case 2:<br>     spelling = &quot;two&quot;<br>   default:<br>     spelling = &quot;other&quot;<br>   }<br>   completion(spelling) <br>}</pre><p>With this approach, we’ll get a nice error, “Constant ’spelling’ used before being initialized”, warning us that spelling hasn’t been initialised for all code paths.</p><h3>An overview of different approaches to share code across platforms — <a href="http://twitter.com/terhechte">Benedikt Terhechte</a></h3><p>Benedikt’s talk was a very entertaining look at cross-platform solutions, and specifically, at using Rust to share business logic between iOS and Android, highlighting the similarities, and shared strengths, between Swift and Rust. As well as being both interesting and informative (I’ll be giving Rust a go in my own projects!), his talk could be an effective resource to show the next overzealous product person bringing up React Native .</p><h3>The Temporal Axis of Space-Time — <a href="https://davedelong.com/">Dave DeLong</a></h3><p>Dave’s talk was a journey into the nuances and challenges of handling dates and time. The key message? Time is hard; lean on Calendar , the &#39;temporal map&#39; provided by Foundation. For further insights, Dave also maintains <a href="https://yourcalendricalfallacyis.com/">yourcalendricalfallacyis.com</a>.</p><h3>In reflection</h3><p>NSSpain 2023 was more than just a conference; it was an amalgamation of knowledge-sharing, cultural experiences, and forming connections within the iOS community. The talks I’ve highlighted only scratch the surface of the plethora of insights I gained. As I continue my journey at Tymit, I am eager to apply the knowledge and ideas gathered from these brilliant minds to drive innovation and enhance our user experiences. Until the next conference, I’ll be diving deeper into SwiftUI, exploring Rust, and always remembering that time is, indeed, hard!</p><p><strong>Written by </strong><a href="https://www.linkedin.com/in/robindouglas96?miniProfileUrn=urn%3Ali%3Afs_miniProfile%3AACoAABgjmGABXqnt-lggwtXpwTKUBhYG2_YEWpE&amp;lipi=urn%3Ali%3Apage%3Ad_flagship3_search_srp_all%3Bmgk0c1uWS92gVWpkQpRD%2FQ%3D%3D">Robin Douglas</a>, iOS engineer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=db36e5fa6e2f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Establishing Data Alignment in Tech]]></title>
            <link>https://medium.com/@tymitengineering/establishing-data-alignment-in-tech-cf03adcfbf3e?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/cf03adcfbf3e</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[business]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[collaboration]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Wed, 21 Jun 2023 13:31:43 GMT</pubDate>
            <atom:updated>2023-06-21T13:52:26.593Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*DMyjrlnxyKuj4x-M.jpeg" /></figure><h3>About the author</h3><p>I’m <a href="https://engineeroutofscope.medium.com">Hector Rodríguez</a>, Principal Platform Engineer for Tymit, a Fintech company (Series A) based in the UK and Spain.</p><p>We work towards creating the best instalment experiences, and Data is a central piece of our everyday work. Concepts like agreements, installments, balances, and transactions, are our bread and butter. They are our critical business concepts, our must-be-aligned Data.</p><p><em>Find the original article </em><a href="https://engineeroutofscope.medium.com/establishing-data-alignment-in-tech-383b689568dd"><em>here</em></a><em>.</em></p><p>One of the most common issues across businesses in this new data-driven era we have been living in is achieving meaningful and valuable results through Data.</p><p>The concept of Data Alignment sits at the core of those enterprises that achieve the very touted milestone of becoming data-driven. It is also the bane of all of those failed Data states most of us have experienced in some way or form. It’s a friction that eats away at business and stakeholders’ trust in Data and leaves leaders fighting alone in an arena of tough choices and guesswork.</p><h3>What is data alignment?</h3><p>Data alignment is a <strong>baseline maturity level of data-driven organisations.</strong> It indicates its members have a shared set of business concepts/definitions at different levels (within a team, across two or more, or in the organization as a whole).</p><p>This description looks good on paper, but why do we even care about data alignment? <em>And do we really need an alignment?</em></p><h3>Case Study — Tech Company Active Users</h3><p>Using one common critical business concept for any B2C or B2B2C enterprise: active users. Both companies with aligned and non-aligned definitions for this critical metric will have their teams wanting to measure in some way the number of users who consume features of their service.</p><p>Even without trend analysis, and advanced analytics, it’s still a decent lagging indicator to estimate the workload and resources required for most of their processes.</p><p>Now, a non-data-aligned team will have a myriad of definitions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/603/0*YUDJXiBP0JktSdsn.png" /></figure><p>A sample of different types of Active Customer definitions for a few teams :)</p><p>Now, <em>what’s really an active user?</em> Depends on who you ask… <em>Is it critical that these 4 verticals think differently about what an “active customer” is?</em></p><p>The answer is quite simple. All of it shows when the quarterly reports come around the corner, the CMO, CFO, and COO sit at the table to discuss the quarterly results. Now one of these leaders starts talking about active users and shows all the neat graphics he has requested internally in preparation for the meeting.</p><p>The other two look at the data points trying to make sense of what they are looking at. At best, they’ll challenge the definition of “active user” and let the conversation continue. At worst, they’ll discard that information. They are talking in different languages, so <em>why bother?</em></p><p>No. <em>No one really imagines that happening, right?</em> <strong>We cannot let that happen! </strong>Who is going to present numbers to the investors? We cannot afford the risk of confusing them with contradicting numbers!</p><p>Our options:</p><ul><li>One of the active customer definitions will win this battle and will be reported in the end. The rest of the practices are cut off and won’t be considered critical for the business.</li><li>All of them will be reported, with lots of asterisks and small font letters filling the presentation deck.</li><li>The CEO comes to save the day, with a compromise metric, everyone else discards the metric they currently use, and the business can finally report as a whole.</li></ul><p>You might think that the third option definitely has the upper hand. And that’s because we’ve put a constraint on the situation. Many enterprises do not recognize the third option as the best. But let’s assume this particular company has that foresight. We had a specific stakeholder that required a business-wide metric, so it makes sense to go that route… right?</p><p>We still need to keep the other definitions. They are required to measure success, to plan for capacity in their respective fields. And we haven’t even discussed that sneaky PM in a Product Squad defining active customers as the users that interact with the latest “coolest feature” they shipped in the last release of the product.</p><p>We dodged a bullet, but we are still not talking the same language, and that’s making us lose collaboration opportunities; it’s creating friction across the business.</p><h3>Untangling the mess</h3><p>Strong Data Practices will usually stumble upon data misalignment. Different teams will request multiple calculations to be performed on top of Data Sources, all of them being identified with the same name. Or even worse, they’ll find the metric already in the system and assume it means what they expect it to mean because <em>“Why wouldn’t this metric be about me!?”.</em></p><p>While this goes unnoticed by the different verticals, the Data team members will be the first to perceive this, and they will default to labelling metrics and data. So we now have a hefty list of “Active Customer” definitions:</p><ul><li>Financially Active Customers</li><li>Marketing Engaged Active Customers</li><li>Support-Requesting Active Customers</li><li>Feature Active Customers</li><li>API Active Customers</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/574/0*h6mUhW-d0wFGb08V.png" /></figure><p>Each team has a tailored definition of what an Active Customer is</p><p>With this mess untangled, people will all stay in their lane, and no confusion will happen. But wait! <em>What are we missing here?</em></p><p>Everyone is very comfortable in their space, but suddenly the Data team needs to maintain 5 separate datasets and 5 independent lines of data modeling to support their data-hungry business verticals. And we’ve only listed five… <strong>This won’t scale well.</strong></p><h3>Establishing a Data Topology</h3><p>It’s likely that while reading the previous section and thinking of a very tech-related business, readers might have realised that at least one of the definitions could potentially encapsulate all the others.</p><p>Remember, we are talking about the tech space. Our enterprise basically uses API calls for everything. <em>Isn’t a paid bill just another API call with changes to the database of the Billing System? Or is the interaction with that “feature” just another API call?</em></p><p>Can’t we make everyone in the company measure their active user metrics through entries on a database or amount of calls to an API? Of course, we can, but we shouldn’t assume everyone can relate to the data models created by engineers. They don’t necessarily map directly to business concepts.</p><p>That’s what the Data team is there for. It will translate those baseline concepts into critical business concepts everyone can understand and work on maintaining lineage. When they get to it, they’ll create a Data Topology that mirrors the Team Topologies of the company.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*EvGEnN_fckHAXkT8.png" /></figure><p>Source: <a href="https://www.ibm.com/garage/method/practices/reason/design-a-data-topology/">https://www.ibm.com/garage/method/practices/reason/design-a-data-topology/</a></p><p>Everyone can trace their business concepts directly to common ground when needed. They only need to follow the topology. Everyone else can inspect the assumptions others made in their definition and start talking in the same language.</p><h3>Data Models and Topology are as good as the Team Topologies</h3><p>So now the Data team has selected Data Modelling tooling and can probably create a pretty good Data Topology that resembles the different stakeholders in the company.</p><p><strong><em>Can we do better?</em></strong></p><p>Of course, we can. The Data team can only work with what it has. It can’t change the Data Topology too much without risking diverging too much from its stakeholders. They are bound by a pre-established “Team Topology” and will quickly hit a “diminishing returns” point. Not too much added value can be achieved by tinkering with the structure.</p><p><strong>The concept of Team Topologies belongs to a book by Matthew Skelton and Manuel Pais.</strong> The book emphasizes the importance of clearly defining team interactions and boundaries to optimize organizational performance. Let’s explore the relationship between alignment and the team structures presented in the book:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/420/0*jYxYdUg4772FB1JG.png" /></figure><h3>Stream-aligned Teams</h3><p>Stream-aligned teams are organized around specific business capabilities or streams of work. They have end-to-end responsibility for delivering a particular product or service.</p><p>Alignment in this context means ensuring that the objectives, goals, and understanding of critical business concepts are consistent within the team, enabling effective collaboration and streamlined delivery.</p><h3>Enabling Teams</h3><p>Enabling Teams provide support, tools, and platforms to stream-aligned teams. They focus on creating an environment that enables stream-aligned teams to perform their work efficiently.</p><p>Alignment between Enabling Teams and Stream-aligned Teams is crucial to ensure that the tools, platforms, and support provided align with the needs and requirements of the Stream-aligned Teams.</p><h3>Complicated Subsystems Teams</h3><p>Complicated Subsystem Teams deal with complex technical components or subsystems that require specialized knowledge. They ensure the smooth functioning and evolution of critical technical aspects of the overall system.</p><p>Alignment with other teams is critical to ensure that the complicated Subsystems Teams have a shared understanding of business objectives, requirements, and business concepts. This alignment helps avoid miscommunication or misunderstandings that could impact the integration and performance of the overall system.</p><h3>Platform Teams</h3><p>Platform teams focus on creating and maintaining shared platforms or infrastructure that support multiple teams. They provide tools, services, and frameworks that enable teams to deliver their products or services more effectively.</p><p>Alignment with other teams is essential to ensure that the platform teams understand the specific needs and requirements of the Stream-aligned teams, enabling them to tailor the platform services and support accordingly.</p><h3>How can we use this knowledge to our advantage?</h3><p>To foster collaboration and clearly defined interfaces, a great leadership team will ensure the Team Topology is the most efficient it can be. These interfaces will filter downstream, directly into the Data Topology.</p><p>Team alignment will foster Data Alignment. Once achieved, data modeling shines, and our Tech Company will have achieved Data Alignment.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cf03adcfbf3e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature Flags in a Multi-Brand World]]></title>
            <link>https://medium.com/@tymitengineering/feature-flags-in-a-multi-brand-world-17fd1a0c17e9?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/17fd1a0c17e9</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Wed, 12 Apr 2023 12:33:26 GMT</pubDate>
            <atom:updated>2023-04-18T09:07:06.217Z</atom:updated>
            <content:encoded><![CDATA[<p>Feature flags are a very cool feature. It allows us developers to work with new and old features that are present in our projects. We can enable or disable these flags so we can test, roll back in the case of a failure or even have A/B test if we want to experiment and measure their impact.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/665/1*Byomaug8yLUAk7hn76JewQ.jpeg" /></figure><p>My name is Jorge, I work here at Tymit, and we wanted to show you how we achieved a fully functional set of Feature Flags in a multi modular and multi branded project.</p><p>The idea of implementing Feature Flags came by after reading this fantastic article <a href="https://jeroenmols.com/blog/2019/09/12/featureflagsarchitecture/">Feature flags — A successful architecture</a> which exceptionally explains the concept of Feature Flags. Although we weren’t in a position to use this approach immediately, we’ve decided to tweak the concept to work for us.</p><h3>Goals</h3><p>Initially, we set out to achieve the following goals:</p><ul><li>Feature Flags should only be present in their module and be completely independent of other features.</li><li>Feature Flags should be fully reactive, so we can change the value and see the changes instantly.</li><li>We should be able to change between local and remote environments so we can also test if they’re working remotely.</li><li>We can use different default values depending on the brand that we’re currently in.</li><li>The module cannot have any knowledge about the brand it’s working on.</li></ul><h3>Problems</h3><p>So we had a pretty good idea of what we wanted and how we wanted to achieve it, but as in any software project, there are always problems that you must face in order to have your new feature to working effectively.</p><ul><li>We have a mix of multi-modular and monolithic projects. Sadly, our previous Feature Flag system are in the monolith, so we need to migrate them.</li><li>Our Feature Flags do not only return Boolean values. Sometimes we have other values like Strings or Integers, that we need to process in-app to get the Boolean that we want.</li><li>The current working version of the Feature Flags is a working solution that is stored in FirebaseRemoteConfig, and Firebase is not reactive.</li><li>We need to be able to provide default values depending on the brand, however the module has no knowledge of the current brand.</li><li>We need to provide support for a permission system that has the users’ identifiers stored inside the FirebaseRemoteConfig value.</li></ul><h3>Show me the code!!</h3><p>Now that’s out of the way, let’s show the solution, shall we? <br>First step — let’s start with the Feature itself or Setting for us.</p><pre>interface Setting&lt;T&gt; {<br>    val key: String<br>    val title: String<br>    val description: String<br>    val defaultValue: T<br>}</pre><p>This interface provides us with basic functionality to know the Setting’s key, a title to show inside a developer interface, a description if needed, and a default value. As we said previously, our Feature Flag could be any primitive value, so let’s force each setting to set the type of the value with a generic to prevent the usage of &lt;Any&gt; or &lt;*&gt;, as we will need to cast it later, which can be unsafe.</p><h3>Features</h3><p>Now that we have our interface, we’ll need two types of Settings — the ones that the application is using to work with working and future features and the ones that we, as developers, are going to use.</p><p>This is an example of a simple feature. As you can see, it extends from Setting, so we have all the properties that we need. It has a default value that is passed via constructor, so we can provide different values depending on the brand.</p><pre>sealed class Feature&lt;T&gt;(<br>    override val key: String,<br>    override val title: String,<br>    override val description: String,<br>    override val defaultValue: T<br>) : Setting&lt;T&gt; {<br>  data class SomeFunctionality(val value: Boolean) : Feature&lt;Boolean&gt;(<br>      key = &quot;some_functionality&quot;,<br>      title = &quot;SomeFunctionality&quot;,<br>      description = &quot;SomeFunctionality&quot;,<br>      defaultValue = value<br>  )<br>  <br>  data class AnotherFunctionality(val value: Boolean) : Feature&lt;Boolean&gt;(<br>      key = &quot;another_functionality&quot;,<br>      title = &quot;AnotherFunctionality&quot;,<br>      description = &quot;AnotherFunctionality&quot;,<br>      defaultValue = value<br>  )<br>}</pre><p>This is an example of a developer-specific feature or TestFeature for us. These are very important because they will let us do things like the change between test and production environments or change the theme on a whim.</p><pre>sealed class TestFeature&lt;T&gt;(<br>    override val key: String,<br>    override val title: String,<br>    override val description: String,<br>    override val defaultValue: T<br>) : Setting&lt;T&gt; {<br>    object DebugWithProduction : TestFeature&lt;Boolean&gt;(<br>        key = &quot;debug_with_production&quot;,<br>        title = &quot;Override Firebase Setting&quot;,<br>        description = &quot;Enable reading feature flag from Firebase on debug builds&quot;,<br>        defaultValue = false<br>    )<br>    data class Theme(val value: String) : TestFeature&lt;String&gt;(<br>        key = &quot;theme&quot;,<br>        title = &quot;Theme&quot;,<br>        description = &quot;This is our theme value to test different themes&quot;,<br>        defaultValue = value<br>    )<br>}</pre><h3>Multi-Brand!!</h3><p>So, where does the value come from? This can be done easily with DI. You see, we wanted to have different values depending on the brand, but no information about the brand that we’re in, so we needed a simple workaround for this.</p><p>First, we added the default values inside the build.gradle of the parent’s project. That way, the project can provide different BuildConfig values depending on the compilation, and this can be used for multiple flavors (release, debug, development, etc…)</p><pre>flavor1 {<br>    dimension &quot;flavors&quot;<br>   <br>    buildConfigField &quot;boolean&quot;, &quot;SOME_FLAG&quot;, &quot;false&quot;<br>    buildConfigField &quot;boolean&quot;, &quot;ANOTHER_FLAG&quot;, &quot;true&quot;<br>}<br><br>flavor2 {<br>    dimension &quot;cobrand&quot;<br>    <br>    buildConfigField &quot;boolean&quot;, &quot;SOME_FLAG&quot;, &quot;true&quot;<br>    buildConfigField &quot;boolean&quot;, &quot;ANOTHER_FLAG&quot;, &quot;false&quot;<br>}</pre><p>Secondly, we needed to add the BuildConfig fields to our DI solution, Koin.</p><pre>single&lt;Boolean&gt;(named(SOME_FLAG)) { BuildConfig.SOME_FLAG  }<br>single&lt;Boolean&gt;(named(ANOTHER_FLAG)) { BuildConfig.ANOTHER_FLAG  }</pre><p>The third and final step is inside the feature flags module.</p><pre>single { Feature.SomeFunctionality(value = get(named(SOME_FLAG))) }<br>single { Feature.AnotherFunctionality(value = get(named(ANOTHER_FLAG))) }</pre><p>And there you have it!!! We have the feature flag singletons that can have different values depending on the flavour they’re on, so if everything fails, they can always get their default value.</p><h3>Getting the real values</h3><p>Ok, so to get the value and allowing the feature to double-check and have a list of permissions, we need to return more than a simple value. For that, we created SettingStatus.</p><pre>class SettingStatus&lt;out T&gt;(<br>    val value: T,<br>    val key: String,<br>    val title: String,<br>    val pids: Array&lt;String&gt;?<br>)</pre><p>Same as with the Feature, it receives a generic value and returns some values that are useful to us</p><ul><li>The value that the Feature returns</li><li>A key to identifying which feature the value is from</li><li>A title to show later in a Developer Screen</li><li>A list of user PIDs to check with the user’s current one so we can have a functional permission system.</li></ul><h3>Observability</h3><p>One of our main concerns was that we needed to develop faster. That meant we had to do fewer application restarts and re-compilations, as we lost a lot of time doing these. To solve this issue, we needed the FeatureFlags to be observable, as it allowed us to change their value in runtime without needing to restart o recompile.</p><p>For us, that was pretty easy. We’re using <a href="https://kotlinlang.org/">Kotlin Programming Language</a>, and one of the top notch features that the language has are Flowables <a href="https://kotlinlang.org/docs/flow.html">Asynchronous Flow | Kotlin</a>.</p><p>It’s a simple API and it’s fully integrated with Coroutines <a href="https://kotlinlang.org/docs/coroutines-guide.html">Coroutines guide | Kotlin</a> our concurrency framework of choice. So we implemented that in our Repository and data sources.</p><h3>A little bit of abstraction</h3><p>Now we need to get this for each of our features. To achieve this, we need to add some kind of data source or provider that can get those values for us. We used DataSource as naming, as it has a little bit more logic and information than a simple data source.v</p><pre>interface SettingDataSource {<br>    fun &lt;T&gt; getSetting(setting: Setting&lt;T&gt;): Flow&lt;SettingStatus&lt;T&gt;&gt;<br>}<br><br>/**<br> * If we have more remote providers we can set an priority to them.<br> */<br>const val TEST_PRIORITY = 0<br>const val MAX_PRIORITY = 1<br>const val MEDIUM_PRIORITY = 2<br>const val MIN_PRIORITY = 3</pre><p>Pretty simple, right? The only thing that we need is a function to get our Setting (doesn’t matter if it is a Feature or TestFeature) and a priority that we will use later for sorting purposes.</p><p>Ah! I almost forgot. See that Flow&lt;SettingStatus&lt;T&gt;&gt;? That allows our Flags to be reactive and observable so we can change it in our console and see the change ipso facto.</p><p>As we can be in different environments (development or production), we need a way to get those values from different places, that is, a remote or production provider that gets us the currently working values, and a local or debug provider.</p><pre>abstract class LocalSettingsDataSource(<br>    private val settingsStore: SettingsDataStore<br>): SettingDataSource {<br>    suspend fun &lt;T&gt; setSetting(setting: Setting&lt;T&gt;, value: T) =<br>        settingsStore.setSetting(setting.key, value = value)<br>}<br><br>abstract class RemoteSettingsDataSource : SettingDataSource {<br>    abstract val priority: Int<br>    abstract var isEnabled: Boolean<br>    abstract suspend fun refreshRemoteSettings()<br>}</pre><p>As you can see, the Local and Remote providers have different values and functionalities</p><ul><li>The LocalDataSource only works in our developer environment, so we only have one of those in the application and is the only one that has the ability to change the value of a Feature Flag</li><li>The RemoteDataSource is a bit different, as we can have multiple of those (firebase, aws, a custom API), so we need a way to know if they&#39;re working properly and sort them by priority if they&#39;re all up.</li></ul><h3>Adding the final touch</h3><ul><li>Feature Flags Done ✅</li><li>Providers ✅</li><li>Logic to choose between providers ❌</li></ul><p>The final step for everything to work properly is to add some logic to let the application choose between providers. Let’s go bit by bit.</p><pre>class SettingsRepositoryImpl(<br>    private val remoteSettingsDataSources: Array&lt;RemoteSettingsDataSources&gt;,<br>    private val localSettingsDataSource: LocalSettingsDataSource,<br>    private val isDeveloperEnvironment: Boolean<br>) : SettingsRepository {...}</pre><p>First of all, we need a repository to do the logic for us, that repository has information about whether it is in developer environment or not, an instance of a LocalSettingsDataSource and a list of RemoteSettingsDataSource.</p><pre>private fun &lt;T&gt; getRemoteSettings(setting: Setting&lt;T&gt;) =<br>    remoteSettingsProviders.filter { it.isEnabled }.minByOrNull { it.priority }<br>        ?.getSetting(setting = setting) ?: flowOf(<br>        SettingStatus(<br>            value = setting.defaultValue,<br>            key = setting.key,<br>            title = setting.title,<br>            pids = null<br>        )<br>    )</pre><p>Secondly, when we want to retrieve the value from a remote provider; we need to check first if it’s enabled and then sort it using the priority, if there is no remote provider available, we need to return a default value the Feature can still work</p><p>So now Remote is working fine GREAT!!!. But we want to go a little further, we need a LocalDataSource and we want to be able to change between local and remote when we&#39;re on developer mode</p><pre>override fun isProductionDebugEnabled(): Flow&lt;SettingStatus&lt;Boolean&gt;&gt; =<br>    localSettingsProvider.getRemoteSetting(TestFeature.DebugWithProduction)<br><br>override fun &lt;T&gt; isRemoteSettingEnabledObservable(setting: Setting&lt;T&gt;): Flow&lt;SettingStatus&lt;T&gt;&gt; =<br>    if (isDeveloperEnvironment) {<br>        if (setting is TestFeature) {<br>            localSettingsProvider.getSetting(setting = setting)<br>        } else {<br>            isProductionDebugEnabled().map {<br>                it.value<br>            }.flatMapLatest { isProductionDebugEnabled -&gt;<br>                if (isProductionDebugEnabled) {<br>                    localSettingsProvider.getSetting(setting = setting)<br>                } else {<br>                    getRemoteSettings(setting = setting)<br>                }<br>            }<br>        }<br>    } else {<br>        getRemoteSettings(setting = setting)<br>    }</pre><p>It seems like a lot but it is really simple. Whenever we want to get a value, first we need to check whether we’re in a developer environment or not. This will not change at runtime, so we only need to check this once to prevent misuse of this feature.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/706/1*zaMWm7eXiTPJXUAJJa4O-A.png" /></figure><p>So now that we’re in a Developer environment, we check if the feature is a TestFeature; if it is, we go to local directly.</p><p>And now, the fun stuff. We have our isProductionEnabled flag that is local and observable. The great thing about this Feature being Observable is that we just need to map the latest value, and it will update the Feature’s DataSource from Local to Remote. That allows us to ship our features completely disabled and test them locally. And whenever product feels confident that it can be released, they only need to enable it on Firebase, API or whatever solution we’re using to enable it.</p><p>This gave us sooooo much developing speed and helped us a lot and I mean A LOT in creating new features and adopt <a href="https://trunkbaseddevelopment.com/">Trunk Based Development</a>.</p><p>The final step was to add a way to change the values only if we’re in a developer environment.</p><pre>override suspend fun &lt;T&gt; setRemoteConfig(setting: Setting&lt;T&gt;, value: T) =<br>    if (isDeveloperEnvironment &amp;&amp;<br>        (setting is TestFeature || isProductionDebugEnabled().map { it.value }.take(1).single())<br>    ) {<br>        localSettingsDataSource.setSetting(setting = setting, value = value)<br>    } else Unit</pre><p>And as with the get method, we need to check if the isProductionEnabled flag has been enabled or not before letting the user change the value, and this only works with the LocalDataSource</p><h3>Final thoughts</h3><p>And I think that’s it. Really the only thing that it is missing is the real implementations of the Local and Remote DataSources, but I think that everyone has their implementations and way of doing things or maybe they have some code that they’re not supposed to share…</p><p>To summarize, I wanted to remind how much easier this has made developing and refactoring both productivity and enjoyment. It’s pretty fun to develop a new feature and ship it directly without thinking if it’s going to affect the user or not or if it’s going to crash because we can disable it by default and just test it locally. We can develop it, merge it to develop, and everything is going to work fine.</p><p>We moved to trunk before having this implemented, and it was pretty tough because we had to think what we were going to ship, and we needed a fully functional feature, but with this, we can just do it bit by bit and do incremental builds of our code.</p><p>Sooo, yeah. Just try it, it will improve your code &amp; your team’s coding speed and, in turn, makes coding more fun.</p><p><strong>This article was written by: </strong><a href="https://github.com/Leondev7">Jorge García</a>, Android engineer.</p><p>Thank you for reading. We are <a href="https://apply.workable.com/tymit/"><strong>hiring</strong></a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=17fd1a0c17e9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamic Features in an iOS Multi-Brand App]]></title>
            <link>https://medium.com/@tymitengineering/dynamic-features-in-an-ios-multi-brand-app-85002bbdbfa4?source=rss-750573d4f412------2</link>
            <guid isPermaLink="false">https://medium.com/p/85002bbdbfa4</guid>
            <category><![CDATA[dynamic-feature-module]]></category>
            <category><![CDATA[tuist]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Tymit Engineering]]></dc:creator>
            <pubDate>Mon, 27 Mar 2023 13:25:14 GMT</pubDate>
            <atom:updated>2023-03-28T12:55:30.056Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sPgVwrE-k3PFdiigvJ0Z2g.jpeg" /><figcaption>AI-generated image: shapes fitting in a giant monolith.</figcaption></figure><p><strong>DRY</strong>, or <strong>Don’t Repeat Yourself</strong>, is a primary goal in software development. It means writing code once and reusing it as much as possible. Code is expensive to create, maintain, and test. Therefore, the more code we can reuse, the better it is for the company and our productivity.</p><p>At <em>Tymit</em>, we began to transform our project into a multi-brand app over a year ago, and with that, we had to extract code from our monolith app into small modules that can be added to each brand as we please.</p><p>When we started splitting our app’s code, we identified two types of modules:</p><ul><li><strong>Clients</strong>: such as the API or persistence, which are dependencies that will interact with the boundaries of the app, interacting with the outside world. They are independent of the business logic.</li><li><strong>Features</strong>: the internal logic of our application will only interact with the business model.</li></ul><p>We use a well-defined directory tree for the project to enable automation between the various tools we use. This would be our main scheme:</p><pre>/ios-app<br>├── App<br>│   ├── Sources<br>│   └── Tests<br>├── Brand<br>│   ├── White<br>│   │   ├── Project.swift<br>│   │   ├── Sources<br>│   │   ├── Resources<br>│   │   ├── Tests<br>│   │   └── Supporting Files<br>│   └── Black<br>│       ├── Project.swift<br>│       ├── Sources<br>│       ├── Resources<br>│       ├── Tests<br>│       └── Supporting Files<br>├── Features<br>│   ├── FeatureA<br>│   │   ├── Project.swift<br>│   │   ├── Sources<br>│   │   └── Tests<br>│   └── FeatureB<br>│       ├── Project.swift<br>│       ├── Sources<br>│       └── Tests<br>└── Packages<br>    ├── APIClient<br>    │   ├── Project.swift<br>    │   ├── Sources<br>    │   └── Tests<br>    ├── FeatureKit<br>    │   ├── Project.swift<br>    │   ├── Sources<br>    │   └── Tests<br>    └── Persitence<br>        ├── Project.swift<br>        ├── Sources<br>        └── Tests</pre><h3>Modularisation</h3><p>When approaching a multi-brand development, it is typical for all brands to implement the same features, with the only differences between them being the images and colours. However, in our case, we wanted to use the highest percentage of code possible and be able to add some specific features for each brand.</p><p>One of the first solutions we thought of was to use <strong>feature flags</strong> to configure the app at runtime. The main problem we encountered with this solution is that we would have to deploy the app with <strong>all the code</strong>, and for example, if one brand required camera permissions and another did not, both brands would need to include that permission to configure it at runtime and prevent crashes. Also, with this approach, we would only have certainty that it would be working with the <strong>feature flag </strong>configuration and we could not test it separately.</p><p>Another solution we were studying was to implement features with <strong>compiler flags </strong>to deliver only the code we would use in the app. We liked this approach more because, during compilation time, <em>Xcode</em> would warn us that we would be trying to access a function or some feature that would not be included in some brand. The problem we found with this approach was that it did not scale well when we had many brands because we will always have to add all possible variants for that brand in the <strong>same file</strong>, and when you have two brands it may be manageable in one file, but as soon as you start having more, that file becomes difficult to maintain and to search which brand you are dealing with.</p><h3>tuist at scale</h3><p>At <em>Tymit</em>, we use <a href="https://tuist.io/"><strong>Tuist</strong></a> to configure the entire project infrastructure. <strong>Tuist</strong> is a command-line tool used to automate the configuration and maintenance of <em>Xcode</em> projects. This tool allows us to generate and manage projects, dependencies, schemes, configuration files, and other elements related to application development in an easy and efficient manner.</p><p><strong>Tuist</strong> defines projects through <em>Swift</em> configuration files, allowing for greater flexibility and customisation in project creation and management.</p><p>We can define templates to describe the projects for our Features and each Brand. These templates will be defined within <a href="https://docs.tuist.io/guides/helpers/">ProjectDescriptionHelpers</a>, which is the module that <strong>Tuist</strong> provides us to reuse code between different <strong>Project.swift</strong>. We will start by defining our Features:</p><pre>//ios-app/Tuist/ProjectDescriptionHelpers/Feature+Project.swift<br><br>import ProjectDescription<br><br>public extension Project {<br>  static func feature(<br>    name: String,<br>    dependencies: [TargetDependency] = []<br>  ) -&gt; Self {<br>    .init(<br>      name: name,<br>      targets: [<br>        .init(<br>          name: name,<br>          platform: .iOS,<br>          product: .staticLibrary,<br>          bundleId: &quot;com.tymit.\(name)&quot;,<br>          sources: [<br>            .glob(.relativeToRoot(&quot;Features/\(name)/Sources/**&quot;)),<br>          ],<br>          dependencies: dependencies<br>        ),<br>        .init(<br>          name: &quot;\(name)Tests&quot;,<br>          platform: .iOS,<br>          product: .unitTests,<br>          bundleId: &quot;com.tymit.\(name).Tests&quot;,<br>          sources: [<br>            .glob(.relativeToRoot(&quot;Features/\(name)/Tests/**&quot;)),<br>          ],<br>          dependencies: [<br>            .target(name: name)<br>          ]<br>        )<br>      ]<br>    )<br>  }<br>}</pre><p>One way to avoid errors is to define a new type called Feature so that it can be consumed directly from the Brand. We generated this type from an <a href="https://github.com/ruby/erb">ERB template</a> and do a <a href="https://ruby-doc.org/current/Dir.html">Dir</a> in the Features path to always have that type synchronised and avoiding to point to incorrect Features names. We will have an extension that will automatically convert this Feature into a TargetDependency type that our Brand will consume.</p><pre>//ios-app/Tuist/ProjectDescriptionHelpers/Feature.swift<br><br>import ProjectDescription<br><br>public enum Feature: String, CaseIterable {<br>  case featureA = &quot;FeatureA&quot;<br>  case featureB = &quot;FeatureB&quot;<br>}<br><br>extension Feature {<br>  var dependency: TargetDependency {<br>    .project(<br>      target: rawValue,<br>      path: &quot;//Features/\(rawValue)&quot;<br>    )<br>  }<br>}</pre><p>Now we can define our description for each Brand project. Following the folder structure we showed you at the beginning, in the App/Sources folder, we will include all the code that will be shared among all Brands, and then we will add a <strong>Sources</strong> folder for each Brand, where we will instantiate and define any and all brand-specific code.</p><pre>//ios-app/Tuist/ProjectDescriptionHelpers/Brand+Project.swift<br><br>import ProjectDescription<br><br>public extension Project {<br>  static func brand(<br>    name: String,<br>    features: [Feature] = [],<br>    dependencies: [TargetDependency] = []<br>  ) -&gt; Self {<br>    .init(<br>      name: name,<br>      targets: [<br>        .init(<br>          name: name,<br>          platform: .iOS,<br>          product: .app,<br>          bundleId: &quot;com.tymit.\(name)&quot;,<br>          sources: [<br>            .glob(.relativeToRoot(&quot;App/Sources/**&quot;)),<br>            .glob(.relativeToRoot(&quot;Brands/\(name)/Sources/**&quot;)),<br>          ],<br>          dependencies: dependencies + features.map(\.dependency)<br>        ),<br>        .init(<br>          name: &quot;\(name)Tests&quot;,<br>          platform: .iOS,<br>          product: .unitTests,<br>          bundleId: &quot;com.tymit.\(name).Tests&quot;,<br>          dependencies: [<br>            .target(name: name)<br>          ]<br>        )<br>      ]<br>    )<br>  }<br>}</pre><p>Now we can define our features independently for each Brand. Let&#39;s start with the example that the <strong>White</strong> brand has <strong><em>featureA</em></strong> and the <strong>Black</strong> brand has <strong><em>featureB</em></strong>.</p><pre>//ios-app/Workspace.swift<br><br>import ProjectDescription<br><br>let project = let project = Workspace(<br>  name: &quot;App&quot;,<br>  projects: [<br>    &quot;//Brands/Black&quot;,<br>    &quot;//Brands/White&quot;<br>  ]<br>)</pre><h3>FeatureKit</h3><p>All the features specific to each Brand have an entry point. That&#39;s where we&#39;re going to split the business logic between Features. For this, we&#39;ll use a module independent of these Features and we&#39;ll call it <strong>FeatureKit</strong>. We&#39;ll use a protocol-witness approach for this library since it will help us create different witnesses in each Brand.</p><h3>Let’s code</h3><p>Let’s imagine that the set of tabs in a UITabBarController changes between different variants. We&#39;ll start by creating our <strong>FeatureKit</strong> library and defining its entry point, which in this case would be the tabs:</p><pre>public struct FeatureClient {<br>  public let tabs: () -&gt; [UIViewController]<br><br>  public init(tabs: @escaping () -&gt; [UIViewController]) {<br>    self.tabs = tabs<br>  }<br>}</pre><p>This library is a dependency that will be necessary at the root of the project. Here, we will declare all the entry points where the app will branch out for each different use case we want for each variant. It is necessary that this library does not contain types that depend on third parties. That is, all the types we declare are primitive.</p><p>Let’s create a <strong>witness</strong> for one of the brands in which we will only load three tabs. We will add this file exclusively to the <strong>White</strong> variant target:</p><pre>//ios-app/Brand/White/Sources/FeatureClient+Live.swift<br><br>import FeatureKit<br><br>extension FeatureClient {<br>  static var live: Self {<br>    .init(<br>      tabs: { <br>        [<br>          OneViewController()<br>          TwoViewController()<br>          ThreeViewController()<br>        ]<br>      }<br>    )<br>  }<br>}</pre><p>Now it’s time to create the witness for the <strong>Black</strong> variant. For this <strong>witness</strong> we will need to load five tabs, where two of them will come from a dependency called <strong>FeatureB</strong>, which contains the UIViewControllers for FooViewController and BarViewController:</p><pre>//ios-app/Brand/Black/Sources/FeatureClient+Live.swift<br><br>import FeatureKit<br>import FeatureB<br><br>extension FeatureClient {<br>  static var live: Self {<br>    .init(<br>      tabs: { <br>        [<br>          OneViewController()<br>          TwoViewController()<br>          ThreeViewController()<br>          FooViewController()<br>          BarViewController()<br>        ]<br>      }<br>    )<br>  }<br>}</pre><p>Afterwards, we just need to invoke FeatureClient.live.tabs() wherever we are going to instantiate our UITabBarController to inject the corresponding tabs. Instead of directly using the reference to the <strong>Live</strong> client, it is preferable to use an approach similar to @Dependency(.featureKit) var featureClient as explained in pointfree <a href="https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/">swift-dependencies</a> so we’re able to test this.</p><pre>import UIKit <br>import Dependencies<br><br>struct Screen {<br>  @Dependecy(\.featureKit) var featureClient<br><br>  var viewController: UIViewController {<br>    UITabBarController(viewControllers: featureClient.tabs())<br>  }<br>}</pre><h3>Testing</h3><p>Since each brand will have an independent test target, we can isolate and test this functionality independently. We can introduce a value that we control in our test environment or use the same dependency we use in <strong>live</strong>.</p><pre>import SnapshotTesting<br>import XCTest<br><br>final class ScreenTests: XCTestCase {<br>  func testBrandTabs() throws {<br>    let screen = withDependencies {<br>      $0.featureKit.tabs = { [MockViewController()] }<br>    } operation: {<br>      Screen()<br>    }<br>    assertSnapshot(matching: screen.viewController, as: .image)<br>  }<br>}</pre><h3>Conclusion</h3><p>To create a white-label app, we have to address diverse challenges.<strong> Not all projects need to split the code between variants</strong>. Ideally, the same code should be used for all variants. Having a single point of reference for all <strong>dynamic features</strong> is an advantage when creating new variants and not forgetting any implementation. Being able to have different witnesses for each variant saves us from having to deal with complex configurations between different targets using the same file. Sharing code between a common app and specific features can be a very complex task, but thanks to <strong>Tuist</strong>, we can create a reliable and truthful scenario. When generating the project, it will verify that the entire dependency tree is described correctly, and we can add more complex functionalities, such as dynamically adding <strong>AppExtensions</strong> to a particular Brand.</p><p><strong>This article was written by: </strong><a href="https://twitter.com/fdzsergio">Sergio Fernández</a>, Principal iOS engineer.</p><p>Thank you for reading. We are <a href="https://apply.workable.com/tymit/"><strong>hiring</strong></a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=85002bbdbfa4" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>