Mustafa Ali2023-12-24T18:20:19+00:00https://mustafaali.net/Mustafa AliThings to do before releasing an app2023-01-18T00:00:00+00:00https://mustafaali.net/2023/01/18/things-to-do-before-releasing-an-app<p align="center">
<img src="/static/post-img/things-to-do-before-app-release/app-development.webp" />
<h6>
Photo by <a href="https://unsplash.com/@balazsketyi?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Balázs Kétyi</a> on <a href="https://unsplash.com/photos/sScmok4Iq1o?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</h6>
</p>
<p>So you’ve been hard at work building an amazing app and are getting ready to share it with the world. As the release date approaches, you start wondering you if you’ve done everything you are supposed to. You want to get this right. It’s hard keeping track of everything even if you’ve done this before, so here’s a simple list to follow:</p>
<h2 id="set-the-correct-application--bundle-id">Set the correct Application / Bundle ID</h2>
<p>Mobile apps need to have a unique <a href="https://developer.android.com/studio/build/configure-app-module#set-application-id">Application ID</a> (Android) / <a href="https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids">Bundle ID</a> (iOS). You can’t change this once the app is published and will be stuck with it forever. Make sure you set the right value. The best-practice for this is to use the reverse DNS notation. If your website is <code class="language-plaintext highlighter-rouge">myawesomeproduct.com</code>, your application / bundle ID should be <code class="language-plaintext highlighter-rouge">com.myawesomeproduct</code>. Some people use <code class="language-plaintext highlighter-rouge">com.myawesomeproduct.mobile</code> or <code class="language-plaintext highlighter-rouge">com.myawesomeproduct.[android/ios]</code> and that’s OK too.</p>
<h2 id="remove-all-hardcoded-secrets-from-the-code">Remove all hardcoded secrets from the code</h2>
<p>Make sure you don’t have any hardcoded secrets like encryption and API keys in your code. Reverse engineering mobile apps is getting easier and if an attacker is sufficiently motivated, they will find and exploit these secrets. Obfuscating your code using tools like Proguard only slows them down, but doesn’t stop them. For keys you have to include in the app, make sure they only work if the application / bundle id matches the one for your application.</p>
<h2 id="test-on-low-mid-and-top-tier-devices">Test on low, mid and top-tier devices</h2>
<p>Testing on emulator/simulator is fine during development, but make sure to test on a wide range of devices before release. You’ll be tempted to test only on the latest flagships, but don’t. Identify a good selection of low, mid and top-tier devices and test on them.</p>
<h2 id="test-on-devices-with-different-screen-sizes">Test on devices with different screen sizes</h2>
<p>Test on both small and large screens. It’s easy to miss UI bugs if you don’t do this. Make sure images scale correctly, text doesn’t get truncated, and all animations work as expected.</p>
<h2 id="test-different-screen-orientations">Test different screen orientations</h2>
<p>Test the app in both portrait and landscape mode. Also test switching between those modes while using the app. It’s easy to miss exceptions and memory leaks that occur due to orientation changes.</p>
<h2 id="test-on-different-os-versions">Test on different OS versions</h2>
<p>First make sure you’ve set the right minimum supported versions for your app. Then make sure you test on at lease the oldest and newest version. There can be subtle difference in API behavior across versions that are easy to overlook. Some versions may have additional rules for using permissions so make sure you account for that.</p>
<h2 id="test-on-devices-from-different-manufacturers">Test on devices from different manufacturers</h2>
<p>This is relevant to Android. Manufacturers sometimes change the behavior of an API under the hood causing the app to behave differently. Pick devices from all the popular device manufactorers (Samsung, Motorola, Google, Xiaomi, etc) and test the app on them. If you don’t want to invest upfront to buy so many test devices, use a service like <a href="https://browserstack.com">BrowserStack</a>.</p>
<h2 id="test-on-slow-networks">Test on slow networks</h2>
<p>Not everyone has access to high-speed WiFi. Test the app on slow, flaky networks to make sure your app works properly.</p>
<h2 id="test-with-all-supported-languages">Test with all supported languages</h2>
<p>If you support multiple languages, test that the translations are rendered correctly. Some languages like German have very long words compared to English which breaks UIs in interesting ways. RTL languages like Arabic read right to left and can also lead to interesting bugs if you are not careful.</p>
<h2 id="test-with-screen-readers">Test with screen readers</h2>
<p>At least 2.2 billion people worldwide are visually impared in some capacity (<a href="https://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment">source</a>). Make sure your app works properly with <a href="https://www.apple.com/accessibility/vision/">Voice Over</a> on iOS and <a href="https://support.google.com/accessibility/android/answer/6283677?hl=en">Talkbalk</a> on Android. Depending on which countries you launch in, this may also be a legal requirement.</p>
<h2 id="have-a-way-to-monitor-stability">Have a way to monitor stability</h2>
<p>Make sure you have crash reporting implemented in the app and familiarize yourself with it. Verify that the crashes are being reported and learn how to monitor the stability of a build. You can use services like <a href="https://www.bugsnag.com/">Bugsnag</a> and <a href="https://firebase.google.com/docs/crashlytics/">Firebase Crashlytics</a> for this.</p>
<h2 id="create-a-playbook-to-release-a-hot-fix">Create a playbook to release a hot-fix</h2>
<p>Releases often do not go as planned so be prepared to release a hot-fix quickly. Write down the steps needed to create a new build and how to publish it to the app stores. Share this broadly so that everyone on your team knows what to do. Make sure all the right people have sufficient permissions to do this. You don’t want to get stuck in position where you have to do an emergency release and the person with app store access is on vacation.</p>
<h2 id="set-up-a-way-to-monitor-and-reply-to-app-store-reviews">Set up a way to monitor and reply to app store reviews</h2>
<p>Lots of bugs are reported in the form of reviews on the app stores. People are very likely to write a review when something doesn’t work as expected and give your app a bad rating. Your app’s app store rating directly impacts search ranking and user conversion so do everything you can to keep it high. Reply to both positive and negative reviews. People often update their review and rating if you reply to them after fixing the issue. Google and Apple don’t make it easy to do this, so use a service like <a href="https://appfigures.com/">Appfigures</a> instead.</p>
<h2 id="get-a-security-audit-done">Get a security audit done</h2>
<p>Building a secure app is hard and it’s easy to make mistakes even though you may have years of experience. Get professional security researchers to perform a security audit on your app and fix any issues they find. If you work for a large company, you will most likely have an in-house team for this. If that’s not the case, there are lots of companies who you can pay to do this for you. This may seem overkill, but it’s better to be safe when the stakes are high.</p>
<h2 id="implement-a-kill-switch">Implement a kill-switch</h2>
<p>First off, congrats on reading this far.</p>
<p>If you do everything I wrote above, there’s a very good chance that your release will go as planned. However, <a href="https://en.wikipedia.org/wiki/Murphy's_law">Murphy’s Law</a> is real and it’s extremely valuable to have a way to disable the app remotely at any time. Security vulnerabilities and bugs that cause loss of money or user info are prime examples of when to use this ability.</p>
<p>The most common way of implementing this is to have a mechanism on the server to block versions older than a specific version of the app from being usable and having client-side code to show provide instructions to the user to update the app. Test this thoroughly and document how to use it so that everyone on the team knows how to do it. Every minute matters when there is a critical issue in production.</p>
<p>Good luck!</p>
Using honesty to escape competition2022-02-07T00:00:00+00:00https://mustafaali.net/2022/02/07/using-honesty-to-escape-competition<blockquote>
<p>“Honesty is actually a blunt instrument, which bloodies more than it cuts. Your honesty is likely to offend people; it is much more prudent to tailor your words, telling people what they want to hear rather than the coarse and ugly truth of what you feel or think.” - Robert Greene, The 48 Laws of Power</p>
</blockquote>
<p>It’s funny how as children, our parents insist us to always be honest, but grown ups are completely incapable of dealing with absolute honesty. Don’t believe me? Try being honest when you partner asks if they are looking fat in an outfit and see how it goes.</p>
<p>As much as we say that “feedback is a gift” and that “we are always looking to improve”, we like people’s honesty mostly when they are praising us. It is nowhere more evident than at work, where people are constantly trying to be in their managers’ good books. You can’t blame them though. When was the last time you saw someone get promoted because they shared their honest opinions all the time? More often than not, such reports are quickly “managed out” of the organization.</p>
<p>I think that honesty, when used with tact, can allow you stand out and be very successful in your career.</p>
<p>As companies get bigger, senior leadership has a hard time keeping their ears to the ground. Good leaders know that having an accurate read of what’s working and what isn’t is essential to be able to do their jobs well. They also know that they are surrounded by yes-men/women who don’t want to risk saying anything controversial and upset their boss.</p>
<p>The best leaders seek out and stay in touch with people who can give them their honest opinions. They do this with people regardless of their titles or seniority in the company.</p>
<p>Here’s how you can end up being one of those people:</p>
<ol>
<li>
<p><strong>Form a well-rounded opinion first</strong> - When you see something you don’t understand, assume positive intent. Lots of people tend to form strong negative opinions without having the full context.</p>
</li>
<li>
<p><strong>Be honest, without being overly negative</strong> - Being honest doesn’t mean you whine about issues all the time. No one likes such people and if you only complain about things, others will simply ignore you.</p>
</li>
<li>
<p><strong>It’s OK to not have an opinion</strong> - When asked for their opinion, people feel compelled to say things just for the sake of doing so. If you don’t have a well-formed opinion yet, say that. They will respect you for it. You can also offer to think about and get back to them.</p>
</li>
<li>
<p><strong>Be open to changing your mind</strong> - Everyone is wrong a lot of the time. It’s OK to admit that you were wrong when provided with facts that you weren’t aware of. Don’t pick a side and stick to it because of your ego. That’s childish.</p>
</li>
</ol>
<p>If you do this well, you’ll stand out in a crowd full of people desperately trying to suck up to their bosses. Leaders will seek you out for advice because their respect your opinion, and give you lots of growth opportunities.</p>
How to monitor the status of your website for free2021-03-12T00:00:00+00:00https://mustafaali.net/2021/03/12/how-to-monitor-website-status-for-free<p>In a <a href="/2019/07/03/how-to-run-your-personal-website-for-free/">previous blog post</a>, I showed how easy it is to host a personal website for free. In this one, I’ll show how you can monitor it 24x7 and get alerted if it goes down for, you guessed it, free!</p>
<p>To be clear, this website hasn’t ever gone down in the several years I’ve been running it. The beauty of using a service like Gitlab Pages is that you don’t have to worry about managing uptime. You just make changes locally, push them to your Git remote and let them take care of the rest.</p>
<p>Why monitor uptime then? Well, I needed a way to play with <a href="https://workers.cloudflare.com/">Cloudflare Workers</a>, so using them to monitor the uptime of this website seemed like a good idea. Cloudflare Workers allow you to deploy serverless code on their edge nodes around the world instantly without worrying about things like load balancing, auto-scaling, etc.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/cloudflare-workers-home-page.png" />
</p>
<p>The entire process took less than an hour and now every two minutes, a Worker running on a Cloudflare edge node somewhere in the world checks if this website is online. If it isn’t, it sends me an alert via Slack and I also have a nice status page over at <a href="https://status.mustafaali.net">status.mustafaali.net</a> to track uptime over the last 90 days.</p>
<p>To get started, go to <a href="https://github.com/eidam/cf-workers-status-page">this</a> Github project and click on the Deploy with Workers button. You’ll be prompted to give Cloudflare Workers permission to fork the project and deploy it using Github Actions.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/deploy-to-workers.png" />
</p>
<p>After that, you’ll have to provide an API token for your Cloudflare account so that the Worker can be deployed and it can read/write to key-value storage. You can generate one under the profile section in the Cloudflare dashboard and use the handy template that is provided.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/api-token.png" />
</p>
<p>Now, go to <code class="language-plaintext highlighter-rouge">config.yaml</code> and add a monitor for the website you want to track.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/add-monitor.png" />
</p>
<p>Next, change the frequency of the cron job in <code class="language-plaintext highlighter-rouge">wrangler.toml</code> to run every 2 minutes (<code class="language-plaintext highlighter-rouge">*/2 * * * *</code>) instead of every minute (<code class="language-plaintext highlighter-rouge">* * * * *</code>). This will make sure you don’t exceed Cloudflare’s free quota.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/update-cron.png" />
</p>
<p>Finally, go to the Settings tab in your repo and add your Slack webhook URL as a secret.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/slack-webhook-secret.png" />
</p>
<p>Push these changes and let Github Actions deploy the Worker on Cloudflare’s infrastructure. While you wait, create a <code class="language-plaintext highlighter-rouge">CNAME</code> record in your DNS settings to make the status page available on your domain. You can pick any subdomain you like, just set the value as the URL of the Worker you just deployed.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/dns-record.png" />
</p>
<p><br /></p>
<p>That’s it! In a couple of minutes, you’ll get a Slack notification with the status of your website.</p>
<p><br /></p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/slack-notification.png" />
</p>
<p><br /></p>
<p>And, the status page will be available at the subdomain you chose in the last step 🎉.</p>
<p align="center">
<img src="/static/post-img/monitor-website-for-free/status-page.png" />
</p>
Building reliable mobile applications2020-08-14T00:00:00+00:00https://mustafaali.net/2020/08/14/building-reliable-mobile-apps<p><em> This post was originally <a href="https://engineering.shopify.com/blogs/engineering/making-shopify-point-of-sale-reliable">published</a> on Shopify’s Engineering Blog. I’m re-posting it here for posterity.</em></p>
<p align="center">
<img src="/static/post-img/reliable-mobile-apps/tap-chip-hero.jpg" />
</p>
<p>Merchants worldwide rely on Shopify’s Point Of Sale (POS) app to operate their brick and mortar stores. Unlike many mobile apps, the POS app is mission-critical. Any downtime leads to long lineups, unhappy customers, and lost sales. The POS app must be exceptionally reliable, and any outages resolved quickly.</p>
<p>Reliability engineering is a well-solved problem on the server-side. Back-end teams are able to push changes to production several times a day. So, when there’s an outage, they can deploy fixes right away.</p>
<p>This isn’t possible in the case of mobile apps as app developers don’t own distribution. Any update to an app has to be submitted to Apple or Google for review. It’s available to users for download only when they approve it. A review can take anywhere between a few hours to several days. Additionally, merchants may not install the update for weeks or even months.</p>
<p>It’s important to reduce the likelihood of bugs as much as possible and resolve issues in production as quickly as possible. In the following sections, we will detail the work we’ve done in both these areas over the last few years.</p>
<h1 id="testing">Testing</h1>
<p>We rely heavily on automation testing at Shopify. Every feature in the POS app has unit, integration, functional, and UI snapshot tests. Developers on the team write these simultaneously as they are adding new functionality to the code-base. Changes aren’t merged unless they include automated tests that cover them. These tests run for each push to the repo in our Continuous Integration environment. You can learn more about our testing strategy here.</p>
<p>Besides automation testing, we also perform manual testing at various stages of development. Features like pairing a Bluetooth card reader or printing a receipt are difficult to test using automation. While we use mocks and stubs to test parts of such features, we manually test the full functionality.</p>
<p>Sometimes tests that can be automated, inadvertently end up in the manual test suite. This causes us to spend time testing something manually when computers can do that for us. To avoid this, we audit the manual test suite every few months to weed out all such test cases.</p>
<h1 id="code-reviews">Code Reviews</h1>
<p>Changes made to the code-base aren’t merged until reviewed by other engineers on the team. These reviews allow us to spot and fix issues early in the life-cycle. This process works only if the reviewers are knowledgeable about that particular part of the code-base. As the team grew, finding the right people to do reviews became difficult.</p>
<p>To overcome this, we have divided the code-base into components. Each team owns the component(s) that make up the feature that they are responsible for. Anyone can make changes to a component, but the team that owns it must review them before merging. We have set up Code Owners so that the right team gets added as reviewers automatically.</p>
<p>Reviewers must test changes manually, or in Shopify speak, “tophat”, before they approve them. This can be a very time-consuming process. They need to save their work, pull the changes, build them locally, and then deploy to a device or simulator. We have automated this process, so any Pull Request can be top-hatted by executing a single command:</p>
<p><code class="language-plaintext highlighter-rouge">dev android tophat <pull-request-url></code></p>
<iframe width="853" height="480" src="https://www.youtube-nocookie.com/embed/qTFrKQVFhrk" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p><code class="language-plaintext highlighter-rouge">dev ios tophat <pull-request-url></code></p>
<iframe width="853" height="480" src="https://www.youtube-nocookie.com/embed/_DSlzkLsECQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>You can learn more about mobile tophatting at Shopify <a href="https://engineering.shopify.com/blogs/engineering/mobile-tophatting-at-shopify-1">here</a>.</p>
<h1 id="release-management">Release Management</h1>
<p>Historically, updates to POS were shipped whenever the team was “ready.” When the team decided it was time to ship, a release candidate was created, and we spent a few hours testing it manually before pushing it to the app stores.</p>
<p>These ad-hoc releases made sense when only a handful of engineers were working on the app. As the team grew, our release process started to break down. We decided to adopt the release train model and started shipping monthly.</p>
<p>This method worked for a few months, but the team grew so fast that it wasn’t working anymore. During this time, we went from being a single engineering team to a large team of teams. Each of these teams is responsible for a particular area of the product. We started shipping large changes every month, so testing release candidates was taking several days.</p>
<p>In 2018, we decided to switch to weekly releases. At first, this seemed counter-intuitive as we were doing the work to ship updates more often. In practice, it provided several benefits:</p>
<ul>
<li>The number of changes that we had to test manually reduced significantly.</li>
<li>Teams weren’t as stressed about missing a release train as the next train left in a few days.</li>
<li>Non-critical bug fixes could be shipped in a few days instead of a month.</li>
</ul>
<p>We then made it easier for the team to ship updates every week by introducing Release Captain and ShipIt Mobile.</p>
<h2 id="release-captain">Release Captain</h2>
<p>Initially, the engineering lead(s) were responsible for shipping updates, which included:</p>
<ul>
<li>making sure all the changes are merged before the cut-off</li>
<li>incrementing the build and version numbers</li>
<li>updating the release notes</li>
<li>making sure the translations are complete</li>
<li>creating release candidates for manual testing</li>
<li>triaging bugs found during testing and getting them fixed</li>
<li>submitting the builds to app stores</li>
<li>updating the app store listings</li>
<li>monitoring the rollout for any major bugs or crashes</li>
</ul>
<p>As you can see, this is quite involved and can take a lot of time if done by the same person every week. Luckily, we had quite a large team, so we decided to make this a rotating responsibility.</p>
<p>Each week, the engineer responsible for the release is called the Release Captain. They work on shipping the release so that the rest of the team can focus on testing, fixing bugs, or working on future releases.</p>
<p>Each engineer on the team is the Release Captain for two weeks before the next engineer in the schedule takes over. We leverage PagerDuty to coordinate this, and it makes it very easy for everyone to know when they will be Release Captain next. It also simplifies planning around vacations, team offsites, etc.</p>
<p>To simplify things even further, we configured our friendly chatbot, spy, to automatically announce when a new Release Captain shift begins.</p>
<p><img src="/static/post-img/reliable-mobile-apps/spy-rc-announcement.jpg" alt="spy-release-captain-announcement" /></p>
<p><br /></p>
<p><img src="/static/post-img/reliable-mobile-apps/rc-reminder.jpg" alt="release-captain-reminder" /></p>
<h2 id="shipit-mobile">ShipIt Mobile</h2>
<p>We’ve automated most of the manual work involved in doing releases using ShipIt Mobile. With just a few clicks, the Release Captain can generate a new release candidate.</p>
<p><img src="/static/post-img/reliable-mobile-apps/shipit-mobile.png" alt="shipit-mobile" /></p>
<p>Once ready, the rest of the team is automatically notified in Slack to start testing.</p>
<p><img src="/static/post-img/reliable-mobile-apps/shipit-slack-notifications.png" alt="shipit-mobile" /></p>
<p>After fixing all the bugs found, the update is submitted to the app store with just a single click. You can learn more about ShipIt Mobile <a href="https://engineering.shopify.com/blogs/engineering/mobile-release-engineering-scale-shipit-mobile">here</a>. These improvements not only make weekly releases easier, but they also make it significantly faster to ship hotfixes in case of a critical issue in production.</p>
<h2 id="staged-rollouts">Staged Rollouts</h2>
<p>Despite our best efforts, bugs sometimes slip into production. To reduce the surface area of a disruption, we make the updates available only to a small fraction of our user base at first. We then monitor the release to make sure there are no crashes or regressions. If everything goes well, we gradually increase the percentage of users the update is available to over the next few days. This is done using Phased Releases and Staged Rollouts in iOS AppStore and Google Play, respectively.</p>
<p>The only exception to this approach is when a fix for a critical issue needs to go out immediately. In such cases, we make the update available to 100% of the users right away. We also can block users from using the app until they update to the latest version.</p>
<p>We do this by having the POS app query the server for the minimum supported version that we set. If the current version is older than that, the app blocks the UI and provides update instructions. This is quite disruptive and can be annoying to merchants who are trying to make a sale. So we do it very rarely and only for critical security issues.</p>
<h2 id="beta-flags">Beta Flags</h2>
<p>Staged rollouts are useful for limiting how many users get the latest changes. But, they don’t provide a way to explicitly pick which users. When building new features, we often handpick a few merchants to take part in early-access. During this phase, they get to try the new features and give us feedback that we can work on before a final release.</p>
<p>To do that, we put features, and even big refactors behind server-side beta flags. Only merchants whose stores we have explicitly set a beta flag will see the app’s new feature. This makes it easy to run closed betas with selected merchants. We also can do staged rollouts for beta flags, which gives us another layer of flexibility.</p>
<h2 id="automated-monitoring-and-alerts">Automated Monitoring and Alerts</h2>
<p>When something goes wrong in production, we want to be the first to know about it. The POS app and backend is instrumented with comprehensive metrics, reported in real-time. Using these metrics, we have dashboards set up to track the health of the product in production.</p>
<p>Using these dashboards, we can check the health of any feature in a geography with just a few clicks. For example, the % of successful chip transactions made using a VISA credit card with the Tap, Chip & Swipe reader in the UK, or the % of successful tap transactions made using an Interac debit card with the Tap & Chip reader in Canada for a particular merchant.</p>
<p>While this is handy, we didn’t want to have to keep checking these dashboards for anomalies all the time. Instead, we wanted to get notified when something goes wrong. This is important because while most of our engineering team is in North America, Shopify POS is used worldwide.</p>
<p>This is harder to do than it may seem because the volume of commerce varies throughout the year. Time of day, day of the week, holidays, seasons, and even the ongoing pandemic affect how much merchants are able to sell. Setting manual thresholds to detect issues can cause a lot of false negatives and alert fatigue. To overcome this, we leverage Datadog’s <a href="https://www.datadoghq.com/blog/introducing-anomaly-detection-datadog/">Anomaly Detection</a>. Once the selected algorithm has enough data to establish a baseline, alerts will only get fired if there’s an anomaly for that particular time of the year.</p>
<p>We direct these alerts to Slack so that the right folks can investigate and fix them.</p>
<p><img src="/static/post-img/reliable-mobile-apps/datadog-slack-1.png" alt="datadog-slack" /></p>
<h1 id="handling-outages">Handling Outages</h1>
<h2 id="air-traffic-control">Air Traffic Control</h2>
<p>In the early days of POS, bugs and outages were reported in the team Slack channel, and whoever on the team had the bandwidth, investigated them. This worked well when we had a handful of developers, but this approach didn’t scale as the team grew. Issues kept going to just a few folks who had the most context, and teams kept getting distracted from regular project work, causing delays.</p>
<p>To fix this, we set up a rotating on-call schedule called Retail ATC (Air Traffic Control). Every week, there is a group of developers on the team dedicated to monitoring how things are working in production and handling outages. These developers are responsible only for this and are not expected to contribute to regular project work. When there are no outages, ATCs spend time tackling tech debt and helping our Technical Merchant Support team.</p>
<p>Every developer on the team is on-call for two weeks at a time. The first week they are Primary ATC, and the next week they are Secondary ATC. Primary ATC is paged when something goes wrong, and they are responsible for triaging and investigating it. If they need help or are unavailable (commute time, connectivity issues, etc.), the Secondary ATC is paged. ATCs are not expected to fix all issues that arise by themselves, while often they can. They are instead responsible for working with the team that has the most context.</p>
<p><img src="/static/post-img/reliable-mobile-apps/atc-announcement.png" alt="datadog-slack" /></p>
<p>Since we offer the POS app on both Android on iOS, we have ATC schedules for developers that work on each of those apps. Some areas, like payments, for instance, need a lot of domain knowledge to investigate issues. So we have dedicated ATCs for developers that work in those areas.</p>
<p>Having folks dedicated to handling issues in production frees up the rest of the team to focus on regular project work. This approach has greatly reduced the amount of context switching teams had to do. It has also reduced the stress that comes with the responsibility of working on a mission-critical mobile application.</p>
<p>Over the last couple of years, ATC has also become a great way for us to help new team members onboard faster. Investigating bugs and outages exposes them to various tools and parts of our codebase in a short amount of time. This allows them to become more self-sufficient quickly. However, being on-call can be stressful. So, we only add them to the schedule after they have been on the team for a few months and have undergone training. We also pair them with more experienced folks when they go on call.</p>
<h2 id="incident-management">Incident Management</h2>
<p>When an outage occurs, it must be resolved as quickly as possible. To do this, we have a set of best practices that the team can follow so that we can spend more time investigating the issue vs figuring out how to do something.</p>
<p>An incident is started by the ATC in response to an automated alert. ATCs use our <a href="https://engineering.shopify.com/blogs/engineering/implementing-chatops-into-our-incident-management-procedure">ChatOps tools</a> to start the incident in a dedicated Slack channel.</p>
<p><img src="/static/post-img/reliable-mobile-apps/datadog-outage-error.png" alt="datadog-slack" /></p>
<p><img src="/static/post-img/reliable-mobile-apps/spy-incident-start.png" alt="datadog-slack" /></p>
<p>Incidents are always started in the same channel, and all communication happens in it. This is to ensure that there is a single source of information for all stakeholders.</p>
<p>As the investigation goes on, findings are documented by adding the 📝 emoji to messages. Our chatbot, spy automatically adds them to a service disruption document and confirms it by adding a ✅ emoji to the same message.</p>
<p>Once we identify the cause of the outage and verify that it has been resolved, the incident is stopped.</p>
<p><img src="/static/post-img/reliable-mobile-apps/datadog-outage-resolved.png" alt="datadog-slack" /></p>
<p><img src="/static/post-img/reliable-mobile-apps/spy-incident-stop.png" alt="datadog-slack" /></p>
<p>The ATC then schedules a Root Cause Analysis (RCA) for the incident on the next working day. We have a no-blame culture, and the meeting is focused on determining what went wrong and how we can prevent it from happening in the future.</p>
<p>At the end of the RCA, action items are identified and assigned owners. Keeping track of outages over time allows us to find areas that need more engineering investment to improve reliability.</p>
<p>Thanks to these efforts, we’ve been able to take an app built for small stores and scale it for some of our largest merchants. Today, we support a large number of businesses to sell products worth billions of dollars each year. Along the way, we also scaled up our engineering team and can ship faster while improving reliability.</p>
<hr />
<p>We are far from done, though, as each year we are onboarding bigger and bigger merchants onto our platform. If these kinds of challenges sound interesting to you, come work with us! Visit our <a href="https://www.shopify.com/careers/teams/engineering">Engineering career page</a> to find out about our open positions. Join our remote team and work (almost) anywhere. <a href="https://www.shopify.com/careers/work-anywhere">Learn about how we’re hiring to design the future together - a future that is digital by default</a>.</p>
<hr />
Advice for new engineering managers2020-05-26T00:00:00+00:00https://mustafaali.net/2020/05/26/advice-for-new-engineering-managers<p>If you are new to engineering leadership or are considering doing it, here are a few things you should know -</p>
<h3 id="great-engineer--great-manager">Great engineer != Great manager</h3>
<p>Being a great engineer does not necessarily mean you’ll be a great manager right off the bat. These are two vastly different disciplines. You will have to learn new skills, unlearn some old ones and deal with very different constraints. Don’t expect it to be a cakewalk.</p>
<h3 id="things-wont-be-binary-anymore">Things won’t be binary anymore</h3>
<p>Things on the technical track are binary - either something works or it doesn’t. In people leadership, it’s extremely rare to have that kind of clarity. Most of the time there will be no clear answer, so you just have to make tradeoffs.</p>
<h3 id="delegate-delegate-delegate">Delegate, delegate, delegate</h3>
<p>If there’s someone on the team who can do a task, don’t do it yourself. It’s tempting to go back to doing things that you have been good at, but don’t fall for it. Tackle different problems and learn new things otherwise you’ll be preventing both yourself and your team from getting better.</p>
<h3 id="your-team-will-be-your-product">Your team will be your product</h3>
<p>Previously you helped build a product, but in a leadership role, your team will be your product. Just as you used to think about making your product better, you should now think like that about your team. Hire the right people, give them opportunities to grow and figure out ways they can get better. You also need to ensure that there is no bureaucratic friction for your team. Deal with the red tape, so they focus on the product.</p>
<h3 id="you-wont-have-all-the-answers-and-thats-ok">You won’t have all the answers and that’s ok</h3>
<p>Get comfortable with not being the most knowledgable person in a room. You’ll have a lot less time to stay up to date and that’s normal. You don’t need to know all the answers, but you do need to know what questions to ask. Surround yourself with really smart people and leverage them to make the right decisions.</p>
<h3 id="most-important-things-youll-do">Most important things you’ll do</h3>
<p>The two most important things you’ll do as a manager is deciding who to hire and who to fire. Always hire for future potential and let underperformers go if you don’t see improvement. The longer you take to make that decision, the more your team will suffer.</p>
<h3 id="develop-patience">Develop patience</h3>
<p>The pace of progress on things you’ll tackle will be much, much slower. It’ll be a while before you start seeing results for things you do. This is very different from what you experience as an engineer. Also, no matter how hard you try, there will always be at least 1 person who isn’t happy with you. Don’t try to please everyone and instead focus on doing the right thing, always.</p>
How I manage my notes2020-03-25T00:00:00+00:00https://mustafaali.net/2020/03/25/how-i-manage-my-notes<p>I’ve spent a great deal of time customizing my development environment to improve productivity. Git, <a href="https://driesvints.com/blog/getting-started-with-dotfiles/">dot files</a>, IDE profiles, <a href="https://www.youtube.com/watch?v=UH6YVv9js3s">keyboard shortcuts</a> and custom macros are extremely powerful and allow configuring everything exactly how I need them to be for my workflow. All these customizations can also be exported and saved somewhere so that if my machine dies, I can set up a new one in no time.</p>
<p>As a people lead, I take a lot of notes about projects, meetings, decisions and interviews which I haven’t been able to manage as well as I can manage code. I set out to fix that by finding a solution that met these requirements -</p>
<ol>
<li>🔓 Open format</li>
<li>📵 Works offline</li>
<li>☁️ Syncs to the cloud</li>
<li>📱 Works on mobile</li>
<li>🆓 No monthly fee</li>
</ol>
<p>Armed with these, I started evaluating various options like -</p>
<ul>
<li>Google Docs</li>
<li>Evernote</li>
<li>Dropbox Paper</li>
<li>Notion</li>
<li>Bear</li>
<li>Simplenote</li>
<li>Apple Notes</li>
<li>Google Keep</li>
<li>OneNote</li>
</ul>
<p>Most of these either use proprietary formats or have a monthly subscription fee. Now, I don’t mind paying for products that provide good value, but my career (hopefully) will be longer than the lifetime of these products. I don’t want to deal with lock-ins caused by proprietary formats and spend time looking for alternatives every few years. In the end, I put together a simple solution using tools that any developer is already familiar with.</p>
<h3 id="format">Format</h3>
<p><a href="https://daringfireball.net/projects/markdown/">Markdown</a> is a lightweight markup language with plain-text formatting syntax that I was already very familiar with. Created by John Gruber (of Daring Fireball fame), it is available under a BSD-style open source license thanks to which there are a tons of editors available on all platforms. Given how popular it is, I don’t expect it go away in my lifetime. It also generates simple text files which can be easily synced to the cloud.</p>
<h3 id="editor">Editor</h3>
<p align="center">
<img src="/static/post-img/syncnotes/typora.png" />
</p>
<p>While every single IDE/code editor I already have installed on my machine support Markdown, I wanted a simple and distractions-free editor to take my notes. I chose <a href="https://typora.io/">Typora</a> since it is, in my opinion, the nicest Markdown editor available today. The best part about it is that it provides seamless live preview so I don’t have to switch between source and rendered modes. I can, however, easily switch to the source view to make syntactical changes if I need to. It also has really intuitive keyboard shortcuts which are very easy to remember. Typora is technically still in beta, but I haven’t run into any stability issues so far.</p>
<h3 id="sync">Sync</h3>
<p>I could have easily put all my notes in a Google Drive / Dropbox folder and called it a day, but I’m not a fan of how these companies are approaching their users’ privacy. I’ve almost completely eliminated Google from my personal life and didn’t want to get sucked back in.</p>
<p>Using Git, I can easily sync all my notes to one or more remote repositories. I created a simple private Git repo in my Github account and started pushing my notes to it. I get version history out of the box and if my computer dies, I just have to clone the repo on the new one.</p>
<p>Committing all new changes manually is tedius though, so I’ve automated it. I first created a simple bash script that commits and pushes my changes.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">cd</span> ~/Notes <span class="o">&&</span> git add <span class="nb">.</span> <span class="o">&&</span> git commit <span class="nt">-m</span> <span class="s2">"Auto sync notes"</span> <span class="o">&&</span> git push origin master
</code></pre></div></div>
<center><small><i>
Committing directly to master is usually looked down upon, but my repo, my rules.
</i></small></center>
<p><br /></p>
<p>To run this script periodically, I created a timed job using <a href="https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html"><code class="language-plaintext highlighter-rouge">launchd</code></a>. Since I wanted it to run only when I’m logged in, I created a <code class="language-plaintext highlighter-rouge">plist</code> file in <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents/</code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="cp"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span>
<span class="nt"><plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span>
<span class="nt"><dict></span>
<span class="nt"><key></span>Label<span class="nt"></key></span>
<span class="nt"><string></span>net.mustafaali.syncnotes<span class="nt"></string></span>
<span class="nt"><key></span>ProgramArguments<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><string></span>/Users/mustafaali/Notes/sync.sh<span class="nt"></string></span>
<span class="nt"></array></span>
<span class="nt"><key></span>Nice<span class="nt"></key></span>
<span class="nt"><integer></span>1<span class="nt"></integer></span>
<span class="nt"><key></span>StartInterval<span class="nt"></key></span>
<span class="nt"><integer></span>3600<span class="nt"></integer></span> <span class="c"><!-- 1 hour --></span>
<span class="nt"><key></span>RunAtLoad<span class="nt"></key></span>
<span class="nt"><true/></span>
<span class="nt"><key></span>StandardErrorPath<span class="nt"></key></span>
<span class="nt"><string></span>/tmp/SyncNotes.err<span class="nt"></string></span>
<span class="nt"><key></span>StandardOutPath<span class="nt"></key></span>
<span class="nt"><string></span>/tmp/SyncNotes.out<span class="nt"></string></span>
<span class="nt"></dict></span>
<span class="nt"></plist></span>
</code></pre></div></div>
<p>And then enabled it</p>
<p><code class="language-plaintext highlighter-rouge">launchctl load net.mustafaali.syncnotes.plist</code></p>
<p>This job gets run every hour and runs the script to commit and push all the changes. It also logs the result of the operation to <code class="language-plaintext highlighter-rouge">/tmp</code> in case I need to debug something.</p>
<p align="center">
<img src="/static/post-img/syncnotes/github-commits.png" />
</p>
<h3 id="mobile-access">Mobile access</h3>
<p>Github recently relaunched their mobile apps, so I can access all my notes on the go if I need to.</p>
<p align="center">
<img src="/static/post-img/syncnotes/mobile.png" />
</p>
<h3 id="pricing">Pricing</h3>
<p>Github has unlimited free private repos, so I don’t have to pay anything to store my notes. I can also easily add another remote repo, say in Gitlab, to have multiple copies for redundancy.</p>
<p><br /></p>
<p>I have this setup running for several months now and it’s working great! If you have any ideas/suggestions, feel free to leave them below!</p>
Conducting good 1:1s2019-08-19T00:00:00+00:00https://mustafaali.net/2019/08/19/conducting-good-1-1s<p align="center">
<img src="/static/post-img/man-and-woman-work-meet.jpg" />
</p>
<p>As a manager you’ll spend a lot of time doing 1:1s, here’s how you can do them well -</p>
<h2 id="schedule-the-meetings-yourself">Schedule the meetings yourself</h2>
<p>You work for your team, not the other way around. Take the time to schedule 1:1s with your reports yourself. While you are doing that, pick a time that works for both of you. You may love getting these out of the way bright and early, but they may also be most productive during that time. If you are not sure about what time works best, just ask them, they’ll see that you really care about their time.</p>
<h2 id="set-the-right-expectations">Set the right expectations</h2>
<p>Make it explicit that this is their meeting. That means they get to decide what will be discussed, not whatever random topic pops up in your head at that time. Many junior devs struggle to coming up with good topics, guide them by asking leading questions like -</p>
<ul>
<li>What is the biggest surprise about what you thought your current role would be like vs what it ended up being?</li>
<li>How do you find working with your team? Is there any relationship there that can be improved?</li>
<li>Is there anything right now that is slowing you down or blocking you?</li>
</ul>
<p>If you/your report are new to the team, spend the first few 1:1s just getting to know them better. Find out what their journey has been like, what setbacks have they had, what motivates them, what their ambitions are, etc. What you learn in these conversations will help build a good rapport and pay off big time when you need to have difficult conversations.</p>
<h2 id="do-your-own-prep">Do your own prep</h2>
<p>1:1s are not a place for a status update, yet many of them end as one. Before the meeting, go through the last project update, look at the Jira board, test the latest build and read what was discussed in the project Slack channel.</p>
<p>Being prepared saves you time understanding a problem and gives you the context to suggest appropriate solutions. It also helps demonstrate that you are not running around clueless and actually know what’s going on. I chuckle to myself every time I see one of my reports get pleasantly surprised when I ask them a very specific question about something they thought I was “too busy” to be aware of. Don’t overdo this though, unless you want to come as a micro-manager.</p>
<h2 id="share-talking-points-in-advance">Share talking points in advance</h2>
<p>If there are any topics that you want to discuss in the 1:1, share them ahead of time. At Shopify, we use <a href="https://www.fellow.app/">Fellow</a> for this, but a simple Google doc shared between the two of you is good enough. Encourage them to do the same for their topics.</p>
<p>Once a month, review the goals that you’ve set for them and the progress they’ve made so far. Add this as talking point ahead of time so that they come prepared. If you do this well, the end of the cycle reviews go off much smoother.</p>
<h2 id="show-up-on-time">Show up on time</h2>
<p>Nothing tells a report that you don’t value them than showing up late to your 1:1s. I know you’re very busy and dealing with major fires, but it doesn’t matter. Let me repeat, it doesn’t matter.</p>
<p>If it’s time for your 1:1 and someone keeps rambling, let them know that you have another meeting and leave them to continue rambling until they are kicked out by the next set of people who need that room. If you really can’t avoid it, let your report know as early as possible so that they don’t end up waiting for you.</p>
<h2 id="dont-talk-listen">Don’t talk, listen</h2>
<p>A 1:1 is your chance to get a peek into what’s really going on in your team. Is that critical project really on track, or is the team just putting on a brave face to try and meet an unrealistic deadline? Are people actually getting along or they just play nice when you are in the pod? Talking feels nice, but every time you talk, you lose the opportunity to learn something.</p>
<p>Every once in a while, a team member will come prepared to rant about something that’s bothering them. This is good, it means that they genuinely care and are not just hanging around until their stock vests.</p>
<p>The best thing to do in such cases is to just make eye contact, nod and shut up. Let them vent until they run out of things to say. Once they are done, ask questions if you need more detail and find out the other side of the story (there’s always another side). Only then you should attempt to solve the problem.</p>
<h2 id="be-fully-present">Be fully present</h2>
<p>Close your laptop, put your phone in silent mode, turn off notifications and listen to what your team member is saying. No one likes it when the other person picks up their phone and starts typing in the middle of a conversation.</p>
<p>Use a paper notebook to take notes or if you really prefer doing it on your laptop, make sure you tell them that you are taking notes and not replying to other people on Slack.</p>
<h2 id="dont-speak-in-managementese">Don’t speak in managementese</h2>
<p>There’s nothing engineers hate more than telling their manager about a problem and getting a long-winded response in managementese. What’s managementese you ask? It’s phrases like -</p>
<ul>
<li>“Can you CIRCLE BACK with her…”</li>
<li>“I want to DOUBLE CLICK on that and…”</li>
<li>“We will ACCOMMODATE YOUR CONCERNS…”</li>
</ul>
<p>Just talk like a normal person with your engineers and save managementese for when you are talking to other managers.</p>
<h2 id="note-down-action-items-and-share-them">Note down action items and share them</h2>
<p>Add the things you both have identified as action items to your shared Google doc along with who is supposed to work on them. Add due dates, if required, if they are time-sensitive. This way, there is no scope for ambiguity or confusion.</p>
<h2 id="actually-do-the-things-that-you-promised">Actually do the things that you promised</h2>
<p>Make time to work on your action items and share the status with them. You expect them to do the same, so it’s only fair that you do it too. Set an example for your team and it’ll be much easier to get them to follow you.</p>
<h2 id="ask-for-feedback">Ask for feedback</h2>
<p>After doing a few 1:1s this way, ask for feedback. Are they finding these meetings useful? Would it be better to meet more frequently or less? Anything that can be done better? Many engineers won’t tell you until you ask because they want to keep their boss happy.</p>
How to run your personal website for free2019-07-03T00:00:00+00:00https://mustafaali.net/2019/07/03/how-to-run-your-personal-website-for-free<p>I spent a lot of time figuring out how to run this website for free. I couldn’t believe how simple it is. I’ll teach you how I do it here so that you don’t have to learn it on your own.</p>
<p>Most people recommend just paying for a platform like Wordpress or Ghost. They are not wrong, hosting your own website can be a lot of work and these platforms take care of everything for you for a monthly fee.</p>
<p>But, you are probably an engineer like myself and I have a little trouble paying for simple things that I know I can do myself. It’s not about the money. There just has to be a way to do it for free without becoming a full-blown server admin. Here’s how I do it -</p>
<h2 id="blogging-engine---jekyll">Blogging engine - Jekyll</h2>
<p><a href="https://jekyllrb.com/">Jekyll</a> is one the most popular static site generator out there. It’s simple and has a lot of plugins and free themes. It allows you to write in Markdown, which most engineers already know how to. I’ve never had to go back and modify anything after the initial set up.</p>
<h2 id="hosting---gitlab-pages">Hosting - Gitlab Pages</h2>
<p><a href="https://gitlab.com">Gitlab</a> offers free private repos and free CI to go with them. I have this blog set up in a private repo and have the CI configured to generate the static site on every push to master and deploy to Gitlab Pages, which is also free. Also haven’t had to go back and change anything after the initial setup. Here’s a <a href="https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/">tutorial</a>.</p>
<h2 id="comments---utterances">Comments - Utteranc.es</h2>
<p>Static sites don’t offer a way to add comments out-of-the-box and <a href="https://notes.ayushsharma.in/2017/09/im-killing-disqus-comments-on-my-blog-heres-why">Disqus is evil</a>. I use <a href="https://utteranc.es">Utterances</a> to offer Github issues-backed comments. This way you are not tracked on this website and I own my data. Took less than 5 mins to set up.</p>
<h2 id="tls---lets-encrypt">TLS - Let’s Encrypt</h2>
<p>Paying for SSL certs is just ridiculous in the age of <a href="https://letsencrypt.org/">Let’s Encrypt</a>. Set up takes less than 5 minutes and the certs get renewed automatically using <a href="https://github.com/JustinAiken/jekyll-gitlab-letsencrypt">this script</a>.</p>
<p><em>[Update - July ‘19]: Gitlab has introduced <a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.html">automatic HTTPS for Pages</a>, so enabling TLS is now as simple as clicking a button!</em></p>
<p><br />
That’s it. You just learnt how to set up and run your personal website for free in less than an hour. You’re welcome.</p>
Efficient travel2019-05-26T00:00:00+00:00https://mustafaali.net/2019/05/26/efficient-travel<p align="center">
<img src="/static/post-img/efficient-travel.jpg" />
</p>
<p><br /></p>
<p>Traveling for work or conferences can be a lot of fun. You get paid to visit different cities, meet people from different backgrounds and experience different cultures and cuisines. However, if you have to do this more than a few times each year, the novelty wears off very quickly and you realize that you are working a lot more hours to do the same amount of work just because you are traveling. Alternately, if you are not productive while traveling, you end up with a mountain of pending work when you get back to the office causing you to regret traveling in the first place.</p>
<p>Over the last 6 years I’ve had the opportunity to travel extensively and after struggling for a while to stay productive on the road, I discovered some tricks to overcome that, which I’m documenting here just in case others find them helpful.</p>
<p><br /></p>
<h2 id="pack-using-a-checklist">Pack using a checklist</h2>
<p>Packing for a trip can be very stressful. Most people spend a lot of time figuring out what to pack, deciding what goes into the carry-on vs the bags that’ll be checked in and still constantly worry that they may have forgotten something important. Others just cram as much stuff as they possibly can into their bags, lug it around throughout the trip and don’t end up needing most of it.</p>
<p>The best way to avoid this is to create a checklist of the things you’ll definitely need for a particular trip. The trick here is to limit this checklist to the things you know you’ll need, not what you could need. Whenever you have to travel, if you just go through this list and pack everything, you don’t need to worry about forgetting to pack something important.</p>
<p>Most of the time, I travel for either 2 days or 1 week at a time, so I’ve created two separate checklists in Google Keep that I use for those trips.</p>
<p align="center">
<img src="/static/post-img/2-day-trip.png" width="500" />
</p>
<p align="center">
<img src="/static/post-img/1-week-trip.png" width="500" />
</p>
<p>99% of the times I’ve not needed anything else and for the times I did, I was able to easily buy it either at an airport or close to the hotel I was staying in.</p>
<p><br /></p>
<h2 id="travel-with-the-right-luggage">Travel with the right luggage</h2>
<p>Most airlines allow one personal item (e.g. a laptop bag) and one small-sized carry-on. In my experience, this is more than enough for most trips. I go on my two-day trips with just a backpack and week-long trips with a backpack and roller bag.</p>
<p>I really like trying out different backpacks so I keep changing them every few months. You can pick anything you like as long as -</p>
<ul>
<li>It is waterproof</li>
<li>Has good cushion/padding for electronics like your laptop, tablet and camera</li>
<li>Has enough space for essentials and small enough so that you don’t end up filling it with unnecessary stuff</li>
</ul>
<p>For the carry-on, I absolutely love and highly recommend the <a href="https://travelpro.com/products/platinum%C2%AE-elite-22-expandable-carry-on-rollaboard%C2%AE?variant=19631221112930">Travelpro Platinum Elite Rollaboard</a>.</p>
<p align="center">
<img src="/static/post-img/travelpro-platinum-elite.jpg" />
</p>
<p>It is light, extremely durable, can fit a large amount of contents and also manage to fit within most airlines’ sizers for overhead luggage. It can seem a little pricey for a carry-on, but well-worth the investment if you travel more than 3-4 times a year.</p>
<p>There’s a newer 4-wheel spinner version of the same bag available now but those usually have less space and are significantly less durable than the 2-wheel versions.</p>
<p>For some trips, it may seem tempting to just pack a big bag. However, if you travel light, you don’t have to check-in any bags which will make your life so much easier. You don’t have to reach the airport too early, have all your stuff with you at all times and not have to wait for your bags to arrive after a long flight. In addition, airlines lose/delay baggage all the time and they can’t do that if your bags are with you.</p>
<p><br /></p>
<h2 id="be-ready-to-survive-off-your-carry-on">Be ready to survive off your carry-on</h2>
<p>Sometimes, you can’t avoid checking in bags like when you are going on a long trip or if the flight is full and the airline agent forces you to check-in your bag at the gate. For such cases, be prepared to be able to be able to spend at least 24 hours comfortably after you land even if you don’t get your checked in bags.</p>
<p>For this, make sure you pack the following in your backpack</p>
<ul>
<li>Passport and any other travel documents</li>
<li>Medications (if any, including emergency medicines)</li>
<li>A change of clothes</li>
<li>All valuable electronics</li>
</ul>
<p>With these, you can at least reach your destination comfortably, freshen up and then chase the airline and travel insurance company.</p>
<p><br /></p>
<h2 id="determine-how-youll-stay-online">Determine how you’ll stay online</h2>
<p>You’ll need internet access to do your job so figure out how you’ll stay online while traveling. Most big airports offer free wifi, but I’d suggest signing up for a good roaming plan if your carrier offers one or signing up for <a href="https://fi.google.com/about/">Google Fi</a>. You can pause the service when not traveling and they are now offering eSIM support which eliminates the need to carry around an additional SIM card. Also, sign up for a trusted VPN like <a href="https://www.f-secure.com/en/home/products/freedome">Freedome</a> or <a href="https://protonvpn.com/">ProtonVPN</a> and you can connect to public wifi networks without having to worry about privacy and security.</p>
<p><br /></p>
<h2 id="pick-your-seat-always">Pick your seat, always</h2>
<p>If you don’t pick a seat while checking in, airlines randomly assign you, what I think, the worst possible seat available on the plane at that moment. Do yourself a favor and pick a good seat ahead of time. Most airlines allow you to do this for free 24 hours before a flight. If your airlines doesn’t, it’s worth paying the small fee because a good seat makes a huge difference in your travel experience.</p>
<p>What’s a good seat then? I usually select the first available aisle seat in front of the plane. Being in an aisle seat means I can get up and stretch my legs as many times as I want and being close to the front means I can get off the plane quickly after landing.</p>
<p><br /></p>
<h2 id="carry-proper-noise-cancelling-headphones">Carry proper noise-cancelling headphones</h2>
<p>Airpods may be all the hotness right now but they are absolutely terrible on flights. They don’t offer good insulation and are just not loud enough to suppress background noise. My <a href="https://www.bose.com/en_us/products/headphones/over_ear_headphones/quietcomfort-35-wireless-ii.html#v=qc35_ii_silver">Bose QC35 II</a>’s are the best travel-related investment I’ve made yet.</p>
<p align="center">
<img src="/static/post-img/bose-qc35-ii.jpeg" />
</p>
<p>They are also a little pricey, but absolutely worth it. They are so good that I do not travel without them anymore. Also, whether you love or hate open offices, these let you tune out the world and get into flow quickly even when you are not traveling.</p>
<p>Bose has been making the best noise-cancelling headphones for years but Sony’s recently launched <a href="https://www.sony.com/electronics/headband-headphones/wh-1000xm3">WH-1000XM3</a> has been receiving raving reviews. I haven’t tried them myself, but have heard good things from friends who bought them. If you are looking to buy a pair, I’d suggest trying both before making a decision.</p>
<p><br /></p>
<h2 id="travel-with-your-own-entertainment">Travel with your own entertainment</h2>
<p>Let’s face it, most inflight infotainment units suck. They are old, painfully laggy, filled with ads and play content on tiny low-res screens. I like to avoid using them altogether and pre-download the content I want to watch before traveling.</p>
<p>I like to spend the first few hours on a flight reading books and watching conference talks because I can get through them much quicker as there are no other distractions. Then I switch to <a href="https://www.plex.tv/">Plex</a> to watch some movies and TV shows. For conference talks, I use <a href="https://github.com/ytdl-org/youtube-dl/">youtube-dl</a> to easily download them to my MacBook.</p>
<p><br /></p>
<h2 id="make-use-of-airport-lounges">Make use of airport lounges</h2>
<p>Unless you travel business class, you’ll be stuck in the general waiting areas at airports for hours which a lot of times can be completely full. You can instead relax in a nice, comfortable lounge, take a warm shower and eat proper food while waiting for a flight.</p>
<p>If you travel infrequently, get a credit card that offers free lounge access at airports. American Express usually offers the best travel perks, but Visa and Mastercard are also starting to get better on this front. If you travel a lot, get a <a href="https://www.prioritypass.com/">Priority Pass</a> to access their lounges anywhere in the world. Most companies let you expense it since it works out cheaper for them overall.</p>
<p>Lounges most importantly offer complimentary high-speed internet as well so you can get some work done in a quiet space before jumping on your next flight. Some even have meeting rooms to take calls. Just make sure you have a reminder set to walk over to the gate otherwise you’ll miss your flight because there are usually no flight announcements inside lounges.</p>
<p><br />
<em>Hope these help and if you like have some other tips, tweet them to me <a href="https://twitter.com/mustafa01ali">@mustafa01ali</a>.</em></p>
Pro-tips for attending Google I/O2018-03-25T00:00:00+00:00https://mustafaali.net/2018/03/25/pro-tips-for-attending-google-io<p align="center">
<img src="/static/post-img/google-io.jpg" />
</p>
<p>If you are an Android developer, there’s no bigger conference than <a href="https://events.google.com/io/">Google I/O</a>. First held in 2008, it’s format has changed a number of times over the years and is currently being held in Mountain View, California over a period of 3 days, jam packed with keynotes, demos, talks, office hours and code labs.</p>
<p>While the conference has been getting bigger each year, it has been getting more and more difficult for developers to attend it. There’s a huge demand for tickets every year, which led to them getting sold out in <a href="https://www.theverge.com/2012/3/27/2905935/google-io-2012-sold-out">just a few minutes</a>. In the last few years, the <a href="https://www.theverge.com/2014/2/19/5427148/google-sets-i-o-conference-for-june-25-26">new lottery system</a> has helped everyone get a relatively fair shot at buying the tickets, but you’ll still have to very lucky to be able to buy one.</p>
<p>So let’s say the overlords at Google deem you worthy to allow you to buy a ticket, thanks to the schedule and the sheer number of people at the venue, it is really hard to make the most of the conference. Most first-time attendees and even the ones who have attended before get frustrated towards the end because of this. However, I have some tips that can help!</p>
<h2 id="why-listen-to-my-advice">Why listen to my advice?</h2>
<p>I’ve been fortunate enough to attend Google I/O since 2013 and full-disclosure - I had no idea what I was doing at the conference for the first few years. I’ve most likely made every stupid mistake that someone can and have learnt from them. So if anything, this blog post will help you avoid those.</p>
<h2 id="tip-1---book-travel-early">Tip #1 - Book travel early</h2>
<p>There are a lot of people traveling in to attend the conference so flight tickets get expensive and hotels get booked very quickly. I’d recommend making your bookings immediately after getting your ticket to avoid over-paying. The I/O website usually has <a href="https://events.google.com/io/attending/#travel">discount coupons</a> for nearby hotels, so make sure to see if they are still available before you make the booking.</p>
<h2 id="tip-2---nearby-hotels-are-not-the-only-option">Tip #2 - Nearby hotels are not the only option</h2>
<p>Pretty much every hotel bumps up their rates significantly during this period, so be prepared to pay good money. However, there are some other ways to stay comfortably without comprising too much.</p>
<h3 id="option-1---airbnb">Option 1 - Airbnb</h3>
<p>This should be fairly obvious to people living in the US, but if you are traveling from abroad and haven’t tried Airbnb yet, you should. Most hosts are pretty good and you should be able to find a place that fits your budget relatively easily. However, avoid booking a place several months in advance, I’ve had several friends tell me that their hosts cancelled their booking so that they could list the places at much higher rates closer to the conference.</p>
<h3 id="option-2---large-airbnb">Option 2 - Large Airbnb</h3>
<p>If you have friends or colleagues attending I/O with you, staying at a large house works out great. You all will have a big place to yourselves and will end up paying much lesser than you normally would staying at a hotel.</p>
<h3 id="option-3---stay-a-little-far-from-the-venue">Option 3 - Stay a little far from the venue</h3>
<p>If you don’t mind commuting, hotels farther from the venue will be cheaper. Google usually provides free shuttles from San Francisco and other locations, so pick a place that’s close to one of the pick up locations. Caltrain/VTA/BART, Uber/Lyft Pool are also fairly convenient and Google usually hands out discount coupons for Uber/Lyft.</p>
<h2 id="tip-3---pack-appropriately">Tip #3 - Pack appropriately</h2>
<p>At the venue, you will mostly likely experience sweltering hot days and chilly evenings and couple of years ago this totally <a href="https://thenextweb.com/google/2016/05/19/google-io-2016-look-not-run-large-event/">ruined</a> the entire conference. If you don’t want to sweat like crazy during the day and shiver in the evenings, bring light clothing, hat, sunglasses, a nice warm jacket and lots of sunscreen.</p>
<p>Also, there will be plenty of walking throughout the day, so wear comfortable shoes and carry a light backpack. You’ll most likely be better off leaving your laptop at the hotel instead of carrying it around all day.</p>
<h2 id="tip-4---register-for-other-events">Tip #4 - Register for other events</h2>
<p>There are usually a lot of events/parties organized around I/O by various companies, so make sure you register for them. Twitter and <a href="http://meetup.com/">Meetup</a> are good sources to find out about these events.</p>
<h2 id="tip-5---pick-talks-to-attend-after-the-keynote">Tip #5 - Pick talks to attend <em>after</em> the keynote</h2>
<p>The conference <a href="https://events.google.com/io/schedule/?section=day">schedule</a> is published months in advance, so most people go in and spend hours selecting all the talks they want to attend. The thing is, the schedule is updated each year right after the keynote to include talks related to all the new things that were just announced. They do this to avoid speculation, rumours and leaking any announcements ahead of time. Once the actual schedule is available after the keynote, you can go book the talks you want to go to.</p>
<h2 id="tip-6---reserve-your-seat-for-the-talks-you-want-to-attend">Tip #6 - Reserve your seat for the talks you want to attend</h2>
<p>Starting in 2017, Google started letting people reserve your seat in advance through the I/O website and apps to elimate huge queues outside the breakout sessions. If they continue to do so this year, it’s a really convenient way to make sure you don’t miss the talk you really want to attend.</p>
<h2 id="tip-7---dont-rely-on-conference-wi-fi">Tip #7 - Don’t rely on conference Wi-fi</h2>
<p>Wi-fi at every conference sucks and I/O is no exception, so don’t rely on it. You’ll be better off with a data plan. In case you don’t have one, trying walking to an area at the venue that’s not crowded, you’ll get better Wi-fi connectivity there.</p>
<h2 id="tip-8---dont-try-to-attend-all-the-talks">Tip #8 - Don’t try to attend all the talks</h2>
<p>There’s a lot going on at I/O apart from just talks. There are really cool demos, code labs and office hours by various engineering teams. Make sure you have enough time to check them out since the talks will anyways be available on Youtube almost immediately anyways.</p>
<h2 id="tip-9---make-the-most-of-the-office-hours">Tip #9 - Make the most of the office hours</h2>
<p>There are lots of Googlers at the conference and are willing to answer deep technical questions. So go meet them during office hours and ask them about the issues you are facing and give feedback on how things can be improved.</p>
<h2 id="tip-10---meet-new-people">Tip #10 - Meet new people</h2>
<p>There are some incredibly smart and talented people at I/O each year, so it’s a great opportunity to meet them in person, build new connections and learn something new from them. If you are standing in a long queue (there will be lots of them), talk to the people around you. If you see the author of a library you use walking around, go say hi. You’ll be surprised by how friendly people in the community can be.</p>
<p>In my opinion, this is the most important thing about I/O. If you are planning on attending just for the talks, you’ll be better off watching the livestream from the comfort of your home and save a whole lot of money.</p>
<p><strong>If you find these useful or have any other tips that I haven’t included here, tweet them to me <a href="https://twitter.com/mustafa01ali">@mustafa01ali</a>.</strong></p>
<p><br />
<em>Special thanks to <a href="https://twitter.com/DarioMungoi">Dario Mungoi</a>, <a href="https://twitter.com/kanawish">Etienne Caron</a> and <a href="https://twitter.com/henrytao_me">Henry Tao</a> for sharing their favorite tips for this post</em>.</p>
Kotlin data classes and sensitive information2018-01-14T00:00:00+00:00https://mustafaali.net/2018/01/14/Kotlin-data-classes-and-sensitive-information<p>One of my most favorite features in Kotlin is <a href="https://kotlinlang.org/docs/reference/data-classes.html">Data Classes</a>. Pretty much every “Introduction to Kotlin” article begins with how it lets you reduce the boilerplate in simple classes like this:</p>
<h3 id="java">Java</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">email</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">company</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">email</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">,</span> <span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">email</span><span class="o">,</span> <span class="n">password</span><span class="o">,</span> <span class="s">""</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">,</span> <span class="nc">String</span> <span class="n">password</span><span class="o">,</span> <span class="nc">String</span> <span class="n">company</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">company</span> <span class="o">=</span> <span class="n">company</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getEmail</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEmail</span><span class="o">(</span><span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getPassword</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">setPassword</span><span class="o">(</span><span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getCompany</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">company</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setCompany</span><span class="o">(</span><span class="nc">String</span> <span class="n">company</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">company</span> <span class="o">=</span> <span class="n">company</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">o</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">o</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">getClass</span><span class="o">()</span> <span class="o">!=</span> <span class="n">o</span><span class="o">.</span><span class="na">getClass</span><span class="o">())</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="nc">Customer</span> <span class="n">customer</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Customer</span><span class="o">)</span> <span class="n">o</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">name</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="o">!</span><span class="n">name</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">customer</span><span class="o">.</span><span class="na">name</span><span class="o">)</span> <span class="o">:</span> <span class="n">customer</span><span class="o">.</span><span class="na">name</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">email</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="o">!</span><span class="n">email</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">customer</span><span class="o">.</span><span class="na">email</span><span class="o">)</span> <span class="o">:</span> <span class="n">customer</span><span class="o">.</span><span class="na">email</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">password</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="o">!</span><span class="n">password</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">customer</span><span class="o">.</span><span class="na">password</span><span class="o">)</span> <span class="o">:</span> <span class="n">customer</span><span class="o">.</span><span class="na">password</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">return</span> <span class="n">company</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">company</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">customer</span><span class="o">.</span><span class="na">company</span><span class="o">)</span> <span class="o">:</span> <span class="n">customer</span><span class="o">.</span><span class="na">company</span> <span class="o">==</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">name</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">name</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">:</span> <span class="mi">0</span><span class="o">;</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">31</span> <span class="o">*</span> <span class="n">result</span> <span class="o">+</span> <span class="o">(</span><span class="n">email</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">email</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">:</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">31</span> <span class="o">*</span> <span class="n">result</span> <span class="o">+</span> <span class="o">(</span><span class="n">password</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">password</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">:</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">31</span> <span class="o">*</span> <span class="n">result</span> <span class="o">+</span> <span class="o">(</span><span class="n">company</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">company</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">:</span> <span class="mi">0</span><span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"Customer{"</span> <span class="o">+</span>
<span class="s">"name='"</span> <span class="o">+</span> <span class="n">name</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", email='"</span> <span class="o">+</span> <span class="n">email</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", password='"</span> <span class="o">+</span> <span class="n">password</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="s">", company='"</span> <span class="o">+</span> <span class="n">company</span> <span class="o">+</span> <span class="sc">'\''</span> <span class="o">+</span>
<span class="sc">'}'</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="kotlin">Kotlin</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Customer</span><span class="p">(</span><span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">email</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">password</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">company</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span>
</code></pre></div></div>
<p><em>(Note: This is just an example, please don’t design your model classes like this.)</em></p>
<p>The nice thing about Kotlin’s Data Classes is that the <code class="language-plaintext highlighter-rouge">equals()</code>, <code class="language-plaintext highlighter-rouge">hashCode()</code> and <code class="language-plaintext highlighter-rouge">toString()</code> methods are automatically generated for you behind the scenes, so you don’t have to write or maintain them yourself. This is super helpful, but let’s see what happens when you print an object of this class:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">customer</span> <span class="p">=</span> <span class="nc">Customer</span><span class="p">(</span><span class="s">"Android"</span><span class="p">,</span> <span class="s">"android@google.com"</span><span class="p">,</span> <span class="s">"Hunter1"</span><span class="p">,</span> <span class="s">"Google Inc."</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">customer</span><span class="p">)</span>
</code></pre></div></div>
<p>Output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Customer(name=Android, email=android@google.com, password=Hunter1, company=Google Inc.)
</code></pre></div></div>
<p>What’s happened here is that the <code class="language-plaintext highlighter-rouge">toString()</code> method that was automatically generated for us got invoked to pretty-print the values stored in the object, including the user’s password.</p>
<p>This is clearly a problem, anyone who has access to the application’s logs can easily know the user’s password. Now, if you turn off logging in release builds, the problem is solved, right? Well, not necessarily.</p>
<p>Let’s say you set your data object as message to an <code class="language-plaintext highlighter-rouge">Exception</code> before throwing it, the crash reporting library that you’ve integrated in your app will most likely call this <code class="language-plaintext highlighter-rouge">toString()</code> method and promptly upload the full output to its servers. The most scary thing about this is that you won’t notice it until it’s too late.</p>
<p>Note that this is not a problem unique to Kotlin. This can also happen for any similar Java classes whose <code class="language-plaintext highlighter-rouge">toString()</code> has been overridden without hiding the sensitive information. Kotlin’s data classes simply hide this detail which makes it easy to shoot yourself in the foot.</p>
<p>Anyways, as with pretty much everything in programming, there are multiple ways in which we can fix this:</p>
<h2 id="solution-1">Solution #1</h2>
<p>The simplest way to solve this is to simply override the <code class="language-plaintext highlighter-rouge">toString()</code> method in your data classes like this:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Customer</span><span class="p">(</span><span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">email</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">password</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">company</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">()</span> <span class="p">=</span> <span class="s">"Customer(name=$name, email=$email, password=REDACTED, company=$company)"</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now if you print the object, the output will not contain the password.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Customer(name=Android, email=android@google.com, password=REDACTED, company=Google Inc.)
</code></pre></div></div>
<p>This works for simple classes, but imagine doing this for classes with lots of properties, we’ll have the same problem of writing and maintaining utility methods.</p>
<h2 id="solution-2">Solution #2</h2>
<p>In Kotlin, only the properties declared in the constructor are included in the auto-generated methods. So, if you declare the data class like this, the password will not be included in <strong>any</strong> of the generated methods, including <code class="language-plaintext highlighter-rouge">toString</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">Customer</span><span class="p">(</span><span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">email</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">company</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">password</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">""</span>
<span class="p">}</span>
<span class="o">..</span><span class="p">.</span>
<span class="p">{</span>
<span class="kd">val</span> <span class="py">customer</span> <span class="p">=</span> <span class="nc">Customer</span><span class="p">(</span><span class="s">"Android"</span><span class="p">,</span> <span class="s">"android@google.com"</span><span class="p">,</span> <span class="s">"Google Inc."</span><span class="p">)</span>
<span class="n">customer</span><span class="p">.</span><span class="n">password</span> <span class="p">=</span> <span class="s">"Hunter1"</span>
<span class="nf">print</span><span class="p">(</span><span class="n">customer</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="output">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Customer(name=Android, email=android@google.com, company=Google Inc.)
</code></pre></div></div>
<p>This is arguably better, but ugly. We can no longer use the constructor to create objects and also if there are several sensitive properties in a class, consumers will have to remember which values to pass in the constructor and which one to set separately.</p>
<h2 id="solution-3">Solution #3</h2>
<p>Instead of overriding the <code class="language-plaintext highlighter-rouge">toString()</code> method of all the data classes that hold sensitive information, we can instead do it in a generic wrapper like this:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ProtectedProperty</span><span class="p"><</span><span class="nc">T</span><span class="p">>(</span><span class="kd">var</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">()</span> <span class="p">=</span> <span class="s">"REDACTED"</span>
<span class="p">}</span>
<span class="kd">data class</span> <span class="nc">Customer</span><span class="p">(</span><span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">email</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="kd">var</span> <span class="py">password</span><span class="p">:</span> <span class="nc">ProtectedProperty</span><span class="p"><</span><span class="nc">String</span><span class="p">>,</span> <span class="kd">var</span> <span class="py">company</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span>
</code></pre></div></div>
<p>Now, if you use it in the data class to hold sensitive values, it doesn’t leak them.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="kd">val</span> <span class="py">customer</span> <span class="p">=</span> <span class="nc">Customer</span><span class="p">(</span><span class="s">"Android"</span><span class="p">,</span> <span class="s">"android@google.com"</span><span class="p">,</span> <span class="nc">ProtectedProperty</span><span class="p">(</span><span class="s">"Hunter1"</span><span class="p">),</span> <span class="s">"Google Inc."</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">customer</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="output-1">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Customer(name=Android, email=android@google.com, password=REDACTED, company=Google Inc.)
</code></pre></div></div>
<p>You can even make your logs look cooler by using the UTF-8 Full Block character (U+2588) instead.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ProtectedProperty</span><span class="p"><</span><span class="nc">T</span><span class="p">>(</span><span class="kd">var</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">()</span> <span class="p">=</span> <span class="s">"███████████"</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="output-2">Output:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Customer(name=Android, email=android@google.com, password=███████████, company=Google Inc.)
</code></pre></div></div>
<p>I personally like this solution since it gives you the best of both worlds - hides sensitive information easily and also retains the proper <code class="language-plaintext highlighter-rouge">hashCode()</code> and <code class="language-plaintext highlighter-rouge">equals()</code> implementations.</p>
<p>Happy coding!</p>
Beware of your app's screenshots2017-06-24T00:00:00+00:00https://mustafaali.net/2017/06/24/beware-of-your-apps-screenshots<p align="center">
<img src="/static/post-img/broken-android.png" />
</p>
<p>I’ve been working on a pretty popular app (>15M downloads) which shall remained unnamed for obvious reasons. It has been in Google Play for over a year now, is highly-rated and we’ve had no issues adding new features and pushing updates.</p>
<p>A few days back, I woke up to see a notification on my phone from the Google Play Developer Console <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.playconsole&hl=en">app</a> saying that my app has been removed and developer console on the web had this message -</p>
<p><img src="/static/post-img/play-store-violation.png" alt="Play Store Message" /></p>
<p>I have nothing but respect for the way Google has been running Google Play Store, but kicking an app out without any notice whatsoever is a little too much. Impersonation of intellectual property is a serious matter and we always take great care in making sure we are following the Play Store policies.</p>
<p>However, just providing a vague, generic message <strong>does not help</strong>. We spent hours racking our brains trying to figure out what the problem was. Finally, a colleague noticed that one of our screenshots, had a tiny logo of a television network. Needless to say, it was completely unintentional.</p>
<p>We quickly replaced that screenshot, submitted an appeal and after a review, the app was reinstated. Overall, the app was not available in the Play Store for almost 12 hours.</p>
<p>This is not the first time this has happened in Google Play and many popular apps have been affected in the past. Now I am not a legal expert by any stretch of the imagination, but strongly feel that these issues can be addressed in a way that is reasonable for all parties involved.</p>
<p>Here are a few suggestions -</p>
<ol>
<li>Provide exact details of where the issue was noticed (screenshots/description/promo video/some screen in the app)</li>
<li>Give app developers at least 24 hours notice before removing the app</li>
</ol>
<p>Making these changes will go a long way in making Google Play an even better place for developers to earn their livelihood and I hope Google will at least consider them.</p>
<p>In the mean time, ALWAYS pay close attention to the screenshots you are uploading. Especially, if you are one of those cool kids who’ve automated the process using tools like <a href="https://fastlane.tools/">Fastlane</a>.</p>
Introducing Dev Tiles — Quick Settings Tiles for Android Developers2017-01-03T00:00:00+00:00https://mustafaali.net/2017/01/03/introducing-dev-tiles<p>I just wrapped up a side-project which is firmly in the “scratching-my-own-itch” category and figured why not share it with the community.</p>
<p>While building Android apps, there are several developer options that I frequently use like enable/disable USB debugging, toggling Demo Mode, etc. However, navigating into the Developer Options menu in the Settings app and finding the option I need in that long list (which OEMs feel compelled to re-order) is irritating.</p>
<p>Inspired by Nick Butcher’s <a href="https://github.com/nickbutcher/AnimatorDurationTile">Animator Duration Tile</a>, I figured it’d be nice if there are tiles for other developer options that are commonly used so I ended up building them myself. Here’s the <a href="https://play.google.com/store/apps/details?id=xyz.mustafaali.devqstiles&utm_source=personalwebsite">app</a> in action —</p>
<div class="video-container">
<iframe width="853" height="480" src="https://www.youtube.com/embed/fKMDJJGR9-U" frameborder="0" allowfullscreen=""></iframe>
</div>
<p><br />
You can <a href="https://play.google.com/store/apps/details?id=xyz.mustafaali.devqstiles&utm_source=personalwebsite">download</a> it from the Google Play Store. As of v1.0.0, it provides the following tiles —</p>
<ul>
<li>Toggle USB debugging</li>
<li>Keep screen on when connected to a computer, but let it turn off when connected to a wall charger</li>
<li>Toggle show taps</li>
<li>Toggle demo mode</li>
</ul>
<p>The <a href="https://developer.android.com/about/versions/nougat/android-7.0.html#tile_api">Tile API</a>, introduced in Android 7.0, makes it quite easy to create tiles that live in the Quick Settings panel. There are lots of posts that cover how to use it in detail, so I won’t repeat the same information here. However, at a high-level, you just have to create a Service that extends <code class="language-plaintext highlighter-rouge">android.service.quicksettings.TileService</code> and override it’s methods to handle user clicks and update the tile UI.</p>
<p>For the curious, the app is open source and you can check out the code on <a href="https://github.com/mustafa01ali/Dev-Tiles">Github</a>.</p>
Android ConstraintLayout and Friends2016-05-30T00:00:00+00:00https://mustafaali.net/2016/05/30/android-constraintlayout-and-friends<p>One of the most exciting developer-facing announcement at I/O this year was the introduction of ConstraintLayout. It is a new type of layout available in the Android support repository built on top of a flexible constraints-based system.</p>
<p>It makes it easy to create flexible user interfaces by allowing developers to declare constraints between widgets instead of having to use complex nested layouts. It is in many ways similar to AutoLayout on the iOS platform and is fairly easy to use, so I won’t go the details of how to use it. If you are unfamiliar with it, I highly recommend watching the following talk and going through <a href="https://codelabs.developers.google.com/codelabs/constraint-layout/index.html#0">this</a> code lab.</p>
<div class="video-container">
<iframe width="853" height="480" src="https://www.youtube.com/embed/sO9aX87hq9c" frameborder="0" allowfullscreen=""></iframe>
</div>
<h2 id="linearconstraintlayout">LinearConstraintLayout</h2>
<p>While playing with <code class="language-plaintext highlighter-rouge">ConstraintLayout</code> using v1.0.0-alpha1 of the Constraint Support Library, I noticed something called <code class="language-plaintext highlighter-rouge">LinearConstraintLayout</code>. It doesn’t show up in the palette inside the layout editor, however it does comes up as a suggestion when you are creating a new layout. You can also add it manually in XML using the following tag —</p>
<p><code class="language-plaintext highlighter-rouge"><android.support.constraint.LinearConstraintLayout></code></p>
<p>It is a simple container that allows you to add equally-spaced widgets either vertically or horizontally, very similar to a <code class="language-plaintext highlighter-rouge">LinearLayout</code>. You can set the orientation using the attribute of the same name in the app namespace.</p>
<p><img src="/static/post-img/linear-constraint-layout.gif" alt="LinearConstraintLayout" /></p>
<p>Looking at the code, it just loops through its children and sets constraints on them based on the orientation so it should comparatively be more light-weight.</p>
<h2 id="tableconstraintlayout">TableConstraintLayout</h2>
<p>There’s also something called a <code class="language-plaintext highlighter-rouge">TableConstraintLayout</code> in this version which presumably makes it easy to create table-styled interfaces, but the layout editor currently throws an error while trying to render it and the source code seems to be a work in progress.</p>
<p><img src="/static/post-img/table-constraint-layout.png" alt="TableConstraintLayout" /></p>
<p>Since the library is in alpha at the time of writing this, it’s fair to assume that these layouts are currently under development and we should get more details and documentation in the coming weeks. It is also possible that Google might decide to get rid of these layouts, so you shouldn’t use them in production.</p>
<p>I’ll update this post when more details are available.</p>
Creating apps for emerging markets2016-04-20T00:00:00+00:00https://mustafaali.net/2016/04/20/creating-apps-for-emerging-markets<p align="center">
<img src="/static/post-img/india-women.jpg" />
</p>
<p><em>This was originally published on the <a href="https://mutualmobile.com/posts/creating-apps-for-emerging-markets">Mutual Mobile blog</a>. I’m re-posting it here for posterity.</em></p>
<p>At Mutual Mobile, we work on a variety of digital products, from banking and contactless payments to smart homes and wearables. Each one comes with its own set of challenges, but every so often, we encounter something new.</p>
<p><a href="http://fastfilmz.com/">FastFilmz</a> had a vision to stream HD movies in India, over slow and unreliable networks, with low cost plans. We partnered with them to help bring their over-the-top (OTT) solution to market. As you can imagine, this is not exactly easy. We were thrilled to help them accomplish it.</p>
<p>Now that the app is <a href="https://play.google.com/store/apps/details?id=com.fastfilmz.android">available</a> and the celebrations are over, we’d like to share what we learned. Creating products for emerging markets presents unique challenges.</p>
<h2 id="building-for-low-end-devices">Building for low-end devices</h2>
<p>FastFilmz launched first on the Android platform, as it is the most popular mobile OS in India. India is the <a href="http://www.gsmarena.com/counterpoint_india_becomes_worlds_second_largest_smartphone_market_surpasses_us-news-16413.php">2nd largest smartphone market</a> in the world after China, recently surpassing the US. This growth is fueled by low-end phones sold at competitive prices by both local and international OEMs. This presents a challenge as we had to design solutions that run well on devices with limited resources.</p>
<h3 id="picking-test-devices">Picking test devices</h3>
<p>Android always gets a bad rep for fragmentation, requiring testing on lots and lots of devices. In our experience, you can efficiently test an application by picking the right set of devices. The key is to understand the target audience. You don’t need to test your app on every device out there.</p>
<p>We worked with FastFilmz to come up with a short list. It was an ideal combination of platform versions, screen sizes, hardware configuration, and manufacturers. We validated the list through data gathered in an extensive user-research exercise. Also, during beta testing, we made sure the app was tested on devices not on our original list to catch any rogue, device-specific bugs.</p>
<h3 id="running-buttery-smooth-animations">Running buttery-smooth animations</h3>
<p>Achieving 60fps on popular Android phones is hard enough. Imagine trying to do that on phones with around half a Gigabyte of RAM. On low-end devices, it is common practice to disable complex animations or run simpler ones instead. If most of your users have low-end hardware, this option just goes out the window.</p>
<p>Our engineers worked side-by-side with our designers to try different ideas for navigation and transitions. We tested them on real devices, tweaked them if something worked well, or went back to the drawing board and repeated the whole process. It was definitely challenging, but totally worth it.</p>
<p>A common advice for engineers is to use low-end hardware while building apps. We not only did that but went a step further. To simulate a real world environment, we installed the most popular apps used by our target customer. This is important. On a device with constrained resources, bad citizens can degrade the overall performance, including your app. This was critical in making sure our animations work well for all customers.</p>
<h2 id="dealing-with-slow-and-unreliable-networks">Dealing with slow and unreliable networks</h2>
<p>Despite the exponential growth of smartphone usage, the state of cellular networks in India is terrible. 4G is just rolling out, and 3G coverage is spotty, even in major cities. Even when there is 3G coverage, devices regularly get bumped down to 2G, especially when indoors. If the users cannot quickly browse the catalog, they won’t be able to use the app.</p>
<h3 id="making-content-always-available-even-when-offline">Making content always available, even when offline</h3>
<p>Our goal was to create an experience with no progress bars. That means users should rarely see loading indicators, irrespective of the quality of the network or even when they are offline.</p>
<p>There are multiple ways to achieve this, and we’ve written custom implementations in the past. However, we were impressed with the capabilities of <a href="https://www.firebase.com/">Firebase</a>. Though it is primarily a real-time database, it has nice offline capabilities, and it syncs fast, even in bad network conditions. What if we could use Firebase to fetch and store the catalog data locally, so we can render it fast, even when the app is offline? We wrote a quick prototype to see if that’s feasible, even on slow networks in small towns. It worked better than we expected.</p>
<p>We used a mix of Firebase and RESTful APIs to power the app. Catalog metadata that must be available to the user both instantly and offline resides in Firebase. Everything else is on servers that provide standard APIs to access it. The key is deciding what data goes where. Putting everything in Firebase would increase the memory the app would consume on users’ devices. We wanted to be considerate of this and not cause customers to uninstall our app to create space.</p>
<p>We were able to leverage the power of Firebase, while keeping the server costs low. It also allows the curation team to add new content or feature content easily by using the Firebase backend. The updates are instantly pushed to all customers.</p>
<p>Since Firebase stores data in JSON format, it has limitations to how you can query it. For instance, you can’t query data that is over 2 levels deep. To work around this, we designed the schema by flattening it as much as possible, while reducing duplication. Overall, we are happy with the way it is working.</p>
<p>For RESTful APIs, we followed the usual best practices, using gzip compression to reduce payload size, implemented HTTP response caching, along with exponential back off and retry. For images, we used <a href="https://developers.google.com/speed/webp/">WebP</a> to reduce load times and memory consumption.</p>
<p>The key thing to note, while providing offline capability in any app, is how much time and effort will go into implementing and maintaining it. Modern frameworks, like Firebase, support most of the use cases and are affordable, even for start-ups. However, there will be use cases where they don’t work. In that case, you may need to roll your own solution.</p>
<h3 id="streaming-video-with-little-or-no-buffering">Streaming video with little or no buffering</h3>
<p>It’s no fun to watch your favorite movie when it keeps buffering all the time. Fortunately, FastFilmz partnered exclusively with V-Nova to deploy their award-winning video compression technology, <a href="http://www.v-nova.com/perseus/">Perseus™</a>. One of the world’s leading video compression technologies, it works well on slow networks, while keeping the data costs down. During our testing, we found it enables quality playback at almost 1/4th the data consumed compared to other similar services.</p>
<p>We also allow customers to download full movies and store them offline. They can watch them anytime. Digital Rights Management is fully supported to prevent piracy.</p>
<h2 id="choosing-the-right-payment-mechanism">Choosing the right payment mechanism</h2>
<p>Monetization is an important aspect of any business, and it is critical to provide an easy way for customers to pay. As per 2015 reports, only 4-6% of Indian consumers have access to mobile payments and most simply don’t trust online payment services.</p>
<p>To work around this, we implemented carrier billing as an exclusive way to accept payments, i.e., allowing consumers to pay with their mobile carrier balance. Based on the feedback we’ve received, customers love the convenience and are more than willing to purchase and renew their subscription using this option.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Creating apps for emerging markets introduces some unique challenges. Fortunately, once you know the issues, they are not difficult to solve. To recap, always remember to:</p>
<ol>
<li>Pick the right target devices, based on user research.</li>
<li>Use real devices, not simulators, daily and set them up to mimic a real user scenario.</li>
<li>Work closely with designers to iterate on different options for animations and transitions.</li>
<li>Use the WebP format for images.</li>
<li>Provide a compelling offline experience using technologies like Firebase.</li>
<li>Choose the right payment platform for your target audience.</li>
</ol>
<p>We love solving new problems and based on the results in the market, customers are loving the result. We hope this was helpful and always welcome questions and comments.</p>
How much can an SSD improve your Android development experience?2016-02-07T00:00:00+00:00https://mustafaali.net/2016/02/07/how-much-can-an-ssd-affect-android-dev-experience<p>Android development tools have improved drastically over the last couple of years. Especially with the move to Android Studio and Gradle, building apps has become a much more pleasant experience. However, there’s still a big performance problem with using Gradle for Android. Compiling and running a non-trivial project can take several minutes and most of the time you forget the changes you were going to test before the app launches on a device or emulator.</p>
<p>A lot of people have written posts with tips and tricks to speed up builds and I’ve pretty much gone through and learnt from every single one of them. However, many people (including folks from Google) suggest using an SSD to improve performance. Thanks to the fine folks at <a href="https://mutualmobile.com">Mutual Mobile</a>, I’ve been building Android apps using a specced-out Macbook Pro with a fancy SSD, so I never had to worry on that front. Recently, I decided to upgrade my secondary MacBook Pro with an SSD and thought it’d be interesting to find out just how much does it improve the overall experience.</p>
<p>After some research, shopping and careful hardware changes, I am done and this post is to document my findings. My approach was somewhat scientific so don’t get mad if the numbers vary slightly for you. Use this as a guide to get a high-level idea of the magnitude of improvement.</p>
<h2 id="the-machine">The machine</h2>
<p><img src="/static/post-img/macbook-specs.png" alt="Macbook specs" /></p>
<p>My ageing 2011 MacBook pro was the perfect guinea pig for this project. To give credit where its due, this machine has aged quite well. It is perfectly capable of small to medium workloads for normal users, but Android Studio was simply unusable. Time to fix that!</p>
<h2 id="hardware">Hardware</h2>
<p>I decided to swap out the SuperDrive with an SSD, after all, who uses optical drives anymore? It also allowed me to continue using the mechanical drive for non-development purposes and save money by going for a lower capacity SSD. Not all SSDs are created equal and performance for drives in the same storage capacity can vary greatly. I found <a href="http://ssd.userbenchmark.com">ssd.userbenchmark.com</a> to be a good resource for comparing different options and finally ended getting a <a href="http://www.amazon.in/Samsung-2-5-Inch-Internal-MZ-75E120B-AM/dp/B00OAJ5N6I">Samsung 850 EVO 120GB</a> from Amazon.</p>
<p><img src="/static/post-img/samsung-evo-ssd.jpeg" alt="Samsung Evo SSD" /></p>
<p>I know SSDs prices have dropped quite a bit in recent times and higher capacity drives have become very affordable. I just didn’t want to spend the extra cash due to the following reasons:</p>
<ul>
<li>I’ll be using this drive only for the OS and most commonly used applications.</li>
<li>I’ll still be using the mechanical drive for everything else.</li>
<li>I get the same read/write performance as the 1TB model so it doesn’t matter what capacity drive I get.</li>
<li>This is a secondary laptop which I occasionally use for development.</li>
</ul>
<p>The superdrive and SSD are not directly interchangeable, so I had to buy a caddy to install the SSD securely in the slot. I went with <a href="http://www.amazon.in/gp/product/B00HD7WBSM?psc=1&redirect=true&ref_=oh_aui_detailpage_o02_s00">this</a> one, again from Amazon.</p>
<p><img src="/static/post-img/ssd-caddy.png" alt="SSD Caddy" /></p>
<p><strong>Total money spent — ₹5,989 (~$88).</strong></p>
<p><br /></p>
<h2 id="tools">Tools</h2>
<p>If you have ever tinkered with modern electronics, you’ll know that having the right tools is extremely important. I made the mistake of not checking if I had all the right bits before I started and ended up struggling to open a few screws, but a friend lent me his iFixit kit and it was smooth-sailing after that.</p>
<p><img src="/static/post-img/ifixit-kit.jpeg" alt="iFixit Kit" /></p>
<h2 id="process">Process</h2>
<p>There are tons of tutorials online that show you step by step how to go about this. I followed this one —</p>
<div class="video-container">
<iframe width="853" height="480" src="https://www.youtube.com/embed/UDXSDnEv9uY" frameborder="0" allowfullscreen=""></iframe>
</div>
<p><br />
Instructions vary slightly for different models, so if you are going to do this yourself, make sure you find the tutorial for your model. YouTube is pretty good source.</p>
<p>After the hardware installation, I performed a clean install of OS X on the SSD and formatted the mechanical hard disk.</p>
<h2 id="optimization">Optimization</h2>
<p>The performance improved quite a bit, but not as much as I was expecting. As per Samsung, the 850 EVOs are rated at up to 540 MB/sec for sequential reads and up to 520 MB/sec for sequential writes. I was not even getting anywhere close to those numbers.</p>
<p>Turns out that the SATA link speed for the superdrive is 3 Gigabit compared to 6 Gigabit for the hard disk controller. I quickly swapped the SSD and hard disk and then things went to a whole new level.</p>
<h2 id="benchmarks">Benchmarks</h2>
<p>Alright, let’s get into the juicy bits, shall we?</p>
<h3 id="boot-time">Boot time</h3>
<p>First, let’s see how much time it takes for a cold boot.</p>
<p><img src="/static/post-img/boot-time.png" alt="Boot time" /></p>
<h3 id="disk-readwrite-performance">Disk Read/Write Performance</h3>
<p>Next, I measured pure disk read/write performance.</p>
<p><em>Before</em>
<img src="/static/post-img/read-write-perf-before.png" alt="Read/Write perf before" /></p>
<p><br /></p>
<p><em>After</em>
<img src="/static/post-img/read-write-perf-after.png" alt="Read/Write perf after" /></p>
<h3 id="launching-android-studio">Launching Android Studio</h3>
<p>Next, I measured how much time it takes Android Studio to launch and finish loading a reasonably large project.</p>
<p><img src="/static/post-img/launching-android-studio.png" alt="Launching Android Studio" /></p>
<h3 id="gradle-sync">Gradle Sync</h3>
<p>Next up, was time taken to perform a Gradle Sync with a dummy change so that it doesn’t download any new dependencies thereby preventing network performance from affecting the time taken.</p>
<p><img src="/static/post-img/gradle-sync.png" alt="Gradle sync" /></p>
<h3 id="running-an-app">Running an app</h3>
<p>Time taken to perform a non-incremental build and deploy it on a Genymotion emulator.</p>
<p><img src="/static/post-img/running-an-app.png" alt="Running an app" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>I did all my testing using Android Studio v1.5.1. With the new improvements across the board in v2.0, which is still in beta, I am certain that things will get even better.</p>
<p>I think this little experiment makes it clear that you don’t need to go out and spend over a thousand dollars to have a reasonably good experience while using Android Studio. Just upgrading an existing desktop or laptop with an SSD is an extremely viable alternative, especially if you are a student or can’t afford to buy a new machine at the moment.</p>
<p>Happy coding!</p>
A boss tells you that you aren't there yet, a leader is busy helping you get there2016-01-22T00:00:00+00:00https://mustafaali.net/2016/01/22/a-boss-tells-you-arent-there-yet<p>It’s easy for many people to tell someone they are not capable enough, it’s convenient to label their dreams as over-ambition. Perhaps they do not want the responsibility of helping shape their team’s careers or maybe they are scared that somebody will get better than them.</p>
<p>True leaders are passionate about helping people reach their goals, they make it their mission to help others realize their full potential. Perhaps they take pride in seeing one of their own succeed or maybe they just want to pay it forward.</p>
Real world networking for engineers2015-09-26T00:00:00+00:00https://mustafaali.net/2015/09/26/real-world-networking-for-engineers<p>Thanks to the nature of my job and being a Google Developer Expert, I get to travel a good bit. This is one part of my life that I thoroughly enjoy. It’s a ton of fun traveling to new cities and meeting interesting people from a wide variety of backgrounds, working on cool new things. This has taught me a great deal about people which I wouldn’t have ever learnt sitting heads-down at my desk churning out code day in and day out.</p>
<p>These days, no matter what conference, meet-up or startup incubator I go to, I see these engineers-turned-entrepreneurs or wannabe-entrepreneurs who go about trying to <strong>“make contacts”</strong>. They come over and introduce themselves, ask what you do and if they think you can do something for them, they expect you to drop everything else and just do it. Conversely, if they don’t think you can do anything for them, they bluntly end the conversation and walk away.</p>
<p>Now I am no networking expert, but you don’t need to be genius to figure out that this approach is self-destructive. Most things in the real world don’t provide instant gratification, like in programming. It’s not like how you write a piece of code, execute it and get the result immediately. The notion that you can meet somebody who will immediately do something to help you is a fallacy. In fact, I’ve seen the victims of this approach go out of their way to make sure even others don’t help such people.</p>
<p><em>So how does an engineer network in the real world? Read on…</em></p>
<h2 id="build-relationships-not-contacts">Build relationships, not contacts</h2>
<p>Over the years, I’ve been incredibly fortunate to have several people go out of their way to help me. Every single one of them has been somebody I nurtured a relationship with over a period of time based on common interests and not because I thought they can help me with something.</p>
<h2 id="nobody-is-obligated-to-help-you">Nobody is obligated to help you</h2>
<p>No matter what your parents/girlfriend/boyfriend/partner thinks, you are not special buddy. Stop thinking yourself as royalty and expect others to do things for you just like that. The faster you get this, the less damage you will cause to yourself.</p>
<h2 id="do-things-for-people-without-expecting-anything-in-return">Do things for people without expecting anything in return</h2>
<p>Don’t be selfish. You won’t lose anything by helping people out. Just do whatever you can, whenever you can, without expecting anything in return. You’ll be surprised how often it’ll come back to you, multiplied many times over.
Do things for people before asking for help</p>
<p>You are not the only person out there looking to get help, pretty much everybody else is. Relationship of any kind is a two-way street. Before asking anything of somebody, ask yourself what you can do to help them. Do it honestly, not to just make them owe you one and especially not when you need something from them. When the time comes, they will be more than happy to return the favour.</p>
<h2 id="say-please-and-thank-you">Say “please” and “thank you”</h2>
<p>The world has an excess of jerks who go about being rude to everybody. When asking for help, don’t forget to say “please”, when somebody helps you out, make sure you thank them genuinely. It won’t make you a lesser man/woman but people will remember how you made them feel.</p>
<blockquote>
<p>“You can easily judge the character of a man by how he treats those who can do nothing for him.” — Malcolm S. Forbes</p>
</blockquote>
<p>Just be nice to people, especially to the ones who can’t do anything for you today. You never know where they (and you) will end up in the future.</p>
Introducing dexinfo Gradle plugin2015-07-18T00:00:00+00:00https://mustafaali.net/2015/07/18/introducing-dexinfo-gradle-plugin<p align="center">
<img src="/static/post-img/dexinfo-plugin.png" />
</p>
<p><em>This was originally published on the <a href="https://mutualmobile.com/posts/introducing-our-dexinfo-gradle-plugin">Mutual Mobile blog</a>. I’m re-posting it here for posterity.</em></p>
<p>As applications are getting increasingly complex, more and more Android developers are running into the DEX 64K limit. I covered this topic–and how to work your way around it–in a previous <a href="/2014/10/20/Dexs-64k-limit-is-not-problem/">post</a>, so make sure you read it if you haven’t done so already.</p>
<p>Most developers don’t worry about this limit while working on their apps, and then end up spending a lot of time and effort to resolve it. We believe that prevention is better than finding a cure. Before you choose to bring a cool library into your code base, one of the things you should seriously consider is how many methods it adds to your application.</p>
<p>To date, we’ve had multiple ways of counting the methods in an application, like <a href="https://gist.github.com/JakeWharton/6002797">this</a> one by Jake Wharton and <a href="https://github.com/mihaip/dex-method-counts">this</a> one by Mihai Parparita. However, my colleague, <a href="https://twitter.com/ayvazjmm">James Ayvaz</a> and I thought we can make things easier by providing this functionality in the form of a Gradle plugin that you can simply integrate into your application. Today, we are happy to announce the plugin is publicly available!</p>
<h2 id="how-to-use-it">How to use it</h2>
<p>Add the dexinfo plugin in your app’s <code class="language-plaintext highlighter-rouge">build.gradle</code> file -</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">buildscript</span> <span class="o">{</span>
<span class="n">repositories</span> <span class="o">{</span>
<span class="n">jcenter</span><span class="o">()</span>
<span class="o">}</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">classpath</span> <span class="s1">'com.mutualmobile.gradle.plugins:dexinfo:0.1.2'</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Apply the plugin after the <code class="language-plaintext highlighter-rouge">com.android.application</code> or <code class="language-plaintext highlighter-rouge">com.android.library</code> plugins -</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'com.android.application'</span>
<span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'com.mutualmobile.gradle.plugins.dexinfo'</span></code></pre></figure>
<p>Build your apk</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>./gradlew <span class="nt">-q</span> assembleDebug</code></pre></figure>
<p>Run the dexinfo task</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>./gradlew <span class="nt">-q</span> dexinfoDebug</code></pre></figure>
<p>Sample output</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>../gradlew dexinfoDebug
:example:dexinfoDebug
Processing /Users/foo/git/example/build/intermediates/dex/devServer/debug/classes.dex
Read <span class="k">in </span>38163 method IDs.
<root>: 182
butterknife: 182
ButterKnife: 48
Action: 1
Finder: 22
1: 3
2: 3
3: 3
Finder[]: 1
Injector: 2
ImmutableList: 4
InjectView: 1
InjectViews: 1
OnCheckedChanged: 1
OnClick: 1
...
internal: 99
Binding: 1
ButterKnifeProcessor: 22
CollectionBinding: 12
Kind: 5
Kind[]: 1
DebouncingOnClickListener: 7
1: 2
ListenerBinding: 5
ListenerClass: 11
NONE: 4
NONE[]: 1
ListenerMethod: 4
Parameter: 5
ViewBinding: 6
ViewInjection: 8
ViewInjector: 18
1: 1
Overall method count: 182
BUILD SUCCESSFUL</code></pre></figure>
<p>You can also specify various options like how far into package paths the count should be reported for, get the count for a specific package, get count only for referenced methods, etc. We’ve been using this internally for some time now and have already saved a lot of time in the process, we hope you’ll find it useful too.</p>
<p>Check it out on <a href="https://github.com/mutualmobile/gradle-dexinfo-plugin">GitHub</a> for more information, and remember that pull requests are always welcome!</p>
DEX’s 64k limit is not a problem anymore, well almost...2014-10-20T00:00:00+00:00https://mustafaali.net/2014/10/20/Dexs-64k-limit-is-not-problem<p>If you are an Android developer reading this, chances are that you’ve heard of the dreaded 64k method limit in Dalvik. In a nutshell, the issue is that in a DEX file, you can reference a very large number of methods, but you can only invoke the first 65,536 of them because that’s all the room you have in the method invocation set. So if your source code and all those cool libraries that you are using, when combined, exceed this limit - kaboom!</p>
<p><img src="/static/post-img/dex-exception.png" alt="Dex Exception" /></p>
<p>For more detailed information about why this happens, read <a href="https://medium.com/@rotxed/dex-skys-the-limit-no-65k-methods-is-28e6cb40cf71">this</a> awesome post on the topic.</p>
<p>Not to be deterred, the amazing Android developer community has come up with some solutions to this problem like <a href="https://gist.github.com/dmarcato/d7c91b94214acd936e42">this</a> and <a href="https://github.com/casidiablo/multidex">this</a>. They work, but require you to do some serious acrobatics to get them working correctly. Last week, a Googler, Ian Lake posted <a href="https://plus.google.com/+IanLake/posts/JW9x4pcB1rj">this</a> on G+ and my excitement knew no bounds.</p>
<p><img src="/static/post-img/multidex-ian-lake.png" alt="Ian Lake's post" /></p>
<p>Having faced this issue myself several times so far, I had to try it out. So here’s what we need to do —</p>
<p>Import <code class="language-plaintext highlighter-rouge">android-support-multidex.jar</code> from <code class="language-plaintext highlighter-rouge">sdk/extras/android/support/multidex/library/libs</code> into your application.</p>
<p>Now based on your project, you have 3 options:</p>
<ol>
<li>If you’ve not created your own Application class, simply declare <code class="language-plaintext highlighter-rouge">android.support.multidex.MultiDexApplication</code> as your application class in <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code></li>
<li>If you already have your own Application class, make it extend <code class="language-plaintext highlighter-rouge">android.support.multidex.MultiDexApplication</code></li>
<li>If your Application class is extending some other class and you don’t want to or can’t change it, override <code class="language-plaintext highlighter-rouge">attachBaseContext()</code> as shown below:</li>
</ol>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyApplication</span> <span class="kd">extends</span> <span class="nc">FooApplication</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">attachBaseContext</span><span class="o">(</span><span class="nc">Context</span> <span class="n">base</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">attachBaseContext</span><span class="o">(</span><span class="n">base</span><span class="o">);</span>
<span class="nc">MultiDex</span><span class="o">.</span><span class="na">install</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Irrespective of which option you choose, what happens is that it generates multiple dex files instead of a single massive one and also takes care of loading all the classes in each one of them at run-time. Neat, right?</p>
<p>Now you must be assuming you can just do this and you can go back to watching cat videos on the internet, well sorry, but that’s not gonna happen. If you run your app after making these changes, you’ll see the same error again.</p>
<p>That’s because, as of the time of writing this, there is no official way of enabling the creation of multiple dex files in Gradle. It is supposedly coming in “a few weeks” but we all know how that works.</p>
<blockquote>
<p>11/8/14: Official support is now available, if you are in a hurry, you can jump to end of this page for instructions on how to use it.</p>
</blockquote>
<p>In the meantime, since Gradle Android plugin v0.9.0, we can actually pass the <code class="language-plaintext highlighter-rouge">-—multi-dex</code> flag to dex to make it generate multiple dex files. To do so, we just need to add the following lines to the app’s <code class="language-plaintext highlighter-rouge">build.gradle</code>.</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">afterEvaluate</span> <span class="o">{</span>
<span class="n">tasks</span><span class="o">.</span><span class="na">matching</span> <span class="o">{</span>
<span class="n">it</span><span class="o">.</span><span class="na">name</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s1">'dex'</span><span class="o">)</span>
<span class="o">}.</span><span class="na">each</span> <span class="o">{</span> <span class="n">dx</span> <span class="o">-></span>
<span class="k">if</span> <span class="o">(</span><span class="n">dx</span><span class="o">.</span><span class="na">additionalParameters</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">dx</span><span class="o">.</span><span class="na">additionalParameters</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'--multi-dex'</span><span class="o">]</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">dx</span><span class="o">.</span><span class="na">additionalParameters</span> <span class="o">+=</span> <span class="s1">'--multi-dex'</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>If we try to build the app now, we’ll see an error again, though a new one, but that’s okay, at least we are making progress.</p>
<p><img src="/static/post-img/dex-top-level-exception.png" alt="Dex top level exception" /></p>
<p>This is because multi-dex mode is currently incompatible with pre-dexing library projects. Pre-dexing is a means of speeding up incremental builds by, well, pre-dexing the dependencies of each module so that the dex task can be made to re-dex only what’s changed.</p>
<p>To make multi-dex mode work, we’ll have to disable pre-dexing. Doing so is pretty straight-forward. Just add the following lines in your <strong>root</strong> build.gradle.</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">project</span><span class="o">.</span><span class="na">ext</span><span class="o">.</span><span class="na">preDexLibs</span> <span class="o">=</span> <span class="o">!</span><span class="n">project</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s1">'disablePreDex'</span><span class="o">)</span>
<span class="n">subprojects</span> <span class="o">{</span>
<span class="n">project</span><span class="o">.</span><span class="na">plugins</span><span class="o">.</span><span class="na">whenPluginAdded</span> <span class="o">{</span> <span class="n">plugin</span> <span class="o">-></span>
<span class="k">if</span> <span class="o">(</span><span class="s2">"com.android.build.gradle.AppPlugin"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">plugin</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">project</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">dexOptions</span><span class="o">.</span><span class="na">preDexLibraries</span> <span class="o">=</span> <span class="n">rootProject</span><span class="o">.</span><span class="na">ext</span><span class="o">.</span><span class="na">preDexLibs</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="s2">"com.android.build.gradle.LibraryPlugin"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">plugin</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">project</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">dexOptions</span><span class="o">.</span><span class="na">preDexLibraries</span> <span class="o">=</span> <span class="n">rootProject</span><span class="o">.</span><span class="na">ext</span><span class="o">.</span><span class="na">preDexLibs</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Now to build your project, do a <code class="language-plaintext highlighter-rouge">./gradlew clean assemble -PdisablePreDex</code> and it will generate multiple dex files and create an APK that you can successfully run on a device/emulator.</p>
<p>This is just a stop-gap measure to make multi-dex work until the official gradle support is released. However, switching from this to the official solution should be a piece of cake, which is why I think it’s a good idea to use it for now. You can see it working in a sample project on <a href="https://github.com/mustafa01ali/MultiDexTest">GitHub</a>.</p>
<h2 id="using-the-official-solution">Using the official solution</h2>
<p>Google added multidex support in v0.14.0 of the Android Gradle plugin, so now things are very straight-forward.</p>
<ol>
<li>Ensure that you are using v21.10 of the Android build tools</li>
<li>Update your application class as described earlier</li>
<li>Modify your gradle config to include the support library and enable multidex</li>
</ol>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="o">...</span>
<span class="n">defaultConfig</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">multiDexEnabled</span> <span class="kc">true</span>
<span class="o">}</span>
<span class="o">...</span></code></pre></figure>
<p>You can set the <code class="language-plaintext highlighter-rouge">multiDexEnabled</code> attribute in buildType or productFlavor sections as well. Now when you run the application, Gradle will generate multiple dex files and build an APK that you can successfully run on a device/emulator.</p>
<p><img src="/static/post-img/multiple-dex-files.png" alt="Multiple Dex files" /></p>
<p>For more information, you can read the official guide, limitations and also check out this sample.</p>
<p><em>Now where’s that tab with the cat videos.</em></p>