<?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[Tokopedia Engineering - Medium]]></title>
        <description><![CDATA[Story from people who build Tokopedia - Medium]]></description>
        <link>https://medium.com/tokopedia-engineering?source=rss----81e70bf054ed---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Tokopedia Engineering - Medium</title>
            <link>https://medium.com/tokopedia-engineering?source=rss----81e70bf054ed---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 00:54:58 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/tokopedia-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[How a Bubble Chat Increases Up to 90% of Seller’s Read Speed]]></title>
            <link>https://medium.com/tokopedia-engineering/how-a-bubble-chat-increases-up-to-90-of-sellers-read-speed-29f96ff907ed?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/29f96ff907ed</guid>
            <category><![CDATA[chat]]></category>
            <category><![CDATA[user-experience]]></category>
            <category><![CDATA[notifications]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-development]]></category>
            <dc:creator><![CDATA[Rizqi Aryansa]]></dc:creator>
            <pubDate>Tue, 27 Feb 2024 04:19:38 GMT</pubDate>
            <atom:updated>2024-02-27T04:19:38.056Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hd3CK8N8wP9uZt6iy9FCEg.png" /></figure><h3>Why should Tokopedia have Bubbles?</h3><p>In Tokopedia, we believe that communication between sellers and buyers are important to ensure the transaction will go smoothly. Like many e-commerce platforms out there, the communication in Tokopedia between sellers and buyers is done mainly using a chat room.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/270/1*1CcBhqZ4aVNtlD-_Mmn9IA.gif" /><figcaption>Credits: <a href="https://giphy.com/gifs/multitasking-hHMEpcih7f1LalTOfj">Best Friends Coffee GIF</a></figcaption></figure><p>On a daily basis, Seller receives a hundred chats. While also they are not 100 percent dedicating their time to replying to chat as they are also doing other work instead. They could be opening the other apps instead of Tokopedia Seller, browsing, watching movies, or even playing games on their phone while a promising buyer was asking for their products in the chat.</p><p>While quick responses will make sellers more trustworthy. It will also increase the probability of the transaction being through as a longer response time can make buyers look for other sellers instead. This is a pain point for sellers since their performance also relies on their response time.</p><h3>Goals</h3><p>So to avoid any miscommunication between Sellers and their potential buyers, Sellers should be able to:</p><ul><li>Access the most urgent chat faster</li><li>Respond faster, Sellers need to be able to access the chatroom faster. So that more incoming orders can be generated</li></ul><p>Here, our team has decided to implement the Bubble Chat features in Tokopedia SellerApp. It enables chats to appear in a pop-up Bubble over other apps’ screens, making it easier for Sellers to see incoming chats and making it easier for them to access the chatroom; hence reply faster.</p><p>As Android Developers explored the opportunity to solve the problem, our research showed that Google has released an API that could help with those use cases. We finally decided to use <strong>Bubbles </strong>API.</p><h3>What is Bubbles? And how is its behavior?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/358/1*mOtaJ1IaSYIe1zlgY7lrhw.gif" /><figcaption>Bubble Chat Demo</figcaption></figure><p>Before we explore the implementation, it’s crucial to understand the overall behavior of this Bubbles feature. This will help us identify the aspects that can be advantageous for us and align with our needs in addressing the initial issues (Sellers and Buyers). Additionally, it allows us to prepare for the potential user or technical challenges by implementing necessary mitigation strategies.</p><p>Bubbles API started in Android 11 or later OS versions, previously developed by the Android team in the developer preview for Android 10. Bubbles are integrated into the Notification system with several user behaviors, including:</p><ul><li>Bubbles can be expanded to reveal app functionality and information or collapsed when not in use.</li><li>Bubbles float on top of other apps’ content, and clicking the shortcut icon in the push notification bar makes them appear.</li><li>The most recent chat bubbles appear on the far left.</li><li>Bubbles only display 5 shortcuts; additional ones are shown by clicking the plus icon, depending on the OS version.</li><li>Android provides one floating Bubble for multiple applications that support Bubbles.</li></ul><h3>Bubbles Implementation Journey on Tokopedia Seller App</h3><p>Alright, In this section we would like to explain in general how to implement notification Bubbles. The Bubbles are created into the Notification API, and according to <a href="https://developer.android.com/develop/ui/views/notifications/bubbles">Bubble Documentation</a>, Bubble API has three components that have to be implemented including the <strong>MessagingStyle</strong>, <strong>SharingShortcut</strong>, and <strong>BubbleMetaData</strong>.</p><p><strong>Prerequisites</strong></p><ul><li>For the <strong>targetSdkVersion</strong>, you should set Android 11 (Android API 30) or later.</li><li>The minimum <strong>AndroidX.core</strong> version required for Bubble support is 1.5.0. However, it is recommended to use version 1.7.0 or the latest version, according to the <a href="https://developer.android.com/jetpack/androidx/releases/core">Androidx.core</a> documentation.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*JRshVimAxM5Zf_WY0mzxrg.png" /><figcaption>Bubbles Components</figcaption></figure><h4><strong>Messaging Styles</strong></h4><p>According to the <a href="https://developer.android.com/develop/ui/views/notifications/build-notification#messaging-best-practices">Notification documentation</a>, Starting Android 7.0 to implement best practices within the Notification, it’s recommended to use <strong>NotificationCompat.MessagingStyle</strong>. This approach allows for more customization, including conversational titles, additional messages, and custom content views (e.g., avatars). There are many benefits to using <a href="https://developer.android.com/reference/android/app/Notification.MessagingStyle">MessagingStyle</a> than the old approach using <strong>BigTextStyle</strong> or <strong>InboxStyle</strong> itself.</p><h4><strong>Sharing Shortcut</strong></h4><p>After implementing messaging styles, we continued to implement the Sharing Shortcut. Notifications must be associated with a reference to a long-lived shortcut, enabling straightforward content sharing in conversations directly from the published notification.</p><h4><strong>BubbleMetaData</strong></h4><p>BubbleMetaData is one of the most important to implement Bubbles, The BubbleMetaData class has the responsibility for launching the intent, setting the Bubble height layout, and displaying the avatar based on the icon parameters including the width and height of the avatar.</p><h4><strong>Person</strong></h4><p>Messaging Styles and Sharing Shortcuts should implement the Person class. Person class is used to associate a sender or recipient with messages within notifications. The Person class will help with the representation of messages by displaying the sender’s name and an adaptive Icon as a Bubble avatar.</p><h4><strong>Icon</strong></h4><p>The icon in the Bubbles is one of the most important to handle your Bubbles to ensure delivering bubbles features with a seamless experience. Since your Bubbles avatar icon comes from the Backend team as an image URL, you need to convert the image URL first to Bitmap in the IconCompat class. In the past, we learned something from this conversion process, if you’re not handling it properly the notification or Bubbles will crash or it will not display to the users.</p><p>How to prevent the crash or issue? When the process of converting the image URL to Bitmap, we need a few seconds to set the threshold time and make sure the notification is not delaying the process of converting the image URL to Bitmap. In case the conversion fails, you should set the default avatar to prevent a broken user’s experience.</p><p>Additionally, to prevent repeated requests for the same image, we utilize a caching mechanism to improve performance.</p><p>For full implementation of Bubbles can be read on the <a href="https://github.com/android/user-interface-samples/tree/main/People">sample app</a>.</p><h3>How are Sellers aware of this Bubbles feature?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/362/1*vdkuJVf5YPTvlAcZTqJ0Lw.gif" /><figcaption>Bubble Chat awareness flow for Seller</figcaption></figure><p>Before we get into explaining the Monitoring Bubbles, we had a challenge: how do we let sellers know we got this cool new feature that could really help them out?</p><p>Currently, the Bubbles entry point is built only at the right bottom of incoming notification and it’s highly and definitely skippable for some users. Even me and my team don’t know how to access Bubbles easily.</p><p>Our sellers have diverse profiles, and not all of them may be familiar with the Bubbles feature. To address this, we collaborated with the Product, Product Design, and Data teams to boost awareness. Our objective is to introduce sellers to Bubbles by implementing a ticker in the Seller chat list and the Seller Chat Settings. The objective is to introduce how to use the Bubbles to our sellers such as implementing the ticker in the Seller Chat list and also in the Seller Chat Setting.</p><blockquote><strong>Impacts</strong>: The feature’s awareness initiative has generated positive results, with an adoption rate in the thousands, showcasing its effectiveness.</blockquote><h3>Monitoring the Bubbles by implementing tracker</h3><p>As Android developers, implementing a chat bubbles is not just about getting things done; it’s crucial to monitor the performance of this bubbles feature. Therefore, we need to implement a tracker within the chat bubbles so that we can improve performance based on user usage in the future.</p><p>To properly implement the tracker, we must understand the lifecycle of the chat bubbles and know how to distinguish between a chat bubbles screen and a regular screen. This ensures that the tracker works accurately, without any errors in its implementation within the chat bubbles.</p><h4>Bubble Lifecycle Explanation</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/851/1*fCjZ6YyTxe-HNa95lzkrJQ.png" /><figcaption>Bubble Lifecycle</figcaption></figure><p>As you know, the Bubbles launched used the Activity class and it is similar to the normal page as usual. In this part, I would like to explain the Bubbles lifecycle that we experienced when implementing the Bubbles feature. Okay, let’s say sellers have many chats from buyers, and notifications are coming.</p><p>The first time a notification is received, Bubbles has not been created yet. When notifications arrive and a seller clicks on the Bubbles chat entry point in the notification tray or popup, it triggers onCreate() and continues until the activity reaches the onResume() lifecycle.</p><p>When the next notification comes and the Bubbles have been created, but the bubble position is collapsed, the Bubbles still stay in the onStop()<strong> </strong>lifecycle with the new flag thus indicating an incoming new message which is a red dot in the Bubble avatar.</p><p><strong>How about the onStop lifecycle in the Bubble Activity?<br></strong>If your page has a back button, you should override the onBackPressed() method as usual. When the user clicks the back button, the activity will enter the onStop() lifecycle.</p><p><strong>How do I destroy the Bubble activity?<br></strong>As you may know, when used programmatically, we simply invoke the finish() method within the Activity class, or you can use cancel(notificationId) it in the notification. Afterward, the Bubble will be destroyed or dismissed. Alternatively, the user can manually drag the Bubble to the close button at the bottom.</p><h4>How do we differentiate the Bubble Screen and the Normal Screen?</h4><p>As mentioned earlier, after understanding the bubble lifecycle, it’s also crucial to know how to distinguish between bubble screens and normal screens to properly implement trackers in a bubble.</p><p>So, the Android API library provided the flag to differentiate between Bubble Screen and Normal Screen. In the starting <a href="https://developer.android.com/jetpack/androidx/releases/core#1.7.0-alpha02">AndroidX Core 1.7.0 </a>version, we’re able to use the isLaunchedFromBubble flag from <strong>ActivityCompat</strong> to differentiate between the Bubble Screen and Normal Screen like the code below.</p><pre>val isLaunchedFromBubble = ActivityCompat.isLaunchedFromBubble(activity)</pre><h3>Mitigate Users’ Issues with Bubbles Settings</h3><h4>How do we allow Sellers to manage settings in the Bubbles Chat?</h4><p>When rolling out a new feature, it’s important to think about ensuring a smooth experience for users who may not be familiar or not comfortable with it. We discovered the Bubbles Settings page in the Android Documentation, which helps in seamlessly introducing the Bubbles feature. This page can be a lifesaver, preventing a ton of complaints, as sellers can easily disable Bubbles directly from the Bubbles settings page.</p><p>Here is an intent name you could redirect to <a href="https://developer.android.com/reference/android/provider/Settings#ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS">Bubbles settings</a> based on your app package.</p><pre>val bubbleSettingsIntent = Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*mOnDlITxiuzDwcW07SQIzg.jpeg" /><figcaption>Bubbles Settings Page</figcaption></figure><p>On the Bubble Settings Page, there are three options you can choose to manage the Bubbles. These are:</p><ul><li><strong>All Conversations can bubble</strong>: When new notifications come from the user (buyer), the notification will always appear as a Bubble. Even though the user doesn’t click on the Bubble notification entry point.</li><li><strong>Selected conversations can bubble: </strong>This preference will be selected if the user has clicked any notifications as the Bubble.</li><li><strong>Nothing can bubble: </strong>If you want all the notifications not appear as a Bubble even though the conversation has already been enabled.</li></ul><h4><strong>Can we check the Bubble setting preference programmatically?</strong></h4><p>Absolutely!! Fortunately, the Android team provided the Bubbles preference method from the <strong>NotificationManager</strong> class, allowing us to easily check whether Bubble settings were enabled or not in the Seller App. Here’s a sample code you can try:</p><pre>private val notificationManager: NotificationManager? = ContextCompat.getSystemService(context, NotificationManager::class.java)<br><br>val bubbleSettingPreference = notificationManager.bubblePreference</pre><p>Also, The <strong>NotificationManager</strong> class has provided the three constants, which can be checked along the <strong>BubblePreference</strong> method</p><ul><li>BUBBLE_PREFERENCE_ALL</li><li>BUBBLE_PREFERENCE_SELECTED</li><li>BUBBLE_PREFERENCE_NONE</li></ul><h3>Recap</h3><ul><li>Our journey was truly worth it as the Bubble Support for Sellers initiative, supported by ticker awareness, help pages accessible within Bubble Chat onboarding, mitigation documents, etc., proved to be genuinely beneficial for our end-users.</li><li>Consider providing seamless experience by converting the image URL to a Bitmap in the IconCompat class for Bubble shortcuts.</li><li>When implementing a new feature, as a developer we should think of a long-run plan, such as mitigating the risk, etc.</li></ul><p>Collaborating with tech, product, and design teams was impactful but posed challenges, especially in implementation. But it pays off! The Seller Bubble feature <strong>increased sellers’ read speed by up to 90%, leading to faster reply speeds and a notable rise in daily orders</strong>. These outcomes highlight how the feature significantly improves communication between sellers and buyers, resulting in increased daily transactions.</p><h3>Credits</h3><p>A big shout-out to our Tech counterparts: <a href="https://medium.com/@reivinoktavianus">Reivin Oktavianus</a>, <a href="https://medium.com/@kelvin.saputra">Kelvin Saputra</a>, our Android Leaders, Product, Product Design, Data, and Test Engineer Teams for making this incredible collaboration a reality! Thank you for your efforts in bringing the Bubble Support for Sellers initiative to live!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=29f96ff907ed" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/how-a-bubble-chat-increases-up-to-90-of-sellers-read-speed-29f96ff907ed">How a Bubble Chat Increases Up to 90% of Seller’s Read Speed</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Decentralize Infrastructure Knowledge through Analytical Infrastructure Encyclopedia]]></title>
            <link>https://medium.com/tokopedia-engineering/decentralize-infrastructure-knowledge-through-analytical-infrastructure-encyclopedia-82908d6e682a?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/82908d6e682a</guid>
            <category><![CDATA[cloud-services]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[multi-cloud]]></category>
            <dc:creator><![CDATA[Timothy Agustian]]></dc:creator>
            <pubDate>Fri, 08 Sep 2023 02:44:03 GMT</pubDate>
            <atom:updated>2023-09-08T02:44:03.777Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ek-4_Ezp3dKBtBkP.jpg" /></figure><h3><strong>Background</strong></h3><p>Imagine your company has many cloud resources and collaborates with multiple cloud service providers. Thousand of resources consist of multiple types of resources distributed unevenly across multiple cloud service providers and you, as the infrastructure engineer need to decentralize this management to make sure that every software engineer has enough visibility to do anything but not too much as many of these also contain confidential information. What should you do?</p><h3>Introducing the Infrastructure Encyclopedia</h3><p><strong>Analytical Infrastructure Encyclopedia</strong> or Let’s just call it <strong>Infrastructure Encyclopedia </strong>will help to unify all of the information into one single place and will help achieve the ultimate decentralization of Infrastructure Information knowledge.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qJmS0y3dEmb1s6SAzMQlxA.png" /></figure><h3>Sources and Integrated Data</h3><p>Some of the examples of sources and data that are usually integrated into the encyclopedia:</p><ol><li><strong>Cloud Services Provider<br></strong>List of Infrastructures, Machine Specification, Storage Specification, Billing, tag, Infrastructure alert and event, etc</li><li><strong>Observability Platform<br></strong>Infrastructure metrics such as CPU, Memory, and disk usage and any other custom metric as well if needed such as API total request, API latency, etc</li><li><strong>Other Platform<br></strong>Another platform that manages Engineer hierarchy, ownership, and any other platform that might elevate the usage of the encyclopedia</li></ol><h3>Layer Explanation</h3><p>The Encyclopedia is separated into 3 layers which have their functioning purposes:</p><ol><li><strong>Integration<br></strong>Which will be oriented toward integrating all of the data above across multiple sources. I think this is easily achievable by using an application service that connects with API through all of the sources. The data can be stored in the data warehouse, or maybe another analytical data store.</li><li><strong>Process<br></strong>Integrated data will be processed internally to make sure that everything is already been processed before moving to the presentation layer.</li><li><strong>Presentation<br></strong>Processed data then will be displayed into any kind of result, be it a sheet report, business insight tool such as Looker Studio, Alert, or used in another application as well</li></ol><h3>End Result</h3><ol><li><strong>Infrastructure Resources Utilization Recommendation &amp; Budgeting</strong></li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fwO2ZpadHYxZLedw2gXPrA.png" /></figure><p>As the encyclopedia already has these data:</p><ul><li>List of resources and billing from cloud services provider</li><li>Utilization usage such as CPU, Memory, Disk, etc. in the observability platform</li><li>Ownership and team hierarchy from another platform</li></ul><p>Creating a recommendation system that can give visibility to all of the engineers based on the owned resources to accelerate optimization action is a doable task alongside the visibility of the expense of each resource.<br>But Don’t forget to implement Role Based Access Control (RBAC) as this information is quite confidential if not targetted correctly.</p><p><strong>2. Infrastructure Resources at Risk Monitor</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SPSo7pcKrEk1_7ozM9Eu7g.png" /></figure><p>Some observability tools already have an agent that pushes all of the information including other installed agent versions, OS versions, etc. Combining with the similar data above, we can construct a dashboard that can show any kind of software that already reaching its End Of Life or is already at risk, making a decentralized patching much easier</p><p>3. Infrastructure Resources Summary</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UEx03DUQ0SSHPKX1e19T1w.png" /></figure><p>As we have many types of instances across cloud service providers, having visibility for all of the resources makes it easier to make any decision based on the data that we have.</p><p>And many more use cases that can be elevated through this encyclopedia</p><h3>In Conclusion</h3><p>If your company has multiple types of resources across multiple providers, Having a way to store all of the necessary information into one single platform will be beneficial as it will accelerate any kind of engineering decision-making and action. <strong>Infrastructure Encyclopedia</strong> works as a foundation for solving many complex problems that utilize infrastructure information.</p><p>In Tokopedia, we are implementing multiple cloud strategies to get the best from each of the providers to further enhance the reliability and capability of our infrastructure. We utilize the encyclopedia as our stepping stone to decentralize any massive act of engineering, from tech improvement initiatives, patching, Optimization, and many more aspects that we elevate.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=82908d6e682a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/decentralize-infrastructure-knowledge-through-analytical-infrastructure-encyclopedia-82908d6e682a">Decentralize Infrastructure Knowledge through Analytical Infrastructure Encyclopedia</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The importance of user distribution in online experiments (AB Testing)]]></title>
            <link>https://medium.com/tokopedia-engineering/the-importance-of-user-distribution-in-online-experiments-ab-testing-bf0735d2ca8?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/bf0735d2ca8</guid>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[tokopedia]]></category>
            <category><![CDATA[ab-testing]]></category>
            <category><![CDATA[ecommerce]]></category>
            <category><![CDATA[experimentation]]></category>
            <dc:creator><![CDATA[Gian Giovani]]></dc:creator>
            <pubDate>Tue, 29 Aug 2023 03:19:58 GMT</pubDate>
            <atom:updated>2023-08-29T04:11:46.119Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CMOPP2ke79IGJUDaVZmFOw.png" /></figure><p>Tokopedia has a strong culture of experimentation, we do lots of experiments across our properties. For example, when you open Search Result Page in Tokopedia App, there’s a pretty good chance you’re part of many experiments running at the same time. With a very high stake, we always try to ensure that every component of our experiment tool is running reliably. In this chance, we will discuss how a good experiment tool does user distribution correctly.</p><h3>What can possibly happen?</h3><p>In a nutshell, the AB testing tool’s job is to distribute users between 2 buckets of control and treatment. However, we noticed that this seemingly simple task is not that simple. There are 3 possible issues that can happen if the distribution is not done well:</p><ol><li>Biased Allocation<br>Ideally, a group of users being allocated to the buckets can represent the diversity of the whole population, in terms of demography, buying power, or other criteria important to Tokopedia. Biased allocation happens when a bucket doesn’t represent this diversity, for example when users with high buying power are too dominant in certain buckets.</li><li>Selection Bias<br>This can happen when a group of users with certain traits is selected, so the result of the experiment is compromised. When a group containing a dominant user with high buying power is selected, it is almost guaranteed that this bucket will perform better.</li><li>Memory Effect<br>Once an experiment is finished, it can have lasting effects on users, good or bad. This lasting effect can affect future experiments if the group of people is distributed unevenly in the next experiment, or worse the group is reused.</li></ol><h3>Modulo operation is not random</h3><p>It seems that those issues are hardly possible to happen, especially when the distribution is done randomly. However, we observed that the commonly used technique of using modulo operation with static or dynamic divisor can suffer from those problems.</p><p>There are common 2 ways of using modulo to distribute users to a bucket:</p><ol><li>Static slot<br>100% population will be divided into 50 slots using % 50, here 1 slot represents 2% of the user. Bucket distribution will be done by selecting slots until the desired population size is reached, e.g. For the 10% experiment, 5 slots will be used.</li><li>Dynamic slot<br>100% population will be divided into an arbitrary number of slots according to the needs. Eg. for an experiment targeting 50% of users, a modulus of 100 is used and the first 50 slots are allocated. In this case, the divisor can be different between experiments.</li></ol><p>As you might see now, the traits of simple modulus-based distribution are :<br>a. It’s not random enough, and the member of a slot never or rarely changes. The randomness of the allocation relies on the order of the user_id, which doesn’t have any randomness guarantee at all. This can lead to a biased allocation problem.<br>b. The slot’s member never changed with the static divisor, even on the dynamic divisor some users will always get allocated to the same slots. Not only memory effect can happen, but each slot can also develop its own set of behaviors/tendencies which can lead to selection bias.</p><h3>Double Hashing: A better way to distribute user</h3><p>So far we gathered 2 important requirements for our experiment tool, it has to be random and well-rotated for every experiment. In Tokopedia we develop a technique to do the allocation and randomization, on the allocation side we still rely on modulus operation, and for the randomization, a hashing function is used. The process is being done twice, that’s why we call this technique <strong>double hashing</strong>.</p><h3>First Step</h3><p>The population is divided into 100 buckets using modulo operation. However, instead of using user_id directly, the user_id will be hashed beforehand to get better randomness.</p><p>user bucket = hash(constant_prefix+user_id)%100</p><p>In this step buckets of users will be randomly allocated according to the experiment target size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/420/1*UHzYlk_fyhLBos3ZmKgPOw.png" /><figcaption>First step Visualization</figcaption></figure><h3>Second Step</h3><p>We further divide allocation from the previous step into 10.000 buckets. Again the user_id will be hashed, this time using a prefix coming from the experiment.</p><p>user bucket = hash(experiment_name+user_id)%10000</p><p>From those 10.000 buckets will be divided further to form Control and Experiment (A/B) buckets.</p><p>By using 2 steps allocation and randomization the intention is to avoid the 3 problems mentioned above and minimize lasting effects between experiments even when their population is overlapped.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/420/1*hhRL3ZSVtvs_mFKo-F-3iA.png" /><figcaption>Second step visualization</figcaption></figure><h3>AA test to prove</h3><p>At the end how good our tool or method to do things will need to be validated, better if the validation can be done regularly before every experiment is started. To prove that there is no Bias on the population in the experiment, AA usually is being used.</p><p>AA test refers to getting statistical significance from 2 different buckets of experiment with before any treatment being introduced. If we see no significance difference between buckets, meaning that our allocation is free from any bias.</p><h3>Tool to “Roll” them all</h3><p>Topic of online experimentation is always interesting to discuss, it combines product development, statistical and scalability challenges. Reliability, scalability and correctness of the system is very important, to make sure that data driven decision making can be done confidently.</p><p>In Tokopedia E2E journey of experimentation including allocation, AA test, AB Test, post analysis and gradual features rollout are being done on top of <strong>Rollence Experimentation Platform</strong>. An homegrown experimentation tool that has been reliably servicing our needs since 2019, as the successor of another homegrown tool we use earlier.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf0735d2ca8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/the-importance-of-user-distribution-in-online-experiments-ab-testing-bf0735d2ca8">The importance of user distribution in online experiments (AB Testing)</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Wasting Time on Unit Testing: How Tokopedia Achieved 8X Faster Results]]></title>
            <link>https://medium.com/tokopedia-engineering/stop-wasting-time-on-unit-testing-how-tokopedia-achieved-8x-faster-results-f0491de2d46a?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/f0491de2d46a</guid>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[bazel]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[efficiency]]></category>
            <dc:creator><![CDATA[Andrean Lay]]></dc:creator>
            <pubDate>Mon, 22 May 2023 03:14:46 GMT</pubDate>
            <atom:updated>2023-05-22T03:14:46.505Z</atom:updated>
            <content:encoded><![CDATA[<h4>Learn How Tokopedia Achieved 8X Faster and More Efficient Unit Testing Results</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dwhxdF0pqybdbXMVQkNA-w.jpeg" /></figure><p>Unit testing is an essential part of software development, especially for large-scale applications that serve millions of users. It’s crucial for the developers to ensure that every feature is tested thoroughly and that every new feature implemented does not create new bugs for existing features because it can cause significant loss for both developers and users.</p><p>While having unit tests is important, maintaining and running the tests can be time-consuming and resource-intensive, especially on a large-scale app that has tens of thousands or more test cases.</p><p>When one cycle of building and unit testing takes too long time, it would decrease the productivity of the developers themselves. This is especially true on a packed CI/CD system that only has few machines handling dozens of developers.</p><p>For our case, we have 9 CI/CD machines with ~65 developers developing daily. Usually each developer works on multiple branches, with each branch triggering both build and unit test job. Can you imagine if single unit test job could take 30 minutes?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/284/1*ms-P4f1pvVGt_egWBF0D6w.png" /><figcaption>Our hard working machine (Credit: <a href="https://www.dreamstime.com/lineartestpilot_info">Lineartestpilot</a> on Dreamstime)</figcaption></figure><p>In this article, we’ll go through our journey to optimize our daily unit testing process in Tokopedia iOS Team, which started with how we did our unit testing back in the days to where we are today.</p><p><strong>The Pain Points<br></strong>By optimizing our unit test performance, we addressed some pain points our team has:</p><blockquote>“OMG! This fix needs to be merged ASAP, but there are 10 idle queues on the CI machine waiting to be executed :(”</blockquote><blockquote>“I just edited our GitHub code owner, the PR only has a single changed file with two new lines. What did you say? I need to run all the unit tests, really?”</blockquote><blockquote>“Ok, my PR should be ready, let’s trigger the CI to run all unit tests.”<br>*30 minutes later*<br>“What? How could my unit test fail? Oh, I forgot to commit my latest changes. 30 mins retrigger here we go, let’s do the housekeeping while waiting.”</blockquote><p><strong>The Physical Education teacher way: let’s run all unit tests!<br></strong>In Tokopedia, we adopted modular app architecture using Bazel as our build system (read more about it <a href="https://medium.com/tokopedia-engineering/how-tokopedia-achieved-1000-faster-ios-build-time-7664b2d8ae5">here</a>). Therefore, all explanations will be based on Bazel, not Xcode’s native build system.</p><p>Our first unit testing process is by querying all unit test targets using the <a href="https://bazel.build/query/guide">bazel query</a>, then run test on each target from query results. Please note that by adopting modular app architecture, our app consists of dozen of modules (targets) for each feature/test case.</p><p>The process is fairly simple but it takes a really long time to run tests on each target individually. This is because every time Bazel runs a build or test, it has its own <a href="https://bazel.build/about/intro#bazel-build-process">build process</a>. Namely the dependency analysis, validation, and linking process that could slow down overall testing time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5SYNA5cH6yR1zxQl" /><figcaption>Our first unit test process</figcaption></figure><p>While this approach is not necessarily bad, it does not work well for our large-scale app. We have hundreds of test targets with tens of thousands of test cases, and running them individually takes a really long time.</p><p>On the other hand, if we have a simple app with a few test cases, the time difference is negligible, so improving the unit test process may not be worth the effort.</p><p>Remember that I mentioned the Bazel’s build process in the last paragraph? Yep, in the next section, we will talk about how we optimize this build process to make it faster. Let’s head into the next section.</p><p><strong>The Marriage Way: where all unit tests unite into one</strong></p><p>After using Physical Education (PE) teacher way for a while, we realized that Bazel’s build process takes a toll on our unit test pipeline performance. The dependency analysis and overhead time starting and ending XCTest runner make our unit test time vary from ~20 mins to ~35 mins because we have hundreds of test targets.</p><p>So we tried to unify all unit test targets into one single target. The idea is to simplify dependency analysis and overhead time caused by running each test one by one. A rough idea of the process is in the following pic:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FMEp0KJPHv_6CN4t" /><figcaption>Our single unit test approach</figcaption></figure><p>The downside of this approach is that it won’t utilize Bazel’s caching to its fullest. Whereas the old approach will cache the test cases that didn’t have code changes.</p><p>After testing it for about ~2 months, we realized that this new approach will run faster compared to the old unit test way despite the downside that I stated before. It saves time on the Bazel’s build process (analyzing packages, dependency, etc) whereas the old approach will repeat it for all test targets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xs1qnyDRFuxyPNQC-IecSQ.png" /><figcaption>Single Unit Test ON vs OFF comparison</figcaption></figure><p>The plot above illustrates that employing a single unit test enables us to achieve more stable unit testing times, with an average duration of approximately 450 seconds. This indicates that the single unit test approach can lead to more consistent and reliable testing results.</p><p>Conversely, using the traditional non-single unit test approach leads to a significant number of spikes in the unit testing time, with the highest value being 50% greater than the highest value obtained using the single unit test approach. This suggests that the traditional approach can result in less predictable and less reliable testing outcomes.</p><p>Lastly, single unit test also significantly decreased our unit test median to ~563s from ~1772s. This indicates that the single unit test approach can also lead to faster and more efficient testing processes, which can ultimately result in time and cost savings.</p><p>Our unit test journey doesn’t end here, we keep trying to find ways to improve our unit test performance to make our workflow more efficient.</p><p><strong>The Buffet Way: where only selected unit test matter</strong></p><p>Here comes our latest approach, let’s use an analogy to make it easier to understand.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/0*-1WQeFZmskgFV5bK" /><figcaption>Spilled Liquid Illustration (Credit: <a href="https://www.istockphoto.com/id/portfolio/PeopleImages?mediatype=photography">PeopleImages</a> on iStock Photo)</figcaption></figure><blockquote>“If you spilled something on your work table, should you wipe all your co-workers tables on our floor?”</blockquote><blockquote>“No, are you crazy? I will only wipe my own table and its surrounding which were hit by its splashes.”</blockquote><blockquote>“Yep, that’s how you should do unit tests too.”</blockquote><p>Do you get it? Our last approach is the selective test where only related unit tests will be tested. So for example, let’s say we have the following structure for our unit test:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*F0x5trZk9xx_LTvo" /><figcaption>Targets structure example</figcaption></figure><p>Let me give some context about what the arrows and circles mean. Each circle means a module and every arrow means that something is dependent on the other thing. So for example in the above diagram, we have modules A and E pointed to module C, which means that modules A and E are dependent on module C. In other words, A and E are the reverse dependencies of C.</p><p>Based on the previous analogy, if module C has some changes (getting milk spilled on), then directly affected modules will be module C itself, and indirectly affected modules will be A and E. So back to our unit test discussion, when we have changes in module C, we should test modules C (obviously), A, and E as illustrated below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iQ4WdcKGmy6LtrqxCi14DQ.png" /><figcaption>Module Dependency Analysis</figcaption></figure><p>Why A and E? You may ask. Because modules A and E use module C as their dependency, so every change in module C will indirectly affect modules A and E. We won’t test the rest of the modules because of their transitive dependency nature. When A depends on B, and B depends on C, if C is correct, then A should be correct too.</p><p>With the long boring chit-chat, let’s jump into the flowchart of our selective test implementation!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*x2Nxi2BbXdzmvPMk" /><figcaption>Our selective test approach</figcaption></figure><p>With the selective test, we implement these three main steps:</p><p><strong>Step 1 — File changes comparison<br></strong>Usually, when a developer starts to create their features, they will create a new branch from the main branch where all changes will be combined. This step will compare the file changes from their branch to this main branch. The output from this step will be a list of paths for each updated file.</p><p><strong>Step 2 — Target path extraction<br></strong>The output from step 1 is only the file path, we need to find its target name before we proceed to the next step. In this step, we will recursively find unique Bazel’s BUILD file path for each directory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*T03izndwSIhPpAHP" /><figcaption>Target path extraction logic</figcaption></figure><p>For example, we have this path:</p><p><strong><em>parent/childOne/childTwo/childThree/ChangedFile.swift</em></strong></p><p>We will recursively find Bazel’s BUILD file from parent/childOne/childTwo/childThree. If Bazel’s BUILD file exists, we will save its path and break the recursion tree.</p><p><strong>Step 3 — Reverse dependencies query<br></strong>After we found the changed target, we will find its reverse dependency using Bazel’s Sky Query <a href="https://bazel.build/query/language#allrdeps">allrdeps</a> command. Then we will combine each reverse dependency into one target just like approach number two above, and test it.</p><p>After selective test is live, we tested it for about ~3 months in a real development environment. We found significant improvement in unit test speed, especially when the file changes only affect small modules that not many modules use as a dependency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v7bBgNyN7CkU8qHQVPS9_A.png" /><figcaption>Selective Test ON vs OFF comparison</figcaption></figure><p>Now let’s get back to comparison plot. As you can see, the main difference in the distribution between selective test ON and OFF is on the lower end of the data. Since selective test focuses on running the minimum amount of tests while staying true, it is expected that we saw increased amount of low (fast) unit testing time. The median of our unit testing duration also decreased to ~250s from ~500s.</p><p>The main advantage of selective test is we also could reach <strong>zero</strong> unit test time (yep, the test is directly marked as passed because there is nothing to be tested!). This is the case when our file changes does not affect any main code base, such as GitHub’s code owner file. The evidence of this advantage is the amount of data point that reach approximately zero second when selective test is activated in above plot.</p><p>In above graph, we could see that single selected test could halven the unit test speed. Thanks to the possibility to reach instant unit test experience when nothing to be tested. Do note that if the file changes are related to a lot of modules, the unit test time will be more or less the same as single unit test from second approach.</p><p><strong>Recap</strong></p><p>Unit testing is one of the most important step in our engineering process to make sure whether something is impacted or not, at least on the feature logic side. Therefore, its essential to improve unit testing time because unit testing could take a very long time, especially in a large-scale app.</p><p>Our unit testing improvement journey wasn’t as smooth as this article stated, it was full of ups, downs, and many experiments which led to where we are today. But as we always say in Tokopedia — <strong><em>Make it happen, make it better.</em></strong></p><p><strong>Credits</strong></p><p>Last but not least, enormous thanks for <a href="https://medium.com/u/9e3511fbad3d">Wendy Liga</a> who started this odyssey with single unit test and involves me on our latest improvement — Selective Test which in total improved our unit testing duration <strong>8X FASTER</strong> compared to first iteration.</p><p>Also thanks to our CI/CD machines for working with us, they are in happier state now 😆</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/1*QhGK1w1F_q-r5GHwN1JfBw.png" /><figcaption>Happy Machine Illustration (Credit: <a href="https://dribbble.com/Leonid_Wozniuk">Leo Wozniuk</a> on Dribbble)</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f0491de2d46a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/stop-wasting-time-on-unit-testing-how-tokopedia-achieved-8x-faster-results-f0491de2d46a">Stop Wasting Time on Unit Testing: How Tokopedia Achieved 8X Faster Results</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia 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 Experience with Flutter in Tokopedia]]></title>
            <link>https://medium.com/tokopedia-engineering/our-experience-with-flutter-in-tokopedia-d560c76cd6db?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/d560c76cd6db</guid>
            <category><![CDATA[tokopedia]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[cross-platform-framework]]></category>
            <category><![CDATA[flutter]]></category>
            <dc:creator><![CDATA[F. Dhani Achmad]]></dc:creator>
            <pubDate>Wed, 21 Sep 2022 05:10:42 GMT</pubDate>
            <atom:updated>2022-09-21T05:13:45.607Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*vPmx_Fyu-qumxcJz.jpeg" /></figure><p>Cross platform native development has been the craze since the first launch of React Native back in 2015.</p><p>Tokopedia had its own history with React Native, having been used for several years on both iOS and Android apps, we soon found out that all the benefits <a href="https://medium.com/tokopedia-engineering/how-do-we-improve-our-most-important-page-on-ios-app-271fc85551e8">came with the cost of poor performance</a> which of course significantly impacted the user experience.</p><p>With the <strong>#FocusOnConsumer</strong> mindset, we eventually decided to sunset all React Native along with the hopes and dreams of having a single code base for both Android and iOS.</p><p><em>…Until Flutter was introduced.</em></p><h3>The Project</h3><p>For the <strong>Tokopedia START Summit 2022</strong> event we wanted to enhance the participant experience from last year by creating an application on all platforms that will support participants by showing the agenda, sponsors, and ongoing sessions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/873/0*JPGGRYqxH1j8JJPr.png" /></figure><p>The mockup of the Application</p><p>The caveat? we only had approximately 3 weeks to develop, publish and announce to the participants, no doubt many questions arose from the team along the same topic of</p><blockquote><em>“How do we deliver an application in less than a month on both Android and iOS in less than 3 weeks?”</em></blockquote><p>Of course going back to a cross platform framework came into mind. Although learning our mistakes we had to ensure the technology and approach we used were all appropriate and scalable enough for future usage.</p><h3>The Development Phase</h3><p>The decision to go ahead with Flutter was because solved our problem of time and manpower, furthermore it was a very good investment. With dozens of new libraries appearing, growing community support, and continuously improved performance we foresaw that Flutter might have a better future. However we were soon faced with several challenges.</p><h4>Challenge #1: Cross Team Collaboration</h4><p>The team consisted of engineers from iOS, Android and Web Platform engineers. Each team had its own set of rules and standards and had to collaborate on the same codebase, this of course created some issues. For example; Some teams prefer to use <em>squash &amp; merge </em>instead of <em>merge commit </em>when merging to the <em>master branch</em> or using <em>rebasing instead </em>of<em> pulling when syncing branches.</em></p><p>We found out that it’s important to have an alignment in the beginning to set the same expectations amongst the engineers in the beginning and also over-communicate any issues they had.</p><h4>Challenge #2: Project Scope &amp; Collaboration with Product Managers</h4><p>As mentioned before, Flutter is relatively new in Tokopedia. Because of this a feature that may only take 2 days to develop in normal conditions, might take an additional 2 days with a new technology. These 2 days act as a buffer because when working with a new technology engineers need time to research and of course debugging when stuff goes sideways.</p><p>It’s important to factor in those additional man days when creating your tech plan and ensure all stakeholders are aware so the team can adjust the scope &amp; timeline accordingly.</p><h4>Challenge #3: Deciding on a State Management</h4><p>State management can be defined as a way to manage states, or various properties that are constantly changing inside of an application. Various platforms have different approaches when handling states, web engineers use state managers like React Context or Redux for React.js, Android Engineers use state holders and state hoisting for Jetpack, and iOS engineers use ObservedObjects for SwiftUI. With different paradigms, it was difficult to settle on solution.</p><p>First on foremost we all agreed that the approach had to be extendable, predictable and of course easy to learn. After several discussions we decided to use BLoC because it solved each of our concerns well:</p><p><strong>Extendable</strong></p><p>BLoC uses the concept of “business logics” that are abstract and can be used in multiple platforms and use cases. As a matter of fact, because of the abstraction and power of BLoC, we ended up using BLoC for most of our state management implementations.</p><p><strong>Predictable</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/730/0*x5oYtXAryZ7PNjg-" /><figcaption>Using state to control UI using BLoC | credited: blog.logrocket.com</figcaption></figure><p>We always want to give the best experience, hence handling every edge case well. For example, what should the users see when there’s a query error? When there’s no internet connection? Or when the data is empty? The power of BLoC gives the powers to us engineers to do just that. BLoC leverages of “states” to control the UI. So we can set the UI to update when the state is “empty data”, “no internet connection”, and “query error”.</p><p><strong>Easy to learn</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*-Lf-eJ6IT87qaxXA.png" /><figcaption>Bloc visualization | credited: <a href="https://bloclibrary.dev/#/">https://bloclibrary.dev</a></figcaption></figure><p>BLoC uses the concept of <em>events</em> that are emitted to update the <em>states</em> of a UI. This approach feels familiar to both mobile and web engineers, making the learning curve faster for everyone.</p><h3>Application Performance and Size</h3><p>Performance is a main concern when using a cross platform framework based on previous experiences. Thankfully we did not see any significant performance degradations when using Flutter.</p><h4>App Size</h4><p>iOS → 29.5MB</p><p>Android → 7.9MB</p><h4>Build Time</h4><p>Average build time for our application took on average between 1m 30s until 2m to build</p><h3>Conclusion</h3><p>Overall our experience on working with Flutter was delightful, most of the challenges we faced was a results of everything except Flutter itself.</p><p>Of course this is all based on our conditions and environment so we recommend all engineers to research thoroughly before going fourth with a new technology like Flutter.</p><p>If you’d like to check out the application itself feel free to download it through these links:</p><p><a href="https://play.google.com/store/apps/details?id=com.tokopedia.eventacademy">Android Play Store</a><br><a href="https://apps.apple.com/us/app/id1632881844">Apple App Store</a></p><h3>References</h3><p><a href="https://blog.logrocket.com/complete-guide-flutter-architecture/">https://blog.logrocket.com/complete-guide-flutter-architecture/</a></p><p><a href="https://flutter.dev/">Flutter - Build apps for any screen</a></p><p><a href="https://blog.logrocket.com/state-management-flutter-bloc-pattern/">https://blog.logrocket.com/state-management-flutter-bloc-pattern/</a></p><p><a href="https://bloclibrary.dev/#/">https://bloclibrary.dev</a></p><p><a href="https://flutter.dev/">https://flutter.dev/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d560c76cd6db" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/our-experience-with-flutter-in-tokopedia-d560c76cd6db">Our Experience with Flutter in Tokopedia</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia 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 Started with iOS App Modularization — An Introduction]]></title>
            <link>https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-an-introduction-e1ee1b3ead6a?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/e1ee1b3ead6a</guid>
            <category><![CDATA[modularization]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Arya Surya]]></dc:creator>
            <pubDate>Fri, 16 Sep 2022 04:02:45 GMT</pubDate>
            <atom:updated>2022-09-20T03:51:09.382Z</atom:updated>
            <content:encoded><![CDATA[<h3>Getting Started with iOS App Modularization — An Introduction</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xba_9rzJQVid_-d4z1j6QQ.jpeg" /></figure><p>This is the first part of the modularization tutorial. Refer to the list below for the complete parts :<br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-an-introduction-e1ee1b3ead6a"><strong>Part 1</strong>: Introduction</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e"><strong>Part 2</strong>: Extracting Catalog</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39"><strong>Part 3</strong>: Final Extraction and Micro App</a></p><p>App modularization is an issue everyone runs into as a software engineer, moreover, if your app is already grown to the scale that it takes too long to build the entire app, just like the Tokopedia app. In our case, we have to find a way to fasten our build time to meet our weekly release and this is where the modularization helps. By modularizing our app, we can choose to build what we need at the moment thus improving our build time and everyone is happy! <br>Tokopedia uses a custom modularization approach to enable fast development, but before going to custom modularize your app, you have to understand the basics of modularization and all the other related things you need to know, this article will cover all of them and guide you to have your first taste of modularization, hang tight!</p><p>There is a slew of solutions you can use to tackle modularization. From splitting codebase chunks to increase maintainability to creating good encapsulation. But what is the motivation behind modularization?</p><p>When should you split your codebase into modularized parts?</p><p>Why should you even do such a thing?</p><h3><strong>What is Modularization</strong></h3><p>Modularization is the practice of dividing features of an application into separated modules so that they are interchangeable, reusable, and easily maintained. If you prefer a less-technical explanation, think of a car stripped off its steer, dashboard, and wheels. All these smaller parts of the vehicle can be fully customized on their own and easily replaced by other parts of the same kind if needed.</p><p>Most teams fall back to the modularization philosophy to split monolith codebases that are too large to handle. It is because as time goes on, the build time grows linearly with the lines of code. Slow build time hurts developers’ productivity in the long run and will result in long waiting times before developers can fully test their apps.</p><p>By splitting the features, developers will only build the needed features without having to compile the entire app. Theoretically, this will decrease the compile time, improving developers&#39; productivity.</p><h3>When Should You Modularize Your App?</h3><p>One question might arise when considering modularization. How do you know if your app needs to be modularized or not?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Gbzx5JEm-p4IuF06" /><figcaption>Photo by <a href="https://unsplash.com/@brucemars?utm_source=medium&amp;utm_medium=referral">bruce mars</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The answer to such a question is pretty subjective since it depends on your aims from the start and your current situation. For example, suppose you are building a startup that needs to have as many features delivered as possible within a tight schedule. In that case, modularizing your app from the very beginning will result in either :</p><p>1. Premature optimization.<br>2. A good foundation while the app is still in its infancy.</p><p>All of these concerns are valid and you must make decisions with this in mind.</p><h3>How to Modularize An iOS App</h3><p>Let’s say your team finally decide it is the best time to perform modularization, and the next question is how to start modularizing your app.<br>Rest assured, you will learn how you can modularize an iOS project step-by-step.</p><p>Consider an illustration below :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PU2Ksil7Cka9QAlGXhF51Q.png" /></figure><p>Let’s say you have an iOS app with three features: catalog, product detail, and shared. One way to get the app modularized is to split the app into four different modules, each with a library and a bundle. In iOS, a module is called a framework, and this article will use those two words interchangeably.</p><p>A library is a collection of resources and the code itself, compiled for one or more architectures, in shorter words, it is all the code you have written. Whereas a bundle in iOS is a directory with subdirectories usually used to store assets, files, and many other specific types of content. The framework itself is a group that contains all the libraries and the resources it needed.</p><p>Furthermore, there are two important frameworks that need to be explained :</p><p>1. Main</p><p>Like its name, the main is your main framework; think of it as your app’s entry point.</p><p>2. Shared</p><p>Think of this framework as the place of all components used within the entire app, containing everything like the code of UI components, (e.g accordions, bottom sheet, popup, etc. ). The shared framework is the best place to start splitting the app since it does not depend on anything externally.</p><p>Moreover, iOS has two types of frameworks, dynamic and static, respectively. <br>A dynamic framework is a bundle of code loaded into an executable at runtime instead of compile time. It behaves exactly like a dynamic library, except for the difference that a dynamic framework is a dynamic library embedded in a bundle along with optional assets, such as images. UIKit and the Foundation frameworks belong to this category.</p><p>On the other hand, a static framework is a bundle containing a static library file. They are statically linked, or copied into the executable binary, and not loaded at runtime.</p><blockquote>For the sake of simplicity, this article will only cover extracting a monolith into dynamic frameworks.</blockquote><p>You can read more about dynamic and static libraries here.</p><p><a href="https://pewpewthespells.com/blog/static_and_dynamic_libraries.html">Static and Dynamic Libraries</a></p><h3>Let’s Get Started</h3><p>Clone the starter project here</p><p><a href="https://github.com/tokopedia/ios-modular-tutorial">GitHub - tokopedia/ios-modular-tutorial: Repository for tutorial code base for our medium blog post</a></p><p>Open the MyAwesomeApp.xcodeproj. Try to run the project to get a clue about the app you are trying to modularize.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/272/0*YWjQby-9H8gu2cH9" /></figure><p>As you can see, the app is pretty straightforward, you have a catalog page showing all the product cards and several rows of promotional and inspirational products. Another feature to add is navigating to the respective product detail page upon tapping one of the product cards.</p><h3>1st Step: Convert App Into Xcworkspace</h3><p>The first thing you have to do to modularize our app is to convert our app to an xcworkspace</p><p>Click file &gt; new &gt; workspace, and set the workspace name as MyAwesome. Then, save the .xcworkspace inside the modular folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m71sEi8gYEW5sb0R4WCviQ.png" /></figure><p>Another window will appear and you can close the previous window. Click File, Add Files to MyAwesomeApp, and add your previous myawesome.xcodeproj to your workspace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LdIMzof8xVnPfFw4wCUXfw.png" /></figure><p>Your Xcode will then become something like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jYoSk4bG5J9JWZWeIGElSQ.png" /></figure><p>Until now, you have successfully added the xcodeproj inside an xcworkspace. Now, you can start to split the features into their own corresponding frameworks.</p><p>Remember that you must start extracting from the least dependable frameworks. By referring to the dependency diagram before, you can see that the least dependable framework is the Shared framework. Start by extracting this framework first.</p><h3>2nd Step: Creating the Shared Framework</h3><p>Create a new framework named Shared and add it to our workspace. Refer to the GIF below for the step-by-step.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*KJj80lKDIyspno6jWOE5SA.gif" /></figure><p>One thing to note here, remember to choose MyAwesomeApp.xcworkspace the place to add the new framework. On top of that, also make sure the group refers to the MyAwesomeApp.xcworkspace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-VpjpFRJOw7p6n3IN3A3vQ.png" /></figure><p>Now your workspace will look something like this, with two xcodeproj inside an xcworkspace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UNn9u2Ytvfg84p2kfC1PzA.png" /></figure><p>The next step is you have to move the all contents of the folder Shared in the MyAwesomeApp framework ( we will call this the main framework in future usages for simpler and better understanding ) to its own Shared framework’s folder. You can use the help of the finder app in order to do so.<br>Right-click on the Shared folder in the main framework, choose Show in Finder , we will call this Shared folder the source folder.<br>Do the same thing for the Shared folder in the Shared framework, and we will call this folder the destination folder.</p><p>Move all the folders inside the source folder to the destination folder, and remember to tick copy items if needed when prompted. You can refer to the GIF below for a better understanding.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*fqatGYOjKtFZs0hmVyW9Fw.gif" /></figure><p>Next, you have to adjust the module property of the moved asset files. Refer to the UI folder in the Shared framework. Click the custom class section on the right pane of the code editor, click the module text field and type Shared, the framework’s name. Doing so will cause the Inherit module from target checkbox will be automatically unchecked.<br>This step is to set the resource file’s module explicitly to the Shared framework since you have moved the file there, if you do not, the file will still think it belongs to the main framework, since it inherits from the target, causing unexpected behaviours.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*07MGue--fzdizIrp8jGcfQ.gif" /></figure><p>Do the same for the InspirationItemCollectionViewCell.xib and InspirationCollectionViewCell.xib .</p><p>The next step is the most essential part of integrating your Shared framework into the main framework.</p><p>Navigate to the main framework, locate the Frameworks, Libraries, and Embedded Content section, and choose the Shared framework to be embedded. This step is to tell the main framework that it has the Shared framework as the dependency when running the app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*5GqJeMoS-gKAqSXTJuhbRw.gif" /></figure><p>Try to run the app, but expect the build to fail because of these errors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*jbD6haaXpltIjreS7P1fLQ.png" /></figure><h3>3rd Step: Resolving the errors</h3><p>Let’s solve the errors sequentially, navigate to the CatalogPageNetworkProvider.swift</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0CS1_eS7rv0VlAF1FqzJJw.png" /></figure><p>As you can see, most of the errors are caused by the file not having the definition of NetworkResult , this is because the NetworkResult is originally located in the Shared folder, and because you have moved all of the Shared’s contents to its own frameworks, the code from the main framework does not have access to them again.</p><p>The solution for this is you have just to import the Shared framework in the file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6260e5399b284bc7c107ea8421a9b4bf/href">https://medium.com/media/6260e5399b284bc7c107ea8421a9b4bf/href</a></iframe><p>But the error persists even if you have imported the framework, this is still happening because apparently the NetworkResult has an internal access control level, any definition that has internal access control level can only be accessed from its own module, and since you are trying to access the enum from outside the framework, you still cannot locate the definition, which explains the error.</p><blockquote>Note that access control level plays a huge role in modularization, you have to determine which code that are exposed and which are not</blockquote><p>Change the access control level to looser access controls, such as public or open. Navigate to the shared framework, find the NetworkResult.swift file and add an access control before the enum definition.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1c1cadc2fac9f48a7b2d4c20fbc19a97/href">https://medium.com/media/1c1cadc2fac9f48a7b2d4c20fbc19a97/href</a></iframe><p>The errors in CatalogPageNetworkProvider.swift should be solved now.</p><p>Navigate to the ProductDetailPageViewController.swift . The errors are because the file cannot find the definition of the Product struct and the load function since those definitions are already moved into the shared framework, hence the solution is the same as the previous errors, which is<br>1. Adjust access control levels<br>2. Import Shared</p><p>Import the shared framework at the top of ProductDetailPageViewController and add a public control level in front of the load function definition in UIImageView+Extension.swift . You should have resolved the errors.</p><p>Next up, navigate to the CatalogPageViewModel.swift , the same errors are happening in this file, it cannot identify the HashDiffable protocol and the ProductResult .<br>You can use a similar solution, import the shared framework on the file and add a public access control to the HashDiffable definition and its isEqual function.</p><p>Lastly, the CatalogPageViewController , where it cannot identify the Product struct. Simply importing the shared framework in this file will solve the error.</p><p>Try building the app and it should have been able to run now.</p><h3>4th Step: Handling Bundle Issues</h3><p>One thing that you will notice upon the app launch is your app does not look like it used to be before modularization, you have only presented a white screen and your list of product cards is gone. Why is this happening?</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F6HNonYwCD4yrHFk7ya%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fgiphy.com%2Fgifs%2Fjoebuddennetwork-thinking-think-rory-6HNonYwCD4yrHFk7ya&amp;image=https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2F6HNonYwCD4yrHFk7ya%2Fgiphy.gif%3Fcid%3D790b76119c595ee6b4721da6473852565f5847b99027131e%26rid%3Dgiphy.gif%26ct%3Dg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="244" frameborder="0" scrolling="no"><a href="https://medium.com/media/a3dc63a8ea67ded873562424ac3f5beb/href">https://medium.com/media/a3dc63a8ea67ded873562424ac3f5beb/href</a></iframe><p>Luckily, looking at the app console, you should see the error’s root. A message : URL NOT FOUND.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hum5JQApwH8PJhA-nYeqmw.png" /></figure><p>If you try to find the error string in the entire app, you will notice that it is only coded in the CatalogPageNetworkProvider.swift . Furthermore, If you look further into the code, the errors are because you can&#39;t locate the ProductData.json</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb9864dbaee6109d97c25992a94a531c/href">https://medium.com/media/cb9864dbaee6109d97c25992a94a531c/href</a></iframe><p>This happens because the ProductData.json is already moved to the Shared framework, but how can you access the file using a bundle? 🤔</p><p>First, to access a bundle from a framework, you have to know its identifier.</p><p>Navigate to the Shared framework, click on the target and inspect the general tab. Turns out you already have a bundle identifier of the shared framework, and you can use this in order to access the files within the framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3UUSjALKfjyNCi4vyKjp0w.png" /></figure><p>Navigate back to the CatalogPageNetworkProvider.swift , change the guard validation in both of the fetchProductand fetchInspiration</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0d7573eb2ef43dc310c7d197905ccd6/href">https://medium.com/media/b0d7573eb2ef43dc310c7d197905ccd6/href</a></iframe><p>Instead of using Bundle.main , use your shared framework’s bundle by initializing it using the bundle ID. Remember to update the bundle ID to your framework’s bundle ID as your organization name most likely will be different.</p><p>Try to run again the app. This time, instead of getting the error message URL NOT FOUND, your app will crash.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FDyvyiFFXF1Yli%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fgiphy.com%2Fgifs%2Fdesusandmero-funny-lol-DyvyiFFXF1Yli&amp;image=https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcGZydm5jM2x3bGN6ZXk2YTFyZWhpMnRqcXhmOTF6dHdwcWx1azY5bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FDyvyiFFXF1Yli%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="244" frameborder="0" scrolling="no"><a href="https://medium.com/media/83f115be1221e5dd73c836b7ffe32afb/href">https://medium.com/media/83f115be1221e5dd73c836b7ffe32afb/href</a></iframe><p>Suppose you try to debug further from the stack trace, you will see that the crash is caused by CatalogPageViewController calling the cellForIndexPath function, which triggers the collectionView to load the ProductCollectionViewCell, but it was unable to do so, causing the crash.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LD4NpSy6bwEPIMHfHNERtg.png" /></figure><blockquote>On the brighter side, this crash proves you have successfully loaded the JSON files since it is happening on the CatalogPageViewController ‘s function that is triggered upon receiving data.</blockquote><p>Your view controller causes the error since it cannot identify the ProductCollectionViewCell . The solution is to specify the bundle where this view controller loads the NIB.</p><p>Locate the viewDidLoad function inside the CatalogPageViewController , and change the code to become :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ee1045a370695ca93d97c7b5819eadff/href">https://medium.com/media/ee1045a370695ca93d97c7b5819eadff/href</a></iframe><p>Try running the app, and you will get another crash caused by the same issue, the InspirationCollectionViewCell could not load its NIB. Locate its awakeFromNIB function and change it into this</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ec49fd17281a3b3379526d6e33cfedca/href">https://medium.com/media/ec49fd17281a3b3379526d6e33cfedca/href</a></iframe><blockquote>Remember to adjust the identifier to your own bundle ID</blockquote><p>After changing the code, try to rebuild the app and it should run successfully!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/272/1*2Fx8uf4jlnLpjTJ0anyRVA.gif" /></figure><p>You have successfully extracted your shared module, leaving only two more features before you finish modularizing your app. We will continue extracting the catalog feature into its framework next.</p><p>You can access the code materials until this point here.</p><p><a href="https://github.com/tokopedia/ios-modular-tutorial/tree/feature/extract_shared">GitHub - tokopedia/ios-modular-tutorial at feature/extract_shared</a></p><p>Now let’s continue to the second part <a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e">here</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1ee1b3ead6a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-an-introduction-e1ee1b3ead6a">Getting Started with iOS App Modularization — An Introduction</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia 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 Started with iOS App Modularization — Extracting Catalog]]></title>
            <link>https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/83d117ad343e</guid>
            <category><![CDATA[modularization]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Arya Surya]]></dc:creator>
            <pubDate>Fri, 16 Sep 2022 03:56:18 GMT</pubDate>
            <atom:updated>2022-09-20T03:52:06.254Z</atom:updated>
            <content:encoded><![CDATA[<h3>Getting Started with iOS App Modularization — Extracting Catalog</h3><p>This is the second part of the modularization tutorial. Refer to the list below for the complete parts :<br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-an-introduction-e1ee1b3ead6a"><strong>Part 1</strong>: Introduction</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e"><strong>Part 2</strong>: Extracting Catalog</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39"><strong>Part 3</strong>: Final Extraction and Micro App</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xba_9rzJQVid_-d4z1j6QQ.jpeg" /></figure><p>In this article, you will continue to extract the catalog feature.</p><p>Refer to the link below for the starting project template, or you can continue with your existing project if you come from the first part of the article.</p><p><a href="https://github.com/tokopedia/ios-modular-tutorial/tree/feature/extract_shared">GitHub - tokopedia/ios-modular-tutorial at feature/extract_shared</a></p><h3>1st Step : Creating The Catalog Framework</h3><p>We will start this article by creating a new framework named Catalog. Choose File &gt; New Project, choose Framework and remember to add it to your xcworkspace . Refer to the GIF below for a better understanding.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*EtN7Xvpg4ScZXcNhkBgpwA.gif" /></figure><p>The next step is to move all the Catalog’s folder contents in the main framework into its framework now, refer to another GIF below for clarity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*_-M9jvkVrvr2JDfT_EmL0A.gif" /></figure><p>The first thing to do upon framework creation is to add the framework’s dependency. In this case, you have to add the Shared framework as a dependency since Catalog relies on it heavily for the models and other assets. Click on the Catalog project, find the targets tab and scroll until the Framework, Libraries, and Embededded Content section. Click the plus icon and choose the Shared framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*a0V6-3V7mXO-rSj1ykxaOw.gif" /></figure><p>The next step is to set asset files in the new Catalog framework to point to its module, click the CatalogPageViewController.xib . Locate the File Owner option on the Placeholders section, then choose the custom class tab, and type Catalog at the Module text field, and the Inherit module from target will be automatically unchecked.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A9UP1REtzaXYN_j47kmefg.png" /></figure><p>Try to run the app but expect the build to be failed because of the ACL and import-related issues.<br>One place that errored first is SceneDelegate.swift located in the main framework. Simply importing Catalog will resolve the issue, but before that, you have to add the Catalog framework as the main framework’s dependency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*6gZXpDbIO6GjHHa5fDGqQQ.gif" /></figure><p>Try to rebuild the app again, but the error will persist. This is caused by ACL since the CatalogPageViewController ‘s access control is internal. Add a public access control before the class definition and make all its exposed functions public, the resulting code will be like this.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/79d4b663b1a1fca2869019571d0e9473/href">https://medium.com/media/79d4b663b1a1fca2869019571d0e9473/href</a></iframe><p>Even after all these ACL modifications, our catalog feature still will not build because the file cannot identify the ProductDetailPageViewController .<br>One might argue that the solution to this is just to<br>1. Extract the product detail into its framework<br>2. Simply import the product detail module at our catalog framework</p><blockquote>This approach will work but it will cause a dependency issue if later, the product detail page needs to use classes/models from the Catalog framework, you will end up having a circular dependency in your app.</blockquote><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FpoOsWEQNzdORIiJ8NH%2Fvideo&amp;display_name=GIPHY&amp;url=https%3A%2F%2Fgiphy.com%2Fclips%2Fjustin-reaction-meme-realization-poOsWEQNzdORIiJ8NH&amp;image=https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2FpoOsWEQNzdORIiJ8NH%2Fgiphy.gif&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=giphy" width="480" height="332" frameborder="0" scrolling="no"><a href="https://medium.com/media/662004c0b2e00b1863a7b9e5a32e3c04/href">https://medium.com/media/662004c0b2e00b1863a7b9e5a32e3c04/href</a></iframe><h3>2nd Step : Designing The App Router</h3><p>A better solution to the previous one mentioned before is you can create an abstraction that acts as a route handler in the Shared framework, then when any features that want to navigate to another module can just use the abstraction without having to be tightly coupled to the destination framework.</p><p>Navigate to the Helper folder inside the Shared framework and add a new file named Router.swift</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b36c980cbc472e7b707bd927900139a/href">https://medium.com/media/4b36c980cbc472e7b707bd927900139a/href</a></iframe><p>This will be a blueprint for our navigation capability. Now, head back to the CatalogPageViewController and replace the didSelectItemAt indexPath function extension to become like this</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f2f72470435bbcf438b6ff5b38dd6483/href">https://medium.com/media/f2f72470435bbcf438b6ff5b38dd6483/href</a></iframe><p>Try to run the app. Now, it will be successfully built but crashed as soon as it runs. The crash is caused by the CatalogPageViewController since it cannot find its XIB.<br>Navigate to CatalogPageViewController , locate its init function and change them.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b9ceb7154b7fc695c7fab6e1f3e3178/href">https://medium.com/media/6b9ceb7154b7fc695c7fab6e1f3e3178/href</a></iframe><blockquote>Bundle(for: Self.self) is used to refer the bundle within the module itself, you can use this directly instead of Bundle(identifier:) since the CatalogPageViewController.xib lives inside its own framework.</blockquote><p>Try to rebuild the app again, and this time the app will successfully run.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*qFap6rN4ivU_RPNMY6t0SA.gif" /></figure><p>But one capability is missing from our app, if you try to click one of the product cards, it will do nothing instead of navigating to the respective product detail page, why is this happening?</p><p>Remember atCatalogPageViewController , you have changed the didSelectItemAt indexPath function definition to use Router instead of manually navigating. The navigation will not work yet because you have not created the definition for the route function, which explains the bug.</p><p>Navigate SceneDelegate.swift inside the main framework. Add the route definition at the very end of the scene function :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/817b9366a0b53936a49d9c15742968a8/href">https://medium.com/media/817b9366a0b53936a49d9c15742968a8/href</a></iframe><p>By doing this, the app will be able to perform navigation from any modules without ending up having a circular dependency or becoming tightly coupled with unnecessary modules.<br>Try to run the app, and the navigation will now work smoothly.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F62PP2yEIAZF6g%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2F62PP2yEIAZF6g%2Fgiphy.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2F62PP2yEIAZF6g%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="247" frameborder="0" scrolling="no"><a href="https://medium.com/media/8bb5afd9dd418be7b94422bf05dd409d/href">https://medium.com/media/8bb5afd9dd418be7b94422bf05dd409d/href</a></iframe><p>Up until this point, you have successfully extracted the Shared and Catalog frameworks, leaving only the Product Detail Page to be extracted.<br>In the next part, you will continue to extract the Product Detail Page into its framework and learn how to build one single framework instead of the entire app.</p><p>Now, let’s continue to the final part <a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39">here</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=83d117ad343e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e">Getting Started with iOS App Modularization — Extracting Catalog</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia 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 Started with iOS App Modularization — Final Extraction and Micro App]]></title>
            <link>https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/29b973a83a39</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[modularization]]></category>
            <dc:creator><![CDATA[Arya Surya]]></dc:creator>
            <pubDate>Fri, 16 Sep 2022 03:54:57 GMT</pubDate>
            <atom:updated>2022-09-20T03:52:51.148Z</atom:updated>
            <content:encoded><![CDATA[<h3>Getting Started with iOS App Modularization — Final Extraction and Micro App</h3><p>This is the final part of the modularization tutorial. Refer to the list below for the complete parts :<br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-an-introduction-e1ee1b3ead6a"><strong>Part 1</strong>: Introduction</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-extracting-catalog-83d117ad343e"><strong>Part 2</strong>: Extracting Catalog</a> <br><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39"><strong>Part 3</strong>: Final Extraction and Micro App</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xba_9rzJQVid_-d4z1j6QQ.jpeg" /></figure><p>Refer to the link below for the starting project template, or you can continue with your existing project if you come from the second part of the article.</p><p><a href="https://github.com/tokopedia/ios-modular-tutorial/tree/feature/extract_catalog">GitHub - tokopedia/ios-modular-tutorial at feature/extract_catalog</a></p><h3>1st Step : Creating the Product Detail Framework</h3><p>Let’s continue this series to extract the Product Detail feature into its own framework.<br>Create a framework and add it to the existing .xcworkspace</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*TFFPAkaSumbxH9Kjz5J4bQ.gif" /></figure><p>Next up, move all the contents inside the ProductDetailPage folder in the main framework to its own framework, then our project will look at this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gor5HUQo0Bf-68h1nFGqnw.png" /></figure><blockquote>Always remember to add the framework’s dependency first before adjusting the framework’s code.</blockquote><p>Add the Shared framework as the ProductDetail ‘s dependency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*pOzP2H8Al4SqWYYo-n-5LA.gif" /></figure><p>Next, don’t forget to change the ProductDetailPageViewController.xib ‘s module name to ProductDetail .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9aj-o86AZPBDZFSrC1wL8g.png" /></figure><p>After that, try to run the app but always expect it to be failed since you have not adjusted anything yet. If you try to inspect the error, it is caused by the file SceneDelegate.swift since it cannot find the definition for ProductDetailPageViewController .</p><p>The solution is to add ProductDetail as the main framework’s dependency, by doing this, the main framework will have access to the ProductDetail framework’s APIs. Refer to the previous GIF to understand how to add a dependency to a framework.</p><p>Next, try to import ProductDetail on the SceneDelegate.swift , the file will become like this.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ece87df187ef293bd7d3f5afad25ba2a/href">https://medium.com/media/ece87df187ef293bd7d3f5afad25ba2a/href</a></iframe><p>The error still has not been resolved due to ACL issues, navigate to ProductDetailPageViewController and modify the code to the following :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ecb14dfd15bb31895ccdaa29ca845d7e/href">https://medium.com/media/ecb14dfd15bb31895ccdaa29ca845d7e/href</a></iframe><p>Try building the app, it will run smoothly with all features working as expected!</p><p>What’s more, you have successfully modularized the monolith app into several modules! 🎊🎊</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FSVH9y2LQUVVCRcqD7o%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FSVH9y2LQUVVCRcqD7o%2Fgiphy.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2FSVH9y2LQUVVCRcqD7o%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="324" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e98db6c2b135d5a17ae2d0d59b9886a/href">https://medium.com/media/6e98db6c2b135d5a17ae2d0d59b9886a/href</a></iframe><h3>2nd Step: The Micro App Concept</h3><p>Now you already know how to split an app into several smaller features and achieve modularization, but what if you want to achieve building only necessary features?<br>Let’s say you have to implement some enhancements on the product detail page, theoretically, you only have to build the respective framework without having to compile the entire app. How can you achieve this?</p><p>Here’s where micro apps jump in, what you have to do is just to create a new app target, inside the framework you are working with. This xcodeproj will act like your playground, and developers needed only to compile this feature on development, hence the name micro app.</p><p>Click on the Product Detail framework, find the Targets tab and click the + button underneath the tab. After the choosing window show up, search for app and name it ProductDetailSample.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*_j-dNH8GCxY4gfgvkbh3Pg.gif" /></figure><blockquote>If your newly generated project files end up being red and can’t be opened like the GIF, quit and reopening the XCode will do the trick.</blockquote><p>Next, add the Product Detailframework as the Product Detail Sample ‘s dependency. This is needed since the sample will only act as a playground for our code in Product Detail , hence you need to import them before you’re able to use them.</p><p>Find the Frameworks, Libraries, and Embedded Content section of the sample’s general tab and add the Product Detail framework.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*09EoAjlkv88gtZxRy5e2TQ.png" /></figure><p>After that, try to run the ProductDetailSample target</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/466/1*qIDQ462uztX-y7xEDFvbYA.png" /></figure><p>You will then be presented with an empty screen, which comes from ViewController.swift in the ProductDetailSample folder.</p><blockquote>Try changing the background colour and check whether the screen changes accordingly. :)</blockquote><p>That’s it. You can use the ViewController to embed the ProductDetailPageViewController or do whatever you want to do with the feature.</p><p>In this tutorial, however, we will try to simply present the ProductDetailPageViewController instead of using the ViewController , to achieve the capability to build only what you need.</p><p>Remove the ViewController.swift and the Main.storyboard .<br>Navigate to ProductDetailSample ‘s general tab and remove the Main from the Main Interface field, and lastly, locate the Info.plist file, expand all the keys from the dictionary until you find a key titledStoryboard Name, and then delete the entry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uZRp1mPGtpdUQy2nqNCPcg.png" /><figcaption>Delete the Main inside the text field and press enter</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EVxHONI3No0hzs2tiN4l5g.png" /><figcaption>Delete the highlighted entry by simply pressing delete</figcaption></figure><p>Finally, navigate to SceneDelegate.swift , change the code inside the scene function to become :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cd087b51fd0e97cfad274f43758e02bf/href">https://medium.com/media/cd087b51fd0e97cfad274f43758e02bf/href</a></iframe><p>Try to run the app again, then it will show only the product detail page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/293/1*w2IWlioQ3GROjQku2tjjzg.png" /></figure><p>Now, if you want to enhance the product detail page, for example by adding an image carousel, product description, or anything else, you can code them and directly build this ProductDetailSample to speed up your development.</p><p>Congratulations! 🎉🎉<br>You have modularized the app and even provided a minor app in one of your features!</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2F3o6nUOsQeBtw6DRYDm%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2F3o6nUOsQeBtw6DRYDm%2Fgiphy.gif&amp;image=https%3A%2F%2Fi.giphy.com%2Fmedia%2F3o6nUOsQeBtw6DRYDm%2F200.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="168" frameborder="0" scrolling="no"><a href="https://medium.com/media/7a90b0cf18da7df27d12646a97f045f0/href">https://medium.com/media/7a90b0cf18da7df27d12646a97f045f0/href</a></iframe><blockquote>you can also go the extra mile to create a minor app for Catalog and Shared framework as well. :)</blockquote><p>The final code of the project can be found at :</p><p><a href="https://github.com/tokopedia/ios-modular-tutorial/tree/feature/micro_app">GitHub - tokopedia/ios-modular-tutorial at feature/micro_app</a></p><h3>Where to Go From Here</h3><p>In this article, you have learnt the definition and some motivations behind modularization, furthermore you have modularized an app yourself and even made a sample app for one of the frameworks, this is just the first step of the modularization journey, there are still so many use cases out there that require us to get help from libraries, or tweak our approach to modularizing. We also have this one story of adopting Bazel in our app to further improve our build time, you can check it here.</p><p><a href="https://medium.com/tokopedia-engineering/how-tokopedia-achieved-1000-faster-ios-build-time-7664b2d8ae5">How Tokopedia Achieved 1000% Faster iOS Build Time</a></p><p>Thanks to <a href="https://medium.com/u/aac7941d4db8">Digital Khrisna</a>, <a href="https://medium.com/u/3aac27ca447">Bondan Eko Prasetyo</a>, <a href="https://medium.com/u/8922102dfc90">Ferico Samuel</a>, and <a href="https://medium.com/u/85accda904b1">Andrey Yoshua</a> for helping to review this series.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=29b973a83a39" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/getting-started-with-ios-app-modularization-final-extraction-and-micro-app-29b973a83a39">Getting Started with iOS App Modularization — Final Extraction and Micro App</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Validating Social Media URLs in Swift]]></title>
            <link>https://medium.com/tokopedia-engineering/validating-social-media-urls-in-swift-5df900b7bcad?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/5df900b7bcad</guid>
            <category><![CDATA[swift-validator]]></category>
            <category><![CDATA[social-media]]></category>
            <category><![CDATA[url]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[url-validator]]></category>
            <dc:creator><![CDATA[Alvin Matthew Pratama]]></dc:creator>
            <pubDate>Mon, 30 May 2022 06:05:22 GMT</pubDate>
            <atom:updated>2022-05-30T06:05:22.337Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j86rEwrJmyC5up3YWBNwAA.jpeg" /></figure><h3>Background</h3><p>In February 2022, Tokopedia introduced an <a href="https://affiliate.tokopedia.com/">affiliate program</a> to all Tokopedia users and the public. To become an affiliate, users need to register their social media accounts by providing the social media URLs. As each social media URL has a different domain and URL path, the application needs to validate the URL according to the social media.</p><p>We agree that <a href="https://en.wikipedia.org/wiki/Regular_expression">regex</a> is one of the easiest ways to solve many parsing problems However, this case is complex due to several rules for each social media and will be challenging to customize in the future. Some social media have more than one URL path for the user page. For example, Youtube has several paths as follows:</p><pre><a href="https://www.youtube.com/user/example">https://www.youtube.com/user/example</a><br>https://www.youtube.com/c/coldplay<br>https://www.youtube.com/channel/UC9zY...</pre><p>One way to solve this is by creating a reusable validator that allows validating specific social media URLs in the simplest way. This article will use and introduce <a href="https://github.com/tokopedia/ios-supervalidator">SuperValidator</a> as our swift validator library to solve this problem.</p><h3>SuperValidator</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CB_eS34xTnFFJ798MWKRDQ.png" /></figure><p><a href="https://github.com/tokopedia/ios-supervalidator">SuperValidator</a> is an option-based validator and an open-source library managed by Tokopedia iOS Engineers. The validator provides options to customize the validation according to your needs. Let’s go through this article to know more about this!</p><h3>URL Validator</h3><p>If we look at the URL anatomy, we can create separate validation for each of the URL’s components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nHylTd9wjy7-lE9uTi0XlQ.png" /></figure><p>SuperValidator has split up the components as parameters such as the following:</p><pre>protocols: [String] = [&quot;https&quot;, &quot;http&quot;, &quot;ftp&quot;],<br>requireProtocol: Bool = false,<br>requireValidProtocol: Bool = true,<br>paths: [String] = [],<br>allowQueryComponents: Bool = true,<br>domainWhitelist: [String] = [],<br>domainBlacklist: [String] = [],<br>fqdn: FQDN = .init() // Fully Qualified Domain Name</pre><h4>protocols</h4><p>An array of strings for valid protocols, such as http or https. If we store https only then it will only allow https.</p><h4>requireProtocol</h4><p>If set to true, then the URL must have a protocol.</p><h4>requireValidProtocol</h4><p>If set to true, then the URL protocol must be the same as one of the valid protocols listed in the protocols options.</p><h4>paths</h4><p>An array of strings for URL paths /shop/{shopID}. <br>There are two types, which include fixed and value.</p><pre>/shop/{shopID}</pre><p>The path will be separated by /, <br>shop is a fixed type, which means the URL path inputted must be exactly the same. <br>{shopID} is a value type, curly braces {} indicate the content inside is the value type.</p><h4>allowQueryComponents</h4><p>If set to false, the query component and anchor are not allowed.</p><h4>domainWhitelist</h4><p>An array of strings for the whitelist domain. It can also use regex.</p><h4>domainBlacklist</h4><p>An array of strings for the blacklist domain. It can also use regex.</p><h4>FQDN</h4><p>FQDN stands for Fully Qualified Domain Name. It will check if the domain is good to go.</p><h3>Basic Usage</h3><p>We can just call isURL function to return a boolean that indicates a valid URL.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9d4e15de60b16441cab8df88c9b8a9e/href">https://medium.com/media/a9d4e15de60b16441cab8df88c9b8a9e/href</a></iframe><p>If you want to customize the validator, you can set the options parameters covered in the previous section.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/34a33ff494951f862b11082ffe451494/href">https://medium.com/media/34a33ff494951f862b11082ffe451494/href</a></iframe><p>You can also use validateURL function to return success or error result. You can use the error type provided by SuperValidator to generate an error message.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2c3d3d1f489a3d7a8b1e32b9c1d075ab/href">https://medium.com/media/2c3d3d1f489a3d7a8b1e32b9c1d075ab/href</a></iframe><h3>Use Cases</h3><p>Let’s break down the users’ social media URLs. They have different domains and paths, and some of them have multiple valid paths. Cases like this can also happen in other URLs. We can create an enum to store options for every social media.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/953e3c79da979dc2ca6eebbad5f704e9/href">https://medium.com/media/953e3c79da979dc2ca6eebbad5f704e9/href</a></iframe><p>Then you can use isURL or validateURL function from SuperValidator.</p><pre><strong>SuperValidator.shared</strong>.isURL(<br>   &quot;https://instagram.com/myuser&quot;, <br>   options: SocialMediaURL.instagram.options<br>)</pre><p><strong>Fortunately</strong>, you don’t need to create a new function for social media URL validation, because SuperValidator already covers this. You can call isURL or validateURL with socialMedia parameters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4eb48ff4e87fda3b3f62a3fe00bc19eb/href">https://medium.com/media/4eb48ff4e87fda3b3f62a3fe00bc19eb/href</a></iframe><p>You can use validateURL to get the error reasons and give it to the user.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c0aaa3a07fd90ec2e3b469194064c1e2/href">https://medium.com/media/c0aaa3a07fd90ec2e3b469194064c1e2/href</a></iframe><p>For more test cases you can see this <a href="https://github.com/tokopedia/ios-supervalidator/blob/main/Tests/SuperValidatorTests/SocialMediaURLValidatorTests.swift">link</a>.</p><h3>Other Validators</h3><p>Besides URL, there are several validators that SuperValidator already covers:</p><h4>FQDN</h4><p>Help validate fully qualified domain names. (<a href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name">Wikipedia</a>)</p><h4>Email</h4><p>Help validate email with options such as length limit of the <a href="https://en.wikipedia.org/wiki/Email_address#:~:text=The%20general%20format%20of%20an,of%20the%20recipient&#39;s%20mail%20system.">local part</a>, whitelist, and blacklist domain.</p><h4>Phone number</h4><p>Help validate phone numbers with International, North American, and Extensible Provisioning Protocol formats, validate country code or the <a href="https://www.iban.com/dialing-codes">national prefix</a>.</p><h3>Future Plans</h3><ul><li>More validators</li><li>Bind with <a href="https://developer.apple.com/documentation/uikit/uitextfield">UITextField</a></li><li>Bind with <a href="https://github.com/ReactiveX/RxSwift">RxSwift</a></li></ul><h3>Contribute🚨</h3><p>Calling contributors!! SuperValidator is open-source, and we’re excited to hear and learn from you. Your experiences will benefit others who use SuperValidator. Let’s make it even better!</p><blockquote><a href="https://github.com/tokopedia/ios-supervalidator">https://github.com/tokopedia/ios-supervalidator</a></blockquote><h3>Credits</h3><p>Thanks to <a href="https://www.linkedin.com/in/christophermienarto/">Christopher Teddy</a> for helping with the development. Kudos to Superman squad and all developers who helped make this happen.</p><p>As always, we have an opening at Tokopedia.<br>We are an Indonesian technology company with a mission to democratize commerce through technology, and our vision is to build an ecosystem where everyone can start and discover anything.<br>Find your Dream Job with us in Tokopedia!<br><a href="https://www.tokopedia.com/careers/">https://www.tokopedia.com/careers/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5df900b7bcad" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/validating-social-media-urls-in-swift-5df900b7bcad">Validating Social Media URLs in Swift</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tokopedia Android App Journey to Support Dark Mode]]></title>
            <link>https://medium.com/tokopedia-engineering/tokopedia-android-app-journey-to-support-dark-mode-71c0beec8668?source=rss----81e70bf054ed---4</link>
            <guid isPermaLink="false">https://medium.com/p/71c0beec8668</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[ui-design]]></category>
            <category><![CDATA[dark-mode]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[tokopedia]]></category>
            <dc:creator><![CDATA[Yehezkiel L]]></dc:creator>
            <pubDate>Fri, 20 May 2022 06:48:18 GMT</pubDate>
            <atom:updated>2022-05-20T07:27:05.887Z</atom:updated>
            <content:encoded><![CDATA[<p><em>co-authored by </em><a href="https://medium.com/u/5836380cffe6"><em>IJ</em></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*rJLGrzURKW145hopP4JM0A.jpeg" /></figure><p>Android 10 has included the Dark Theme to its devices a while ago, and its adopters are rising quickly over time. Dark Mode has many benefits, such as reduced battery usage, improved visibility, reduced eye strain, etc.</p><p>This article will share and explain Tokopedia team’s journey to convert our Android App to support Dark Mode. It will be bundled with some technical implementation as well.</p><h3>Opening</h3><p>We know that users have a massive demand for the Dark Mode feature. Well, the wait is over!!, Tokopedia has officially published its support for <strong>Dark Mode in the Beta phase</strong>. We’ve spent a lot of time and effort converting our Tokopedia App, which consists of hundreds of modules to accommodate dark mode support. There were lots of problems that we encountered in our quest. I will share all of them in this article and the solution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GBgxqS9HflErawKkU4sUaA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7nLxwaNFNt5AC2M3JlKiAQ.png" /><figcaption>Bill Page</figcaption></figure><h3>Prerequisite</h3><ul><li><strong>Basic Knowledge of Dark Mode</strong></li></ul><p>It would be best to have basic knowledge about how to make an application that supports dark mode. Therefore, this article will only share major hurdles in converting our application. For the official documentation about dark themes, please refer to this <a href="https://developer.android.com/guide/topics/ui/look-and-feel/darktheme">link</a>.</p><ul><li><strong>Color Helper Library</strong></li></ul><p>We also have an<strong> internal library for storing and managing color</strong>. This library is only a module that contains colors.xml with values-night resources. We recommend using this approach. So the color in your <strong>application is centralized in one place, and less effort is needed to do any maintenance.</strong></p><ul><li><strong>Force App to Light Mode</strong></li></ul><p>While we were in the development phase, we applied the code that forced the app to <strong>light mode only</strong>. Force to light mode will <strong>prevent the app from using dark mode in production</strong> while developers are still in the development phase.</p><p>Imagine if we have ten pages and only two of them already supports the dark mode and the device theme of the users is in the dark theme, then the users will have a broken experience with <strong>dark and light mode mixed up in the app</strong>. If we don’t force the app to light mode, the app will follow the device system theme.</p><p>Use the below code to force your app to light mode (place it in your application class).</p><pre>AppCompatDelegate.<em>setDefaultNightMode</em>(<br>AppCompatDelegate.<em>MODE_NIGHT_NO</em>);</pre><p>Finally, after all of the pages support dark mode. Then we can start using the default system theme and begin to remove the code that we created to force to light mode.</p><h3>How do we convert hundreds of modules to Dark Mode <strong>with as little effort as possible</strong>?</h3><p>Since we have hundreds of modules owned by several teams, the manual approach is not feasible and requires too much effort. The manual approach means we have to change the colors one by one, create night res folders for colors, or even check and change the theme. Those could take forever to convert and the chance of making a mistake is high.</p><p>Our solution is to create an automation script to convert each particular module to dark mode.</p><h3>Automation <strong>Script</strong></h3><p>The script is created in python. Basically, it will traverse to the selected module and <strong>scan all of the files that generally contain colors</strong> such as .kt, .java, .xml. The script will ignore files with types aside from the ones mentioned before.</p><p>If the script finds the type of file, for example, ProductFragment.kt, it will open the file and read lines one by one to search for any occurrence of color.</p><pre>for line in fileinput.input({path_to_product_fragment}, inplace=1):<br>    # find if line contains color with regex</pre><p>Regex is used to get each color in each particular step because each step has different color target to be replaced. This regex for example :</p><pre>&#39;com\.[\w_]+\.[\w_]+\.R\.color\.[\w_]+|R\.color\.\w+|\.color\.\w+|@color/\w+|android.R.color.\w+|@android:color/\w+</pre><p>After a color is found in a line. The script will check whether the color is available on our dark mode library. If it’s available, then the color needs to be converted to the correct color. Then the script will get the certain color that matches and replace it. Example of library :</p><pre>Old Color, Dark Mode Color</pre><pre>R.color.Blue_B100,R.color.Unify_B100<br>@color/Blue_B100,@color/Unify_B100<br>R.color.Blue_B200,R.color.Unify_B200<br>@color/Blue_B200,@color/Unify_B200<br>R.color.Blue_B300,R.color.Unify_B300<br>@color/Blue_B300,@color/Unify_B300<br>R.color.Blue_B400,R.color.Unify_B400</pre><p>For instance, in this line of xml file, we get the color as @color/Blue_B100 and the script will change it based on the color library.</p><pre>android:textColor=&quot;@color/Blue_B100&quot; &lt;- before converted</pre><pre>android:textColor=&quot;@color/Unify_B100&quot; &lt;- after converted</pre><h3>How it works?</h3><p>The high level flow can be presented in this flow chart:</p><figure><img alt="Simplify Flow" src="https://cdn-images-1.medium.com/max/206/1*w0OR143IbXCFPtf7PpeGBA.png" /></figure><p>In Tokopedia we’re doing three steps to completely convert all colors to dark mode support :</p><ol><li><strong>Convert all existing colors from the library that are not supported</strong> <strong>as the dark mode</strong>.<br>As we mentioned before, We have an internal library that centralized our color. Unfortunately, the previous colors that we used are not supported as a dark mode because those do not have night value. So we run the script as we describe above.</li><li><strong>Convert all custom colors into our internal colors.<br></strong>In Tokopedia we have hundreds of modules, each module has its custom colors (usually located at res -&gt; values -&gt; colors.xml). It’s another problem if we want to convert it to dark mode support. So the script will do :<br>- Get all of the custom colors in our source code then place it in one place.<br>- Get the hex color of each and find the nearest similar color to our color library. The nearest color script will return the correct closest color result.<br>- Change the color to the result and change the color in class.</li><li><strong>Convert all hard coded colors into our internal colors.<br></strong>In this case, we will cater the hard coded hex color in our class, for example, android:background=”#123456&quot;. The approach is fairly similar to the first one :<br>1. Loop all of the android classes<br>2. Find hex color by using this regex r’#([0–9a-fA-F]{3}|[0–9a-fA-F]{6}|[0–9a-fA-F]{8})\b’<br> 3. Get the hex color and pass into nearest color picker script. The script will return our correct library color. Example : #123213 will turn into @color/Unify_Black<br>4. Change the color in class to the result correct color.</li></ol><h3><strong>Other Tool</strong></h3><ul><li><strong>Nearest Color Script. </strong>You can create your own algorithm to find the closest color to color, by giving the list of colors. By using <a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm">KNN Algorithm</a>, <em>python</em> already has a library to find the nearest neighbor by using scipy, example <a href="https://stackoverflow.com/a/57497281">click here.</a><br>Turn your library color and your input color to <em>RGB</em> . Submit to the <em>scipy</em> and get the nearest <em>RGB</em> in the list.</li><li><strong>Theme Changer Script. </strong>This script would change our existing theme to theme that recommended by google to dark mode support (Theme.AppCompat.DayNight).</li><li><strong>Dark Mode Coverage Dashboard<br></strong>To maintain dark mode coverage in our app and ensure all of the new codes are ready for dark mode, We’ve created a dashboard to track whether the modules are ready or not. This tool is also linked to Pull Request Checker in our Jenkins. <br>If there is new pull request that detected with less than 100% coverage, then the <strong>PR will be block and developer’s feature can’t be merge to our master branch</strong>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/1*rl8ur9Kavtr4vV9It06tyA.png" /></figure><h3>Best Practice</h3><ul><li>Prevent creating theme styles with the same name; the exact name can cause dark mode not to work.</li><li>If using CardView, use app:cardBackgroundColor instead of android:background to apply dark mode color.</li><li>Use a custom color that doesn’t have any night-valuesto the color you don’t want to change in dark mode.</li><li>Beware of strings.xml; always check this class because the color resource is possible located in strings.xml file.</li><li>Avoid color reference (@color/blue_night) in .xml that contains vector; just hardcoded the color with hex (#003c94) to prevent crash in lower os.</li><li>Create a unique identifier to prevent the script to keep your intended custom color. So whenever the script is re-run, your color will not be converted. For example, I’m using _dms_ (dark mode support) as an identifier :<br>&lt;color name=”product_detail_dms_tradein_blue”&gt;#32afff&lt;/color&gt;</li><li>If your app contains webview, we also need to support it to dark mode. First thing first, the web itself should support dark mode. Then to tell the web whether we need to use dark mode or not is, the app needs to send some additional flag to webview from the header. Google also has an approach for this check <a href="https://developer.android.com/guide/webapps/dark-theme">here</a>.</li><li>In case to keep the dark mode experience always good and on track, we need some prevention and automation to block the feature that not using dark mode color. Usually, we block the Github pull request auto merge and wait till developer fix with the correct color.</li></ul><h3>Result</h3><p>These are some of the results of dark mode support in our Tokopedia customer app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qyT2k0D1xwJnDgvRW7SMZw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M43T1hRqadQqRYMvSwcBPg.png" /><figcaption>Home Page</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Oyy1n6IWhgE8Fa2yThThg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TLMlnQLyh_tAQywtMA9rRg.png" /><figcaption>Product Page</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SU3QyjXXKKAW_akd51r_ww.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*--Vc93j7SBLy_kMMgPTRHw.png" /><figcaption>Chat Page</figcaption></figure><p>That’s pretty much how we convert our app to dark mode. Automation helps us a lot and reduces human errors. But we still have to continuously improve our dark mode experience because some of the cases need to be manually converted and are not being catered in the script.</p><p>As always, we have an opening at Tokopedia. We are an Indonesian technology company with a mission to democratize commerce through technology and help everyone achieve more. Find your Dream Job with us in Tokopedia!<br><a href="https://www.tokopedia.com/careers/">https://www.tokopedia.com/careers/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=71c0beec8668" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tokopedia-engineering/tokopedia-android-app-journey-to-support-dark-mode-71c0beec8668">Tokopedia Android App Journey to Support Dark Mode</a> was originally published in <a href="https://medium.com/tokopedia-engineering">Tokopedia Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>