<?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[Wantedly Engineering - Medium]]></title>
        <description><![CDATA[All about engineering &amp; design at Wantedly - Medium]]></description>
        <link>https://medium.com/wantedly-engineering?source=rss----ef38f0cd006f---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Wantedly Engineering - Medium</title>
            <link>https://medium.com/wantedly-engineering?source=rss----ef38f0cd006f---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 23 Jun 2026 19:57:12 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/wantedly-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[“hi18n”, a TypeScript-first internationalization library — getting started guide]]></title>
            <link>https://medium.com/wantedly-engineering/hi18n-a-typescript-first-internationalization-library-getting-started-guide-ddbc34c5153b?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/ddbc34c5153b</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[internationalization]]></category>
            <dc:creator><![CDATA[Masaki Hara]]></dc:creator>
            <pubDate>Tue, 21 Jun 2022 02:01:17 GMT</pubDate>
            <atom:updated>2022-06-21T02:01:17.345Z</atom:updated>
            <content:encoded><![CDATA[<h3>“hi18n”, a TypeScript-first internationalization library — getting started guide</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*T4hihsNaw8G9vZB0" /><figcaption>Photo by <a href="https://unsplash.com/@etiennegirardet?utm_source=medium&amp;utm_medium=referral">Etienne Girardet</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>“hi18n” is an internationalization library (more specifically, translation management library) for JavaScript/TypeScript and is currently under active development in Wantedly.</p><p><a href="https://github.com/wantedly/hi18n">GitHub - wantedly/hi18n: message internationalization meets immutability and type-safety</a></p><p>It roughly has the following features:</p><ul><li>TypeScript compiler ensures that you are using correct translation ids and translation parameters.</li><li>Designed to match declarative paradigms such as React.</li><li>Integrates well with existing ecosystems (such as Webpack) without extra configuration. For example, Webpack’s hot reloading and chunk splitting work out-of-the-box with hi18n. This happens just thanks to its modern architecture and requires no bundler-specific code.</li></ul><p>We’ll describe design principles in detail in the coming blog posts. In this article, however, we provide a <strong>quick start for those interested in our library</strong>.</p><h3>Setting it up</h3><p>In this article, we assume you have a React + TypeScript project. First, install the following packages:</p><pre>npm install @hi18n/core @hi18n/react-context @hi18n/react<br>npm install -D @hi18n/cli</pre><pre># Or:<br>yarn add @hi18n/core @hi18n/react-context @hi18n/react<br>yarn add -D @hi18n/cli</pre><p>Then create files for translation data. Depending on your preference, you can configure it in two ways: <strong>single-file</strong> or <strong>multi-files</strong>. We use the latter in this guide.</p><p>In the multi-file configuration, you have N+1 files where N is the number of languages.</p><pre>// src/locale/index.ts<br>// (other names are fine)<br>// This file defines the list of translatable strings.</pre><pre>import { Book, Message } from &quot;@hi18n/core&quot;;<br>import catalogEn from &quot;./en&quot;;<br>import catalogJa from &quot;./ja&quot;;</pre><pre>export type Vocabulary = {<br>  // &quot;&lt;Translation ID&gt;&quot;: Message&lt;{ &lt;Parameters&gt; }&gt;; (or simply Message; if you don&#39;t need parameters)<br>  &quot;example/greeting&quot;: Message&lt;{ name: string }&gt;;<br>};</pre><pre>// This is a bundle of translations for all languages (English and Japanese in this case).<br>export const book = new Book&lt;Vocabulary&gt;({<br>  en: catalogEn,<br>  ja: catalogJa,<br>});</pre><pre>// src/locale/en.ts<br>// (other names are fine)<br>// This file contains translated messages in a specific language (i.e. English).</pre><pre>import { Catalog, msg } from &quot;@hi18n/core&quot;;<br>import type { Vocabulary } from &quot;.&quot;;</pre><pre>export default new Catalog&lt;Vocabulary&gt;({<br>  // &quot;&lt;Translation ID&gt;&quot;: msg(&lt;translated message&gt;),<br>  &quot;example/greeting&quot;: msg(&quot;Hello, {name}!&quot;),<br>});</pre><pre>// src/locale/ja.ts (in the same manner as en.ts)</pre><pre>import { Catalog, msg } from &quot;@hi18n/core&quot;;<br>import type { Vocabulary } from &quot;.&quot;;</pre><pre>export default new Catalog&lt;Vocabulary&gt;({<br>  &quot;example/greeting&quot;: msg(&quot;こんにちは、{name}さん!&quot;),<br>});</pre><p>Then define a command for synchronizing translations and translation IDs:</p><pre>// package.json<br>{<br>  &quot;scripts&quot;: {<br>    &quot;i18n:sync&quot;: &quot;hi18n sync &#39;src/**/*.ts&#39; &#39;src/**/*.tsx&#39;&quot;<br>  }<br>}</pre><p>Then you can start using hi18n.</p><p>If you are using ESLint, we recommend <a href="http://npmjs.com/package/@hi18n/eslint-plugin">our ESLint plugin</a>. Just install the package and extend the preset called plugin:@hi18n/recommended .</p><h3>Using translated messages</h3><p>There are several ways to use the translated messages in your application code.</p><h4>With useI18n</h4><p>This is the most standard way if you are using React. To use useI18n, you first need to configure a locale (usually at the root of the tree).</p><pre>// An example with explicit ReactDOM call.<br>// You may need to place it in a different place (like _app.ts).<br>import { LocaleProvider } from &quot;@hi18n/react&quot;;<br></pre><pre>const root = ReactDOMClient.createRoot(/* ... */);<br>root.render(<br>  &lt;LocaleProvider locales=&quot;ja&quot;&gt;<br>    {/* ... */}<br>  &lt;/LocaleProvider&gt;<br>);</pre><p>Then use useI18n to start translating everywhere in the tree.</p><pre>import { useI18n } from &quot;@hi18n/react&quot;;<br>// You need to explicitly import the translation data (which we call a book).<br>import { book } from &quot;../../locale&quot;;</pre><pre>const Greet: React.FC = () =&gt; {<br>  // It returns a function for translation using the current locale (from the context) and the book you supplied.<br>  const { t } = useI18n(book);<br>  return &lt;&gt;{t(&quot;example/greeting&quot;, { name: &quot;太郎&quot; })}&lt;/&gt;;<br>};</pre><h4>With &lt;Translate&gt;</h4><p>The &lt;Translate&gt; component is suitable for translating messages interleaved with markups or React elements.</p><pre>import { Translate } from &quot;@hi18n/react&quot;;<br>// You need to explicitly import the translation data (which we call a book).<br>import { book } from &quot;../../locale&quot;;</pre><pre>const Greet: React.FC = () =&gt; {<br>  return &lt;Translate book={book} id=&quot;example/greeting&quot; name=&quot;太郎&quot; /&gt;;<br>};</pre><p>Special to &lt;Translate&gt; , you can pass a React element as a parameter to the translation:</p><pre>const UnreadMessages: React.FC = () =&gt; {<br>  const unreadCount = 2;<br>  if (unreadCount === 0) return null;</pre><pre>  // en: &quot;You have &lt;link&gt;{count,plural,one{# unread message}other{# unread messages}}&lt;/link&gt;&quot;<br>  // ja: &quot;&lt;link&gt;{count,number}通の未読メッセージ&lt;/link&gt;があります&quot;<br>  return &lt;Translate book={book} id=&quot;example/unread&quot; count={unreadCount}&gt;<br>    &lt;a key=&quot;link&quot; href=&quot;https://example.com/inbox&quot; /&gt;<br>  &lt;/Translate&gt;;<br>};</pre><h3>Synchronizing translations and translation IDs</h3><p>Use hi18n sync command to synchronize translation IDs between the application and the translation data.</p><pre>hi18n sync &lt;globs...&gt; [--exclude &lt;glob&gt;] [--check | -c]</pre><p>Define a task using package.json:</p><pre>// package.json<br>{<br>  &quot;scripts&quot;: {<br>    &quot;i18n:sync&quot;: &quot;hi18n sync &#39;src/**/*.ts&#39; &#39;src/**/*.tsx&#39;&quot;<br>  }<br>}</pre><p>and use it to update your translations:</p><pre>npm run i18n:sync</pre><pre># Or:</pre><pre>yarn i18n:sync</pre><p>Watch out for your unsaved changes to the translations; it may rewrite TypeScript/JavaScript files containing the translations.</p><p>This command does the following:</p><ul><li>it comments out unused translations, and</li><li>it generates a boilerplate for translations required by the application, but not defined yet.</li></ul><p>Sometimes translations that are still in use are accidentally commented out. This is likely caused by a mistake in the application:</p><ul><li>You may have used hi18n in a way that the tool cannot detect translation usages.</li><li>Or you may have commented out some portion of the application for debugging, and forgot to uncomment them before running the sync command.</li></ul><p>In any case, just fix the problem in the application and rerun the synchronization. Then the command undoes the comment-out.</p><h3>Adding a new translatable message</h3><p>To add a new message, you can follow the steps below:</p><p>First, add a message using t.todo or &lt;Translate.Todo&gt; instead of t or &lt;Translate&gt; .</p><pre>t.todo(&quot;example/new&quot;);<br>&lt;Translate.Todo book={book} id=&quot;example/new&quot; /&gt;;</pre><p>Then use the hi18n sync command to generate the boilerplate. Fill in the actual translations and remove .todo or .Todo .</p><p>You may also get the same result by editing everything by hand.</p><h3>Other things you can do with hi18n</h3><p>These things will be covered in later posts.</p><ul><li>Plural forms and number formats. The Intl API must be available in the browser to use them.</li><li>t.dynamic, Translate.Dynamic , and translationId API to dynamically select messages.</li><li>Our ESLint plugin @hi18n/eslint-plugin ensures the correct use of hi18n. It also has helper rules such as migration from Lingui.</li><li>You can simply create multiple Books if you need to split translation data. This is useful if you need to reduce bundle size or if you need to use hi18n in a library.</li></ul><h3>Colophon</h3><p>This article was originally <a href="https://www.wantedly.com/companies/wantedly/post_articles/399501">written in Japanese</a> and translated to English by the author.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ddbc34c5153b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/hi18n-a-typescript-first-internationalization-library-getting-started-guide-ddbc34c5153b">“hi18n”, a TypeScript-first internationalization library — getting started guide</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A failed experiment about CSS containment]]></title>
            <link>https://medium.com/wantedly-engineering/a-failed-experiment-about-css-containment-e659f49db428?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/e659f49db428</guid>
            <category><![CDATA[css]]></category>
            <category><![CDATA[core-web-vitals]]></category>
            <category><![CDATA[chrome-dev-tools]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Aubin De Traversay]]></dc:creator>
            <pubDate>Thu, 10 Mar 2022 10:20:53 GMT</pubDate>
            <atom:updated>2022-03-10T10:20:53.166Z</atom:updated>
            <content:encoded><![CDATA[<p>As I mentioned in my previous post <a href="https://medium.com/wantedly-engineering/tips-about-improving-seo-as-a-developer-218c26a8fbe7">Tips about improving SEO as a developer</a>, the DX team at Wantedly recently worked on improving our SEO scores. Part of this effort was to improve our <a href="https://developers.google.com/search/blog/2021/04/more-details-page-experience?hl=en">Page Experience metrics</a>, which generally includes page load performance.</p><p>Since every new task is an occasion to learn something, and new features are constantly added to the web platform, I saw it as an opportunity to try a new CSS property from the <a href="https://drafts.csswg.org/css-contain/">CSS Containment Spec</a>.</p><h3>Introducing content-visibility</h3><p>The content-visibility<strong> </strong>property is a feature introduced a little while ago that allows to specify that an element might not need to be rendered on screen immediately. Its typical use-case is to set it to off-screen elements so the browser doesn’t have to do work until the user scrolls down the page. For more info about how it works, you should <a href="https://web.dev/content-visibility/">consult this article on Web.dev</a>.</p><figure><img alt="A comparison of loading the same page without and with the new CSS property. It shows that the time spent by the browser to render the page goes from 232ms to 30ms" src="https://cdn-images-1.medium.com/max/1024/0*BQx1UIjsh-9gMdb7" /><figcaption>Illustration from web.dev as linked above. <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a></figcaption></figure><p>My use-case was a page listing a potentially large number of items, and refactoring it for pagination was an effort we didn’t want to spend time on yet. Large pages with thousand of DOM nodes are very costly to render, to the point of making some crash. That case thus looked like an easy win with the content-visibility: auto;property: a few lines of CSS lead to a reducing the amount of time spent on layout work from 3 to 6 times on large lists! (This seems to correspond to the improvements indicated in the article linked above.) And with the magic of CSS, this is also a safe change as incompatible browsers will ignore the property.</p><p>The second step was then to set the contain-intrinsic-size to give the off-screen elements a placeholder size. This property is very important for the user experience, without it the scrollbar becomes a very unreliable indicator. Commit and ship, then go on to do other tasks.</p><h3>The unexpected result</h3><p>Early on, the results of this change were positive — albeit very small, but worth it for such a small change. But soon after, the page started to show up in our Search Console diagnostics as having a bad <a href="https://web.dev/cls/">Cumulative Layout Shift</a> (later CLS) score.</p><p>When using the Web Vitals diagnostic tool from the Chrome Dev Tools and scrolling up-and-down the page, the metric did increase despite no particular layout work being done due interactions.</p><figure><img alt="A screenshot demonstrating the overlay in the top-right corner of a page and the value for each of 3 core web vitals. Largest Contentful Paint, First Input Delay, and Cumulative Layout Shift. The latter is showing a value of 0.22." src="https://cdn-images-1.medium.com/max/1024/1*-TYU94GBjHVgBv8PmzHHnw.png" /><figcaption>Chrome Devtool’s Rendering → Core Web Vitals overlay will show you in how the CLS evolves as you interact with a page</figcaption></figure><p>I had to check all moving parts around the page in the performance profiler to see what layout operations were at the source of this issue, but none seemed to pass this investigation. It’s only by eliminating all these suspects that I was left with my last change to the page: the addition of content-visibility and contain-intrinsic-size .</p><p>When adding these, I actually made a mistake: due to the complexities of responsive webpages, the target list elements had a height that would vary depending on the device. I assumed that contain-intrinsic-size was merely used for the scrollbar and that an approximation of the final size was good enough. However that caused the browser to re-adjust the size of each element coming into the viewport as the user scrolls! This constant layout recalculations were akin to constantly setting the height property via Javascript, a very expensive task, causing subtle but constant layout jank.</p><figure><img alt="Shows a timeline from the Chrome Devtools indicating regular Layout shift sections in red." src="https://cdn-images-1.medium.com/max/1024/1*epBjyEf_RZNVD77z6ZvKiw.png" /><figcaption>Extract of the Chrome Dev Tools’ Performance panel when scrolling the page</figcaption></figure><p>Another solution I was looking forward to is to use the contain-intrinsic-size: auto $size; variant that makes the browser remember the size of previously computed elements, shipped in Chrome 98. But that only eliminates a part of the work as the browser still has to compute each element at least one, and my short experiment actually made the CLS worse.</p><p>The takeaway is: be careful with contain-intrinsic-size and learn how to measure your performance!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e659f49db428" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/a-failed-experiment-about-css-containment-e659f49db428">A failed experiment about CSS containment</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tips about improving SEO as a developer]]></title>
            <link>https://medium.com/wantedly-engineering/tips-about-improving-seo-as-a-developer-218c26a8fbe7?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/218c26a8fbe7</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[seo]]></category>
            <dc:creator><![CDATA[Aubin De Traversay]]></dc:creator>
            <pubDate>Fri, 18 Feb 2022 06:00:06 GMT</pubDate>
            <atom:updated>2022-02-18T06:00:05.943Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mMUA6CmbuLxpeJ-V" /><figcaption>Photo by <a href="https://unsplash.com/@albertorestifo?utm_source=medium&amp;utm_medium=referral">Alberto Restifo</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Recently at Wantedly, the whole DX team worked on improving the SEO score of our site. The SEO world is ever-changing, and we constantly have to adapt to the latest evolution of search engines.</p><p>During this task, focusing on the coding side rather than content, I learned a few tips that I can share with you today, mostly focusing on Google’s expectations.</p><h3>Are you exceeding your Crawl Budget?</h3><p>Due to the web being so huge and computing power being limited, Google’s bot limits the amount of pages it will crawl for a given site and how often it will come back for updates. This is what they call “<strong>Crawl Budget</strong>”: if you submit too many pages — notably via a sitemap — Google is likely to defer visiting those pages and will clog the backlog of pages to analyze. This may lead to long delays before your precious content is indexed and available in the search results. Google considers a site with <strong>more than 100k pages</strong> to be “large”, meaning that you should look into optimizing your crawl budget for your site(s) that pass this threshold.</p><p>As with anything SEO, this is still very dependent on the perceived quality of the content. If your pages are all well referenced and see a lot of traffic coming from various sources — such as social networks — the budget that Google will allocate to your site will be far bigger, even if you have a massive amount of pages.</p><p>When you update pages, make also sure to <a href="https://developers.google.com/search/docs/advanced/appearance/publication-dates">set a timestamp somewhere in the page</a> — or in the sitemap — to indicate that there has been an update, saving the computing time needed to compare the page with previous versions, and signifying that your pages’ information is up-to-date</p><h3>Sitemaps are recommendations, not orders</h3><figure><img alt="A screenshot of Google’s search console “Coverage” panel, showing the number of pages in each category. There are 500K valid pages, and 193K excluded pages." src="https://cdn-images-1.medium.com/max/1024/0*1UJtIXaaeBVaW37b.png" /><figcaption>See how many pages are excluded on Google’s Search Console</figcaption></figure><p>I previously mentioned that Google bots will only visit a limited number of pages on your site. Populating sitemaps is a way to signify that you consider some pages worth visiting, and inform the search engines about pages that have very few links available, thus being hard to reach.</p><p>But be aware that it still is only a recommendation of which pages to visit. Google’s crawler will very often de-prioritize those pages in favor of ones that see more external links and traffic. It is also very likely that Google <strong>does not crawl nor index </strong>some of the pages from your sitemap, if it finds them to be not relevant enough.</p><p>Note also that <strong>there is no automated way to un-submit pages</strong> from the sitemap. If pages have been added to the sitemap and the bot downloaded it already, it will likely visit that page even if you remove it from the sitemap afterwards, and thus will cause coverage errors due to pages missing. A simple example of this issue that we’ve had on Wantedly is spam accounts. There exist a way to cancel indexing of pages, but it is only through the <a href="https://search.google.com/search-console/removals">search console interface</a>, thus not really scalable to large amount of pages.</p><h3>Google bots are smarter than you think</h3><p>Or at least <em>I</em> thought they were.</p><figure><img alt="Screenshot of a panel in the Search Console, that shows the details about the crawling error, and a graph with the number or URLs affected and its progression through time." src="https://cdn-images-1.medium.com/max/1024/1*GzjWlQwAwetjTMiPNEjZ6Q.png" /></figure><p>A strange error appeared on the Coverage part of the Search Console: “Submitted URL seems to be a Soft 404.”</p><p>Google’s engines are able to analyze the contents of the page and find out that despite returning a 200 HTTP code — successful request — the page is almost empty. Thus <strong>it might as well be a 404 that does not deserve indexing</strong>. Example of where this happens on our sites: a page that list items that can be filtered by a category of some sort, but has no element for that category thus showing an empty list.</p><p>(My guess is the actual implementation of this feature probably starts by diffing that page with other ones and comparing the similar parts in the HTML that would be the application shell, menus, etc. The rest of the diff would be the actual content for that specific URL, and if it’s very short or doesn’t have any link, can be considered empty?)</p><h3>But smart doesn’t mean perfect</h3><p><strong>“[Warning] Indexed, though blocked by robots.txt”</strong> told me Google, in a yellow text with scary icons. Sounds like a simple error of configuration on the robots.txt file that is used to indicate pages that should not be crawled or indexed.</p><p>Yet… this URL pattern has never been set to our robots.txt . After double-checking, trying the — not always very helpful— tools provided by Google, such as the <a href="https://www.google.com/webmasters/tools/robots-testing-tool">Robots testing tool</a>, everything looked fine.</p><p>After furiously searching on the web, I found <a href="https://support.google.com/webmasters/thread/4420484/crawl-blocked-by-robots-txt-while-it-s-not?hl=en&amp;msgid=12574692">this comment</a> on the Help forums:</p><blockquote>(it’s just unfortunately confusingly <em>saying </em>its blocked by robots.txt, but really its blocked because not an web page)</blockquote><p>If I am to believe this user, the warning message is just a catch-all message that indicates the crawler is not able to read the contents of the page. So lesson learned: be always cautious about the automated diagnostics and learn to find the real issues on your site.</p><h3>Closing words</h3><p>I hope these small tips will help you find your way in the weird jungle that is SEO optimization. Remember, all Google wants is the top search results to provide pages with interesting content to its users, so that they want to use the site again. Making sure your pages are easy to parse and analyze should result in improvements!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=218c26a8fbe7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/tips-about-improving-seo-as-a-developer-218c26a8fbe7">Tips about improving SEO as a developer</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Our Journey Towards a Testable Android App]]></title>
            <link>https://medium.com/wantedly-engineering/our-journey-towards-a-testable-android-app-5bdf0d9bd2fc?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/5bdf0d9bd2fc</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Malvin Sutanto]]></dc:creator>
            <pubDate>Mon, 14 Jun 2021 04:24:25 GMT</pubDate>
            <atom:updated>2021-06-14T04:25:43.798Z</atom:updated>
            <content:encoded><![CDATA[<p>How we refactored our legacy Android codebase to make it testable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PFEMOOjK3GDPYUR6" /><figcaption>Photo by <a href="https://unsplash.com/@patrickian4?utm_source=medium&amp;utm_medium=referral">Patrick Fore</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The Wantedly Android app was first launched in 2014, which was some time ago, hence our codebase for our Android app is quite old. When I joined Wantedly 3 years ago, there were quite a few issues with the test suite that was in place. The number of test cases was low and it was not possible to produce a test coverage report. The test cases that we have were also quite flaky and difficult to maintain. Whenever we want to update or add a new test case, it required a lot of complicated setups and it was very difficult to test a single component of the app independently.</p><p>We knew that we need to improve this if we want to scale our development and improve the productivity of the Android engineers. With this post, I want to share what we did to make our Android codebase testable.</p><h3>Defining the Objectives</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/1*wFeOXhxrEr8cMtVM8t65GA.png" /></figure><p>I think it’s really important that we define what we need to implement and achieve. In our case, we created a document that outlines the goals, benefits, strategy, and conventions of writing tests. This document also served as the centerpiece of discussions for the engineers and the stakeholders so that everybody is on the same page, and can also be used for references for new members in the future. I’ll try to briefly show what our document looks like.</p><p><strong>Goals:</strong> To create a solid framework and infrastructure to easily build, test, and release our apps with ease of mind.</p><p><strong>Benefits: </strong>A reliable test suite allows engineers to make changes quickly and release the app confidently. It provides a shorter feedback cycle during development, making it easier to debug issues in the application. It also allows us to simulate hard-to-replicate edge cases consistently. A well-documented test suite can also be used as documentation to help in knowledge sharing.</p><p><strong>Strategy:</strong> Structure the test suite with <a href="https://martinfowler.com/articles/practical-test-pyramid.html">the testing pyramid</a> (we’ll talk more about this in detail later). We also need each “component” in the app to be independently testable, but still can be tested together if needed. We will run the test suite on every pull request. Any pull requests to the main branch need to have added/removed test cases, and we’ll also ensure that the coverage doesn’t drop.</p><p><strong>Conventions:</strong> Ensure that the test cases are consistent across the codebase. We will use a unified test method naming (something like <strong>given</strong><em>Condition</em>_<strong>when</strong><em>Action</em>_<strong>then</strong><em>Result</em>) to ensure consistency and make it easier to write and maintain. Consistent test method naming allows us to quickly identify which test case is failing in CI. The test cases should be small and test only 1 aspect of the code.</p><h3>Our Approach to Testing</h3><p>The best strategy to structure your test suite will depend on your team and codebase. Here, I will describe what our approach was in introducing testable architecture into our legacy codebase.</p><h4>Testing Pyramid</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SDszoJ6oy-xJipyxtnv_zg.png" /></figure><p><a href="https://martinfowler.com/articles/practical-test-pyramid.html">Testing pyramid</a> allows us to group tests into buckets depending on the number of components involved, execution time, and maintenance effort of each group. It also gives us an idea of how many test cases we need to write for each group. Ideally, you want to write a large number of test cases that are easy to maintain. Hence, you want a smaller number of tests as you go up the pyramid. Typically, from the top, you’ll have the end-to-end tests, then integration tests, and then finally the unit tests at the bottom of the pyramid.</p><p>Here’s how we structured our test suite using the testing pyramid:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ReJdAQQNOg2MvMNrof4SYg.png" /></figure><p>At the top of the pyramid, we have our end-to-end testing with <a href="https://medium.com/wantedly-engineering/android-application-testing-with-firebase-robo-test-c674e1754298">Firebase Robo Test</a>. Then, we have our Fragments’ Espresso UI Tests, which are run as instrumented tests. We test the Fragments together with the ViewModel as our Fragments are tied heavily with the implementation of our ViewModel. We also replace any dependencies that are required by the ViewModel (such as repository classes) with test doubles to allow easier testing. Finally, we have repository and utility class tests that are run as local unit tests with <a href="http://robolectric.org/">Robolectric</a>.</p><blockquote>Note: Repository classes that <a href="https://developer.android.com/training/data-storage/room/testing-db#android">interact with SQLite database, should be run as instrumented tests</a>.</blockquote><h4>Test Doubles</h4><p><a href="https://martinfowler.com/bliki/TestDouble.html">Test doubles</a> are replacement objects for classes that we use in production, whose behaviors can be controlled during testing. We use test doubles to create a predictable condition for our tests. They are also very important to allow us to test our components in isolation. In Wantedly, we primarily make use of 2 types of test doubles, mocks, and fakes.</p><p><strong>Mock</strong>: a type of test double that can respond to a set of method calls whose answers have been defined. It can also check whether a method in the test double has been or has not been invoked. Some of the popular libraries that can help you create mock classes in Android are <a href="https://github.com/mockito/mockito">Mockito</a> and <a href="https://mockk.io/">MockK</a>.</p><pre>interface UserRepository {<br>  suspend fun getUser(userId: Int): User<br>}</pre><pre>// Create a mock UserRepository with MockK<br><strong>val mockRepo = mockk&lt;UserRepository&gt;()</strong></pre><pre>// Set getUser to return a fake User object every time it&#39;s called.<br><strong>coEvery { mockRepo.getUser(any()) } answers {<br>  </strong>User(<br>    id = firstArg(),<br>    firstName = &quot;John&quot;,<br>    lastName = &quot;Smith&quot;<br>  )<br><strong>}</strong></pre><pre>// Usage<br>launch <strong>{</strong><br>  <strong>mockRepo.getUser(1)</strong> // Will return User(1, &quot;John&quot;, &quot;Smith&quot;)<br><strong>}</strong></pre><p><strong>Fake</strong>: a type of test double that uses some sort of “shortcut” implementation to act like the real class. If you use interfaces to declare your class dependencies, then creating a fake implementation is quite straightforward.</p><pre>interface UserRepository {<br>  suspend fun getUser(userId: Int): User<br>}</pre><pre>// Create a fake implementation of a UserRepository<br><strong>class FakeUserRepository : UserRepository {</strong><br>  <strong>override suspend fun getUser(userId: Int): User {</strong><br>    return User(<br>      id = userId,<br>      firstName = &quot;John&quot;,<br>      lastName = &quot;Smith&quot;<br>    )<br>  <strong>}</strong><br><strong>}</strong></pre><h4>Writing Tests</h4><p>As I mentioned above, we introduced several conventions, such as test method naming, to ensure that the tests are consistent across the codebase. This helps us describe the scenario being tested, and also helps us quickly identify regression in our code. We also treat our test code as production code, meaning that SOLID principle still applies to the test code.</p><p>Other than that, we also use other third-party libraries to help us write test cases more easily. For example, we are using <a href="https://github.com/KakaoCup/Kakao">Kakao</a>, a Kotlin DSL wrapper for Espresso, to write our UI test as we find the DSL is much more readable. We also wrote a library to create fake objects using reflection to help generate fake data for testing.</p><p>Since most of the components that we’re testing are similar to each other (Fragments, ViewModels, etc). We created a few JUnit rules to make setting up those tests easier. For example, we have a FragmentTestRule that contains several other JUnit rules such as <a href="https://developer.android.com/reference/androidx/arch/core/executor/testing/InstantTaskExecutorRule">InstantTaskExecutorRule</a>, <a href="https://medium.com/@fabioCollini/testing-asynchronous-rxjava-code-using-mockito-8ad831a16877">TaskSchedulerRule</a>, and <a href="https://github.com/android/architecture-components-samples/blob/5d284bf229f08bbd1a7282316f540e1c2a2bad32/GithubBrowserSample/app/src/androidTest/java/com/android/example/github/util/DataBindingIdlingResourceRule.kt">DataBindingIdlingResourceRule</a> to ensure that the Fragment tests are <a href="https://hackernoon.com/flaky-tests-a-war-that-never-ends-9aa32fdef359">deterministic and not flaky</a>.</p><p>Our approach was UI testing to test UI logic, not the appearance of the UI element. Checking whether a Button is displayed correctly in a specific color or size with Espresso is difficult, however, you can easily check whether that button has the correct label or whether it is clickable. If you want to test the appearance of a Button, I’d suggest you take a look at screenshot testing instead.</p><h4>Testing Components in Isolation</h4><p>The ability to test components independently is important to ensure that we can focus on writing the test cases for each specific component. To achieve that, we make use of dependency injection such as Dagger and constructor parameters to pass in the required dependencies for each class. This allows us to easily replace those dependencies with test doubles during testing. Consider the following example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4w4KVHup4IziYyp8mfxIcA.png" /></figure><pre>interface UserApi {<br>  suspend fun getUser(userId: Int): User<br>}</pre><pre>interface UserRepository {<br>  suspend fun getUser(userId: Int): Flow&lt;User&gt;<br>}</pre><pre>class UserRepositoryImpl(<br>  <strong>val userApi: UserApi,</strong> <br>  val userDb: UserDB<br>) : UserRepository {<br>  <br>  override fun getUser(userId: Int): Flow&lt;User&gt; { <br>    // Fetches user data from UserAPI and store it <br>    // in the local DB if necessary.<br>    ... <br>  }<br>}</pre><pre>class UserViewModel(<br>  private val userId: Int,<br>  <strong>private val userRepository: UserRepository</strong><br>) : ViewModel() {</pre><pre>  private val _user: MutableLiveData&lt;User&gt; = MutableLiveData()<br>  val user: LiveData&lt;User&gt; = _user<br>  <br>  init {<br>    viewModelScope.launch {<br>      // Observe changes from UserRepository.<br>      <strong>userRepository.getUser(userId)</strong>.collect { user -&gt;<br>        _user.value = user<br>      }<br>    }<br>  }<br>}</pre><pre>class UserFragment : Fragment() {<br>  <br>  // Assume that UserViewModel is injected through Dagger.<br><strong>  @Inject</strong><br>  <strong>lateinit var viewModel: UserViewModel</strong><br>  <br>  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {<br>    super.onViewCreated(view, savedInstanceState)<br>    ...</pre><pre>    // Observe state changes from LiveData.<br>    <strong>viewModel.user.observe</strong> { ... }<br> <br>    <strong>someButton.setOnClickListener {</strong><br>      // Use Navigation Component to handle navigation events.<br>      <strong>findNavController().navigate(R.id.to_other_destination)</strong><br>    <strong>}</strong><br>  }<br>}</pre><p>When testing UserRepository, we can replace the implementation of UserApi like so:</p><pre>@RunWith(AndroidJUnit4::class)<br>class UserRepositoryTest {<br>  @JvmField<br>  @Rule<br>  val repositoryTestRule = RepositoryTestRule()<br>  <br>  <strong>@MockK</strong><br>  <strong>lateinit var userApi: UserApi</strong><br>  <br>  @Before<br>  fun setUp() {<br>    MockKAnnotations.init(this)<br>  }  <br>  <br>  @Test<br>  fun givenUserApiReturnsUser_ThenEmitUser() = runBlockingTest {<br>    <br>    // Set up mocked UserApi to return a fake user.<br>    <strong><em>coEvery</em> {</strong> <strong>userApi.getUser(any())</strong> <strong>}</strong> answers { <br>      User(<br>        id = firstArg(),<br>        firstName = &quot;John&quot;,<br>        lastName = &quot;Smith&quot;<br>      )<br>    }    <br>    <br>    // Replace the dependencies of UserRepository with mocks.<br>    <strong>val userRepository = UserRepositoryImpl(<br>      userApi, <br>      repositoryTestRule.db<br>    )</strong><br>    <br>    // Assert that repository emits the fake user.<br>    <strong>val user = userRepository.getUser().single()</strong><br>    assertEquals(&quot;John&quot;, user.firstName)<br>    assertEquals(&quot;Smith&quot;, user.lastName)<br>  }<br>  ...<br>}</pre><p>When testing UserViewModel, we only need to replace the UserRepository implementation:</p><pre>@RunWith(AndroidJUnit4::class)<br>class UserViewModelTest {<br>  @JvmField<br>  @Rule<br>  val viewModelTestRule = ViewModelTestRule()<br> <br>  <strong>@MockK<br>  lateinit var userRepository: UserRepository</strong></pre><pre>  @Before<br>  fun setUp() {<br>    MockKAnnotations.init(this)<br>  }<br>  <br>  @Test<br>  fun givenUserRepostiroyEmitsUser_ThenUserLiveDataIsUpdated() {<br>    <br>    // Set user repository to emit a fake user.<br>    <strong><em>coEvery</em> {</strong> <strong>userRepository.getUser(any())</strong> <strong>}</strong> answers { <br>      flowOf(<br>        User(<br>          id = firstArg(),<br>          firstName = &quot;John&quot;,<br>          lastName = &quot;Smith&quot;<br>        )<br>      )<br>    }<br>    <br>    // Replace the dependencies of UserViewModel with mocks.<br>    <strong>val viewModel = UserViewModel(1, userRepository)</strong><br>    <br>    // Assert that user LiveData emits the fake user.<br>    <strong>val user = viewModel.user.getOrAwaitValue()</strong><br>    assertEquals(&quot;John&quot;, user.firstName)<br>    assertEquals(&quot;Smith&quot;, user.lastName)<br>  }<br>}</pre><blockquote>You can find getOrAwaitValue extension <a href="https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/test/java/com/android/example/livedatabuilder/util/LiveDataTestUtil.kt#L30">here</a>.</blockquote><p>As I mentioned before, we usually test our Fragment and ViewModel together, hence here’s how we test UserFragment:</p><pre>@RunWith(AndroidJUnit4::class)<br>class UserFragmentTest {<br>  @JvmField<br>  @Rule<br>  val fragmentTestRule = FragmentTestRule()<br> <br>  <strong>@MockK<br>  lateinit var navController: NavController</strong><br> <br>  <strong>@MockK<br>  lateinit var userRepository: UserRepository</strong>  <br>  <br>  @Before<br>  fun setUp() {<br>    MockKAnnotations.init(this)<br>  }<br>  <br>  // Launch the fragment using FragmentScenario.<br>  <strong>private fun launchFragment(): FragmentScenario&lt;UserFragment&gt;</strong> {<br>    <strong>return launchFragmentInContainer {</strong><br>      <br>      // Create the Fragment manually and replace any<br>      // dependencies as needed. <br>      UserFragment().also { userFragment -&gt;<br>        <strong>userFragment.viewModel = UserViewModel(1, userRepository)</strong><br><br>        fragmentTestRule.dataBindingIdlingResourceRule<br>          .setFragment(userFragment)<br>      }<br>    <br>    <strong>}</strong>.onFragment { userFragment -&gt;<br>      <br>      // Replace the NavController with a mocked NavController<br>      // to test UserFragment in isolation.<br>      <strong>Navigation.setViewNavController(<br>        it.requireView(), <br>        navController<br>      )</strong><br>    <br>    }<br>  } <br>  <br>  @Test<br>  fun givenUser_ThenDisplayUserDetails() {<br>    <br>    // Set fake user similar to ViewModel.<br>    <strong><em>coEvery</em> { userRepository.getUser(any()) }</strong> answers { <br>      flowOf(<br>        User(<br>          id = firstArg(),<br>          firstName = &quot;John&quot;,<br>          lastName = &quot;Smith&quot;<br>        )<br>      )<br>    }<br>    <br>    <strong>launchFragment()</strong><br>        <br>    // Assert views with Kakao.<br>    <strong>onScreen&lt;UserScreen&gt; {<br>      firstNameTextView.hasText(&quot;John&quot;)<br>      lastNameTextView.hasText(&quot;Smith&quot;)<br>    }</strong>  <br>  }<br>  <br>  @Test<br>  fun whenClickSomeButton_ThenNavigateToOtherDetailsFragment() {<br>    <strong>launchFragment() </strong><br>    <br>    // Trigger UI interactions with Kakao.<br>    <strong>onScreen&lt;UserScreen&gt;{ </strong><br>      <strong>someButton.click()</strong><br>    <strong>}</strong><br>    <br>    // Verify navigation events using the mocked NavController.<br>    <strong><em>verify </em>{ navController.navigate(R.id.to_other_destination) }</strong><br>  }<br>}</pre><blockquote>You can also use <a href="https://developer.android.com/guide/navigation/navigation-testing#test_fragment_navigation">TestNavHostController</a> as a replacement for your NavController.</blockquote><p>This kind of setup also allows us to easily combine components during integration tests. For example, we can use Dagger to replace only the UserApi with a mock API client during testing, and keep the rest of the dependency with the real implementation.</p><h4>Pull Requests and Coverage</h4><p>We run our test suite for each pull request on our CI server and this allows us to measure the code coverage of each branch. However, we don’t use the coverage percentage to guide us on how many test cases we have to write. We believe that we need to focus on the most critical path of the codebase, rather than the number of test cases.</p><p>Given that we are dealing with legacy code, we need to introduce the test suite gradually. It’s impossible to refactor the whole application at once because, with that approach, we will not be able to develop any feature in parallel. Without parallel development, we will not be able to deliver any updates or fixes to our users. Thus, we decided to write test cases for components that we’re are currently working or refactoring on, and track our overall progress using code coverage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/595/1*3H6iuqO_mk-1BUWfsVsDmA.png" /></figure><p>Code coverage report allows us to easily find out which components have been or have not been tested. In addition, we also introduce a minimum coverage threshold and increase that threshold gradually to ensure that we don’t add more code that has not been tested into the codebase.</p><h3>Issues that we Encountered</h3><p>The whole journey towards a testable app was not all smooth. Here are several issues that we encounter during the refactoring and how we dealt with these issues.</p><ul><li><strong>Mismatches between actual implementation and the testing document.</strong> When we first created our test document, there were a lot of unknowns. What we ended up implementing is quite different from what we initially wanted. Hence, we needed to iterate on the testing document. We needed to adapt to technical changes and adjust our approach accordingly. For example, we wanted to make use of <a href="https://www.spekframework.org/">gherkin syntax with Spek</a> to help us structure our test suite. However, Spek does not currently support instrumented tests for Android. Given that we want to maintain consistencies between our local unit test and instrumented test (as we were hoping for <a href="https://medium.com/androiddevelopers/write-once-run-everywhere-tests-on-android-88adb2ba20c5">Project Nitrogen</a>), we decided not to use Spek.</li><li><strong>Writing and maintaining an automated test suite requires a lot of time investment.</strong> Implementing proper test cases will inevitably require a lot more time. We as engineers need to communicate that to the stakeholders and manage their expectations. However, I believe that in the long run, writing automated tests is a good investment to make since most of the manual tests are quite repetitive and can be automated. Investing in an automated test suite also allowed us to make changes to our code faster.</li><li><strong>Execution time gets longer as the test suite grows.</strong> The number and type of tests in the test suite will heavily impact the execution time of the testing pipeline. If there’s a large number of UI tests, the execution time can be very long and it can slow down the development. For us, we have about 1000 unit tests and 300 UI/ integration tests. Running our test suite on our CI server takes about 5 minutes for unit tests and 15 minutes for UI tests. Executing UI tests takes a lot of time even after we introduced <a href="https://github.com/Flank/flank">Flank</a> to shard our tests. If you have a bigger test suite, you might want to take a look at <a href="https://dropbox.tech/mobile/revamping-the-android-testing-pipeline-at-dropbox">Dropbox’s approach</a> to selectively run the test suite for code that changed or that could have been affected by the change.</li><li><strong>Generating code coverage with Jacoco can be difficult.</strong> Even though support for Jacoco comes out of the box with Android Gradle Plugin. Setting it up can be quite difficult, especially when upgrading AGP’s version. In our experience, Jacoco will often throw compilation errors or show incorrect coverage reports when AGP is updated. As you can see in our branch coverage graph, we encountered a big dip in the coverage due to this <a href="https://issuetracker.google.com/issues/170915843">issue</a> when we updated AGP to 4.2. There are also other issues that you have to pay attention to, such as <a href="https://issuetracker.google.com/issues/171125857">this</a>, <a href="https://issuetracker.google.com/issues/178015739">this</a>, and <a href="https://github.com/gradle/gradle/issues/5184#issuecomment-457865951">this</a>.</li></ul><h3>Wrap up</h3><p>While it took a lot of time to see the benefit and result of our effort to implement a testable app, I believe that the testing setup for our Android app has improved significantly in the last 2 years. Of course, we can’t just stop here because there’s still a lot of room for improvement. Creating a testable app needs consistent efforts from everybody, and it’s very important to get buy-in from your team members and stakeholders before starting your journey in implementing a testable architecture. Writing automated tests should be an integral part of your development process and it should not slow down the pace of the development.</p><p>An automated test suite can help you quickly identify regression during development. However, I don’t think you can 100% rely on an automated test suite because there are problems that a manual QA process can find more easily, such as animation issues, UI or layout issues, incorrect use of test doubles, or unhandled edge cases, etc. So, it’s still important to have a manual test in your development cycle.</p><p>Thank you for reading this article. I hope it can give you some ideas on how to implement a testable architecture in your Android app.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5bdf0d9bd2fc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/our-journey-towards-a-testable-android-app-5bdf0d9bd2fc">Our Journey Towards a Testable Android App</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Different Approaches in Consuming KMM Modules in iOS]]></title>
            <link>https://medium.com/wantedly-engineering/different-approaches-in-consuming-kmm-modules-in-ios-7957c722b114?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/7957c722b114</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Malvin Sutanto]]></dc:creator>
            <pubDate>Fri, 19 Feb 2021 08:35:44 GMT</pubDate>
            <atom:updated>2021-02-19T08:35:44.819Z</atom:updated>
            <content:encoded><![CDATA[<p>Exploring various methods to work with KMM modules in an iOS project that is located in a separate repository.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*331N13Ntws2sKHq7" /><figcaption>Photo by <a href="https://unsplash.com/@pundalex?utm_source=medium&amp;utm_medium=referral">Lex Sirikiat</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>With Kotlin Multiplatform for Mobile (KMM), you can share code between Android and iOS projects. When setting up a new KMM project, if you follow the <a href="https://github.com/Kotlin/kmm-sample">KMM sample</a> setup, you’ll have iOS, Android, and KMM shared code located in a single repository. However, when integrating KMM into existing apps, most likely your Android and iOS apps are located in separate repositories, with their own dedicated tooling and CI/CD pipeline.</p><p>Sharing KMM code with existing Android projects is pretty simple as you can easily upload your KMM into a Maven repository, but when it comes to iOS, it’s not that straightforward. In this article, I will show you different methods to distribute your KMM code so that it can be consumed in your iOS project.</p><blockquote>This article is written based on Kotlin 1.4.20 and is not an exhaustive list of distribution methods for a KMM module. The Gradle build scripts in this article are all written in Kotlin.</blockquote><h3>CocoaPods Gradle plugin and git submodule</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N9fHNRKFsRS-0ei3YlDWWw.jpeg" /></figure><p>native.cocoapods is an official Gradle plugin from JetBrains that adds <a href="https://cocoapods.org/">CocoaPods</a> integration into a KMM project. To make use of this plugin, simply <a href="https://kotlinlang.org/docs/reference/native/cocoapods.html">apply the </a><a href="https://kotlinlang.org/docs/reference/native/cocoapods.html">native.cocoapods Gradle plugin in the build.gradle</a> file of your KMM project and set up the necessary CocoaPods parameters. This plugin will add the necessary Gradle tasks to generate a podspec file for your KMM project. If you notice, the generated podspec file has a script_phase attribute that will automatically execute a Gradle task to compile the KMM module into a native framework when you run/build from XCode.</p><p>In this approach, you need to ensure that the generated .podspec file is pushed into the remote git repository, and then in your iOS repository, create a git submodule that points to the latest branch/release of your KMM project. Then, simply add the dependency to the podspec file in your iOS project’s Podfile.</p><pre>target &#39;MyIosApp&#39; do<br> // other dependencies. <br> pod &#39;MyKmmModule&#39;, :path =&gt; &#39;/path/to/kmm/submodule&#39;<br>end</pre><p>There’s one more step to add before you can run pod install. Since the KMM module dependency is introduced as a vendored framework, CocoaPods needs to have access to the framework file during the pod install process to be able to configure the framework correctly. However, since the real framework file is only going to be generated when we run build in XCode, we need to run the following Gradle task before running the pod install command.</p><pre>/path/to/kmm/submodule/gradlew generateDummyFramework</pre><p>Now, you should be able to run pod install in your iOS project and then use the KMM shared code in XCode.</p><p><em>The current Gradle plugin only supports </em><strong><em>Debug</em></strong><em> and </em><strong><em>Release</em></strong><em> configuration for iOS projects. If you’re using custom configurations, you can add a custom Gradle task to add additional xcconfig settings and override the default configuration.</em></p><pre>val podspec by tasks.existing(PodspecTask::class) {<br>  doLast {<br>    val outputFile = outputs.files.singleFile<br>    val text = outputFile.readText()<br>    val newText = text<br>      // Workaround: https://youtrack.jetbrains.com/issue/KT-42023<br>      .replace(&quot;spec.pod_target_xcconfig = {&quot;,<br>        &quot;&quot;&quot;<br>          spec.pod_target_xcconfig = {<br>            &#39;KOTLIN_CONFIGURATION&#39; =&gt; &#39;Release&#39;,<br>            &#39;KOTLIN_CONFIGURATION[config=Debug]&#39; =&gt; &#39;Debug&#39;,<br>        &quot;&quot;&quot;.trimIndent()<br>      )<br>      .replace(&quot;\$CONFIGURATION&quot;, &quot;\$KOTLIN_CONFIGURATION&quot;)<br>    outputFile.writeText(newText)<br>  }<br>}</pre><h4>Pros</h4><ul><li>native.cocoapods is officially developed by JetBrains, and you can expect improvements in the future.</li><li>There’s little to no additional maintenance or work necessary to set up and share KMM code with iOS.</li></ul><h4>Cons</h4><ul><li>Your XCode build might be slow since it needs to download the Gradle dependencies and execute Gradle build, especially for a clean build.</li><li>Managing versioning with git submodule is not easy.</li></ul><h3>Generate and distribute universal (fat) frameworks</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OqhlY13WvBfUXsUoebTOqg.jpeg" /></figure><p>With the above CocoaPods and git submodule approach, XCode will execute a Gradle task to build the KMM module’s framework file whenever you run build in XCode. This task can be cached by Gradle, so if there are no changes in your KMM code, it should be pretty fast. But in a CI environment, you might have issues with slow build time.</p><p>Fortunately, as your KMM project is located in a separate repository, the KMM code itself should not change very often, hence, you can actually <a href="https://kotlinlang.org/docs/reference/mpp-build-native-binaries.html">create a final native binary to be consumed in the iOS project</a>. Moreover, since this can be delegated to the release pipeline of the KMM module’s CI/CD system, this would have a minimal impact on your XCode build.</p><p>You can create custom Gradle tasks in your Gradle build script to generate the universal (fat) frameworks for your KMM module:</p><pre>kotlin {<br>  val iosX64 = iosX64(&quot;iosX64&quot;) {<br>    binaries {<br>      framework {<br>        baseName = &quot;MyKmmModule&quot;<br>        embedBitcode(&quot;disable&quot;)<br>      }<br>    }<br>  }<br>  val iosArm64 = iosArm64(&quot;iosArm64&quot;) {<br>    binaries {<br>      framework { <br>        baseName = &quot;MyKmmModule&quot;<br>        embedBitcode(&quot;bitcode&quot;)<br>      }<br>    }<br>  }</pre><pre>  // Create a debug framework for both X64 and Arm64 architecture.<br>  task.register&lt;FatFrameworkTask&gt;(&quot;debugFatFramework&quot;) {<br>    baseName = &quot;MyKmmModule&quot;<br>    destinationDir = file(&quot;$buildDir/fat-framework/debug&quot;)<br>    from(<br>      iosX64.binaries.getFramework(&quot;DEBUG&quot;),<br>      iosArm64.binaries.getFramework(&quot;DEBUG&quot;)<br>    )<br>  }</pre><pre>  // Create a release framework for Arm64 architecture.<br>  task.register&lt;FatFrameworkTask&gt;(&quot;releaseFatFramework&quot;) {<br>    baseName = &quot;MyKmmModule&quot;<br>    destinationDir = file(&quot;$buildDir/fat-framework/release&quot;) <br>    from(<br>      iosArm64.binaries.getFramework(&quot;RELEASE&quot;)<br>    )<br>  }<br>}</pre><p>Once you have the framework files built, it’s up to you and your iOS team to decide on how to share and consume these framework files.</p><h4>Pros</h4><ul><li>Building framework files can be delegated on the KMM module’s release pipeline in your CI/CD infrastructure, hence, there will be minimal impact on build time on your XCode build.</li><li>No dependency on Gradle and Maven for your iOS project.</li></ul><h4>Cons</h4><ul><li>You need to maintain the Gradle scripts for building these framework files.</li><li>Depending on how you decide to distribute the framework files, you might need additional work around the distribution pipeline and authentication around it.</li></ul><h3>Using transitive dependencies with Gradle</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y854lacCYss9Zl4PocF_qg.jpeg" /></figure><p>The other approach that you can use to consume KMM code from your iOS project is to create a separate Kotlin multiplatform project inside the iOS repository, add the dependencies to the KMM module using Gradle dependencies, and then expose that dependency using transitive dependencies. With this setup, managing the dependencies and version update should be much easier than using a git submodule approach. On top of it, we can also use native.cocoapods Gradle plugin to expose the KMM module to iOS.</p><p>To make sure that the headers for KMM modules are correctly exported, you need to enable transitiveDependency flag in the Gradle build script. Additionally, you can also include multiple KMM modules dependencies, effectively creating an umbrella framework for multiple KMM modules.</p><pre>plugins {<br>  kotlin(&quot;multiplatform&quot;) version &quot;1.4.20&quot;<br>  kotlin(&quot;native.cocoapods&quot;) version &quot;1.4.20&quot;<br>}</pre><pre>kotlin {<br>  ios {<br>    // native.cocoapods Gradle plugin will declare <br>    // the binary framework creation.<br>    // Since, duplicate binary name is not supported, <br>    // we need to configure the binary settings declared by <br>    // native.cocoapods plugin to mark the dependency as an<br>    // exported dependency.<br>    binaries<br>      .filterIsInstance&lt;Framework&gt;()<br>      .forEach {<br>        it.transitiveExport = true<br>        it.export(&quot;com.my.kmm:module:1.0.0&quot;)<br>    }<br>  }<br>  sourceSets {<br>    getByName(&quot;iosMain&quot;) {<br>      dependencies {<br>        api(&quot;com.my.kmm:module:1.0.0&quot;)<br>      }<br>    }<br>  }<br>  cocoapods {<br>    // Set up cocoapods parameters.<br>  }<br>}</pre><p>Similar to the git submodule approach, you need to run gradlew podspec task from the Kotlin multiplatform project to generate the latest podspec file. Then, add the dependency to the generatedpodspec file in your iOS project’s Podfile and run gradlew generateDummyFramework and pod install and you should now be able to invoke KMM functions inside your iOS project.</p><h4>Pros</h4><ul><li>Allows you to import multiple KMM modules.</li><li>Dependencies are declared in a Gradle build file which is easier to manage.</li></ul><h4>Cons</h4><ul><li>You need to maintain a separate KMM project inside the iOS project.</li><li>Build time is similar to CocoaPods with git submodule approach because you still need to download and execute Gradle build for KMM modules.</li><li>Needs to ensure gradlew podspec is executed when a dependency is updated.</li></ul><h3>Swift Packages</h3><p>Swift Package is a new format introduced by Apple to share reusable components between projects. There is a great article written by John O’ Reilly on how to use Swift Package Manager to consume KMM module in an iOS project. If you’re interested, you can find the article here: <a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/">https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/</a></p><h3>Summary</h3><p>The approaches that I’ve shared above are by no means exhaustive. There are other methods that you can use to consume a KMM module from your iOS project. Also, the pros and cons of each approach that I’ve written might not apply to your project and team. It’s up to you to decide what works best for your configuration. However, I hope this can give you some ideas on some of the possibilities.</p><p>For us at Wantedly, we decided to use native.cocoapods Gradle plugin with Git submodule approach. Mainly because it’s one of the official Gradle plugins developed by JetBrains and we feel like the additional build time is a good trade-off for additional work necessary to create a custom solution to consume KMM module from our iOS project. Also, if you look at the <a href="https://blog.jetbrains.com/kotlin/2021/01/results-of-the-first-kotlin-multiplatform-survey">result of the Kotlin Multiplatform survey published recently by JetBrains</a>, there seem to be some improvements that we can expect in this area.</p><p>Thank you so much for reading this article, I hope you found it useful!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7957c722b114" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/different-approaches-in-consuming-kmm-modules-in-ios-7957c722b114">Different Approaches in Consuming KMM Modules in iOS</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kotlin 1.4 Online Event Recap]]></title>
            <link>https://medium.com/wantedly-engineering/kotlin-1-4-online-event-recap-a4937803f256?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/a4937803f256</guid>
            <category><![CDATA[kotlin-libraries]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Malvin Sutanto]]></dc:creator>
            <pubDate>Fri, 16 Oct 2020 10:22:03 GMT</pubDate>
            <atom:updated>2020-10-16T10:22:03.744Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Iiq-kr8IYxwdywhhdmzY3Q.png" /><figcaption><a href="https://kotlinlang.org/lp/event-14/">Kotlin 1.4 Online Event by JetBrains</a></figcaption></figure><p><a href="https://kotlinlang.org/lp/event-14">Kotlin 1.4 Online Event</a>, as the name suggests, is a 4-day online event (from 12 Oct to 15 Oct 2020) that focuses on talking about new features that are introduced in the Kotlin 1.4. This event was live-streamed on YouTube, so it’s free for everyone, and they also have recorded sessions and slides available on the <a href="https://kotlinlang.org/lp/event-14/">event’s page</a>. Most of the speakers are people from JetBrains who are directly involved with the development of the Kotlin language and libraries.</p><p>Viewers were able to interact with the live-stream in multiple ways. They can ask questions about the talk using YouTube chat, Q&amp;A forms, and a Twitter hashtag of <a href="https://twitter.com/search?q=%23kotlin14ask">#kotlin14ask</a>, and these questions will then be answered during the live stream. Then, there’s also <a href="https://kotlinlang.org/lp/event-14/#talks">QuizQuest</a>, a series of mini-tests to check what we’ve learned from the talks. There are different prizes to be won and some of the prize categories are active until 26 Oct 2020. There’s also a virtual booth where you can have a discussion with people from the JetBrains team, though this has limited availability.</p><p>This year, we were able to participate in the Kotlin 1.4 Online Event and in this article we want to talk about our summary of the event. There were a lot of good sessions, and summarizing them will be difficult. There will be a lot of important points that are not covered, so we encourage you to watch the <a href="https://kotlinlang.org/lp/event-14/#talks">recorded sessions</a> if you’re interested. We will also include the YouTube link for each talk.</p><h3>Talks from Day 1 - General Overview</h3><h4>Opening Keynote</h4><p><strong>The Kotlin Team</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FpD58Dw17CLk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpD58Dw17CLk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpD58Dw17CLk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b1d5b5d83fe37de918ed1a230817bfe/href">https://medium.com/media/4b1d5b5d83fe37de918ed1a230817bfe/href</a></iframe><p>The first event of the day is the opening keynote from Andrey Breslav, the project lead of Kotlin language, and the Kotlin team. They talked about the journey of Kotlin, the announcement of the <a href="https://blog.jetbrains.com/kotlin/2020/10/new-release-cadence-for-kotlin-and-the-intellij-kotlin-plugin/">new release cadence</a>, the current state of Kotlin and the recent <a href="https://blog.jetbrains.com/kotlin/2020/08/kotlin-1-4-released-with-a-focus-on-quality-and-performance/">1.4 updates to the language on performance and quality improvements</a>. He also talked about the <a href="https://kotlinlang.org/roadmap.html#kotlin-roadmap">current focus of the Kotlin team</a>, the new Kotlin compilers, their effort on server-side kotlin, and their vision of Kotlin Multiplatform.</p><h4>Staying in Touch</h4><p><strong>Egor Tolstoy</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FEi32LzH1pe8%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DEi32LzH1pe8&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FEi32LzH1pe8%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/95369ca365771381f1c1ba3d80ce9c24/href">https://medium.com/media/95369ca365771381f1c1ba3d80ce9c24/href</a></iframe><p>The next session talked about how we can contribute to the Kotlin language. The community can contribute by submitting Pull Requests on GitHub, reporting issues through YouTrack, sharing feedbacks of EAP, participating in user reviews through <a href="https://kotl.in/interview">kotl.in/interview</a>, answering questions in StackOverflow, and many others (see: <a href="https://kotl.in/contribute">kotl.in/contribute</a>). Egor also mentioned that we can <a href="https://www.jetbrains.com/help/idea/settings-usage-statistics.html">enable sharing of anonymous usage statistics</a> to help JetBrains analyze and prioritize features, which currently is only enabled by 8% of all developers who’re using IntelliJ. He also outlined how important community feedback is when prioritizing features in Kotlin language.</p><h4>New Language Features in Kotlin 1.4</h4><p><strong>Svetlana Isakova</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F9ihevvUCoG0%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D9ihevvUCoG0&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F9ihevvUCoG0%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/0983cdcc2e22c86dc642f00e92afe8d9/href">https://medium.com/media/0983cdcc2e22c86dc642f00e92afe8d9/href</a></iframe><p>This session talked about the new language features that were introduced to 1.4 updates, the content of this talk is very similar to <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#language-features-and-improvements">language features and improvements section of What’s New in 1.4 updates</a>. In this session, Svetlana also mentioned the <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#new-more-powerful-type-inference-algorithm">new type inference algorithm that is available due to the new compiler included in 1.4</a>, <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#unified-exception-type-for-null-checks">unified exception type for null checks</a>, and also <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#new-modes-for-generating-default-methods">new modes for generating default methods in interfaces when targeting Kotlin/JVM</a>.</p><h4>Quality and Performance — Kotlin 1.4 in IntelliJ IDEs</h4><p><strong>Anton Yalyshev</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F-5ZjMwNzWrk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D-5ZjMwNzWrk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F-5ZjMwNzWrk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/24abe8af2615c57c9de27fb873b215e3/href">https://medium.com/media/24abe8af2615c57c9de27fb873b215e3/href</a></iframe><p>In this session, Anton Yalyshev talked about the performance and quality improvement of Kotlin 1.4 plugin. Majority of developers downtime is due to UI lags (31%) and indexing (28%) and Kotlin 1.4 plugin fixed a lot of UI freezes, deadlocks, and improved the CPU/Memory consumption to make development smoother. Kotlin 1.4 plugin also reduced plugin exceptions by 1.5–2x, improved the stability of the debugger, formatter, and refactoring, and also introduced a new debugger for coroutines. He further touched on <a href="https://kotlinlang.org/roadmap.html">the future improvements to the plugin</a>. Lastly, he announced that Kotlin IntelliJ plugin repository will be moved into the IntelliJ platform repository for a faster development cycle.</p><h4>A Look Into the Future</h4><p><strong>Roman Elizarov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F0FF19HJDqMo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D0FF19HJDqMo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F0FF19HJDqMo%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/59036afd068bae553b595178232af634/href">https://medium.com/media/59036afd068bae553b595178232af634/href</a></iframe><p>In our opinion, this is the most interesting session of the day. Roman’s talk about the future of Kotlin is divided into 2 broad sections, near future and distant future. Near future section addressed KMM and JetBrains commitment to support newer Java APIs like Java Records and Sealed classes. While distant future section showed previews of how Kotlin language might evolve in the future with new operators and language features like namespace extension, multiple receivers and decorators for functions, private/public property type, immutability, and inline classes. He also discussed how JetBrains evaluated Kotlin language APIs design and features.</p><h3>Talks from Day 2 - Libraries</h3><h4>Coroutines Update</h4><p><strong>Vsevolod Tolstopyatov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FE5bje5HgKs0%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DE5bje5HgKs0&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FE5bje5HgKs0%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4624a923287d12ae44cbb0a43d2e1672/href">https://medium.com/media/4624a923287d12ae44cbb0a43d2e1672/href</a></iframe><p>Day 2 opened with a talk on updates to coroutines by Vsevolod Tolstopyatov. He touched on <a href="https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-rc-debugging-coroutines/">the new coroutines debugger</a> and simplified coroutines stack trace in IntelliJ IDEA. Then, he talked about various updates to Kotlin Flow since 1.3, the introduction of <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/">StateFlow</a> and <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/">SharedFlow</a>, and the background behind these classes, and JetBrains’ philosophy on adding new operators to Flow. The new coroutine updates also benefit Android projects (with 30% reduced DEX size, better start time and memory/CPU consumption) and Java projects (with integration with blocking calls, <a href="https://github.com/reactor/BlockHound">BlockHound</a>, and JDK 9 <a href="https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html">java.util.concurrent.Flow</a>). Lastly, he touched on their plan for the future of coroutines, like the stability of certain features, and time-specific operators such as debounce or throttle, and more.</p><h4>kotlinx.serialization 1.0</h4><p><strong>Leonid Startsev</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FAzi57n59ICM%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DAzi57n59ICM&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FAzi57n59ICM%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/62266d59b7d92d9fb386ade125c49774/href">https://medium.com/media/62266d59b7d92d9fb386ade125c49774/href</a></iframe><p>The next talk is “kotlinx.serialization 1.0” by Leonid Startsev. In this session, he discussed why JetBrains created <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a> and how it differs from other JSON parsing libraries, and then showed how serialization APIs in kotlinx.serialization looks like. Then, he also talked about the stable features in 1.0 like @Serializable annotation and polymorphic serialization, and features that are experimental in 1.0, such as custom serial formats and protobuf serialization. He also mentioned new features that are new to 1.0 release, and what are their plans for kotlinx.serialization library. Finally, he showed how we can integrate kotlinx.serialization into our projects, and how to upgrade from previous versions of kotlinx.serialization.</p><h4>News From the Kotlin Standard Library</h4><p><strong>Svetlana Isakova</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FNJjEFXeiuKY%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DNJjEFXeiuKY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FNJjEFXeiuKY%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b6b83f4286b040f7fc58e2d26cf7e154/href">https://medium.com/media/b6b83f4286b040f7fc58e2d26cf7e154/href</a></iframe><p>Svetlana talked about <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#standard-library">the new functionalities and changes to the Kotlin standard library</a>. In Kotlin 1.4, the Kotlin team tried to make the naming of functions to be more consistent, for example, <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#new-functions-for-arrays-and-collections">by adhering to naming conventions</a>, such as running*, *orNull, *Indexed, *IndexedOrNull. 1.4 also introduced a new collection type ArrayDeque (a double-ended queue implemented with a circular buffer) and a couple of new functions for optimised <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#bit-operations">bit manipulation</a>. She also mentioned a couple of changes that make <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#kotlin-multiplatform">working with the standard library in multiplatform projects easier</a>, such as <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#common-exception-processing-api">stack trace</a> and <a href="https://kotlinlang.org/docs/reference/whatsnew14.html#exception-handling-in-objective-cswift-interop">exception</a> handling. Then, she also talked about new experimental functionalities like collection builders and time measurement APIs, and how @OptIn and @RequiresOptIn annotations behave.</p><h4>Introducing DateTime</h4><p><strong>Ilya Gorbunov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FYwN0kAMNvXI%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DYwN0kAMNvXI&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FYwN0kAMNvXI%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/d84b9840fe7dd41263c7441e462d895c/href">https://medium.com/media/d84b9840fe7dd41263c7441e462d895c/href</a></iframe><p>In this session, Ilya Gorbunov introduced <a href="https://github.com/Kotlin/kotlinx-datetime">kotlinx.datetime</a> library, an official library from JetBrains to work with date and time in Kotlin. In this library, there are 2 flavours of time, Instant and LocalDate (and LocalDateTime), and depending on your purpose you’ll prefer one over the other. He also talked about how they differ and example use cases, such as preserving timezone data or daylight saving issues. Then, he gave a few examples of how Kotlin APIs have influenced the APIs of the library. Finally, he also touched on its interoperability with each platform’s date-time implementation, their plans with kotlinx.datetime, and the importance of the community feedback in shaping this library.</p><h3>Talks from Day 3 - Android and Multiplatform</h3><h4>State of Kotlin in Android</h4><p><strong>Florina Muntenescu</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FFRCASfAMXoQ%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DFRCASfAMXoQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FFRCASfAMXoQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/64a6bd6d478449ba434fb805b646250f/href">https://medium.com/media/64a6bd6d478449ba434fb805b646250f/href</a></iframe><p>The first session of day 3, from Florina Muntenescu, talked about the impact of Kotlin language on Android apps development. She shared statistics of how android app has improved since adopting Kotlin and also explained what the recent Kotlin 1.4 update brought; like improved tooling performance (incremental annotation processor, IDE performance, instant execution) and features for library authors (explicit API mode, <a href="https://goo.gle/ksp">KSP</a>, and R8 support for Kotlin metadata). She highlighted that coroutines are the recommended solution for asynchronous programming and they integrate nicely with JetPack libraries. She also mentioned how Google is committed to develop and push Kotlin forward, from adopting a Kotlin-first approach on Android, working on open source projects (like gRPC and Protobuf), adoption of Kotlin in Google’s apps, improved documentation and learning resources, etc.</p><h4>It’s Time for Kotlin Multiplatform Mobile</h4><p><strong>Ekaterina Petrova</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FPW-jkOLucjM%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DPW-jkOLucjM&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FPW-jkOLucjM%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4bfbed0af69c198b139fa85c7826937e/href">https://medium.com/media/4bfbed0af69c198b139fa85c7826937e/href</a></iframe><p>Ekaterina Petrova talked about the current frameworks that we can use for cross-platform programming (Rect Native, Xamarin, etc) and explained how KMM is different from those frameworks. Kotlin multiplatform allows developers to share parts of the code between iOS and Android, such as data/business logic. She presented some illustrations of how we can share code through sample architecture and layers, and she also gave sample build files that we can use in a typical KMM project. She also showcased the available toolings around KMM, such as Android Studio plugin, CocoaPods integration, etc. Then, she also showed how KMM works seamlessly in the iOS ecosystem, from interoperability with Objective-C/Swift, integration with Cocoapods, to running and debugging iOS application in Android Studio. Finally, she also shared a few resources to learn more about KMM: <a href="https://kotl.in/kmm">kotl.in/kmm</a>, <a href="https://kotl.in/kmm-doc">kotl.in/kmm-doc</a>, <a href="https://kotl.in/kmm-cases">kotl.in/kmm-cases</a>, and <a href="https://kotl.in/kmm-plugin">kotl.in/kmm-plugin</a>.</p><h4>Kotlin/JS in 1.4 and Beyond</h4><p><strong>Sebastian Aigner</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FKnCixUvhmhk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DKnCixUvhmhk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FKnCixUvhmhk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/f4ebbb929551dda06d900afca1882a3d/href">https://medium.com/media/f4ebbb929551dda06d900afca1882a3d/href</a></iframe><p>In this session, Sebastien Aigner talked about the state of Kotlin/JS in 1.4. He introduced the new Gradle plugin for Kotlin/JS and multiplatform projects, and the deprecation of previous kotlin2js and kotlin-dce-js plugins. This new plugin includes a fully managed local yarn installation to allow working with npm dependencies, and also adds support for Webpack bundles. Kotlin 1.4 also improved the interoperability with other web technologies, such as <a href="https://kotlinlang.org/docs/reference/js-external-declarations-with-dukat.html">TypeScript</a>, decoupling of browser APIs from Kotlin releases, and accessing Node.js APIs through kotlin-nodejs. Then, he presented the future of Kotlin/JS with the new alpha IR compiler for reduced bundle size, support for ES6 modules, better interoperability with @JSExport annotation, and auto-generated TypeScript definitions. Finally, he briefly gave an update on the state of Kotlin and WebAssembly.</p><h4>Diving into Kotlin Multiplatform</h4><p><strong>Dmitry Savvinov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F5QPPZV04-50%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D5QPPZV04-50&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F5QPPZV04-50%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/0ada5326c4ec61e19b9bf2b05b71fbe0/href">https://medium.com/media/0ada5326c4ec61e19b9bf2b05b71fbe0/href</a></iframe><p>One of the most technical talks of the event, “Diving into Kotlin Multiplatform” by Dimitry Savvinov talked about the internal workings of multiplatform library publishing in 1.3 and how Kotlin 1.4 uses intermediate source sets to allow the new multiplatform library publishing format and hierarchical project structure. He also described the .klib format and .kotlin_metadata files, how Gradle metadata helps with Kotlin project’s dependency management, and how Kotlin toolchain automatically infers common code in native dependencies. Most of the contents in this talk are internal APIs, so it can change without prior notice and we are not supposed to rely on its implementation. If we do want to rely on these internal APIs implementations, we should let JetBrains know of our use case.</p><h3>Talks from Day 4 - Kotlin for Server-Side</h3><h4>Server-side Development with Kotlin</h4><p><strong>Anton Arhipov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FZFKxAYvQ7wQ%3Fstart%3D935%26feature%3Doembed%26start%3D935&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DZFKxAYvQ7wQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FZFKxAYvQ7wQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff300d8f0fbfab7486da5effcb058427/href">https://medium.com/media/ff300d8f0fbfab7486da5effcb058427/href</a></iframe><p>Day 4 talks are a bit different as most of the talks were actually live instead of recorded. The first talk is an update on server-side development with Kotlin, where Anton Arhipov listed out most popular frameworks that we can use to build server-side apps with Kotlin. Included in this list are the very popular <a href="https://spring.io/guides/tutorials/spring-boot-kotlin/">Spring</a> framework, a framework by JetBrains called <a href="https://ktor.io/">Ktor</a>, and many other polyglot frameworks (a full list of frameworks is available at <a href="https://kotlin.link/">https://kotlin.link/</a>). He also shared multiple companies that are currently using Kotlin for their back-end and how it impacted their development, like <a href="https://blog.jetbrains.com/blog/2019/12/05/welcome-to-space/">JetBrains Space</a>, <a href="https://speakerdeck.com/mtvarga/future-of-jira-software-powered-by-kotlin?slide=2">Atlassian Jira</a>, <a href="https://medium.com/expedia-group-tech/introducing-graphql-kotlin-client-b32dc3061a6f">Expedia Group</a>, <a href="https://medium.com/adobetech/streamlining-server-side-app-development-with-kotlin-be8cf9d8b61a">Adobe</a>, and many more. Then he also touched on how Kotlin is enabling cloud-native application using serverless technologies like <a href="https://site.kotless.io/">Kotless</a>.</p><h4>The State of Kotlin Support in Spring</h4><p><strong>Sébastien Deleuze</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FZFKxAYvQ7wQ%3Fstart%3D2359%26feature%3Doembed%26start%3D2359&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DZFKxAYvQ7wQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FZFKxAYvQ7wQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/d7e4105b43de0f7eafd2a5df3fe46bdb/href">https://medium.com/media/d7e4105b43de0f7eafd2a5df3fe46bdb/href</a></iframe><p>In the next session, Sébastien Deleuze talked about the recent updates in Spring framework that allows better compatibility with Kotlin language. He mentioned that <a href="https://start.spring.io/">Spring initialzr</a> now supports setting up a new project with Kotlin, and the sample code in Spring framework documentation is now also available in Kotlin. He also explained the differences between Spring frameworks and programming model (annotations vs Kotlin DSL) that are available in Spring, first-class support for coroutines, Kotlin multiplatform support, <a href="https://github.com/rsocket/rsocket-kotlin">RSocket</a> supports, and null safety. He later continued with a discussion of <a href="https://www.graalvm.org/docs/introduction/">GraalVM</a> and did a quick demo of Spring 2.4 running on GraalVM. He also briefly touched on future topics like functional Spring with KoFu and JaFu.</p><h4>Ktor - Past, Present, and Future</h4><p><strong>Hadi Hariri</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FZFKxAYvQ7wQ%3Fstart%3D4736%26feature%3Doembed%26start%3D4736&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DZFKxAYvQ7wQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FZFKxAYvQ7wQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a8ac398892ab2e589d9f2d5ef591dfa0/href">https://medium.com/media/a8ac398892ab2e589d9f2d5ef591dfa0/href</a></iframe><p>In this talk, Hadi Hariri talked about <a href="https://ktor.io/">Ktor</a>, a multiplatform framework by JetBrains for creating web applications. He explained how Ktor came about and what are the inspirations for Ktor. Currently, JetBrains is investing quite a lot in Ktor, with more developers, better site, blog, and documentation. JetBrains is also committing to 3 major/minor releases yearly and monthly patch releases, and proper deprecation cycle. He then also gave an example of how the structure of a Ktor application on the server-side looks like and then continued with a live demo of using different <a href="https://ktor.io/docs/intro.html#features">features</a> inside Ktor. Lastly, he gave a glimpse of how Ktor will evolve in the future, such as improved onboarding and developer experience, better feature parity between client and server components, and increased performance.</p><h4>Serverless Development with Kotlin and Kotless</h4><p><strong>Vladislav Tankov</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FS5sB1qHpRPQ%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DS5sB1qHpRPQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FS5sB1qHpRPQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/46608997fc58f0e3ebdb160ad48bedf8/href">https://medium.com/media/46608997fc58f0e3ebdb160ad48bedf8/href</a></iframe><p>In the last talk of the event, Vladislav Tankov talked about <a href="https://site.kotless.io/">Kotless</a>, a serverless framework for Kotlin. He started by discussing the pros and cons of a serverless framework, and how Kotless deal with these drawbacks. He later described features that are included in Kotless, different supported frameworks (Ktor, Spring, and Kotless’ DSL), cloud platforms (AWS, with support for GCP and Azure in the works), runtimes (Kotlin/JVM and GraalVM, with Kotlin/JS in development), and also a feature to help local development using local cloud emulation. He later explained how kotless works in general and how it integrates with different cloud technologies, like AWS DynamoDB, and then continued with a short demo of Kotless in action, and finally discussed what are their plans with Kotless. However, he mentioned in the Q&amp;A session that Kotless is currently not ready for production use.</p><h3>Summary</h3><p>This year is the first time that this event is held fully online due to the ongoing global pandemic and we would like to commend JetBrains for the successful event. We also think that the interactions on YouTube live and chat, and the way questions are selected during the Q&amp;A sessions brought up a lot of interesting and insightful discussions.</p><p>Since we are using Kotlin only for Android and <a href="https://medium.com/wantedly-engineering/moving-from-react-native-to-kotlin-multiplatform-292c7569692">Multiplatform Mobile</a>, not for JS and backend, our summary on JS and server-side events might be slightly off. However, we can say that we enjoyed all the talks and our personal favourites are “Looking into the Future”, “Coroutines Updates”, and “It’s time for Kotlin Multiplatform Mobile”.</p><p>Through this event, we learnt that the community plays a big part in shaping the language, and Kotlin’s conciseness and expressiveness made a lot of developers try (and stay with) the language. We hope that there will be more events like this in the future and maybe we can attend those events in person.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a4937803f256" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/kotlin-1-4-online-event-recap-a4937803f256">Kotlin 1.4 Online Event Recap</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moving from React Native to Kotlin Multiplatform]]></title>
            <link>https://medium.com/wantedly-engineering/moving-from-react-native-to-kotlin-multiplatform-292c7569692?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/292c7569692</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[react-native]]></category>
            <dc:creator><![CDATA[Malvin Sutanto]]></dc:creator>
            <pubDate>Mon, 05 Oct 2020 09:42:11 GMT</pubDate>
            <atom:updated>2020-10-05T09:42:11.508Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aF-waCdi8MDJ-bUo" /><figcaption>Photo by <a href="https://unsplash.com/@bantersnaps?utm_source=medium&amp;utm_medium=referral">bantersnaps</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><blockquote>This article was originally written in Japanese by my colleague <a href="https://medium.com/u/19b77e1b0bba">久保出雅俊</a> . You can find the original article at <a href="https://www.wantedly.com/companies/wantedly/post_articles/282562">https://www.wantedly.com/companies/wantedly/post_articles/282562</a></blockquote><p>In this article, we are going to talk about the reasoning behind our decision to adopt, and move away from React Native, and why we decided to adopt Kotlin Multiplatform in our mobile apps.</p><h3>Why we adopted React Native in the first place</h3><p>In 2018, we decided to do a full rewrite of our iOS application. This required a huge amount of effort and time that involves almost everybody in the mobile app team.</p><p>In parallel with the iOS app rewrite, we also wanted to introduce a new feature called “Discover”, where users can find various interesting contents inside our platform. However, since most of the mobile engineers were focusing on rewriting the application, we decided to introduce React Native to allow web engineers to work on this feature separately. “Discover” feature was initially introduced in the old version of our iOS app and our data showed that it performed admirably, hence we decided to bring it into the new iOS app as well.</p><p>Subsequently, we introduced “Discover” to our Android app, and our apps’ architecture roughly looked like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G2t4bXC50Ge69rnkSpUflw.png" /><figcaption>iOS and Android app architecture with shared React Native component.</figcaption></figure><h3>Our issues with React Native</h3><p><em>This list of issues were issues that are specific to us and do not represent issues with React Native as a whole.</em></p><h4>Lack of active maintainers</h4><p>Once the “Discover” feature was stable, the web engineers that were originally involved with the React Native development went back to work on the web platform, and nobody was actively maintaining the React Native repository.</p><p>Updating Xcode or other build tools became an issue because most of the time we need to update React Native toolings first. These updates sometimes were not straightforward and ended up costing us a lot more effort than necessary. This process of updating the build tools quickly became a major pain point for mobile engineers.</p><h4>Differences in technology</h4><p>Even when the mobile engineers decided to maintain the React Native repository themselves, they lack the skill and knowledge of the inner workings of React Native, and learning these skills is not cheap.</p><p>Also with the subsequent introduction of React Native component into our Android app, it’s hard to work with React Native without the knowledge of both iOS and Android platform, as each OS works differently.</p><h4>Discrepancies in the UI/UX</h4><p>There were some elements that only work on the iOS platform, and when we decided to integrate “Discover” feature into our Android app, we had to add a condition to hide or remove said elements. This created a discrepancy of feature between both apps.</p><p>There were also some minor differences between native UI elements and React Native UI elements. Hence, UI elements that exist in both native and React Native might look similar but have some disparity (e.g., touch feedback on touchable elements in Android app).</p><h4>Productivity issues</h4><p>With the increase of build tools around the app, build time became excessively slow and complicated.</p><h3>Removing React Native</h3><p>For quite some time, these issues remained unsolved. But recently, we got an opportunity to work on the React Native component itself, thus we decided to also evaluate the current implementation and plan the future of our app. While it is true that React Native has really nice features such as hot reload and easy to implement UI framework, we think that our current issues outweigh its benefits and potential, hence, we decided to quit React Native altogether.</p><h3>Kotlin Multiplatform</h3><p>With regards to the engineers’ productivity of the mobile platform as a whole, duplicate implementations (like network API calls) that are required on both iOS and Android are very costly, and sometimes, can have subtle differences. We wanted to tackle these issues and we thought cross-platform technology might be a good fit, so we decided to evaluate Kotlin Multiplatform and introduce it into our app.</p><p><a href="https://kotlinlang.org/docs/reference/multiplatform.html">Kotlin Multiplatform</a> (Kotlin MPP), is a Kotlin language feature that allows you to share code that is written in Kotlin between multiple platforms.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/944/1*WJu1I9PO_oyu1E6Zx-MOPA.png" /><figcaption>How Kotlin Multiplatform works. <a href="https://kotlinlang.org/docs/reference/multiplatform.html">https://kotlinlang.org/docs/reference/multiplatform.html</a></figcaption></figure><ul><li>Code that is written using Common Kotlin will be recompiled to native code on each target platform.</li><li>Allows you to write native code of each platform in Kotlin.</li><li>Access platform-specific APIs using the expected/actual declarations.</li></ul><p><a href="https://kotlinlang.org/lp/mobile/">Kotlin Multiplatform Mobile</a> (KMM) is an official site that showcases usages of MPP for mobile applications, and in it, there’s an example on how we can make use of MPP to share business logic between different platforms but still makes use of the native UI elements. We decided to follow this example and implement our MPP architecture as such.</p><h3>MPP architecture</h3><p>In our iOS App, we make use of <a href="https://github.com/ReactorKit/ReactorKit">ReactorKit</a>, a flux-like Swift application framework. As for Android, we also make use of a similar flux-like framework that we built internally with Android’s ViewModel. Naturally, in the case of our MPP implementation, we wanted to adopt a similar model with ReactorKit and flux-like architecture in an effort to reduce the cost of learning and implementing a new architecture.</p><p>To achieve that we make use of the following libraries:</p><ul><li>Kotlinx Coroutines.</li><li>Ktor Client.</li><li>Kotlinx Serialization.</li><li>SQLDelight for application’s single source of truth.</li></ul><p>… and a few other libraries. It has a remarkably similar architecture to <a href="https://github.com/touchlab/KaMPKit">KaMPKit</a> implementation that is showcased inside KMM.</p><h3>Mobile app architecture</h3><p>At the time of this writing, we just finished removing React Native component from our Android app and this is the current architecture of both of the apps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lec-kr0n3ALHRwzmdVPrgQ.png" /><figcaption>Current iOS and Android app architecture.</figcaption></figure><p>With the implementation of MPP and native UI element, we saw a tremendous PV improvement of 46% on one of our screen that was originally implemented with a WebView. Furthermore, it has a 50% smaller app download size.</p><p>iOS adoption of MPP is currently underway, and in the future, we hope to broaden the application of MPP to improve the overall productivity of all mobile engineers in the company.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/790/1*4XqoX5BxGYMK2bAfwEdv2g.png" /><figcaption>Future iOS and Android app architecture.</figcaption></figure><h3>Pros and cons of MPP</h3><p>Here are the pros and cons that we encountered when working with the current iteration of MPP.</p><h4>Pros</h4><ul><li>From the point of view of an Android app, MPP module is just like another Kotlin module, and introducing an MPP module to an existing Android project was very smooth. We didn’t notice any difference when moving the business logic from Android to MPP.</li><li>When the interface of an MPP component has been decided, implementing UI in Android and business logic in MPP can be done in parallel by 2 different engineers.</li><li>Since there is no “UI” element in MPP, you are forced to write unit tests to validate your code. This naturally yields a high code coverage.</li></ul><h4>Cons</h4><ul><li>MPP is still an experimental feature. It works, but you still have to take account of that fact.</li><li>Different memory management model in iOS (Kotlin/Native). In Kotlin/Native code, there’s a concept of <a href="https://kotlinlang.org/docs/reference/native/immutability.html">immutability</a> that does not apply to Kotlin/JVM code. When working with coroutines, you’ll often encounter InvalidMutabilityException if you’re not careful. There are <a href="https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/">plans</a> to change this model, but in the meantime, you have to be careful of which thread your code is invoked in.</li><li>From the point of view of an iOS app, MPP doesn’t look very different from React Native implementation. However, UI components, which usually requires a deep knowledge of each platform to implement, will not be shared and you won’t expect differences as drastic as React Native. There’s also an official guide on <a href="https://kotlinlang.org/docs/mobile/introduce-your-team-to-kmm.html#prepare-for-questions">how to prepare your team for MPP</a>, as long as you follow this guide, you should be able to create a component that is easy to maintain for all engineers.</li></ul><h3>Summary</h3><p>We have talked about why we adopted and quit React Native implementation in our mobile app, and also why we decided to adopt Kotlin Multiplatform. There is still some work to be done to remove React Native completely from our apps, but we hope that with our experience and learnings with React Native, we can continue to examine different new cross-platform technologies, such as MPP.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=292c7569692" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/moving-from-react-native-to-kotlin-multiplatform-292c7569692">Moving from React Native to Kotlin Multiplatform</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deleting unused code with Ruby-on-Rails and Git]]></title>
            <link>https://medium.com/wantedly-engineering/deleting-unused-code-with-ruby-on-rails-and-git-d5268bb83647?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/d5268bb83647</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Camille Drapier]]></dc:creator>
            <pubDate>Wed, 16 Sep 2020 10:27:53 GMT</pubDate>
            <atom:updated>2020-09-16T10:27:53.653Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-fOLCqCakgM4QhfF" /><figcaption>Photo by <a href="https://unsplash.com/@davideibiza?utm_source=medium&amp;utm_medium=referral">Davide Baraldi</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>One of my recent projects was to try to identify unused pieces of code and remove them safely. This can be quite a difficult task in Ruby, as there is little way to be certain what calls what.</p><p>A solution to this problem of identifying such code is to record all calls for a relatively long period on your production server, and then see what hasn’t been called. For this purpose, we developed a custom solution on top of the <a href="https://github.com/riseshia/oneshot_coverage">oneshot_coverage</a> gem; but after taking a step back, I think I would recommend the <a href="https://github.com/danmayer/coverband">coverband</a> gem for this purpose (it has a polished UI, is maintained, based on standard tools such as Redis, etc.).</p><p>Assuming you now have a list of suspiciously unused methods, you can either be satisfied with that (you should probably not), delete them at once, and move on; or try to dig a bit more. This is the topic I would like to cover in this post, and by reading it, I hope you will learn more about this process and the tools I used to follow said process.</p><p>If you have numerous tests and excellent test coverage, this whole process might seem excessive to you, as you would simply say that if the tests continue passing after deleting the code, then everything is good. You would be right, but things are not always as simple!</p><p>I will try to use a real example from our application to illustrate my point. Here, I found a method called related_posts in our PostArticle model that seemed to be unused for a while on our production platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*ewDSPRddgbNKHS7R2LbxvA.png" /><figcaption>The method is loaded by the code loader (green), but the code inside the method is never executed, which means no call for this method was recorded.</figcaption></figure><h3>Current references</h3><p>The very first action you should do, before deleting the method blindly, is checking if the method name appears anywhere else in your code or potential dependents of your code. For a simple project, you can just search the method’s name in your favourite IDE, you can also use <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> in a folder containing your project and its dependents. This first check is fundamental because your method could simply be called by some other code but is not for some understandable reasons (very rarely used feature, randomness, code specific to some platform, code not actively used yet, problem in the coverage tool, etc.).</p><p>One thing you want to be aware of at this point is because there could be some meta-programming calling your method, you might want to check for some sub-string of your method name. Usually in these cases, your method would have some kind of resemblance pattern with other methods declared close by, so you would want to search for the discriminatory part of the method name. For example, if you have two methods process_carrots and process_potatoes , you might have somewhere a code calling .send(&quot;process_#{food_item}&quot;) inside some sort of iteration over the food items. In that case, you would like to know whether a particular food item is still called or not, and thus you would search for that name (i.e: carrots or potatoes respectively).</p><p>In my case, there are many matches, so I need to check manually for each of them if they are related, and while most of them are not, I noticed another model (that inherit from the same base model) that has a method with the same name. I checked with our coverage tool, and as suspected it is also apparently unused.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/732/1*daibk9IhMudrxaIObbyT1w.png" /><figcaption>Checking references with VSCode</figcaption></figure><p>Note that in this case, checking for dependent projects is not necessary as our models are not used outside of our project. For the sake of completeness, here is the command assuming you are in a folder containing all your relevant projects.</p><pre>rg &quot;related_posts&quot;</pre><h3>The method’s origin</h3><p>The second thing you should ask yourself is when was the method implemented, and were there any hints towards how it was used then. It will give you other hints of code you might want to delete (that is linked to the feature, but that the production-code-coverage tools cannot detect because it is outside of its scope, for example: views, styles, JS, scripts, configurations, etc.). It might also give you more information on how the code is called and ultimately make yourself confident that you are deleting something that is indeed unused.</p><p>To do this, I like using the GitLense extension for VSCode, which gives me a link to the commit on GitHub, but you can also do a normal git blame on your file. If the methods you are looking at have been modified since they have been introduced you might need to chain git blames for finding their original commit (You could also use git log at this point).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/966/1*cGve5ua72gIFx4NJryyZ4A.png" /><figcaption>Using GitLense, hover the grayed text at the right of the code to trigger a toolbox with more options, then click on the top-right pointing arrow to open the commit on your git service (here GitHub)</figcaption></figure><pre>git blame app/models/post_article.rb -L 54,54</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-1o_NDeOQ7Xjwxa5qtAVpA.png" /><figcaption>Using git blame</figcaption></figure><p>If I have a &quot;commit hash&quot; from running command-line git commande, I usually just copy it, insert it in a GitHub URL to show commits, and from there retrieve the PR, but there might be better approaches depending on the tools you use.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zm2NSuQr45N_ik6cvsPeBA.png" /><figcaption>Using GitHub to display a commit and retrieve the related Pull Request</figcaption></figure><p>In this case, the method was not originally introduced by this commit but was transferred from the parent model to the child models with a slightly different implementation. At this point there are two things I need to do:</p><ul><li>Check the original branch where the code was introduced, and see how it was called.</li><li>Check the branch where the method was split between models and see if the calling code was also changed.</li></ul><p>In this case, the code was only originally used in one place, and this calling code was changed in the “split branch” at the same time as the related_posts method itself.*</p><p>One additional thing that should be checked is whether there could be some additional resources (JS, CSS, etc.) to delete: resources that were introduced at the same time than these prior modifications that might also not be used anymore. In the present case, there were none I could think of while reading the original code.</p><h3>Tracking the calling code</h3><p>Now that we know how the code was introduced, I would like to know more about how the calling code was changed for the related_posts method which seems not to be called anymore. Was it changed to meta-programming at some point? Was the code just removed and the underlying method forgotten? Is this code still called, but somehow I missed it with my checks so far?</p><p>By checking the “splitting branch” code changes, I know that the method I want to delete was called in a file called _post_footer.haml.html . But unfortunately, this file does not exist any more. To know when the code was removed from that file I use the following command (note that this also works if the files still exists)</p><pre>git log -S &quot;related_posts&quot; -- app/views/post_articles/_post_footer.html.haml</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QDs-ldVqFH6qP-5VlZl1Uw.png" /><figcaption>Use of “git log -S”</figcaption></figure><p>I have seen this command fail a few times for deleted files. In that case, I fallback to a broader command that will give all commits to the file from the most recent commit. Usually, it is then quite fast to find the commit that removed the call to the code you want to delete.</p><pre>git log -p -- app/views/post_articles/_post_footer.html.haml</pre><p>By looking at the branch where the commit 995ed4d7 happened (same process as when we got the commit hash before), I can see that the related feature was in fact refactored: the implementation was changed and the original method was simply not deleted.</p><p>In this particular case, I found out that another call to the original method was deleted at the same time in that last “refactoring branch”, so I get a bit suspicious (but not too much!), check quickly when it was introduced again and ensure there were no other calls that were left unchecked. And finally, I am quite confident deleting this code is now safe*.</p><p>To sum up, in this archaeological journey we understood:</p><ul><li>That the code appears not to be used.</li><li>For what reasons it was introduced and modified at some point.</li><li>When it was not called any more and by what it was replaced.</li></ul><p>So we can create a “delete commit” for this code, create a PR where we sum-up our findings, and have/display the confidence* that it is a safe/legitimate change.</p><p>*NB: This is by no mean a way to be absolutely certain that your code is not called somewhere by some weird meta-programming. It is always possible that such a meta-programming code to be introduced in between the creation/use of the method and it’s apparent “un-call”; I do not believe there is a way to be absolutely sure about this. Even if you would git log the whole repository with a discriminatory part of the method name, it could still be possible not to find something that you are about to break. In the end, this is a compromise: with the help of automated testing, reviews, production code coverage, etc, to be fairly confident the deletion is safe.</p><p>I hope you have learned a bit more about ruby and git tools you can use to try deleting your old code in a confident manner. Some might argue that this is too complex and time-consuming to just delete some old unused code, and they might be right, but establishing this process can save your company some time: during the review process for example, if you show that you did investigate the matter thoughtfully and thus the reviewer can check it easily without spending too much of their own time. Also, being able to detect additional potential code/resources that can be deleted can save you time in the long run.</p><p>As for me, during the course of my code-deletion project, I have successfully deleted thousands of lines of ruby (and other thousands of JS, CSS, translations, configuration) by following this method without breaking anything (so far!), so I wanted to share this method as I haven’t seen much literature on the subject. 🤗</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d5268bb83647" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/deleting-unused-code-with-ruby-on-rails-and-git-d5268bb83647">Deleting unused code with Ruby-on-Rails and Git</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting up-to-date Ruby versions with distro’s rbenv]]></title>
            <link>https://medium.com/wantedly-engineering/getting-up-to-date-ruby-versions-with-distros-rbenv-c757524bf5db?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/c757524bf5db</guid>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Masaki Hara]]></dc:creator>
            <pubDate>Wed, 18 Dec 2019 06:37:29 GMT</pubDate>
            <atom:updated>2019-12-18T06:37:28.997Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7sUq7stpGo98UnFmfUp_bA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@marcojodoin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Marc-Olivier Jodoin</a> on <a href="https://unsplash.com/s/photos/rush-towards?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>You know <a href="https://www.ruby-lang.org/en/news/2019/12/17/ruby-2-7-0-rc1-released/">Ruby 2.7.0-rc1 has been released</a>? It’s awesome!</p><p>However, there’s an annoying thing I face every time a new version of Ruby is released: I had to wait for several days to get an up-to-date distro version of ruby-build. Especially, homebrew users will probably agree with me.</p><p>I’ve recently found a clever solution to this: <strong>manually downloading a manifest</strong>.</p><p>In ruby-build, we simply have one manifest per one version, and we can <a href="https://github.com/rbenv/ruby-build/tree/master/share/ruby-build">list them on GitHub</a>. Download the version you want from there:</p><pre>wget &#39;https://raw.githubusercontent.com/rbenv/ruby-build/master/share/ruby-build/2.7.0-rc1&#39;<br><br># Alternatively, with curl<br>curl -O &#39;https://raw.githubusercontent.com/rbenv/ruby-build/master/share/ruby-build/2.7.0-rc1&#39;</pre><p>You can pass a filename to rbenv install, which just looks like a normal way of specifying a version:</p><pre>rbenv install 2.7.0-rc1  # points to a file called ./2.7.0-rc1 </pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c757524bf5db" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/getting-up-to-date-ruby-versions-with-distros-rbenv-c757524bf5db">Getting up-to-date Ruby versions with distro’s rbenv</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing Android Multi-module Project with Gradle Plugin and Kotlin]]></title>
            <link>https://medium.com/wantedly-engineering/managing-android-multi-module-project-with-gradle-plugin-and-kotlin-4fcc126e7e49?source=rss----ef38f0cd006f---4</link>
            <guid isPermaLink="false">https://medium.com/p/4fcc126e7e49</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[multi-module-project]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Malvin Sutanto]]></dc:creator>
            <pubDate>Mon, 09 Dec 2019 02:00:28 GMT</pubDate>
            <atom:updated>2019-12-09T02:00:28.443Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uVeajESNoENDHwaP" /><figcaption>Photo by <a href="https://unsplash.com/@jankolar?utm_source=medium&amp;utm_medium=referral">Jan Kolar</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Lately, more and more teams are adopting a multi-module setup for their Android projects. The number of modules for each project can vary, and some projects can even have more than a hundred modules. Managing these modules involves a lot of copying and sharing common settings and codes between <strong>build.gradle</strong> files. Some teams use separate Gradle files and apply from: statements to try to solve this issue. However, it is not very scalable once you exceed a certain number of files.</p><p>In this post, I will introduce how we can make use of a custom Gradle plugin to help us manage Android project modules. We will also provide some customizability into our plugin so that each module can customize the build parameters. Then we will also see how we can integrate other plugins, e.g., Jacoco, from our multi-module plugin. This is how our <strong>build.gradle.kts</strong> file will roughly look like at the end.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/029afed94e58e2b0833866d5058ecf51/href">https://medium.com/media/029afed94e58e2b0833866d5058ecf51/href</a></iframe><p>Compare this with a typical <strong>build.gradle</strong> file that roughly achieves the same thing:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/717caaeb20aa4de1a945ef9fd578f500/href">https://medium.com/media/717caaeb20aa4de1a945ef9fd578f500/href</a></iframe><blockquote>This implementation is made using Android plugin 3.6.0-beta04 and Gradle 6.0.1</blockquote><h3>Gradle Plugin with Kotlin</h3><p>Let’s look at some examples of what the plugin can do for each module:</p><ul><li>Applies important and required plugins, such as kotlin-android and kotlin-android-extensions.</li><li>Configures common android module settings for a module (minSdkVersion, targetSdkVersion, etc.) but still allows each module to override any settings.</li><li>Specifies the default proguard files.</li><li>Enables supported Java 8 features across all modules.</li><li>Adds required dependencies to a module, such as kotlin-stdlib</li><li>Configures Jacoco for each module and allow custom configuration block that supports both kotlin and groovy script.</li></ul><p>Now, before creating the Gradle plugin, first, we need to create a <strong>buildSrc/build.gradle.kts</strong> file in the root folder and add the required dependencies.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/32fed9c520e1410bd3fccd811601fd6c/href">https://medium.com/media/32fed9c520e1410bd3fccd811601fd6c/href</a></iframe><blockquote>Since we declared the dependency to both android-gradle-plugin and kotlin-gradle-plugin in this file, we can remove the classpath dependencies to both artifacts from our <strong>root/build.gradle</strong>.</blockquote><p>And then in <strong>buildSrc/src/main/kotlin</strong>, we will create the plugin class that implements the Plugin interface.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f1ca428fa8e5a36ed7b1103da971b971/href">https://medium.com/media/f1ca428fa8e5a36ed7b1103da971b971/href</a></iframe><p>The apply method will be invoked in the <a href="https://docs.gradle.org/current/userguide/build_lifecycle.html"><strong>project evaluation </strong>step of Gradle build lifecycle</a> and it will be the entry point into our plugin.</p><h4>Apply Other Plugins</h4><p>Since we want our plugin to automatically apply kotlin-android and kotlin-android-extensions to our modules, we simply add apply plugin statements to our project like so:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eedada7d60b55c1d67cab1835fa4406e/href">https://medium.com/media/eedada7d60b55c1d67cab1835fa4406e/href</a></iframe><h4>Configure Common Android Settings</h4><p>There are many settings that can be shared between an application, library, test, and feature module. Since we want this plugin to manage the configuration between all type of modules, we are going to make use of a class named BaseExtension from the Android Gradle plugin.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/63964f55f75ecc60fb61fb04426986a5/href">https://medium.com/media/63964f55f75ecc60fb61fb04426986a5/href</a></iframe><p>You can also apply custom configuration for a specific type of module by using the respective extension type, e.g., AppExtension for an application module.</p><h4>Default Proguard Files</h4><p>We also want the plugin to automatically pick-up the correct proguard files for each module’s <strong>release</strong> configuration, be it for an application or a library module.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/455cbfe595c8252f7c3dbfa72a057c0a/href">https://medium.com/media/455cbfe595c8252f7c3dbfa72a057c0a/href</a></iframe><h4>Enable Supported Java 8 Features</h4><p>To enable Java 8 features for each module, we need to set the sourceCompatibility, targetCompability, and kotlinOptions.jvmTarget. Setting the jvmTarget for Kotlin is not that straightforward, but it’s not that difficult either.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/05eae88789d235c81a2c3ddf8cb424e7/href">https://medium.com/media/05eae88789d235c81a2c3ddf8cb424e7/href</a></iframe><h4>Adding Required Dependencies</h4><p>Sometimes, there are libraries that we need across different modules. Some libraries that come to mind arekotlin-std-lib, androidx-core, and testing libraries. Adding the dependencies to them through the plugin is very simple:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/70465b3697b45df7f5c364862f41e9e1/href">https://medium.com/media/70465b3697b45df7f5c364862f41e9e1/href</a></iframe><h4>Jacoco and Custom Settings Through Extension</h4><p>Setting up a Jacoco task for each module can be quite complicated since you have to write and register the task on each module. Our plugin provides a good opportunity to set-up a simple and unified way to introduce coverage report for each module. As an example, let’s say we want to have the following settings that we want each module to specify:</p><ol><li>Whether to generate the coverage report for this module.</li><li>File pattern for classes to be excluded from each module’s coverage report.</li></ol><pre>android <strong>{ ...</strong> <strong>}</strong></pre><pre>myOptions <strong>{</strong><br>  jacoco <strong>{</strong><br>    isEnabled = true<br>    excludes<strong>(</strong><br>      &quot;file/pattern/to**Exclude**&quot;<br>    <strong>)</strong><br>  <strong>}</strong><br><strong>}</strong></pre><p>To allow custom configuration for the plugin, we first need to create an <strong>extension</strong> class that declares the configurable parameters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ea421a36f20ac5618398d4b4bdd80bd/href">https://medium.com/media/6ea421a36f20ac5618398d4b4bdd80bd/href</a></iframe><p>For it to work with Gradle API, we need to make our classes and members open. Also, we’re going to use Action class from Gradle API so that it will play nicely with Groovy scripts. It is a good idea to initialize the <strong>extensions</strong> and <strong>options</strong> to some sane defaults in order to minimize the required configurations in each module. For example here, code coverage is enabled by default on all modules, but each individual module can disable it through its <strong>build.gradle</strong> file.</p><p>We will then register the extension in our plugin to make the configuration block available in the build script:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/504ddf2584f655824299dafa464af18d/href">https://medium.com/media/504ddf2584f655824299dafa464af18d/href</a></iframe><p>And to read the configuration values that have been set by each module, we will have to use Project.afterEvaluate block since the values are set during the evaluation step of our modules.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f15e63899e7bee3eec0b0d64b8d831d5/href">https://medium.com/media/f15e63899e7bee3eec0b0d64b8d831d5/href</a></iframe><p>If everything is set up correctly, we will now be able to invoke :module:jacocoDebugReport (or jacocoFlavorDebugReport) Gradle task that will run the unit tests and then generate a coverage report for us.</p><h4>Final Steps</h4><p>Now that the Gradle plugin is completed, we can then register the plugin using gradlePlugin block in <strong>buildSrc/build.gradle.kts</strong> so that it is available in our module’s <strong>build.gradle</strong> file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/89d7b07ee077f51119172a38b75f1a36/href">https://medium.com/media/89d7b07ee077f51119172a38b75f1a36/href</a></iframe><p>Finally, we apply the plugin into each module and add overrides or custom configurations as we see fit.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/029afed94e58e2b0833866d5058ecf51/href">https://medium.com/media/029afed94e58e2b0833866d5058ecf51/href</a></iframe><h4>Other Benefits of Using Gradle Plugin</h4><p>With this Gradle plugin approach, our build scripts are now relatively shorter and easier to migrate to Kotlin DSL. Also, Gradle plugin can be applied to both Groovy and Kotlin DSL, allowing us to migrate our build scripts module by module based on our needs instead of converting all of them at once.</p><p>After the <strong>build.gradle</strong> files have been migrated into <strong>kts</strong>, we can make use of extension functions (declared in <strong>buildSrc/src/main/kotlin</strong>) to simplify our build files even further, like so:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e201841a0c5166fa131df9bee2ac8d39/href">https://medium.com/media/e201841a0c5166fa131df9bee2ac8d39/href</a></iframe><p>Managing a multi-module project with a custom Gradle plugin is good for common Gradle script configurations that need to be applied across different modules, making <strong>build.gradle</strong> scripts simpler and more maintainable. It also provides a single location where we can define and manage common parameters like targetSdkVersion and minimumSdkVersion while also allowing each module to define its own overrides.</p><p>Custom Gradle plugin also provides an opportunity for us to extend the capability of a Gradle script through custom extensions and Kotlin DSL. Allowing us to create a more complex build configuration while still keeping it easy to read. Moreover, with Kotlin DSL, we can further simplify our build script with extension functions.</p><p>You can find the sample implementation of this plugin in the following repository:</p><p><a href="https://github.com/malvinstn/gradle-plugin-android-multi-module">malvinstn/gradle-plugin-android-multi-module</a></p><p>Thank you for reading this post. I hope you found it useful!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4fcc126e7e49" width="1" height="1" alt=""><hr><p><a href="https://medium.com/wantedly-engineering/managing-android-multi-module-project-with-gradle-plugin-and-kotlin-4fcc126e7e49">Managing Android Multi-module Project with Gradle Plugin and Kotlin</a> was originally published in <a href="https://medium.com/wantedly-engineering">Wantedly Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>