<?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[Engineering At GoodNotes - Medium]]></title>
        <description><![CDATA[We’re the makers of GoodNotes, the iPad note-taking app loved by students, educators, and paperless individuals all around the world. - Medium]]></description>
        <link>https://medium.com/engineering-at-goodnotes?source=rss----af49331afdd8---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Engineering At GoodNotes - Medium</title>
            <link>https://medium.com/engineering-at-goodnotes?source=rss----af49331afdd8---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 03 Nov 2026 06:32:09 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/engineering-at-goodnotes" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[GoodDevs at WASM IO 2023]]></title>
            <link>https://medium.com/engineering-at-goodnotes/gooddevs-at-the-wasm-io-c9981272bc10?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/c9981272bc10</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[webassembly]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Tue, 04 Apr 2023 06:41:59 GMT</pubDate>
            <atom:updated>2023-04-04T06:42:35.103Z</atom:updated>
            <content:encoded><![CDATA[<p>Practice makes perfect. That’s why our team is continuously learning and making the best use of the resources available to improve and constantly evolve. Today, part of our cross-platform team had the chance to attend the biggest WebAssembly event in Europe. Motivated by Marcos Cárceles, the cross-platform team Engineering Manager, Antonio, Trujy, Mauro, and Pedro went to Barcelona to enhance their Web Assembly skills. If you want to know how it went, keep reading!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yBVCchJMEIbxEFqXD2CukQ.png" /></figure><p>The conference started with awesome talks. WASM is an exciting ecosystem growing daily thanks to developers from many different programming languages like Swift, Rust, C++, Python, etc. For this event, Tyler McMullen opened the conference with a talk about WASM’s history, current state, and future outside of the browser. Covering technical details and community effects that make this not just viable but super valuable. And how it all ties together into a future that gives us the freedom to run code where and how we want to.</p><p>For our team, this talk was really interesting. WebAssembly is the technology our cross-platform team uses to be able to reuse the Swift code written for years to be able to make GoodNotes the best note-taking and PDF markup app. Thanks to WebAssembly, our team can reuse thousands of lines of code almost for free and focus on shipping the best possible product instead of duplicating a massive, already-written codebase.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UfD6a08UtxgwscAo_T5F2g.jpeg" /></figure><p>This was just the beginning, and it was an amazing talk. Later this day talks like “Wasmer Things: An Upside-Down Guide to WebAssembly” by Edoardo Vacci or “Mobile app development with WebAssembly GC” by Vivek Sekhar completely got our attention. Low-level details related to how WASM works under the hood or WasmGC works were among the best. Digging deep down into the tech stack we use helps us identify improvement areas in our codebase to build a better product.</p><p>Vivek Sekhar’s lightning talk about <a href="https://github.com/WebAssembly/gc">WebAssembly GC</a> was one of the best surprises this event gave us. If using WASM GC, we can improve our performance by efficiently supporting high-level managed languages to WebAssembly, via struct and array types that enable language compilers targeting Wasm to integrate with a garbage collector in the host VM be a really nice improvement for us.</p><p>Right after having lunch, another talk got our attention. This time because of the special mention to Yuta Saito! Yuta joined our team more than one year ago and contributed to our project with awesome technical improvements. We are lucky to have Yuta on our team and also glad to see how he’s also an important reference developer for other engineers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hnZdvyG7axk7Bw7wLK0-OA.jpeg" /></figure><p>The first day was about to end when we noticed there was an excellent lighting talk about to start! “Rust and WebAssembly threads on production: a field report” by Roberto Vidal. An amazing talk about how Roberto and the rest of the team built them, what they tried, what worked, and what is yet to work! As you may expect, WebAssembly threads unlock a game-changing primitive for the Web and other targets. Workloads that require shared memory and parallelization can finally leverage the full power of the underlying platform.</p><p>Ready for the second day, our team woke up willing to listen to awesome talks. Checking the agenda there were promising talks. From new toolchains to cloud computing or even blockchain!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dibWW73VRUE5GTxI5oInDQ.jpeg" /></figure><p>There was a special talk we wanted to mention. If you work with WASM, you know what we are talking about. If you have never heard about WASM, the fact that we specially mention this talk may be shocking. “WebAssembly debugging demystified” by Nisanthan Nanthakumar. A thorough review of the DWARF debug file format and the tools available to be able to include breakpoints, evaluate expressions and inspect our code. Believe it or not, debugging WASM binaries while executing the binary on a browser was not straightforward for us. Since we started the project, the team has not been able to inspect variables or evaluate expressions with the tooling provided by standard browsers like Google Chrome. However, this talk gave us information about some tooling we can use to inspect the WASM binaries and check in detail the memory allocated.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yp_QLaPlqhkBkqw-pdO_yA.png" /></figure><p>Before ending the day and the conference, our front-end team decided to get out of their comfort zone and learn about something different and experimental for them: Grain and Lunatic. Lunatic is a runtime for building distributed, highly concurrent systems with WebAssembly, and Grain is a programming language that makes getting up and running with WebAssembly a breeze. This talk was about the ins and outs of Lunatic, from its process spawning model to how it can run untrusted code and ultimately build a full application that can withstand enormous amounts of traffic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ew8Zk515mubqP69agkHMqA.jpeg" /></figure><p>The event ended on Friday afternoon, and it was time to return home. We had an awesome time at WASM IO! Our team works remotely, and having the chance to see each other on-site in events like this is something we love. However, this is not it! After the conference, we have to check some details and some notes we took to see how we can improve our usage of this technology. This is a list of tools and projects we want to check in detail we’ve found interesting:</p><ul><li><a href="https://github.com/rustwasm/twiggy">Twiggy:</a> A WASM binary size profiler.</li><li><a href="https://github.com/rustwasm/wasm-snip">wasm-snip</a>: Optimize the binary size by replacing unused functions with a nop version.</li><li><a href="https://github.com/alexcrichton/wasm-gc">wasm-gc</a>: Promising Google Project we want to try as soon as it is available.</li><li><a href="https://docs.docker.com/desktop/wasm/">Docker + WASM</a>: Docker support for WASM.</li><li><a href="https://github.com/Shopify/javy">Javy</a>: Running JS into WASM.</li><li><a href="https://github.com/WebAssembly/wabt">WABT</a>: Tooling to be able to work with wasm files.</li><li><a href="https://github.com/kowasm/kowasm">Kowasm: </a>Fullstack development with Kotlin and WASM.</li></ul><p>This article was written by:<br><a href="https://twitter.com/pedro_g_s">Pedro</a>, Mauro, <a href="https://twitter.com/AntonioCandinho">Antonio</a>, and <a href="https://twitter.com/fjtrujy">Trujy</a> part of the cross-platform team at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9981272bc10" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/gooddevs-at-the-wasm-io-c9981272bc10">GoodDevs at WASM IO 2023</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Who watches the watchers? Mutation Testing]]></title>
            <link>https://medium.com/engineering-at-goodnotes/who-watches-the-watchers-mutation-testing-5db732d709ad?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/5db732d709ad</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[testing-tools]]></category>
            <category><![CDATA[software]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Mon, 06 Mar 2023 09:03:46 GMT</pubDate>
            <atom:updated>2023-03-06T09:03:46.211Z</atom:updated>
            <content:encoded><![CDATA[<p>Have you ever thought about the quality of your tests? In addition to code coverage and the number of test suites, how can we measure if our tests are good enough? <strong>How many changes do I have to apply to the production code before detecting a bug using our automated tests?</strong> I you find these questions interesting, keep reading because you’ll find this blog post quite interesting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AA8YsWAGNtg7t8_yR8iYFQ.png" /></figure><h3>Why should I care about Mutation Testing?</h3><p>Whenever we develop a program, we, as engineers, should always <strong>guarantee this software is working as expected</strong> and any future change we implement when adding some new features or updating any behaviour doesn’t break it. To be able to do this, we frequently use automated testing. Automated tests are our safety net and a guarantee of the correct behaviour of our program. Obviously, all of them run on CI and use many different testing strategies! <strong>Let’s take a look at the list of automated testing strategies we use at GoodNotes:</strong></p><ul><li>Classic unit tests, including parametric ones.</li><li>Integration tests for storage and network usage.</li><li>HTTP stubbing.</li><li>Contract tests for the API.</li><li>Screenshot tests.</li><li>UI tests mocking API integration.</li><li>Property-based tests.</li><li>End-to-end tests.</li><li>etc</li></ul><p>Up to this point, we may think having all these tests and using many different testing strategies is more than enough to ensure the quality of the project. However, <strong>how can we measure the quality of our tests?</strong> What tools can we use to ensure our safety net is safe enough and any future change breaking the app will be detected automatically? It’s time to use mutation testing!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*htAuxS618Bwa8cQKFWJbGQ.png" /><figcaption>Richard Lipton</figcaption></figure><p><a href="https://www.researchgate.net/publication/235050055_Mutation_Analysis">Originally proposed by Richard Lipton et al in 1979, Mutation Analysis (AKA Mutation Testing)</a> is an automated (or not) <strong>testing strategy we can use to check if our tests will help us and identify bugs when our software evolves over time.</strong> It can be a more pragmatic, complex, and interesting approach than just using code coverage or the number of tests as a quality metric. It can also be used as a forensic tool for our codebase.</p><p>The idea is simple. We first get a test suite passing, then we apply some random changes to our code, ensuring these are valid in build time, and last but not least, we run our tests again. <strong>If we don’t see our tests failing… we fail with them!</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GMNSwgj9DmvHzEfIsd26iw.png" /></figure><p>Build-time valid code mutations should make our tests fail unless these mutations are semantically equivalent to the original code, obviously! I<strong>f we change our production code and the behaviour changes, we should get a test letting us know there is something we should check.</strong> Maybe it’s not a bug, but a behaviour we updated, so we should update our tests accordingly to ensure this new behaviour is covered.</p><p>Up to this point, you may be thinking this is too much for your app. And maybe right, but now think about payment systems’ software. How would you feel if I randomly change the software computing your payslip and tests are still passing? <strong>Critical paths like payments or health-related software are great candidates if we want to use mutation testing, </strong>but this testing strategy may not be so important for other elements where fault tolerance is cheaper.</p><p>Now that we know how this tool works, it’s time to review the <strong>list of mutations</strong> we can apply before executing our tests. Stryker, a well know mutation-testing tool got a list <a href="https://stryker-mutator.io/docs/mutation-testing-elements/supported-mutators/">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fwmjx_3oZ4hyqmk9ovfDrg.png" /></figure><h3>Time to get your hands dirty!</h3><p>Would you like to practice mutation testing? We’ve got you! <strong>We created an exercise for you</strong>. You can clone <a href="https://github.com/pedrovgs/typescriptkatas">this repository</a> and follow the exercise. In this repository, you’ll find a TypeScript module written already implementing a simplified version of a kata named “KataPotter”. You’ll only find the production code, so your goal is to write all the tests you want and then evaluate if the tests written are able to survive mutations. These are the rules the ``checkout`` function follows in order to check the cart prices for this kata:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QN8HfWm_3XXDqXMWJxNJsA.png" /><figcaption>* HP == HarryPotter book. So 1 HP5 → 8€ means the price of 1 Harry Potter #5 is 8€.</figcaption></figure><p>These are the steps you have to follow in order to complete this exercise:</p><ul><li>git clone <a href="https://github.com/pedrovgs/typescriptkatas">https://github.com/pedrovgs/typescriptkatas</a></li><li>cd typescriptkatas</li><li>git checkout practice-mutation-testing</li><li>yarn install</li><li>yarn test</li><li>Write as many tests as you want and check code coverage: yarn test &amp;&amp; open coverage/index.html</li><li>yarn test:mutate &amp;&amp; open reports/mutation/mutation.html</li></ul><p>Once you get to this point, <strong>you’ll find that you have quite a high coverage (probably 100%) however when you evaluate your tests using mutation testing, you’ll find there are some mutations surviving!!</strong> In my case, I got 4 mutations surviving, 1 timeout, and 58 mutations killed!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zh4KQU8y35k6i8E4gIE8Gg.png" /><figcaption>* Results may vary depending on the tests you wrote.</figcaption></figure><p>This report from the mutation testing tool is just great. Stryker (the tool we are using) has found we can replace a “*” operator with a “/” operator in our code and nobody would notice!! How is this possible? Because there was a missing test scenario we didn’t include it!</p><h3>What about other programming languages?</h3><p>Would you like to use it tomorrow in your projects? Here we got a list of mutation testing tools for different languages:</p><ul><li>JavaScript / TypeScript, C#, Scala: <a href="https://stryker-mutator.io/">https://stryker-mutator.io</a></li><li>Swift: <a href="https://github.com/muter-mutation-testing/muter">https://github.com/muter-mutation-testing/muter</a></li><li>Python: <a href="https://mutatest.readthedocs.io/">https://mutatest.readthedocs.io</a></li><li>Rust: <a href="https://github.com/llogiq/mutagen">https://github.com/llogiq/mutagen</a></li><li>Java &amp; Kotlin: <a href="https://pitest.org/">https://pitest.org</a></li></ul><h3>Conclusions</h3><p>As you can see, mutation testing is able to identify <strong>changes in your code that are not covered by your tests</strong>, even when your code coverage can be 100%. After running this very same exercise with the GoodNotes engineering team we reached some conclusions we’d like to share with you:</p><ul><li>Coverage by itself without assertions doesn’t help.</li><li>This is not the fastest tool, but that doesn’t mean it’s not interesting. It means it’s not designed to be used with slow test suites.</li><li>Evaluating mutations only on files changed for every PR would be a nice integration point for CI.</li><li>More interesting than code coverage.</li><li>When you find a mutation surviving, it’s a chance to include a test or refactor your code.</li><li>It’s complementary to other testing strategies and coverage (obviously).</li><li>Can help to identify coupling sometimes.</li><li>Not safe for slow test suites.</li><li>Worst naming ever.</li><li>Some mutations would never be applied by humans or are equivalent so this means → Bad signal/noise ratio for us…</li><li>This strategy demonstrates how type safety and some FP features to reduce risk when coding.</li><li>A good starting point could be to include it for incremental changes in your PRs.</li><li>Way more useful in 1979!</li></ul><p><strong>This article was written by: </strong><a href="https://twitter.com/pedro_g_s"><strong>Pedro Gómez</strong></a><strong> &amp; </strong><a href="https://twitter.com/tomasruizlopez"><strong>Tomás Ruiz-López</strong></a>, Senior Software Engineers at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5db732d709ad" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/who-watches-the-watchers-mutation-testing-5db732d709ad">Who watches the watchers? Mutation Testing</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GoodDevs at NSSpain 2022]]></title>
            <link>https://medium.com/engineering-at-goodnotes/gooddevs-at-nsspain-2022-7a97e98d0d06?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/7a97e98d0d06</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[team-building]]></category>
            <category><![CDATA[macos]]></category>
            <category><![CDATA[goodnotes]]></category>
            <category><![CDATA[conference]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Fri, 23 Sep 2022 11:21:01 GMT</pubDate>
            <atom:updated>2022-09-23T12:59:11.194Z</atom:updated>
            <content:encoded><![CDATA[<h4>Diary of an unconventional work week</h4><p>GoodDevs are at NSSpain 2022 September 2022! After more than 2 years without on-site conferences, our engineering team decided to join one of the biggest iOS conferences in Europe, NSSpain X — we couldn’t say no to the chance to participate with one speaker and also sponsor NSSpain, a great event full of awesome speakers and top iOS devs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0MOdhAIRBy7BbjQj8pr_mQ.jpeg" /></figure><p>Our team got to Logroño on Wednesday night. Nine developers, five from the iOS team and four from the cross-platform team, got ready to plan and review all the talks and speakers participating at the event the next day. After a thorough review, we decided there were many talks we were excited to hear, including “Composable Architecture at Scale” by <a href="https://twitter.com/merowing_">Krzysztof Zablocki</a>, “Improving the iOS development process guided by technical metrics” by Jessica Hernández and obviously, “Swift WebAssembly + GoodNotes, a cross-platform story!” by myself!</p><p>The first day started and we couldn’t be happier. We arrived together, ready to enjoy the talks with the iOS community. “Improving the iOS development process guided by technical metrics” was one of our favorite talks! Jessica explained how to use tools to measure, improve and act accordingly to improve development workflows guided by metrics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CcZ4rSBtehIPw0Crr9Gjeg.jpeg" /><figcaption>Jessica Hernández</figcaption></figure><p>On the afternoon of the first day, Pedro (the author of this post btw) gave a talk explaining our experience and technical details on how we’re developing GoodNotes for other platforms like Windows, Android, or web browsers reusing the iOS codebase. Pedro’s talk covered in detail how we crafted our cross-platform project using WebAssembly + Swift and explained some interesting technical challenges and solutions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H6UwqaqlxsreYyeGLnJ6ig.jpeg" /><figcaption>Pedro on stage (Photo: Arturo Gutiérrez)</figcaption></figure><p>When the day was over, we gathered together again and reviewed the next day’s talks. Again talks like “How to choose the right branching strategy for your team (and why it matters!)” by <a href="https://twitter.com/ifbarrera">Isabel Barrera</a>, and “Rewriting SoundCloud in SwiftUI” by Matias Villaverde and Rens Breur got our attention.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yZonPszwWLLUrIt76JbnRg.jpeg" /><figcaption>The event was decorated with the GoodNotes Blue</figcaption></figure><p>As expected, Isabel’s talk was fantastic — covering different branching strategies for version control systems was an inspiration for us as a team because it made us think twice about our use of Git as VCS in our mono repository. We also had the chance to speak to her in person during the breaks about the event, Logroño, and our projects!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gX0cR4W8q1-aRam1mUhXzA.jpeg" /><figcaption><a href="https://twitter.com/ifbarrera">Isabel Barrera</a></figcaption></figure><p>On Friday afternoon, the event ended, and it was time to head home. We had an awesome time at NSSpain! Our team works remotely and having the chance to see each other on-site in events like this is something we love.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MI3xXcpqbO7F8_mLT3E49w.jpeg" /><figcaption>The team visited a local winery for some extended fun</figcaption></figure><p>Hear what the team has got to say:</p><blockquote>“The conference went well, and we even randomly chatted with several <strong>potential candidates</strong>! And it was satisfying seeing people walking around with<strong> GoodNotes</strong> <strong>tote bags</strong> each day! ”</blockquote><p>— Miguel Ángel Díaz, Senior iOS Engineer</p><blockquote>“Pedro’s talk was one of the most technical meetings in the whole conference. Also, <strong>he was the only one using swift outside of the iOS/macOS environments.</strong></blockquote><blockquote>It felt welcoming to attend this kind of event with the GoodNotes teams; considering that most of us are working remotely, it is a great opportunity to share time with your workmates, meet each other, and obviously have fun together.”</blockquote><p>— <a href="https://twitter.com/fjtrujy">Francisco Javier Trujillo Mata</a>, Senior iOS Engineer</p><blockquote>“Going back to an in-person event has been a great experience. <strong>I was able to meet many people from my team for the first time</strong>, see Pedro’s talk, and speak with many known and unknown faces from the community. Pretty interesting talks, people, and lots of fun!”</blockquote><p>— Pablo Carcelén, Senior iOS Engineer</p><blockquote>We had a wonderful experience at NSSpain. The talks were great. <strong>It was very interesting to see how other companies solve similar problems that we’re facing</strong> and learn about new topics from some great presenters. <strong>But if there’s something that makes NSSpain particularly unique, it is the social aspect</strong>. Perhaps motivated by the very first in-person NSSpain after 3 years, the atmosphere was perfect. Everyone was super approachable, and we had some fantastic talks with speakers and other attendees during breaks and social events planned by the organisers. It was also a great opportunity to meet in-person some team members that many of us didn’t have a chance to meet yet.</blockquote><blockquote>Our colleague Pedro showed once again his excellent presentation skills. <strong>In 45 minutes he managed to give a great overview of the past, present and future of Cross-Platform, give credit to the team, promote GoodNotes and share that we’re hiring.</strong> Nice work, buddy!</blockquote><p>— <a href="https://twitter.com/JuanjoRamos82">Juanjo Ramos Rodríguez</a>, Engineering Manager</p><h4><strong>Did you go to the NSSpain this year and if yes, how did you find it? Leave a comment below! Our team loved it and we will be back next year and will try to be on the stage again - see you there?</strong></h4><p><strong>This article was written by:<br></strong><a href="http://twitter.com/pedro_g_s"><em>Pedro Gómez</em></a><em> is a </em>Senior Software Engineer focused on new technologies, open-source, clean code, and testing. He is now working as a Senior Software Engineer in the cross-platform team at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a97e98d0d06" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/gooddevs-at-nsspain-2022-7a97e98d0d06">GoodDevs at NSSpain 2022</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tested and Fixed — A step forward in shipping GoodNotes Web Viewer]]></title>
            <link>https://medium.com/engineering-at-goodnotes/tested-and-fixed-a-step-forward-in-shipping-goodnotes-web-viewer-bc54f571fea4?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/bc54f571fea4</guid>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[case-study]]></category>
            <category><![CDATA[webassembly]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Tue, 12 Apr 2022 11:32:17 GMT</pubDate>
            <atom:updated>2022-04-12T12:18:26.749Z</atom:updated>
            <content:encoded><![CDATA[<h3>Tested and Fixed — A Step Forward in Building GoodNotes Web Viewer</h3><h4>A Case Study on Swift &amp; Web Assembly Performance</h4><p>WebAssembly is a significant component of the GoodNotes Web project. It is powerful yet challenging to adopt as it is relatively new and lacks community resource support. The performance issue is one of the main challenges we are passionate about resolving; as in GoodNotes, we deeply care about user experiences and want to make sure GoodNotes is as smooth and slick across all platforms.</p><p>Unlike most previous performance improvements we’ve done in the past where all the solutions applied were related to the UI layer or specific hot paths, this time we decided to go one level deeper in our analysis.</p><p>In this blog post, we break down how we found a performance issue in our app for a specific toolchain (Swift WebAssembly) and fixed it — if you like performance case studies, keep reading!</p><h3>Defining the Environment</h3><p>Our first idea was to make a comparison using Unit Tests in order to <strong>check the exact same piece of code on different platforms and compare the</strong> <strong>execution times</strong>. In this way, we could have a slight idea about how slow it is to run Swift WebAssembly code compared to pure Swift Code.</p><p>The platforms to compare were:</p><ol><li>Swift Natively (running in macOS)</li><li><a href="https://wasmer.io/">WASMER (using WebAssembly binary)</a></li><li>Chrome (using WebAssembly binary)</li></ol><p>Thanks to WASMER, we can execute the <strong>WASM</strong> binary generated by the unit tests easily on our computer without using any browser. As you can imagine, using WASMER is useful just as a reference for us — in the end, we’re interested in executing it inside the <strong>browser</strong>.</p><p>Keep in mind as well, that one of the reasons why the performance is different between the platforms is because the toolchain itself is different. There are some specific libraries used in Swift WebAssembly that are different to Native Swift. A really interesting example is the libc :</p><ul><li>Web Assembly: <a href="https://github.com/WebAssembly/wasi-libc">https://github.com/WebAssembly/wasi-libc</a></li><li>Linux Native: <a href="https://launchpad.net/ubuntu/+source/glibc">https://launchpad.net/ubuntu/+source/glibc</a></li></ul><h3>The Chosen Unit Test</h3><p>We were trying to identify some expensive operations within the project that we’re just using <strong>purely </strong><strong>Swift</strong> for (we can also execute Javascript code from Swift, so these examples aren’t valid for the purpose of this specific task). As we noticed the issue was related to the networking layer, we decided to use a profiler and find the point taking most of our execution time. After using the profiler we noticed there was a huge JSON string being parsed and taking too long to finish. Once the expensive operation was identified, we decided to use it as a reference for our unit tests. We saved the JSON we were parsing and noticed it was a huge one (5.5MB actually) and used it to run a unit test in order to measure the execution time before making any conclusions.</p><p>The test we wrote was something like this:</p><pre>func testDeserialize_NotesHugeFromMemory() throws {<br>    let notesTextJSON = URL.fetchNotesHugeJSON!<br>    let notes = try JSONDecoder().decode(NotesData.self, from: notesTextJSON)<br>    XCTAssert(notes.data.count &gt; 0)<br>}</pre><p>Keep in mind the meassure functionality from XCTest is not available in Swift Web Assembly yet, so this is why we didn’t use it here.</p><h3>Analyzing Results</h3><p>Now that we have everything ready for running the test and checking the output, it’s time to review the results in terms of execution times on the following figures:</p><h3>Outputs</h3><ol><li><strong>Natively &amp; NO OPTS</strong><br>Test Case ‘HybridRenderingFetchTests testDeserialize_NotesFromMemory’ passed (0.697 seconds)</li><li><strong>Natively &amp; OPTS</strong><br>Test Case ‘HybridRenderingFetchTests testDeserialize_NotesFromMemory’ passed (0.597 seconds)</li><li><strong>WASMER &amp; NO OPTS</strong><br>Test Case ‘HybridRenderingFetchTests.testDeserialize_NotesFromMemory’ passed (2.026 seconds)</li><li><strong>WASMER &amp; OPTS</strong><br>Test Case ‘HybridRenderingFetchTests.testDeserialize_NotesFromMemory’ passed (1.84 seconds)</li><li><strong>Chrome &amp; NO OPTS</strong><br>Test Case ‘HybridRenderingFetchTests.testDeserialize_NotesFromMemory’ passed (5.542 seconds)</li><li><strong>Chrome &amp; OPTS</strong><br>Test Case ‘HybridRenderingFetchTests.testDeserialize_NotesFromMemory’ passed (4.464 seconds)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YiOv8DKXFZpJKtFXBx3QUQ.png" /></figure><p>At this point, I was <strong>quite disappointed</strong> with the <strong>Swift WebAssembly performance in general.</strong> <br>Honestly, I was expecting numbers to be approx. 20% slower, but I never expected this huge difference; for an end-user, it means the app is about <strong>7 times slower</strong>…</p><p>As you may have noticed, there is a difference in our execution times depending on the optimizations applied. Remember that all the compilers offer a way of <a href="https://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options">setting some </a><a href="https://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options">Optimization Flags</a> used during compilation. Usually, when running Unit Tests, all these flags are disabled; however, we are also interested in seeing what the real end-user behaviour will be, so that’s why we executed the unit test in both scenarios.</p><ul><li>Testing a package without optimization flags</li></ul><pre>swift test</pre><ul><li>Testing a package with optimization flags</li></ul><pre>swift test -Xswiftc -O</pre><h3>Don’t Give Up Yet!</h3><p>When I finished measuring, I didn’t want to give up and present these numbers because I was thinking to myself, “something else must be wrong over here”, so I decided to share these numbers with the <strong>Swift WASM community</strong> to see if I could find some lights of hope there. After some conversations with the principal maintainers of the <strong>Swift WebAssembly</strong> toolchain, they agreed that these numbers were too strange. They started to investigate deeper the reason for it and quickly found a mistake: <strong>some of the libraries being shipped in the toolchain were being generated in debug mode instead of release mode. </strong>More concretely, the library that was affecting us was Foundation.</p><p>Here you have the PR where they solved the mistake:<br><a href="https://github.com/swiftwasm/swift/pull/4355/files">https://github.com/swiftwasm/swift/pull/4355/files</a></p><h3>Updated Results After Applying the Fix</h3><p>Once we identified the issue and updated the toolchain, we tried to run the test again. Take a look at the numbers once more:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JcpkoX58TZ1wq04M2Z0Khw.png" /></figure><p>As you can see, execution times are now closer to the native executions — that’s amazing! If we compare <strong>before vs after</strong> just focussing on <strong>Chrome &amp; Optimizations</strong>, we are now executing the same code almost 3 times faster. This is a massive improvement!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UPohggA5cdIIMn81Z0Xatw.png" /></figure><p>For me, the conclusion of this story is that sometimes we just need to speak with the appropriate person. Just by dedicating some time and sharing information we have been able to <strong>improve the performance</strong> of our app <strong>without a single line of code being changed</strong>.</p><h3>Extra Ball</h3><p>After the fix, all the libraries are being compiled in RELEASE mode, and the binary generated .wasm is now <strong>lighter</strong> as well:</p><p><strong>From:</strong></p><pre>After stripping debug info the main binary size is 17.55 MB</pre><p><strong>To:</strong></p><pre>After stripping debug info the main binary size is 15.97 MB</pre><p>After compressing the WASM binary before generating the bundle, our production binary is now about <strong>1.6 MB lighter</strong>.</p><p><strong>This article was written by: </strong><a href="https://twitter.com/fjtrujy"><strong>Francisco J. Trujillo</strong></a>, a Senior Software Engineer focused on new technologies, open-source, clean code, and testing. He is now working as a Senior Software Engineer in the cross-platform team at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc54f571fea4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/tested-and-fixed-a-step-forward-in-shipping-goodnotes-web-viewer-bc54f571fea4">Tested and Fixed — A step forward in shipping GoodNotes Web Viewer</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamic Member Lookup in Swift, a hidden gem!]]></title>
            <link>https://medium.com/engineering-at-goodnotes/dynamic-member-lookup-in-swift-a-hidden-gem-7913c5a9ff40?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/7913c5a9ff40</guid>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[dynamic-member-lookup]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Thu, 03 Feb 2022 11:26:21 GMT</pubDate>
            <atom:updated>2022-02-03T16:57:36.049Z</atom:updated>
            <content:encoded><![CDATA[<h4>Swift Features</h4><h4>And how it’s helping cross-platform projects at GoodNotes</h4><p>Almost 4 years ago, a Swift Evolution Proposal by Chris Lattner was implemented, and one I thought I would never use: “SE0195- Introduce User-defined ’Dynamic Member Lookup’ Types”. Dynamic Member Lookup Types is a language killer feature that will let you write Swift in the way you would expect from languages like JavaScript or Python. However, in a world where statically typed languages and compilers are trending, why do I think this feature is so relevant? If you find my question interesting, just keep reading.</p><p>Thanks to DML you can write Swift code like this:</p><pre>@dynamicMemberLookup<br>struct Foo {<br>    subscript(dynamicMember bar: String) -&gt; String {<br>        // Do what you need to do here with the param and return any<br>        // type specified on the function.<br>        return bar<br>    }<br>}</pre><p>Whenever you add the “@dynamicMemberLookup” annotation, the compiler will force you to implement a subscript function like the one you can read above and will let you write code like this:</p><pre><strong>let</strong> foo = Foo()<br>let anything: String = foo.anything<br>let anything2: String = foo.anything2<br>let anything3: String = foo.anything3</pre><p>As you can see, there is no attribute declared with names like “anything”, “anything2” or “anything3”. However, the Swift compiler will let you use these variables and will not break the build when you use them! You can even create different subscript methods if you want and return different types if needed!</p><pre>@dynamicMemberLookup<br>struct Foo {<br>    subscript(dynamicMember bar: String) -&gt; String {<br>       return bar<br>    }</pre><pre>    subscript(dynamicMember bar: Int?) -&gt; Int? {<br>       return bar<br>    }<br>    <br>    subscript(dynamicMember bar: Int) -&gt; Int {<br>       return bar<br>    }<br>}</pre><p>As a natural extension of DML, a new Swift feature named “Dynamic Callable” was born. A similar feature is already implemented in other languages like Kotlin but with a different name. You can find more information <a href="https://kotlinlang.org/docs/operator-overloading.html#invoke-operator">here</a>. This is another nice feature that allows you to invoke functions on top of struct/classes instances without any function name like this:</p><pre>@dynamicCallable<br>struct Foo {<br>    func dynamicallyCall(withKeywordArguments args: Int) -&gt; Int {<br>        ....<br>    }<br>}</pre><pre><strong>let</strong> fooInstance = Foo()<br><strong>let</strong> bar = fooInstance()</pre><p>I thought I would never need a syntax like this in a language like Swift. We always try to use the compiler to reduce complexity in our software and help us to develop faster. Thanks to the compiler we can validate part of our implementation in build time, and this is something we use more every day. However, when you need to interact with languages like JS or Python this API will really simplify the way you write code. But remember, with great power comes great responsibility! Just because you can now access any property without the compiler breaking your build, it doesn’t necessarily mean this code is safe. You now have to ensure your DML implementation will always behave accordingly and will not crash in runtime.</p><p>Why is this feature so important for us? Because we are implementing a really cool project where we need to interact with JavaScript via Swift code. Actually, we write JavaScript code using Swift, so we need to access different APIs that are not declared in build time. The code we write heavily relies on the DML feature, and thanks to this language capability we are able to access any JavaScript function/method/variable/instance with an easy-to-read and clear syntax. We just need to be careful and ensure our code works as expected and we have no typos, just as if you were writing JS.</p><p>Let me show you what our code looks like using this feature with a simple example:</p><pre>class HTMLCanvas {</pre><pre>    private(set) var canvas: JSObject<br>    ....<br>}</pre><pre>private extension HTMLCanvas {<br>    func applyStyle(_ size: PSize) {<br>        let style = self.canvas.style.object!<br>        style.width = .string(size.width)<br>        style.height = .string(size.height)<br>    }<br>}</pre><p>As seen above, we can access an attribute named “style” and modify the value easily without any previous declaration of these values as part of the “JSObject” implementation. This is how the “JSObject” class implementation is done:</p><pre>@dynamicMemberLookup<br>public class JSObject: Equatable {<br>    ....<br>    /// Returns the `name` member method binding this object as `this` context.<br>    ///<br>    /// e.g.<br>    /// ```swift<br>    /// let document = JSObject.global.document.object!<br>    /// let divElement = document.createElement!(&quot;div&quot;)<br>    /// ```<br>    ///<br>    /// - Parameter name: The name of this object&#39;s member to access.<br>    /// - Returns: The `name` member method binding this object as `this` context.<br>    @_disfavoredOverload<br>    public subscript(_ name: String) -&gt; ((ConvertibleToJSValue...) -&gt; JSValue)? {<br>        guard let function = self[name].function else { return nil }<br>        return { (arguments: ConvertibleToJSValue...) in<br>            function(this: self, arguments: arguments)<br>        }<br>    }<br>    ....</pre><p>Really cool, isn’t it? You can find many more examples of DML usage on <a href="https://github.com/swiftwasm/JavaScriptKit">JavaScriptKit</a>, an open-source project developed by the <a href="https://github.com/swiftwasm">Swift WebAssembly team.</a></p><p>For us, using this feature improved our code readability a lot, but there were some challenges we needed to face as well. For example, it forced us to evaluate optional values we knew weren’t null frequently when accessing values we knew were valid in runtime because the DML API was configured to return an optional value. We also found that Xcode navigation doesn’t work when trying to inspect a DML value, so this is an obvious downside we’ve faced when using this API.</p><p>Is there any improvement we can get on top of this feature? Sure, we can add types definition on top of our DML implementation! There are some APIs declared on dynamic languages like JS we can wrap with a type definition and implement using DML. Actually, this is a frequently-used solution based on a software design pattern called <a href="https://en.wikipedia.org/wiki/Adapter_pattern">adapter/wrapper</a>. A simplified version of one possible implementation in Swift would look like this:</p><pre>struct Style {<br>    ....<br>    var width: String? {<br>        get {<br>            // Read from the JSObject using DML here<br>        }<br>        set {<br>            // Write in the JSObject using DML here<br>        }<br>    }<br>    <br>   var height: String? {<br>       get {<br>           // Read from the JSObject using DML here<br>       }<br>       set {<br>           // Write in the JSObject using DML here<br>       }<br>   }<br>   ....<br>}</pre><pre>struct HTMLDiv {<br>  let style: Style<br>}</pre><p>Based on these types you can now write type-safe code checked by the compiler in build time like this:</p><pre>div.style.width = &quot;100px&quot;<br>div.style.height = &quot;100px&quot;</pre><p>The compiler will let you know if you write code that is not correct in build time.</p><pre>div.style.width = 3 // Build error because width should be a String not an Int</pre><p>The better you design your type, the better API you’ll be able to provide. The more information you add to your types, the safer your code will be to develop. Here you have an example of how you could use Swift enums to define the values a width or height param can accept instead of just a plain String:</p><pre>enum CssSize {<br>    case pixels(value: Int)<br>    case em(value: Int)<br>    case percentage(value: Float)<br>    case auto<br>    case maxContent<br>    case minContent<br>    ....<br>}</pre><p>However, adding a type definition on top of already existing APIs is a huge effort! So while the type definition is developed, we can still use DML to access any value in runtime. Keep in mind that this feature will help you to invoke functions and methods declared by you and your team in languages like JS from Swift and create your own type wrappers on top of the DML invocations.</p><p><strong>This article was written by:<br></strong><a href="http://twitter.com/pedro_g_s"><em>Pedro Gómez</em></a><em> is a </em>Senior Software Engineer focused on new technologies, open-source, clean code, and testing. He is now working as a Senior Software Engineer in the cross-platform team at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7913c5a9ff40" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/dynamic-member-lookup-in-swift-a-hidden-gem-7913c5a9ff40">Dynamic Member Lookup in Swift, a hidden gem!</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How We Work Smarter, Not Harder]]></title>
            <link>https://medium.com/engineering-at-goodnotes/how-we-work-smarter-not-harder-92edb6dc1d87?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/92edb6dc1d87</guid>
            <category><![CDATA[smart-working]]></category>
            <category><![CDATA[smart-work-vs-hard-work]]></category>
            <category><![CDATA[developer-tools]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Thu, 11 Nov 2021 11:37:17 GMT</pubDate>
            <atom:updated>2021-11-11T11:37:17.075Z</atom:updated>
            <content:encoded><![CDATA[<p>“Work smarter, not harder” is one of GoodNotes’ mottos. You can find it deep down in our values, there is even a section talking about this on our <a href="https://www.goodnotes.com/careers/">careers page</a>! But what does it mean exactly for an engineer? How can we achieve the “work smarter” ethos when, for example, we start a project or we work on a feature? In this post I would like to review with you some tools and tips we use here in the GoodNotes’ engineering team to work smarter, not harder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MhuyJK3ykuRA38t8pmFIgg.jpeg" /></figure><h4>Keep It In Mind Since Day 1</h4><p>When we start a new project, no matter what technology or stack we are using, there are always a bunch of tools we configure in order to simplify some tasks. Linters, code formatters, code coverage tooling, etc. Configuring all these tools takes time, but it’s time well spent because it helps us to avoid repetitive tasks in the future — from formatting in our code to checking whether our build and tests are passing. That’s why you should keep it in mind from the very first day. The usage of certain tools enables us to work way faster and get a development pace we couldn’t achieve otherwise. In this post you can find a list of tools we use in different technologies/stacks and why we find them interesting.</p><h4>Automate Linters and Formatters</h4><p>These are the keys to success if you want to avoid comments like this on your pull requests:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FThuEyvx8QsUI_T-n5VjAA.png" /></figure><p><a href="https://github.com/realm/SwiftLint">Swiftlint</a>, <a href="https://eslint.org/">ESLint</a>, <a href="https://prettier.io/">Prettier</a>, or <a href="https://pylint.org/">PyLint</a> are just some examples. These tools help not only to automatically format our code but also to ensure we follow the coding convention the team specified.</p><p>Another example is the configuration of rules to avoid common patterns in our code, like the usage of the famous JavaScript “console.log” or force unwraps on Swift. You can even create your own custom rules if needed to automatically break the build if there are any rules the new code doesn’t comply with.</p><h4>CI &amp; CD</h4><p>It is fundamental to ensure the changes we’ve done in our codebase make the build and tests pass automatically. But what about deployment? If you, my dear reader, are an iOS developer, how long have you been waiting for XCode to build archive your app on your own laptop? Our suggestion is to start every project working on CI &amp; CD tasks. We are using GitHub actions and Circle CI right now, but any custom Jenkins configurations or any other tool will work for you as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BObKkZVLLgk_xbbyFIQWaw.png" /></figure><p>For every new PR, there are some jobs we’ve configured to build our projects, evaluate our linters, run our tests and deploy new versions if needed. This automated process empowers us to spend our time on what really matters: building the best possible GoodNotes app for our users.</p><h4>Best Tools For the Job!</h4><p>There are tons of open-source tools you can use when building your project. Some of them can speed up your development process significantly. That’s why we spend time looking for the best tool for the job whenever we are working on our app.</p><p>There are some libraries, quite similar between platforms, that we always use on our projects. Here you have an interesting list:</p><p>There are some libraries, quite similar between platforms, we always use in our projects. Here you have an interesting list:</p><ul><li><strong>Screenshot testing libraries:</strong> <a href="https://github.com/pointfreeco/swift-snapshot-testing">Swift snapshot testing library</a> + <a href="https://docs.cypress.io/guides/tooling/visual-testing">cypress and the visual testing tooling</a> are our favourites. It helps us to ensure our app shows the expected UI using pixel-perfect precision!</li><li><strong>Data serialisation tooling:</strong> We heavily rely on <a href="https://github.com/apple/swift-protobuf">Swift protobuf</a> implementation. It makes our life way easier and the performance is awesome!</li><li><strong>Dependency management libraries:</strong> <a href="https://www.google.com/search?q=cocoapods&amp;rlz=1C5CHFA_enES969ES969&amp;oq=cocoapods&amp;aqs=chrome..69i57.929j0j1&amp;sourceid=chrome&amp;ie=UTF-8">CocoaPods</a>, <a href="https://swift.org/package-manager/">Swift Package Manager</a>, <a href="https://yarnpkg.com/">yarn</a>, and <a href="https://www.npmjs.com/">npm</a>, are just some of them. We couldn’t work with these tools, literally.</li><li><strong>Loggers:</strong> <a href="https://www.datadoghq.com/lpg/?utm_source=Advertisement&amp;utm_medium=GoogleAdsNon1stTier&amp;utm_campaign=GoogleAdsNon1stTier-BrandCV&amp;utm_content=Brand&amp;utm_keyword=%2Bdata%20%2Bdog&amp;utm_matchtype=b&amp;utm_campaignid=9551169254&amp;utm_adgroupid=95325237782&amp;gclid=EAIaIQobChMI2a6j9uGA9AIVl4BQBh392QgeEAAYASAAEgJmWPD_BwE">DataDog’s</a> logging tool hidden under an adapter or connected to loggers like <a href="https://github.com/CocoaLumberjack/CocoaLumberjack">CocoaLumberjack</a>.</li><li><strong>Styling tools:</strong> <a href="https://styled-components.com/">StyledComponents</a> and <a href="https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html">Auto Layout</a> are two great examples. Best tool for the job on two different platforms, React and iOS. Combined with a design system, it will make your UI shine!</li></ul><h4>Plan Ahead</h4><p>However, this is not only about libraries and tooling, working smarter is an attitude as well! That’s why we work using a 6-week development cycle with a dedicated planning period in advance. Planning before coding, thinking before writing a single line of code — that’s our best tool! You think way faster than you code, so we recommend taking the time to think and plan before tackling any task. Review the requirements, take notes, design your solution and ask for feedback before implementing anything. If you plan, it serves as your first iteration, so you’ll be able to work more efficiently later on as this will speed up the feedback process and you can iterate again as soon as possible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dtT2tyVQlpHu7ik-JD8Svg.png" /></figure><p>These are the tips and tools we use in our Engineering Team for working smarter, not harder. Are there any of your favourites we didn’t mention? Please, write a comment below and let us know!</p><p><strong>This article was written by:<br></strong><a href="http://twitter.com/pedro_g_s"><em>Pedro Gómez</em></a><em> is a </em>Senior Software Engineer focused on new technologies, open-source, clean code, and testing. He is now working as a Senior Software Engineer in the cross-platform team at GoodNotes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=92edb6dc1d87" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/how-we-work-smarter-not-harder-92edb6dc1d87">How We Work Smarter, Not Harder</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Towards a Design System in iOS: Typography]]></title>
            <link>https://medium.com/engineering-at-goodnotes/design-system-ios-typography-9459cb842673?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/9459cb842673</guid>
            <category><![CDATA[typography]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ipad]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Mon, 28 Jun 2021 08:50:19 GMT</pubDate>
            <atom:updated>2021-06-28T08:50:03.680Z</atom:updated>
            <content:encoded><![CDATA[<p>In our <a href="https://medium.com/engineering-at-goodnotes/towards-a-design-system-in-ios-colors-63a3a0289ad7">previous post</a>, we discussed about the importance of having a <strong>Design System</strong> to enhance consistency in the design of our apps, improving the discovery and adoption of new features. We also looked at colors in our design system and how we customized <a href="https://amzn.github.io/style-dictionary/#/">Style Dictionary</a> to deal with light and dark mode variants for each color, and have a nice API to use colors as extensions to <em>UIColor</em>.</p><p>In this post, we are going to focus on <strong>typography</strong> and how we can customize it, without losing the ability to adapt it for accessibility.</p><figure><img alt="Dynamic type sizes and weights for our application typography" src="https://cdn-images-1.medium.com/max/1020/1*K1xaxAgUpjJ94Ennb1rbcg.png" /></figure><h3>Dynamic Type</h3><p><strong>Dynamic Types</strong> in iOS are semantic categories that assign size and weight to the font of a text given their relevance in the user interface. These can be headlines, titles, or footnotes, among others.</p><p>The key benefit of setting your font sizes and weights through these categories is that your text will adjust accordingly when the <strong>accessibility</strong> settings are changed. If your users decide to increase the font size, each category will increase proportionally to match the selected system setting. Otherwise, if you set fixed values for your font size, your users won’t be able to benefit from that feature.</p><h3>Customizing dynamic types</h3><p>Default dynamic sizes have pre-determined font sizes and weights that make sense for each category. However, this does not mean we cannot customize our own if we want to change the default font, size or weight for a given dynamic type.</p><p>In our case, we want to perform small changes to the sizes of each text style, and have it available in different weights (regular and bold). Style Dictionary also allows for generating custom font style code, but again, the default behavior does not exactly match our needs; therefore, we decided to extend its functionality with a custom plugin that uses a <a href="https://handlebarsjs.com/">Handlebars</a> template:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/19d6a659e32dd90cc5b04b8832697a03/href">https://medium.com/media/19d6a659e32dd90cc5b04b8832697a03/href</a></iframe><p>This creates an extension on <em>UIFont</em> that computes a font for a given text style, applying our customized size and weight for such style. An example of the generated code looks like:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1bf1c78a472bf83d1ef42aac92c411fc/href">https://medium.com/media/1bf1c78a472bf83d1ef42aac92c411fc/href</a></iframe><p>The extension on <em>UIFont</em> allows us to easily use it to programatically assign a font to any <em>UIKit</em> component, such as a <em>UILabel</em>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d19190c31e36b962481d0baa1ea9e0d7/href">https://medium.com/media/d19190c31e36b962481d0baa1ea9e0d7/href</a></iframe><h3>Conclusions</h3><p>Following the approach we described in the previous post about colors, we are able to generate all custom font styles we need in our iOS application, from a description provided by our designers using Figma. It results in a nice extension of <em>UIFont</em> that maintains the same benefits regarding accessibility and responsive design, while having a nice API to consume.</p><p><em>Did you like what we are doing? Do you want to join us, and help us shape up the future of GoodNotes? </em><a href="http://goodnotes.com/careers?utm_source=medium&amp;utm_medium=referral&amp;utm_campaign=design%20system%20typography"><em>We are hiring!</em></a></p><p><strong>This article was written by:<br></strong><a href="https://twitter.com/tomasruizlopez"><em>Tomás Ruiz-López</em></a><em> is a Senior Software Engineer working in the iOS team at GoodNotes. He enjoys learning new technologies in the Apple ecosystem and looking at them from the prism of Functional Programming. He has developed several open source projects related to the Bow library, an attempt to bring FP concepts into Swift.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9459cb842673" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/design-system-ios-typography-9459cb842673">Towards a Design System in iOS: Typography</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Towards a Design System in iOS: Colors]]></title>
            <link>https://medium.com/engineering-at-goodnotes/towards-a-design-system-in-ios-colors-63a3a0289ad7?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/63a3a0289ad7</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ipad]]></category>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[app-development]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Wed, 02 Jun 2021 08:53:49 GMT</pubDate>
            <atom:updated>2021-06-02T08:53:49.193Z</atom:updated>
            <content:encoded><![CDATA[<p>An important aspect to the success of an application is <strong>how it looks</strong>, and <strong>how easy it is to use</strong>. These two aspects are what we call <strong>UI/UX</strong>, and they are not easy to get right, even though, fortunately, the iOS frameworks already give us a very good result with default components.</p><p><strong>Consistency </strong>is key to achieve a good UI/UX in an app. From a user’s perspective, it is paramount that things that are the same, look the same, and are used the same. This helps them relate knowledge from previous features in your app, or in the system, and quickly learn how to use new functionality. But that does not stop there; from a developer’s perspective, it also has great benefits, as existing components and interactions can be easily reused.</p><p>As part of our efforts to enhance consistency across GoodNotes, our team has been working on a Design System: a library of tokens and components that we can refer to when implementing new features into the app. It serves as a common language between designers and developers to unambiguously refer to certain items in our software and improve the way we work.</p><p>We are still in the process of adopting it, but we found some interesting aspects that we would like to share with you. In this first post, we will focus on <strong>colors</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3RgaFZH65hLTRc0pkh04Wg.png" /></figure><h3><strong>Colors in iOS</strong></h3><p>In our project, we try to use named colors defined in our Assets in Xcode. This lets us define semantic colors that include variants for <strong>light</strong> and <strong>dark</strong> modes, and get the right one without too much hassle.</p><p>This also lets us have a visual representation of all colors in the application, and verify that they are correct in both themes. These colors are available through the Interface Builder, or we can instantiate them using the <em>UIColor(named:)</em> initializer.</p><p>There is a small problem though: our designers usually work in Figma, not in Xcode. We need a way to import their list of colors into our project. Moreover, when they change something, we don’t want to go over the list of colors and manually update them in our Assets.</p><p>There are tools to automate the generation of colors from Figma, like <a href="https://amzn.github.io/style-dictionary/#/">Style Dictionary</a>, and it not only works for iOS, but for other platforms, which may become useful in the future. However, if you take a look at the generated output, it has two main shortcomings:</p><ol><li>It does not use named colors in Assets.</li><li>It does not distinguish between light and dark mode colors.</li></ol><h3><strong>Tweaking Style Dictionary</strong></h3><p>Fortunately, Style Dictionary is customizable through plugins, so we wrote one to let us customize the output to match our needs. The task became surprisingly easy thanks to using <a href="https://handlebarsjs.com/">Handlebars</a>, a template system that can render our data in the format we would like.</p><p>First, we created a template for named colors that allows us to put together both light and dark versions of a color. It took us a bit of reverse engineering how Xcode represents colors in the <em>colorset</em> folders.</p><p>The template looks like:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0b61150f762dae2b71efea0a6a7e7687/href">https://medium.com/media/0b61150f762dae2b71efea0a6a7e7687/href</a></iframe><p>Most of the template corresponds to the JSON format that Xcode uses to represent named colors, but the values enclosed by double curly braces (<em>{{ }}</em>) are the gaps that we can fill with light and dark variants of our color. We run this template for each color our designers define in Figma.</p><p>Finally, in order to have a convenient way of using these colors programaticaly, we generate a custom UIColor extension with values for the generated colors:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fe28131f69aa8878720a27194db4bb8b/href">https://medium.com/media/fe28131f69aa8878720a27194db4bb8b/href</a></iframe><p>This way, whenever our designers refer to the color <em>GoodNotesDenim600</em>, with variants for light and dark mode, we only need to call <em>UIColor.goodNotesDenim600</em>, and the system will handle variants for us. If, for some reason, our designers decide to add new colors, or update existing ones, we only need to generate them again.</p><p>In case we transitioned to use <strong>SwiftUI</strong> (or you are already using it), our template to generate colors wouldn’t change much:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7b1ea795f5067df66885610174d0753f/href">https://medium.com/media/7b1ea795f5067df66885610174d0753f/href</a></iframe><p>Similar to its UIKit counterpart, this offers a good API to work with colors in SwiftUI. The Assets catalog is shared, so we can even have both versions (<em>UIColor</em> and <em>SwiftUI.Color</em>) coexisting in the same code base.</p><h3><strong>Conclusions</strong></h3><p>Style Dictionary is a very useful tool to translate resources provided by designers using Figma, into resources that can be targeted to different platforms, like iOS in our case. However, it has some shortcomings when it comes to dealing with named resources, and light and dark modes. Thanks to its powerful extension capabilities, we can customize the generation process to fulfill our needs and unlock additional benefits, like nicer APIs to work with colors.</p><p><em>Did you like what we are doing? Do you want to join us, and help us shape up the future of GoodNotes? </em><a href="http://goodnotes.com/careers?utm_source=medium&amp;utm_medium=referral&amp;utm_campaign=design%20system%20colors"><em>We are hiring</em></a><em>!</em></p><p><strong>This article was written by:<br></strong><a href="https://twitter.com/tomasruizlopez"><em>Tomás Ruiz-López</em></a><em> is a Senior Software Engineer working in the iOS team at GoodNotes. He enjoys learning new technologies in the Apple ecosystem and looking at them from the prism of Functional Programming. He has developed several open source projects related to the Bow library, an attempt to bring FP concepts into Swift.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=63a3a0289ad7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/towards-a-design-system-in-ios-colors-63a3a0289ad7">Towards a Design System in iOS: Colors</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Welcome to our Engineering Blog!]]></title>
            <link>https://medium.com/engineering-at-goodnotes/welcome-to-our-engineering-blog-bbb1c17969f1?source=rss----af49331afdd8---4</link>
            <guid isPermaLink="false">https://medium.com/p/bbb1c17969f1</guid>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ipad]]></category>
            <dc:creator><![CDATA[GoodNotes]]></dc:creator>
            <pubDate>Mon, 31 May 2021 09:51:58 GMT</pubDate>
            <atom:updated>2021-05-31T09:51:58.344Z</atom:updated>
            <content:encoded><![CDATA[<p>For quite a long time, you have been able to read <a href="https://medium.goodnotes.com/">our stories</a> about how GoodNotes can help you improve your productivity by going paperless, or about our company culture and how we aim to have a nice work-life balance.</p><p>We love sharing these experiences with you and hearing your feedback! And we like it so much that we have decided to take a step further and share something more with you.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OVNvIue3H-VQG2THvZI7vQ.jpeg" /></figure><p><strong>This is the first post in our Engineering Blog!</strong></p><p>Here, you will find how our GoodEngineers build the internals of our product. With this blog, we aim to give you more insights about how it is working at GoodNotes from a technical point of view. You will read about our particular take to different problems that, most likely, you will also find in your projects as well, and we will offer tips and tricks that may not be so widely known. But we don’t want this to be unidirectional! We’d love to hear your feedback and hints on how you tackle the same problems: we are here to learn as much as to share.</p><p>In this blog, you will find content from our different teams, ranging from iOS Development to Machine Learning. Our engineers are eager to start posting content, but we’d also like to hear from you: <strong>which topics would you like us to cover?</strong></p><p>Finally, we want to remind you that we are hiring! If you like the things we are doing, and think you can contribute to make our product better, <a href="http://goodnotes.com/careers?utm_source=medium&amp;utm_medium=referral&amp;utm_campaign=engineering%20blog%20welcome">take a look at our openings</a>. We’ll be glad to welcome you to our team.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bbb1c17969f1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/engineering-at-goodnotes/welcome-to-our-engineering-blog-bbb1c17969f1">Welcome to our Engineering Blog!</a> was originally published in <a href="https://medium.com/engineering-at-goodnotes">Engineering At GoodNotes</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>