<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feed-style.xsl"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Julio Zynger]]></title><description><![CDATA[Blog posts, presentations, GitHub, and more.]]></description><link>https://www.juliozynger.com</link><generator>GatsbyJS</generator><lastBuildDate>Sun, 26 Jul 2026 22:48:27 GMT</lastBuildDate><atom:link href="https://www.juliozynger.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><item><title><![CDATA[Putting my inbox away]]></title><description><![CDATA[For about fifteen years my email "system" was: read a message, decide it was handled, and then leave it exactly where it was. I never archived anything. So my inbox quietly grew to 15,000+ messages, of which a grand total of twelve were unread. Every day I'd glance at whatever was new that morning…]]></description><link>https://www.juliozynger.com/articles/putting-my-inbox-away/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/putting-my-inbox-away/</guid><pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For about fifteen years my email &quot;system&quot; was: read a message, decide it was handled, and then leave it exactly where it was. I never archived anything. So my inbox quietly grew to &lt;strong&gt;15,000+ messages&lt;/strong&gt;, of which a grand total of twelve were unread. Every day I&apos;d glance at whatever was new that morning and ignore the wall of history behind it. It worked, technically. It also felt like living in a room where nothing ever gets put away.&lt;/p&gt;
&lt;p&gt;I&apos;d been meaning to fix it forever. What finally did it was realizing I didn&apos;t have to &lt;em&gt;do&lt;/em&gt; it by hand. A little &lt;a href=&quot;https://github.com/googleworkspace/cli&quot;&gt;command-line tool for Google Workspace&lt;/a&gt; had been rattling around my head ever since I stumbled onto &lt;a href=&quot;https://justin.poehnelt.com/posts/rewrite-your-cli-for-ai-agents/&quot;&gt;Justin Poehnelt&apos;s post on rewriting CLIs for AI agents&lt;/a&gt; during some research for work. He built the thing to be driven by an AI in the first place, and it shows. I&apos;d braced for a painful login-and-permissions ordeal to connect it to my personal Google account; it turned out to be basically one command. So I pointed Claude at it and, instead of clicking through thousands of emails, just asked questions: &lt;em&gt;how big is this really? who emails me most? what are all these old labels?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The surprises were the fun part.&lt;/p&gt;
&lt;p&gt;The first: my &quot;unread&quot; was secretly doing three jobs at once. It meant &lt;em&gt;new&lt;/em&gt;, and &lt;em&gt;I still need to deal with this&lt;/em&gt;, and &lt;em&gt;I want to read this later&lt;/em&gt;, all at the same time. No wonder it never felt clean. Once I split those into separate places (using a Gmail setting called &lt;a href=&quot;https://support.google.com/mail/answer/9694882?hl=en&quot;&gt;Multiple Inboxes&lt;/a&gt; I never knew existed until that afternoon), the whole thing calmed down. (There&apos;s a nice &lt;a href=&quot;https://x.com/KatieKeithBarn2/status/1949766679837147431&quot;&gt;step-by-step walkthrough&lt;/a&gt; if you want to set one up yourself.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/putting-my-inbox-away/inbox-layout.svg&quot; alt=&quot;A mock email homepage: an Inbox holding only actionable mail, a Follow-up rail, and a To Read pane stacked with newsletters&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Roughly how it looks now: only what&apos;s actionable stays in the inbox, things I owe a reply sit in &quot;Follow up,&quot; and newsletters pile up in &quot;To Read.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The second one I did not see coming. I went in expecting to find heaps of newsletter junk to unsubscribe from. There basically wasn&apos;t any. When I actually looked, the &quot;spam-looking&quot; senders were things like a job offer, a note from a recruiter, an apartment contract. My inbox was never &lt;em&gt;clutter&lt;/em&gt;. It was an unindexed archive of pretty much everything that had ever happened to me, and I&apos;d simply never asked it what was inside.&lt;/p&gt;
&lt;p&gt;There was even something oddly sentimental in it. My old folder labels turned out to read like a timeline of my life: university, a year abroad, a startup that didn&apos;t make it, a job that led to another. I hadn&apos;t set out to keep a diary. It just accumulated.&lt;/p&gt;
&lt;p&gt;By the end of the afternoon the whole pile was archived (not deleted, just &lt;em&gt;put away&lt;/em&gt; and still searchable), the dead weight was gone, and new mail now sorts itself: newsletters wait in a &quot;read later&quot; pile, receipts file themselves out of sight, and only genuinely new things land in front of me. The one habit I kept is embarrassingly simple: &lt;strong&gt;when I&apos;m done with something, I put it away.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s a small thing. But my inbox finally feels like mine again, and I mostly just chatted with a computer to get there. 🤖&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why I stopped saying "legacy"]]></title><description><![CDATA[Outside of software, legacy is a word of pride. People talk about the legacy they want to leave — what they hope to be remembered for, what they want to pass on. The word is used with reverence. In software, it's a complaint. The legacy database. The legacy frontend. The legacy auth flow. I try to…]]></description><link>https://www.juliozynger.com/articles/why-i-stopped-saying-legacy/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/why-i-stopped-saying-legacy/</guid><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Outside of software, &lt;em&gt;legacy&lt;/em&gt; is a word of pride. People talk about &lt;a href=&quot;https://www.reddit.com/r/OverSeventy/comments/1p1ksbh/whats_your_legacy/&quot;&gt;the legacy they want to leave&lt;/a&gt; — what they hope to be remembered for, what they want to pass on. The word is used with reverence.&lt;/p&gt;
&lt;p&gt;In software, it&apos;s a complaint. The legacy database. The legacy frontend. The legacy auth flow.&lt;/p&gt;
&lt;p&gt;I try to never use it that way.&lt;/p&gt;
&lt;p&gt;In our industry, &lt;em&gt;legacy&lt;/em&gt; carries baggage. It implies obsolescence, accumulated pain, something to be embarrassed about. And yet — in almost every case I&apos;ve worked on — the &quot;legacy&quot; system is the one paying the bills. It&apos;s the system serving traffic right now, the one customers are happily using, the one funding the migration to its replacement.&lt;/p&gt;
&lt;p&gt;As &lt;a href=&quot;https://en.wikipedia.org/wiki/Legacy_system&quot;&gt;Bjarne Stroustrup observed&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Legacy code often differs from its suggested alternative by actually working and scaling.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The word shapes how teams treat it. Once a system is &lt;em&gt;legacy&lt;/em&gt;, it stops getting investment. Bug reports get triaged differently. New hires are told not to learn it. Engineers who maintain it feel like they&apos;re working on something already dead.&lt;/p&gt;
&lt;p&gt;And the label sticks. A system usually earns the &quot;legacy&quot; tag the moment a replacement starts being planned — but &lt;a href=&quot;https://www.juliozynger.com/articles/default-to-the-end-state&quot;&gt;migrations drag, or don&apos;t finish at all&lt;/a&gt;. The negative connotation lingers in the organization for years. People want distance from it. Meanwhile the system everyone has decided is &lt;em&gt;legacy&lt;/em&gt; keeps doing its job, waiting for a successor that may never quite arrive.&lt;/p&gt;
&lt;p&gt;I use &lt;strong&gt;classic&lt;/strong&gt; instead. The classic-cars analogy I owe to &lt;a href=&quot;https://kyle.cascade.family/posts/how-to-actually-migrate-complex-systems-in-infrastructure/#step-1-shit-all-over-the-legacy-code&quot;&gt;Kyle&apos;s post on migrations&lt;/a&gt; — a system that&apos;s earned its place, that we still respect, that we choose to maintain even as we build its successor. &lt;em&gt;Vintage&lt;/em&gt; works too, with the same reframing.&lt;/p&gt;
&lt;img src=&quot;https://www.juliozynger.com/images/articles/why-i-stopped-saying-legacy/classic-car-unsplash.jpg&quot; alt=&quot;A teal classic saloon car parked alongside a red brick wall&quot; /&gt;
&lt;p&gt;This isn&apos;t just vocabulary. In the &lt;a href=&quot;https://www.juliozynger.com/articles/default-to-the-end-state&quot;&gt;previous post&lt;/a&gt;, the renamed build script was &lt;code class=&quot;language-text&quot;&gt;proto:gen:classic&lt;/code&gt;, not &lt;code class=&quot;language-text&quot;&gt;proto:gen:legacy&lt;/code&gt;. That wasn&apos;t an accident.&lt;/p&gt;
&lt;p&gt;When I find myself reaching for &lt;em&gt;legacy&lt;/em&gt;, I stop. The system in front of me is probably what&apos;s keeping the lights on. I owe it a better word.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Default to the end-state]]></title><description><![CDATA[A platform team I work with is moving our compute from  to . The migration plan, roughly: Multi-arch the container builds. Add  to the service's Terraform module call (touch every repository). Once every service has flipped the flag, remove it (touch every repository again). Step 3 is where I want…]]></description><link>https://www.juliozynger.com/articles/default-to-the-end-state/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/default-to-the-end-state/</guid><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A platform team I work with is moving our compute from &lt;code class=&quot;language-text&quot;&gt;x86_64&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;arm64&lt;/code&gt;. The migration plan, roughly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Multi-arch the container builds.&lt;/li&gt;
&lt;li&gt;Add &lt;code class=&quot;language-text&quot;&gt;graviton = true&lt;/code&gt; to the service&apos;s Terraform module call &lt;em&gt;(touch every repository)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Once every service has flipped the flag, remove it &lt;em&gt;(touch every repository again)&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Step 3 is where I want to stop.&lt;/p&gt;
&lt;p&gt;Step 3 is a &lt;strong&gt;second sweep&lt;/strong&gt; across every repository that participated in step 2 — re-opening each service, deleting the flag, re-deploying. It is the most expensive part of the migration and the easiest to defer indefinitely. Most migrations never finish step 3.&lt;/p&gt;
&lt;p&gt;There is a cheaper version of the same plan. &lt;strong&gt;Change the flag&apos;s polarity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of &lt;code class=&quot;language-text&quot;&gt;graviton = true&lt;/code&gt;, expose &lt;code class=&quot;language-text&quot;&gt;legacy_x86 = true&lt;/code&gt;. The default — the result of doing nothing — is now the migration target. Step 2 becomes &lt;em&gt;&quot;services that aren&apos;t ready opt out&quot;&lt;/em&gt;. Step 3 becomes &lt;em&gt;&quot;the platform team deletes the flag handling in a single PR&quot;&lt;/em&gt;. No second sweep. No long tail.&lt;/p&gt;
&lt;p&gt;The same trap shows up outside of infrastructure. When we &lt;a href=&quot;https://medium.com/zenjob-tech-blog/adopting-grpc-at-zenjob-the-technology-was-the-easy-part-56adc860b9df&quot;&gt;migrated our protobuf TypeScript generation&lt;/a&gt; from one toolchain to another, every package depended on a build script called &lt;code class=&quot;language-text&quot;&gt;proto:gen&lt;/code&gt; to produce its TypeScript bindings. The natural instinct was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduce a second build script &lt;code class=&quot;language-text&quot;&gt;proto:gen:v2&lt;/code&gt; next to the existing &lt;code class=&quot;language-text&quot;&gt;proto:gen&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Migrate every package to call &lt;code class=&quot;language-text&quot;&gt;proto:gen:v2&lt;/code&gt; &lt;em&gt;(touch every repository)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Once every package is on &lt;code class=&quot;language-text&quot;&gt;v2&lt;/code&gt;, delete the old script and rename &lt;code class=&quot;language-text&quot;&gt;proto:gen:v2&lt;/code&gt; back to &lt;code class=&quot;language-text&quot;&gt;proto:gen&lt;/code&gt; &lt;em&gt;(touch every repository again)&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Same shape. Two sweeps.&lt;/p&gt;
&lt;p&gt;The polarity-flipped version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rename the existing script to &lt;code class=&quot;language-text&quot;&gt;proto:gen:classic&lt;/code&gt; &lt;em&gt;(touch every repository, but no behavior change)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Introduce the new generator under the canonical &lt;code class=&quot;language-text&quot;&gt;proto:gen&lt;/code&gt; name. New code lands on it by default.&lt;/li&gt;
&lt;li&gt;Migrate packages off &lt;code class=&quot;language-text&quot;&gt;proto:gen:classic&lt;/code&gt; opportunistically, and delete it once nothing depends on it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;proto:gen&lt;/code&gt; always means &lt;em&gt;the current canonical generator&lt;/em&gt;, so no package has to touch its build script just to keep up with the migration.&lt;/p&gt;
&lt;p&gt;The underlying idea has a name borrowed from API design: the &lt;a href=&quot;https://blog.codinghorror.com/falling-into-the-pit-of-success/&quot;&gt;&lt;strong&gt;pit of success&lt;/strong&gt;&lt;/a&gt;. Doing nothing should land you in the state you want. A flag — or a script name, or a default config — that points away from the target inverts that. Doing nothing keeps you in the past.&lt;/p&gt;
&lt;p&gt;So when I find myself proposing a migration flag, I ask:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If every team ignores my Slack message, where do they end up?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the answer is &lt;em&gt;the old behavior&lt;/em&gt;, I flip the flag around.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Zenjob is Betting on TypeScript: An Organizational Decision]]></title><link>https://medium.com/zenjob-tech-blog/zenjob-is-betting-on-typescript-an-organizational-decision-5359e20d694f</link><guid isPermaLink="false">https://medium.com/zenjob-tech-blog/zenjob-is-betting-on-typescript-an-organizational-decision-5359e20d694f</guid><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Adopting gRPC at Zenjob: The Technology Was the Easy Part]]></title><link>https://medium.com/zenjob-tech-blog/adopting-grpc-at-zenjob-the-technology-was-the-easy-part-56adc860b9df</link><guid isPermaLink="false">https://medium.com/zenjob-tech-blog/adopting-grpc-at-zenjob-the-technology-was-the-easy-part-56adc860b9df</guid><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[What Engineers Need from an All-Hands: It's Not Tech Updates]]></title><link>https://medium.com/zenjob-tech-blog/what-engineers-need-from-an-all-hands-its-not-tech-updates-7aad171c9eab</link><guid isPermaLink="false">https://medium.com/zenjob-tech-blog/what-engineers-need-from-an-all-hands-its-not-tech-updates-7aad171c9eab</guid><pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[SoundCloud Echo: Next-Level Humane Registry with Backstage]]></title><link>https://developers.soundcloud.com/blog/soundcloud-echo-next-level-backstage</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-echo-next-level-backstage</guid><pubDate>Mon, 19 Sep 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Obvious Ownership: A Sensible Humane Registry]]></title><link>https://developers.soundcloud.com/blog/obvious-ownership-humane-registry</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/obvious-ownership-humane-registry</guid><pubDate>Thu, 06 Jan 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Did I Break You? Reverse Dependency Verification]]></title><link>https://developers.soundcloud.com/blog/did-i-break-you</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/did-i-break-you</guid><pubDate>Tue, 25 May 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[Tests Under the Magnifying Lens]]></title><link>https://developers.soundcloud.com/blog/tests-under-the-magnifying-lens</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/tests-under-the-magnifying-lens</guid><pubDate>Tue, 02 Feb 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[SQLite: when Insert means Delete]]></title><description><![CDATA[Modernizing some of SoundCloud's Android app storage layers, I've been especially invested in databases, and have been migrating a lot of the core entities of the app between our in-house ORM and industry-known alternatives, as Room and SQLDelight. There are many parts to that work, ranging from…]]></description><link>https://www.juliozynger.com/articles/sqlite-when-insert-means-delete/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/sqlite-when-insert-means-delete/</guid><pubDate>Tue, 30 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Modernizing some of SoundCloud&apos;s Android app storage layers, I&apos;ve been especially invested in databases, and have been migrating a lot of the core entities of the app between our in-house ORM and industry-known alternatives, as &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/room&quot;&gt;Room&lt;/a&gt; and SQLDelight.&lt;/p&gt;
&lt;p&gt;There are many parts to that work, ranging from &lt;em&gt;understanding the current standings&lt;/em&gt; of the schemas to coming up with improvements, risk mitigation or reducing overall tech-debt. It has been a great opportunity to learn about the internals of &lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt;, the RDBMS that powers most mobile apps, widely popular in Android but that also backs iOS&apos; CoreData framework.&lt;/p&gt;
&lt;p&gt;Today, I want to describe an interesting scenario we&apos;ve hit while using a few more advanced features of SQLite: &lt;strong&gt;Foreign Key trigger actions&lt;/strong&gt;, but more deeply, how their usage in combination to &lt;strong&gt;insertion statements&lt;/strong&gt; can raise unexpected effects.&lt;/p&gt;
&lt;h2&gt;Foreign Key Actions&lt;/h2&gt;
&lt;p&gt;Let&apos;s imagine a schema with two tables, &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; and a junction table &lt;code class=&quot;language-text&quot;&gt;TrackCreator&lt;/code&gt; whose schema is as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/sqlite-insert-delete/schema-diagram.png&quot; alt=&quot;Schema diagram&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; TrackCreator &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;track_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;Track&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ACTION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CASCADE&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having a junction table is great because it allows us to write queries that JOIN the columns of either of the referenced tables, for example, if we need to get the creator name for a given Track.&lt;/p&gt;
&lt;p&gt;Now, if either the &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; or the &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt; that created it gets deleted or have its id altered, SQLite can automatically update our &lt;code class=&quot;language-text&quot;&gt;TrackCreator&lt;/code&gt; table if we set a &lt;strong&gt;Foreign Key action&lt;/strong&gt; for these references.&lt;/p&gt;
&lt;p&gt;SQLite&apos;s &lt;a href=&quot;https://www.sqlite.org/foreignkeys.html&quot;&gt;documentation does a great job&lt;/a&gt; explaining what foreign keys are, and more interestingly the possible &lt;strong&gt;&lt;a href=&quot;https://www.sqlite.org/foreignkeys.html#fk_actions&quot;&gt;actions&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;one can register as triggers&lt;/em&gt; to an &lt;strong&gt;update&lt;/strong&gt; or &lt;strong&gt;delete&lt;/strong&gt; in the parent table.&lt;/p&gt;
&lt;h2&gt;Conflict resolution&lt;/h2&gt;
&lt;p&gt;SQLite also accounts for possible &lt;a href=&quot;https://www.sqlite.org/lang_conflict.html&quot;&gt;conflict resolutions&lt;/a&gt; when constraints are violated: on the example above, stating an &lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt; for an already existing &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt; would violate the &lt;code class=&quot;language-text&quot;&gt;PRIMARY KEY&lt;/code&gt; uniqueness constraint on either table.&lt;/p&gt;
&lt;p&gt;Most ORMs make it really easy to define a conflict resolution, altering the &lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt; statement to include the &lt;code class=&quot;language-text&quot;&gt;ON CONFLICT&lt;/code&gt; clause:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/sqlite-insert-delete/insert-or-replace.gif&quot; alt=&quot;INSERT OR REPLACE&quot;&gt;&lt;/p&gt;
&lt;p&gt;When working with &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/room&quot;&gt;Room&lt;/a&gt;, that can be done on the Dao by specifying it in an annotation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Dao&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; TrackDao &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@Insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onConflict &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OnConflictStrategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REPLACE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; List&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TrackEntity&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;Now, on the setup above, a &lt;em&gt;surprising&lt;/em&gt; effect would happen when inserting an already existing &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;User&lt;/code&gt;. As &lt;a href=&quot;https://www.sqlite.org/lang_conflict.html&quot;&gt;SQLite&apos;s documentation&lt;/a&gt; states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When a &lt;a href=&quot;https://www.sqlite.org/lang_createtable.html#uniqueconst&quot;&gt;UNIQUE&lt;/a&gt; or &lt;a href=&quot;https://www.sqlite.org/lang_createtable.html#primkeyconst&quot;&gt;PRIMARY KEY&lt;/a&gt; constraint violation occurs, the REPLACE algorithm deletes (…) prior to inserting or updating the current row.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So here we are:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/sqlite-insert-delete/insert-delete-flow.png&quot; alt=&quot;Insert Delete Flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;And at that point, our &lt;strong&gt;ON DELETE action will trigger&lt;/strong&gt;, effectively removing the entry from the junction table as well. That means queries that rely on the JOIN statement will yield no results.&lt;/p&gt;
&lt;h2&gt;Android specificities&lt;/h2&gt;
&lt;p&gt;During our investigation, we were made aware of this behaviour by reading SQLite log statements. On Android, the mechanism to enable logging isn&apos;t straightforward, but &lt;a href=&quot;https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#enableWriteAheadLogging()&quot;&gt;exists&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When inserting &lt;code class=&quot;language-text&quot;&gt;Track&lt;/code&gt;s that violate the uniqueness constraint of the primary key, we confirm our hypothesis:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;BEGIN EXCLUSIVE;&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;INSERT OR REPLACE INTO `Users` (...) VALUES (...)&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;INSERT OR REPLACE INTO `Users` (...) VALUES (...)&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- TRIGGER room_table_modification_trigger_trackuser_DELETE&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- UPDATE room_table_modification_log SET invalidated = 1 WHERE table_id = 7 AND invalidated = 0&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- TRIGGER room_table_modification_trigger_trackuser_DELETE&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- UPDATE room_table_modification_log SET invalidated = 1 WHERE table_id = 7 AND invalidated = 0&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- TRIGGER room_table_modification_trigger_users_DELETE&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- UPDATE room_table_modification_log SET invalidated = 1 WHERE table_id = 0 AND invalidated = 0&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- TRIGGER room_table_modification_trigger_users_INSERT&quot;
V/SQLiteStatements: /data/com.soundcloud/databases/sc.db: &quot;-- UPDATE room_table_modification_log SET invalidated = 1 WHERE table_id = 0 AND invalidated = 0&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Starting on version 3.24.0 (2018–06–04), SQLite does provide an &lt;a href=&quot;https://www.sqlite.org/draft/lang_UPSERT.html&quot;&gt;UPSERT&lt;/a&gt; statement (following the syntax established by PostgreSQL) but the &lt;strong&gt;bundled version of SQLite in the Android framework is &lt;a href=&quot;https://developer.android.com/reference/android/database/sqlite/package-summary&quot;&gt;not quite up-to-date&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is still possible to &apos;manually implement&apos; &lt;code class=&quot;language-text&quot;&gt;UPSERT&lt;/code&gt; by mimicking SQLite&apos;s syntax:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/sqlite-insert-delete/upsert-manual.gif&quot; alt=&quot;UPSERT manual implementation&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Time for a migration&lt;/h2&gt;
&lt;p&gt;In our case, we could relax our constraint definition by dropping the &lt;code class=&quot;language-text&quot;&gt;ON DELETE&lt;/code&gt; action trigger, instead of implementing the &lt;code class=&quot;language-text&quot;&gt;UPSERT&lt;/code&gt;. Here, the usage of SQLite brings a bit more work: the &lt;code class=&quot;language-text&quot;&gt;ALTER TABLE&lt;/code&gt; statements omit many of the advanced clauses other SQL engines provide, including &lt;code class=&quot;language-text&quot;&gt;CONSTRAINT&lt;/code&gt; manipulation related.&lt;/p&gt;
&lt;p&gt;So, in order to change the constraint definition for the foreign key, we require a multi-step migration, that will copy the existing data into a temporary table and rename it afterwards:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execSQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal multiline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&quot;
    CREATE TABLE IF NOT EXISTS TrackCreator_temp (
        `track_id` TEXT NOT NULL,
        `user_id` TEXT NOT NULL,
        PRIMARY KEY(`track_id`, `user_id`),
        FOREIGN KEY(`track_id`) REFERENCES `Track`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION ,
        FOREIGN KEY(`user_id`) REFERENCES `User`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
        )
    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trimIndent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Move all data to temporary table&lt;/span&gt;
database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execSQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;INSERT INTO TrackCreator_temp SELECT * FROM TrackCreator&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Drop mis-scheme table&lt;/span&gt;
database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execSQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DROP TABLE TrackCreator&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Rename temporary table to definitive name&lt;/span&gt;
database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execSQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ALTER TABLE TrackCreator_temp RENAME TO TrackCreator&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Recreate indexes if needed (omitted for brevity)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;One must be attentive to the underlying mechanisms of third-party tooling. Dealing with persistence and databases, it pays off to get acquainted to some degree of detail, especially given the cost of a &lt;em&gt;post-factum&lt;/em&gt; change.&lt;/p&gt;
&lt;p&gt;Being embedded, and in an almost feature-freeze on mobile OSs, SQLite&apos;s lean API still provides &apos;eureka moments&apos;. There are always opportunities for learning.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[FloorPlan: Visualize database evolution]]></title><description><![CDATA[Easier peer reviews and documentation-as-code At SoundCloud, we are around 20 Android engineers, working in a multi-module project with more than 10 databases, with multiple tables each. And that's only the Android app! As software matures, more functionality is added, code gets moved, re-written or…]]></description><link>https://www.juliozynger.com/articles/floorplan-visualize-database-evolution/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/floorplan-visualize-database-evolution/</guid><pubDate>Tue, 16 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Easier peer reviews and documentation-as-code&lt;/h2&gt;
&lt;p&gt;At SoundCloud, we are around 20 Android engineers, working in a multi-module project with more than 10 databases, with multiple tables each. And that&apos;s only the Android app!&lt;/p&gt;
&lt;p&gt;As software matures, more functionality is added, code gets moved, re-written or removed. In the same rhythm, team members change and with them historical knowledge might get lost. For that reason, it is important to have a well-organized project, that is inviting for new contributors while still digestible for one-off readers.&lt;/p&gt;
&lt;p&gt;Understanding how data is structured can be very helpful as an initial step for diving into the abstract modeling of the business domain. Database schemas, then, provide that entry window, but building the mental model out of a machine-readable format can be a daunting task even for seasoned engineers, especially when the project grows large and changes rapidly.&lt;/p&gt;
&lt;h2&gt;Enter FloorPlan&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/julioz/FloorPlan&quot;&gt;FloorPlan&lt;/a&gt; is an open source Kotlin library to translate database schemas into &lt;a href=&quot;https://www.dbml.org/&quot;&gt;DBML&lt;/a&gt; definitions and &lt;a href=&quot;https://www.wikiwand.com/en/Entity%E2%80%93relationship_model&quot;&gt;ER diagrams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/floorplan/floorplan-demo.gif&quot; alt=&quot;FloorPlan demo&quot;&gt;&lt;/p&gt;
&lt;p&gt;It is distributed as a &lt;a href=&quot;https://julioz.github.io/FloorPlan/run/&quot;&gt;CLI tool&lt;/a&gt; and as a &lt;a href=&quot;https://search.maven.org/artifact/com.juliozynger.floorplan/floorplan-gradle-plugin&quot;&gt;Gradle Plugin&lt;/a&gt;, for manual interaction and integration with CI environments.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;floorPlan &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schemaLocation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;schemas/&quot;&lt;/span&gt;&lt;/span&gt;
  outputLocation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;floorplan-output/&quot;&lt;/span&gt;&lt;/span&gt;
  outputFormat &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    svg &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://julioz.github.io/FloorPlan/&quot;&gt;full documentation&lt;/a&gt; and &lt;a href=&quot;https://github.com/julioz/FloorPlan/tree/master/sample-android-project&quot;&gt;sample plugin integration&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Versioning 🗒️&lt;/h3&gt;
&lt;p&gt;As you already do for the &lt;a href=&quot;https://www.writethedocs.org/guide/docs-as-code/&quot;&gt;rest of your documentation&lt;/a&gt;, checking in human-readable representations of your application&apos;s logic is a great way to build historical knowledge on the codebase, and what ended up in production.&lt;/p&gt;
&lt;p&gt;By using &lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;, developers can iterate on proposed schema changes, easily review them and also bring in non-technical peers by lowering the learning curve into database technologies.&lt;/p&gt;
&lt;h3&gt;Code review 🔎&lt;/h3&gt;
&lt;p&gt;An useful recipe is to check-in each version of the database as a &lt;a href=&quot;https://www.dbml.org/&quot;&gt;DBML&lt;/a&gt; schema, and then later, leverage the power of standard &lt;code class=&quot;language-text&quot;&gt;diff&lt;/code&gt;ing tools to quickly spot changes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;$ diff -u 4.dbml 5.dbml
&lt;span class=&quot;token coord&quot;&gt;--- 4.dbml      2020-06-12 12:20:09.000000000 +0200&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ 5.dbml      2020-06-12 12:33:15.000000000 +0200&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -1,9 +1,8 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;Table play_queue {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  _id int [pk, increment]
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  entity_id int [note: &apos;nullable&apos;]
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  entity_type int [note: &apos;nullable&apos;]
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  entity_urn varchar [note: &apos;not null&apos;]
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  related_entity varchar [note: &apos;nullable&apos;]
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  source varchar [note: &apos;nullable&apos;]
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  source varchar [note: &apos;not null&apos;]
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  context_type varchar [note: &apos;nullable&apos;]
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  context_query varchar [note: &apos;nullable&apos;]
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  played int [note: &apos;nullable&apos;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the spirit of full automation, these can then be written as pull request comments after a CI build:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/floorplan/pr-comment.png&quot; alt=&quot;PR comment showing schema changes&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Documentation 📝&lt;/h3&gt;
&lt;p&gt;With the power of &lt;a href=&quot;https://graphviz.org/&quot;&gt;GraphViz&lt;/a&gt;, FloorPlan can also output other rendering formats, such as vector SVGs, rasterized PNGs and diagram DOT files, allowing for even further integration with your team&apos;s workflow.&lt;/p&gt;
&lt;p&gt;At SoundCloud, we host an internal documentation portal, completely &lt;a href=&quot;https://www.mkdocs.org/&quot;&gt;based on markdown files&lt;/a&gt;, that now can automatically include diagrams for our client application databases as pull requests are merged:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/floorplan/docs-portal.png&quot; alt=&quot;Documentation portal with database diagrams&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Extensibility 🗜️&lt;/h3&gt;
&lt;p&gt;FloorPlan&apos;s API is designed for extensibility, and its &lt;a href=&quot;https://julioz.github.io/FloorPlan/architecture/#processing-pipeline&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Consumer&lt;/code&gt; API&lt;/a&gt; allows for integration with different RDBMS engines and ORMs. It doesn&apos;t matter which source database ORM schema structure you use, with the help of a &lt;code class=&quot;language-text&quot;&gt;Consumer&lt;/code&gt; implementation, FloorPlan will be able to translate the database schema.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/floorplan/architecture.png&quot; alt=&quot;FloorPlan architecture diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;On the Android space, for instance, there are &lt;a href=&quot;https://github.com/cashapp/sqldelight&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/room&quot;&gt;popular&lt;/a&gt; &lt;a href=&quot;https://github.com/realm/realm-java&quot;&gt;libraries&lt;/a&gt; to communicate with SQLite, each providing a different feature-set, that FloorPlan can integrate with.&lt;/p&gt;
&lt;p&gt;That doesn&apos;t make its usage Android-specific, though, since a &lt;code class=&quot;language-text&quot;&gt;Consumer&lt;/code&gt; for MySQL, Postgres or Oracle could also be built by leveraging these RDBMS&apos;s schema exporting tools. In fact, one can work with &lt;a href=&quot;https://www.dbml.org/js-module/#api&quot;&gt;already existing tools&lt;/a&gt; to use DBML itself as an input to FloorPlan.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;Integrate &lt;a href=&quot;https://github.com/julioz/FloorPlan&quot;&gt;FloorPlan&lt;/a&gt; in your project&apos;s workflow, contribute and provide feedback so it can improve further!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Be a good client: request prioritization]]></title><description><![CDATA[The story is more common than it seems: a new feature is developed and tested extensively in-house, but only when the client applications are deployed, the engineering team discovers the extra production load and seasonal request rate. A hot-fix is needed! But now it's too late: we cannot force our…]]></description><link>https://www.juliozynger.com/articles/be-a-good-client-request-prioritization/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/be-a-good-client-request-prioritization/</guid><pubDate>Mon, 23 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The story is more common than it seems: a new feature is developed and tested extensively in-house, but only when the client applications are deployed, the engineering team discovers the extra production load and seasonal request rate. A hot-fix is needed! But now it&apos;s &lt;em&gt;too late&lt;/em&gt;: we cannot force our users to update their apps or refresh the page on their web browser.&lt;/p&gt;
&lt;p&gt;As we&apos;ve discussed in &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-jitter&quot;&gt;part 1 of this series&lt;/a&gt;, clients are in a way bigger number than our servers, and even though we have applied some of the techniques previously described to make our clients smarter, there&apos;s more we can do on the server-side to make the entire system more resilient.&lt;/p&gt;
&lt;h2&gt;Remote Tuning&lt;/h2&gt;
&lt;p&gt;When communicating to a backend, it is a good idea for clients to expose as much information as possible about their requests. That way, a server will be in a better position to qualify and prioritize the calls it receives.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Keep in mind, the recommendations here apply only to technical aspects of the requests, and &lt;strong&gt;if you fully control&lt;/strong&gt; the infrastructure — do not identify personal user information, and most importantly, do not share these with 3rd-parties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Client identification&lt;/h3&gt;
&lt;p&gt;By exposing a client name and its version, the backend can categorize requests and even workaround possible client bugs. It is especially interesting to agree on a structured form for that value, allowing for parsing, sorting and filtering. There is already a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent&quot;&gt;standard for web browsers&lt;/a&gt;, but it proves very valuable on mobile apps as well.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;func serve_request:
  if (client_id = android_app &amp;amp; client_version = 1234):
    // suppose there is a retry loop bug in that app version
    return Response(400)
  else:
    handle_request()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Another aspect that can be useful from a backend perspective is bucketing of client variability throughout a singular client version. For example, if there are feature toggles or A/B tests in place, sharing variant identifiers will be helpful to identify and isolate arising issues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-request-prioritization/client-identification.png&quot; alt=&quot;Client identification and categorization&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Serving order&lt;/h2&gt;
&lt;p&gt;In the event of a partial outage, servers can decide to drop requests to relieve load in the overall system. In fact, the faster a circuit-breaking mechanism triggers, the more resources will be saved. &lt;a href=&quot;https://www.thoughtworks.com/insights/blog/bff-soundcloud&quot;&gt;API gateways&lt;/a&gt; are good candidates for such logic, but in some cases it can also be applied throughout the service net.&lt;/p&gt;
&lt;p&gt;Introducing a company-agreed index of criticality for certain features and severity levels in case of degradation will support ranking which paths have serving priority. The server can then reject requests that fall under the lowest levels of the ranking. As exemplified by &lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/handling-overload/#criticality-00sDCK&quot;&gt;Google&apos;s SRE book&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For example, when a system displays search results or suggestions while the user is typing a search query, the underlying requests are highly sheddable (if the system is overloaded, it&apos;s acceptable to not display these results).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The criticality index can be &lt;strong&gt;composed by several sub-metrics&lt;/strong&gt;: for example, if a serving-path goes through usually overloaded services or of expensive scalability or if it is known to have caused retry storms in the past.&lt;/p&gt;
&lt;p&gt;We can also &lt;strong&gt;take in account UX-aspects to bump the criticality&lt;/strong&gt; of a request path, for example, whether a request was triggered by a user or happened from an automated job action, or whether the client application was in foreground or background in a mobile device.&lt;/p&gt;
&lt;p&gt;As an alternative, besides rejecting requests, one can also decide to &apos;gracefully degrade&apos; the response, &lt;strong&gt;skipping the expensive paths that aren&apos;t critical&lt;/strong&gt; to the overall user experience. For example, suppose fetching an audio track metadata from a saturated backend, a server can decide to skip calculating how many likes the track has and use a sensible default instead:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-request-prioritization/graceful-degradation.png&quot; alt=&quot;Graceful degradation example&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Budgets&lt;/h2&gt;
&lt;p&gt;Additionally to &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-retries&quot;&gt;circuit-breaking limits for retries&lt;/a&gt; per request or per route, a client can also choose to apply a retry budget to limit the incoming load on the server and prevent request storms. Libraries like &lt;a href=&quot;https://twitter.github.io/finagle/guide/Clients.html#retries&quot;&gt;Twitter&apos;s Finagle&lt;/a&gt; and &lt;a href=&quot;https://linkerd.io/2/features/retries-and-timeouts/&quot;&gt;Linkerd&lt;/a&gt; bundle in that concept and allow for customization of the budgeting rules.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-request-prioritization/retry-budgets.png&quot; alt=&quot;Retry budgets in a service stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;In general, that will mean that the application will &lt;strong&gt;keep track of the ratio between incoming requests and retries&lt;/strong&gt;, and define a threshold as a configurable limit, that applies over a limited period of time.&lt;/p&gt;
&lt;p&gt;Once a client goes over the threshold for the stipulated time-frame, it has its requests cancelled. In other words, &lt;strong&gt;a client can retry as much as it want to, as long as the ratio is maintained&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Since large applications tend to be a stack of services with dependencies on each other, it is key that requests are only retried at the layer immediately above of the rejecting one. On the example above, a rejection on the first service would prevent clients (pictured by the phone icons in the image) to retry further and save all potentially incoming &lt;a href=&quot;https://landing.google.com/sre/sre-book/chapters/handling-overload/#fig_load-balance-overload_dependency-stack&quot;&gt;combinatorial load&lt;/a&gt; on the database server down the line.&lt;/p&gt;
&lt;p&gt;A more generic perspective over that concept is to establish a &lt;a href=&quot;https://twitter.github.io/finagle/guide/Servers.html#concurrency-limit&quot;&gt;concurrency limit&lt;/a&gt;, which will bound the total number of requests a server will handle concurrently, and optionally provide a queue of waiting requests. All of the incoming requests on top of these values get automatically rejected.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What we&apos;ve seen throughout this small series is that &lt;strong&gt;resilience is a characteristic of a system as whole&lt;/strong&gt;, and it builds on top of all of its individual pieces.&lt;/p&gt;
&lt;p&gt;On &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-jitter&quot;&gt;part 1&lt;/a&gt; we have optimized the requests&apos; path from the clients&apos; perspective, on &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-retries&quot;&gt;part 2&lt;/a&gt; improved the relationship between client and servers while still providing a good user experience and here on part 3, we got back control from the field by moving toggles, limits and flags to the backend.&lt;/p&gt;
&lt;p&gt;In summary, we&apos;ve collected a few techniques that will help scaling products in a way that we can also rely on them even in the event of a full or partial outage scenarios.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Be a good client: retries]]></title><description><![CDATA[When a request to a server fails, it is very tempting to issue another try to the same route, as a way to increase reliability of systems or mask transient problems to the end-users. How often is this a good idea? Never. Well, more or less. Today, many RPC libraries provide developers with built-in…]]></description><link>https://www.juliozynger.com/articles/be-a-good-client-retries/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/be-a-good-client-retries/</guid><pubDate>Wed, 11 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When a request to a server fails, it is very tempting to issue another try to the same route, as a way to increase reliability of systems or mask transient problems to the end-users.&lt;/p&gt;
&lt;p&gt;How often is this a good idea? &lt;strong&gt;Never&lt;/strong&gt;. Well, more or less.&lt;/p&gt;
&lt;p&gt;Today, many RPC libraries provide developers with built-in functionality for retrying requests, making it really easy to hammer servers with more and more load. Thus, &lt;strong&gt;retrying requests by default is not a good idea&lt;/strong&gt;, instead, one must consider the situation and only then decide whether a retry is worth it.&lt;/p&gt;
&lt;h2&gt;More jitter&lt;/h2&gt;
&lt;p&gt;In the event of a transient outage, or full unavailability, retrying clients will increase the load on the server, potentially causing an overload in what would otherwise be a regular number of requests, causing even more clients to issue retries. This &lt;a href=&quot;https://en.wikipedia.org/wiki/Snowball_effect&quot;&gt;snowball effect&lt;/a&gt; is what is effectively alleviated by introducing jitter into the system, not only before the initial request, but also in subsequent retries.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-retries/retry-snowball.png&quot; alt=&quot;Retry snowball effect&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Exponential backoff&lt;/h2&gt;
&lt;p&gt;There are several techniques that an engineer can employ to further relieve stress on their servers, of them, exponential backoff is the most popular. In a nutshell, this means &lt;strong&gt;varying the amount of time between retries&lt;/strong&gt; instead of having a fixed time window.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;func sync() {
  val jitter = random(0.5, 1.5)
  val back_off_wait = 0.5 // waiting time between requests
  wait(jitter.to_seconds)

  success = perform_network_request()

  while !success {
    wait((back_off_wait * jitter).to_seconds)
    success = perform_network_request()
    back_off_wait *= 2 // increase waiting time per attempt
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Circuit-breaking&lt;/h2&gt;
&lt;p&gt;Making the waiting time longer would help even out the load in the event of a transient server outage, but very rapidly this pseudocode above would cause clients to wait an unreasonable amount of time to then perform their follow-up request.&lt;/p&gt;
&lt;p&gt;Not only this is a bad idea from a UX perspective, it would also mean that all of this load would be then deferred to a later point in time in which the server would already be correctly functioning, and possibly the user isn&apos;t interested in its outcome anymore.&lt;/p&gt;
&lt;p&gt;So, besides backing-off, one would also define a circuit-breaking mechanism to prevent useless or unwanted retries. In this case, a &lt;strong&gt;maximum threshold for the waiting time&lt;/strong&gt; between retries before informing the end-user of the failure.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;func sync() {
  val jitter = random(0.5, 1.5)
  val back_off_wait = 0.5
  val max_waiting_time = 10 // circuit-breaker
  wait(jitter.to_seconds)

  success = perform_network_request()

  while !success &amp;amp;&amp;amp; back_off_wait &amp;lt;= max_waiting_time {
    wait((back_off_wait * jitter).to_seconds)
    success = perform_network_request()
    back_off_wait *= 2
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Error codes and HTTP&lt;/h2&gt;
&lt;p&gt;There are cases in which a client can be assured a server will not be capable of fulfilling a request, which we can account for. In fact, HTTP defines codes that one can use to help decide whether to issue a retry, becoming part of the circuit-breaking condition.&lt;/p&gt;
&lt;p&gt;Clearly, one should not retry requests that failed because of client errors (for example, a missing query parameter, or incorrect &lt;code class=&quot;language-text&quot;&gt;Authorization&lt;/code&gt; header). Independently of the load on the server, these requests would result in the same errors if retried.&lt;/p&gt;
&lt;p&gt;It is, however, potentially a good idea to retry both network errors and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#Server_error_responses&quot;&gt;server errors&lt;/a&gt;. The first could happen for a temporary fault in connectivity and possibly recovered later on, while the latter could ensure recuperation when the transient outage is resolved or the load is relieved.&lt;/p&gt;
&lt;h2&gt;The server in command&lt;/h2&gt;
&lt;p&gt;For a few specific status codes, headers can also be used to pass additional information to optimize communication timing and reduce load. In the event of rate limiting (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429&quot;&gt;429&lt;/a&gt;), a known interruption of service or overload (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503&quot;&gt;503&lt;/a&gt;), the &lt;code class=&quot;language-text&quot;&gt;Retry-After&lt;/code&gt; header can be used to indicate how long the client should wait before making another request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Seen as a hint, it is still responsibility of the client application to change its behavior given the presence of that header. In fact, many modern &lt;a href=&quot;https://hg.mozilla.org/mozilla-central/log?rev=Retry-After&quot;&gt;browsers&lt;/a&gt; already support its detection, and even &lt;a href=&quot;https://support.google.com/webmasters/answer/7238004&quot;&gt;Google&apos;s crawler&lt;/a&gt; is aware of it to determine when to visit websites.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This mechanism is especially interesting since having a dynamic value means &lt;strong&gt;moving the control of the request streams to the server&lt;/strong&gt;, allowing for it to adjust accordingly to its current incoming load. In fact, all of the techniques mentioned above could also be applied by having remotely-fetched values, from the threshold of retries to the amount of jitter per request. The client, then, becomes responsible only to define sensible defaults.&lt;/p&gt;
&lt;p&gt;When dealing with I/O and networking, the one certain thing for every engineer is that there must be error handling logic in place. Even better, when possible, we can pretend errors never happened by gracefully recovering! Being conscious of how clients and servers talk, and keeping in mind the trade-offs within our infrastructure, &lt;em&gt;the smarter&lt;/em&gt; we try, the better.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There&apos;s even more we can do to make our client-server relationship a healthy one. So far, we have looked into techniques for full outage scenarios, but being a good citizen also means &lt;em&gt;behaving well when&lt;/em&gt; partial failures are ongoing or ideally to prevent them altogether. We&apos;ll dive deeper in these in &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-request-prioritization&quot;&gt;part 3 of this series&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Be a good client: jitter]]></title><description><![CDATA[Periodically triggered jobs are very common in modern applications, especially in the client-server model: uploading notes, analytics events or backing up data are good examples of timely operations that a developer might decide to trigger at a specific rate, for example once per hour, or at 3 a.m…]]></description><link>https://www.juliozynger.com/articles/be-a-good-client-jitter/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/be-a-good-client-jitter/</guid><pubDate>Wed, 04 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Periodically triggered jobs are very common in modern applications, especially in the client-server model: uploading notes, analytics events or backing up data are good examples of timely operations that a developer might decide to trigger at a specific rate, for example once per hour, or at 3 a.m. when the user isn&apos;t actively interacting with the device.&lt;/p&gt;
&lt;p&gt;Now, handling the computational load can get tricky since the number of clients is a magnitude larger than servers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-jitter/code-jitter.png&quot; alt=&quot;Synchronized client requests&quot;&gt;&lt;/p&gt;
&lt;p&gt;It turns out that, as your application gets more successful, more clients will synchronize requests to your backend. That means your servers will see higher network traffic as these timely operations trigger (in a similar fashion to what&apos;s known as the &lt;a href=&quot;https://en.wikipedia.org/wiki/Thundering_herd_problem&quot;&gt;Thundering Herd problem&lt;/a&gt;), as such:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-jitter/network-spikes.png&quot; alt=&quot;Network traffic spikes at clock ticks&quot;&gt;&lt;/p&gt;
&lt;p&gt;For your users, the higher load in traffic will mean higher response latencies, and potentially errors due to network timeouts. In a catastrophic scenario, a big enough number of clients or scheduling of call retries could even lead to denial of service (&lt;a href=&quot;https://en.wikipedia.org/wiki/Denial-of-service_attack#Distributed_DoS&quot;&gt;DDoS&lt;/a&gt;): what would be a small outage or a quick interruption of service will snowball by having clients enqueueing more and more retries that would themselves also fail.&lt;/p&gt;
&lt;p&gt;Operationally, this would mean a &lt;strong&gt;waste of resources&lt;/strong&gt; and will translate as an increase in cost to maintain your infrastructure. While most of the time the infrastructure will be idle, engineers will over-scale the services to be able to serve the high load of requests on the clock-tick moment.&lt;/p&gt;
&lt;p&gt;Ideally, we want to distribute the load evenly across time, while still being able to serve the same number of requests, or in a visual way, this is how the distribution should look like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/be-a-good-client-jitter/network-distributed.png&quot; alt=&quot;Network traffic distributed evenly&quot;&gt;&lt;/p&gt;
&lt;p&gt;We can do so by purposefully introducing &lt;a href=&quot;https://en.wikipedia.org/wiki/Jitter&quot;&gt;jitter&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Jitter&lt;/strong&gt; is the variation in periodicity of a signal or periodic event from its target or true frequency.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In practical terms, this means &lt;strong&gt;purposefully advancing/delaying the next call outside of what the expected clock tick would be&lt;/strong&gt;. For a network request that usually would take an order of hundreds of milliseconds to be performed, we can introduce a delay of a few milliseconds to even out the load on the servers.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;func sync() {
  // instead of immediately performing the call
  // we add jitter to distribute the network load
  val jitter = random(0.5, 1.5)
  wait(jitter.to_seconds)
  perform_network_request()
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Some libraries feature configurable jittering (see &lt;a href=&quot;https://github.com/Netflix/Hystrix&quot;&gt;Netflix Hystrix&lt;/a&gt;, &lt;a href=&quot;https://github.com/resilience4j/resilience4j&quot;&gt;Resilience4J&lt;/a&gt;, &lt;a href=&quot;https://github.com/twitter/finagle&quot;&gt;Twitter&apos;s Finagle&lt;/a&gt;, &lt;a href=&quot;https://googleapis.github.io/google-http-java-client/&quot;&gt;Google&apos;s HttpClient&lt;/a&gt;, &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/workmanager&quot;&gt;Android&apos;s WorkManager&lt;/a&gt;), while others give full control (and responsibility) to the application developer (&lt;a href=&quot;https://github.com/App-vNext/Polly&quot;&gt;.NET Polly&lt;/a&gt;, &lt;a href=&quot;https://square.github.io/okhttp/&quot;&gt;OkHttp&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsurlconnection&quot;&gt;NSURLConnection&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The key here is understanding the specific use-cases, and where the sweet spot is to &lt;strong&gt;balance incurring traffic in your server infrastructure and data consistency between client and server&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/T58lGKREubo&quot;&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Jitter is especially important when associated to other resiliency techniques, like batching, call retries and exponential backoff. We&apos;ll look further at these in &lt;a href=&quot;https://www.juliozynger.com/articles/be-a-good-client-retries&quot;&gt;part 2 of this series&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Media Projection and Audio Capture]]></title><description><![CDATA[Starting from Android Lollipop, developers have an API that can be used to capture parts or the entire visualization of a device's screen: MediaProjection. James O'Brien gave a great description of that usage in this article. From Android 10, the MediaProjection API was extended to support the audio…]]></description><link>https://www.juliozynger.com/articles/media-projection-and-audio-capture/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/media-projection-and-audio-capture/</guid><pubDate>Wed, 30 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting from Android Lollipop, developers have an API that can be used to capture parts or the entire visualization of a device&apos;s screen: &lt;a href=&quot;https://developer.android.com/reference/android/media/projection/MediaProjection&quot;&gt;MediaProjection&lt;/a&gt;. James O&apos;Brien gave a great description of that usage in &lt;a href=&quot;https://medium.com/androiddevelopers/mediaprojection-is-not-just-for-screen-share-926c55b9d7c5&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From Android 10, the MediaProjection API was extended to support the audio capture use-case. That is especially interesting if your app does any sort of streaming or &lt;em&gt;Twitch-like&lt;/em&gt; broadcasting. In some cases, though, you might want to have finer control over when or what can be captured, either for user-privacy reasons, or content-protection (i.e copyright).&lt;/p&gt;
&lt;p&gt;At SoundCloud, we cared for both use-cases; and as we worked on preparing our app for targeting API Level 29, making sure only the expected actors could capture our audio was critical. We want to be able to support use-cases like &lt;a href=&quot;https://blog.google/products/android/live-caption/&quot;&gt;Live-Caption&lt;/a&gt;, but in turn, also protect our copyrighted content to prevent leaks or piracy.&lt;/p&gt;
&lt;p&gt;To do so, simply &lt;a href=&quot;https://developer.android.com/guide/topics/media/playback-capture&quot;&gt;following the documentation&lt;/a&gt; wasn&apos;t enough; we also wanted to verify the solution actually worked. For that reason, we have built a demo application that uses the audio capturing API to interact with our app, just like a third-party app would do. Here&apos;s how we&apos;ve done it.&lt;/p&gt;
&lt;h2&gt;Before we get started&lt;/h2&gt;
&lt;p&gt;Given we will be recording what the user&apos;s device is playing, we are first required to request a few permissions. Make sure to prompt the user at the appropriate time for the &lt;code class=&quot;language-text&quot;&gt;RECORD_AUDIO&lt;/code&gt; permission.&lt;/p&gt;
&lt;p&gt;Also, since the audio capturing operation will be long-standing, we will need a foreground service to keep the user informed of the execution. For that reason, don&apos;t forget to declare the &lt;code class=&quot;language-text&quot;&gt;FOREGROUND_SERVICE&lt;/code&gt; permission on your AndroidManifest file, too.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  ...
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;uses-permission&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.permission.RECORD_AUDIO /&gt;
  &amp;lt;uses-permission android:name=&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token attr-name&quot;&gt;android.permission.FOREGROUND_SERVICE&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  ...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Audio Capture&lt;/h2&gt;
&lt;p&gt;For privacy reasons, audio/video capturing is special on Android in comparison to other permission requests, in the sense that a capturing app must prompt the user for explicit approval every time a &lt;code class=&quot;language-text&quot;&gt;MediaProjection&lt;/code&gt; is needed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/media-projection-and-audio-capture/media-projection-dialog.png&quot; alt=&quot;MediaProjection confirmation dialog&quot;&gt;&lt;/p&gt;
&lt;p&gt;A request to the &lt;code class=&quot;language-text&quot;&gt;MediaProjectionManager&lt;/code&gt; system service can be done with a single-liner, but must happen on the context of the UI so that a confirmation dialog can be displayed.&lt;/p&gt;
&lt;p&gt;From Android 10 and later, a &lt;code class=&quot;language-text&quot;&gt;Service&lt;/code&gt; must be running and call startForeground to post a Notification before we can obtain the MediaProjection instance; failing to do so will cause a SecurityException. The actual audio capturing operation does not need to be done in the Service code, but you absolutely need a Service, even if its sole purpose is to manage the lifecycle of the Notification. Don&apos;t forget to declare the foregroundServiceType on your AndroidManifest declaration:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;manifest&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  ...
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;application&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;service&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;.AudioCaptureService&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;foregroundServiceType&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mediaProjection&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    ...
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have all the pieces in place, we can obtain &lt;code class=&quot;language-text&quot;&gt;MediaProjection&lt;/code&gt; instance to perform the audio capturing.&lt;/p&gt;
&lt;h3&gt;Audio Capture&lt;/h3&gt;
&lt;p&gt;Here is when things get interesting; the audio capture configuration API is very flexible and provides many hooks for its definition, so we can optimize our specific media use-cases. We will pass both an &lt;code class=&quot;language-text&quot;&gt;AudioPlaybackCaptureConfiguration&lt;/code&gt; and an &lt;code class=&quot;language-text&quot;&gt;AudioFormat&lt;/code&gt; object to the AudioRecord instance we will use to fill our audio data buffers.&lt;/p&gt;
&lt;p&gt;The first object will define which type of media we will capture (&lt;code class=&quot;language-text&quot;&gt;USAGE_MEDIA&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;USAGE_GAME&lt;/code&gt;), and we can optionally define inclusion/exclusion app UIDs to filter which apps we are (or aren&apos;t, respectively) interested in capturing.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;AudioFormat&lt;/code&gt; defines how the audio data will be encoded. We can set the capture sample rate in Hz, the number of channels for capture, and which encoding to be used; from raw PCM samples with values varying from 8, 16 or floating point precision, to even compressed samples of different types, varying from MP3, AAC, AC3 and more, based on the devices encoding capabilities.&lt;/p&gt;
&lt;p&gt;Notice how important these parameters are depending on the use-case: if you plan to upload the capture audio to the cloud or store them to disk, you might prefer encoded samples for their lower sample/byte ratio, but on the other hand if your use-case is of &lt;a href=&quot;https://wiki.videolan.org/Demuxing/&quot;&gt;demuxing&lt;/a&gt; or post-processing you might be interested in more precise renditions in PCM.&lt;/p&gt;
&lt;p&gt;For our example, validating our first-party app being captured by a third-party, it is enough to define the simplest combination of static properties: mono PCM-16 raw audio. For a more complex use-case, one could make them dynamic based on the target captured material properties. Fortunately, the documentation for &lt;code class=&quot;language-text&quot;&gt;AudioFormat&lt;/code&gt; is quite extensive and describes well all available usage options.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AudioPlaybackCaptureConfiguration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mediaProjection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addMatchingUsage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AudioAttributes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;USAGE_MEDIA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; audioFormat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AudioFormat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setEncoding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AudioFormat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ENCODING_PCM_16BIT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSampleRate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setChannelMask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AudioFormat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CHANNEL_IN_MONO&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

audioRecord &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AudioRecord&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAudioFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audioFormat&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAudioPlaybackCaptureConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Holding the &lt;code class=&quot;language-text&quot;&gt;AudioRecord&lt;/code&gt; object, we can call startRecording to initiate the flushing of audio samples into our predefined buffer, which can be an in-memory or file OutputStream.&lt;/p&gt;
&lt;p&gt;Notice this is a performance-critical operation: interruptions on the read thread will cause audio glitches and crackling (read more about low-latency audio rendering on &lt;a href=&quot;https://www.juliozynger.com/articles/your-app-and-low-latency-audio-output&quot;&gt;my other article&lt;/a&gt;). On top of that, we also don&apos;t want to block the UI thread with our recording execution, so it is advisable to run the recording in a &lt;code class=&quot;language-text&quot;&gt;Thread&lt;/code&gt; of its own.&lt;/p&gt;
&lt;p&gt;In our example, we will convert the PCM-16 integer samples into a &lt;code class=&quot;language-text&quot;&gt;ByteArray&lt;/code&gt; to be written to disk, so we must keep note of the endianness to be able to properly perform the samples&apos; playback later on.&lt;/p&gt;
&lt;p&gt;Once we&apos;re done with the capture, we &lt;code class=&quot;language-text&quot;&gt;stop&lt;/code&gt; the AudioRecord, release all heavy resources and stop our foreground service.&lt;/p&gt;
&lt;h2&gt;Once with the data…&lt;/h2&gt;
&lt;p&gt;Performing playback of the captured PCM data on Android is possible, even though it isn&apos;t done with the friendlier or more commonly used APIs of &lt;code class=&quot;language-text&quot;&gt;MediaPlayer&lt;/code&gt;, but by using AudioTrack.&lt;/p&gt;
&lt;p&gt;For that reason, and for visualization purposes, we suggest pulling out the captured data for processing with a desktop-app solution such as the free &lt;a href=&quot;https://www.audacityteam.org/download/&quot;&gt;Audacity&lt;/a&gt; audio editor. There, we have fine-tuned control to import the raw data and specify all of the parameters we have previously defined for encoding, sample rate, bit precision and even byte endianness.&lt;/p&gt;
&lt;p&gt;By using the demo app, we could then verify that capturing of audio samples only happened for the media we explicitly specified, and all of our content was protected according to our business requirements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/media-projection-and-audio-capture/audacity-screenshot.png&quot; alt=&quot;Captured audio waveform in Audacity&quot;&gt;&lt;/p&gt;
&lt;p&gt;For more details of how the API is designed and insights to our experiment at SoundCloud, check out the public GitHub repository for the sample app we&apos;ve built. You can use it to verify your app reacts as you expect to audio capturing. There, you will also find a full implementation, deeper clarification and code-comments for further adaptation of the code to apply it to your recording use-case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/julioz/AudioCaptureSample&quot;&gt;julioz/AudioCaptureSample&lt;/a&gt;&lt;/strong&gt; - Sample app for Android 10&apos;s AudioPlaybackCapture API, which allows applications to capture the audio of other apps.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Thanks to &lt;a href=&quot;https://twitter.com/rciovati&quot;&gt;Riccardo&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/preusslerBerlin&quot;&gt;Danny&lt;/a&gt; for the proofreads.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SoundCloud Is Playing the Oboe]]></title><link>https://developers.soundcloud.com/blog/soundcloud-is-playing-the-oboe</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/soundcloud-is-playing-the-oboe</guid><pubDate>Fri, 21 Jun 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Native Code and Debug Symbols]]></title><description><![CDATA[It is a common misconception to think that software development is all about writing code, but in reality, that is not where engineers invest most of their time. Instead, most of the effort is put in designing the application and understanding how it behaves once it is deployed. As applications grow…]]></description><link>https://www.juliozynger.com/articles/native-code-and-debug-symbols/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/native-code-and-debug-symbols/</guid><pubDate>Tue, 23 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It is a common misconception to think that software development is all about writing code, but in reality, that is not where engineers invest most of their time. Instead, most of the effort is put in designing the application and understanding how it behaves once it is deployed.&lt;/p&gt;
&lt;p&gt;As applications grow larger and more complex, it is critical to establish a process around how bug fixing can be organised. For Android apps, there are multiple tools that will support teams to collect and analyse crashes and other debugging information.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/native-code-and-debug-symbols/crash-reporting-flow.png&quot; alt=&quot;Crash reporting flow&quot;&gt;&lt;/p&gt;
&lt;p&gt;Similarly to applications that include JVM code (Java/Kotlin) shrinked with tools like Proguard, if the application includes native code (usually C or C++), it might be trickier to get actionable information from stack-traces/tombstones.&lt;/p&gt;
&lt;h2&gt;Debug symbols&lt;/h2&gt;
&lt;p&gt;When a native binary is compiled into a shared object (.so), it might include &lt;strong&gt;debug symbols&lt;/strong&gt;, which represent additional information on top of the symbol table of the program. In other words, they include information such as function and variable names, and many other bits related to the original source code.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://player.vimeo.com/video/275423618&quot;&gt;View embedded content&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Shameless plug: Here, I cover &lt;strong&gt;how native libraries get linked and loaded&lt;/strong&gt; in a program to be run on Android&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;All this extra data can take up a lot of space (sometimes multiple times the size of the actual object code!), and especially for Android, that might inflate the final APK size in a considerable amount, since devices of different CPU architectures also require matching shared object files. For that reason, build tools (such as Gradle) decide for removing the extra debug symbol table, in a process called stripping (read more about &lt;code class=&quot;language-text&quot;&gt;strip&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We can use tools like &lt;a href=&quot;https://sourceware.org/binutils/docs/binutils/nm.html&quot;&gt;nm&lt;/a&gt; to display the symbol tables of binaries. For example, when I run it over stripped and symbolicated builds of the same library, the output is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ nm flipper-stripped/obj/armeabi-v7a/libflipper_stripped.so
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
$ nm flipper-stripped/obj/armeabi-v7a/libflipper_stripped.so &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
-----------------------------------------------------
$ nm flipper-symbolicated/obj/armeabi-v7a/libflipper_symbolicated.so
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 002b2540 W _ZNK11M3UPlaylist8toStringEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 00251abc W _ZNK12JniException4whatEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 002aa5ec W _ZNK12PropertySets3HLScvN4Json5ValueEEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 002ab008 W _ZNK12PropertySets4HTTPcvN4Json5ValueEEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 00336324 T _ZNK13SQLiteBackend15getRowStatementEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; 0033766c W _ZNK13SQLiteBackend16hasBuiltInCryptoEv
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
$ nm flipper-symbolicated/obj/armeabi-v7a/libflipper_symbolicated.so &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;407230&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not surprisingly, when building an AAR that contains each of these builds, the disk space usage difference is noticeable:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-lh&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; juliozynger  26M Apr &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt; flipper-stripped.aar
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; juliozynger 113M Apr &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt; flipper-symbolicated.aar&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see above, while stripping binaries is a great idea for saving space, it will prove challenging when an engineer needs to debug an application. So, similarly to how Proguard mapping files are exposed during the build process, native debug symbols can also be stored separately from the release binary and later used to symbolise it with tools like &lt;a href=&quot;https://developer.android.com/ndk/guides/ndk-stack&quot;&gt;ndk-stack&lt;/a&gt; or addr2line.&lt;/p&gt;
&lt;p&gt;Usually, that means the release binaries are stored in the jni subdirectory and the symbolicated binaries in the obj subdirectory. Some third-party crash-reporting SDKs, like &lt;a href=&quot;https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android&quot;&gt;Firebase Crashlytics&lt;/a&gt;, BugSnag, etc. take advantage of that standard and provide ways to upload the debug symbols so that an engineer can get digestible information directly from their web dashboards.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/native-code-and-debug-symbols/fabric-symbolicated.png&quot; alt=&quot;Fabric symbolicated crash report&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you build your own native libraries, make sure to output the binaries following the &lt;strong&gt;directory arrangement&lt;/strong&gt; (jni→obj) to benefit from the automatic upload. If you have a more complex setup for building your custom library (for example, stripping symbols in a later stage) and use Gradle, you can also disable stripping of symbols by adding the following to your library&apos;s AAR generation script:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;apply plugin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.android.library&apos;&lt;/span&gt;

android &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
  packagingOptions &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// specify the path to your object binaries, or generally:&lt;/span&gt;
    doNotStrip &lt;span class=&quot;token string&quot;&gt;&apos;**.so&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/julioz/ed8fd5007a6ac96bc9cdefb266796880&quot;&gt;Here you can find&lt;/a&gt; a simplified version of the script we use at SoundCloud to make sure we store our symbolised and stripped binaries in the correct directories.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Attributions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Icon: collecting by Takao Umehara from the Noun Project&lt;/li&gt;
&lt;li&gt;Icon: QoS by Stefan Traistaru from the Noun Project&lt;/li&gt;
&lt;li&gt;Icon: Bug fixing by Symbolon from the Noun Project&lt;/li&gt;
&lt;li&gt;Image: Fabric&apos;s symbolicated crash&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Once again, many thanks to Miloš Pešić for reviewing the content&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Release Quality and Mobile Trains]]></title><link>https://developers.soundcloud.com/blog/quality-mobile-trains</link><guid isPermaLink="false">https://developers.soundcloud.com/blog/quality-mobile-trains</guid><pubDate>Wed, 03 Apr 2019 00:00:00 GMT</pubDate></item><item><title><![CDATA[Your app and low-latency audio output]]></title><description><![CDATA[When building any software, we want to provide the best possible experience to our users, giving them the feeling of being in full control and taking the most value out of our applications. With audio-related software, that is not different: a very important metric for tracking playback performance…]]></description><link>https://www.juliozynger.com/articles/your-app-and-low-latency-audio-output/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/your-app-and-low-latency-audio-output/</guid><pubDate>Mon, 18 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building any software, we want to provide the best possible experience to our users, giving them the feeling of being in full control and taking the most value out of our applications. With audio-related software, that is not different: a very important metric for tracking playback performance is &lt;strong&gt;latency&lt;/strong&gt;: minimizing the time it takes from pressing the &apos;play&apos; button and listening to music is key for providing a seamless impression.&lt;/p&gt;
&lt;p&gt;Today we will visit some concepts on digital audio, learn about how part of it is done on Android devices, and understand how buffers play a key-role in providing the smoothest playback experience to users as possible.&lt;/p&gt;
&lt;h2&gt;Sound travels in waves…&lt;/h2&gt;
&lt;p&gt;…and a very common method to represent analog signals (such as waves!) is &lt;strong&gt;pulse-code modulation&lt;/strong&gt; (PCM). Basically, the wave amplitude is measured at a regular time interval. Each one of these values is called a sample. A lot of these samples are necessary to represent actual sound — for example, more than 44 thousand measurements are made every second!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/your-app-and-low-latency-audio-output/pcm-sampling.png&quot; alt=&quot;PCM sampling visualization&quot;&gt;&lt;/p&gt;
&lt;p&gt;Stereo sound is nothing more than two waves being transmitted at the same time (nowadays, &lt;strong&gt;surround&lt;/strong&gt; systems provide even more than two channels!). If we want to play stereo sound, then, we&apos;ll need to sample the two channels and digitally store them. In PCM terms, a frame is a set of one sample per channel. So, for stereo sound, a PCM frame will contain data representing two samples.&lt;/p&gt;
&lt;p&gt;Every modern phone out there has, as part of its circuits, a component capable of converting digital data (0s and 1s) to analog data (in this case, variable voltage, that will be passed to speakers). These are called &lt;a href=&quot;https://en.wikipedia.org/wiki/Digital-to-analog_converter&quot;&gt;digital-to-analog-converters&lt;/a&gt; (DACs).&lt;/p&gt;
&lt;p&gt;To process data, they require information to be fed at a constant rate, and also in blocks of a specific size. The DAC knows where it should look for data — that is a region of memory the operating system specifies and in which we (application developers) should write to, with a very fancy name: the &lt;strong&gt;buffer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/your-app-and-low-latency-audio-output/buffer-dac.png&quot; alt=&quot;Buffer and DAC interaction&quot;&gt;&lt;/p&gt;
&lt;p&gt;The DAC will reach out to the buffer at a fixed time interval to get one of these data blocks (or &lt;strong&gt;chunks&lt;/strong&gt;), but if we fail to write to the buffer when the DAC visits it, the hardware will output silence. That is a very bad experience for users and the reason music stops playing or they listen to crackles/noise (a.k.a. popcorn), so we should avoid it at all costs, by constantly writing to the buffer as soon as possible.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;latency&lt;/strong&gt; comes in — the time we take to feed the DAC (or, in other words, to fill the buffer). In Android, audio tasks are among the highest priority ones in the entire system: a completion of one of these tasks will even interrupt other unrelated tasks in the OS level, so that the processing is as fast as it can be.&lt;/p&gt;
&lt;h2&gt;A special characteristic of Android devices…&lt;/h2&gt;
&lt;p&gt;… is that they can come in many shapes and sizes, but also hardware architectures, and manufacturers ship different DACs within their boards. To be able to get the most (or the least!) out of the latencies, we can also &apos;ask&apos; the OS (Android 4.1 and above) what is &lt;em&gt;the most suitable configuration&lt;/em&gt; for their buffer so we can work the best with the embedded DAC.&lt;/p&gt;
&lt;p&gt;By metrifying SoundCloud&apos;s app, we were able to collect some interesting statistics about how variable were the hardware configurations for this considerably big userbase &lt;em&gt;(taken in mid-2018)&lt;/em&gt;:&lt;/p&gt;
&lt;!-- markdownlint-disable MD033 --&gt;
&lt;div class=&quot;table-row&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample Rate (Hz)&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;48,000&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44,100&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96,000&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88,200&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32,000&lt;/td&gt;
&lt;td&gt;&amp;#x3C; 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Buffer size (#frames)&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;192&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;960&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;480&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1920&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h3&gt;Sample rate&lt;/h3&gt;
&lt;p&gt;One of the things we can do is to configure our application to fill the buffer with the optimal number of samples per second — the &lt;strong&gt;sample rate&lt;/strong&gt;. Usual examples of sample rates are 44.1kHZ (i.e: 44.100 samples per second) or 48kHZ, but different DACs might have different optimal values.&lt;/p&gt;
&lt;p&gt;If you don&apos;t use the DAC&apos;s expected sample rate, the OS will have to &lt;em&gt;resample&lt;/em&gt; your data to match that, and then the audio processing won&apos;t go through the fast path (taking precious milliseconds from our low latency goal).&lt;/p&gt;
&lt;p&gt;We can use one &lt;code class=&quot;language-text&quot;&gt;AudioManager&lt;/code&gt; API to query that value from the system and then pass it to the player in our application:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; audioManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSystemService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AUDIO_SERVICE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; AudioManager
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; outputSampleRate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; audioManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AudioManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROPERTY_OUTPUT_SAMPLE_RATE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Buffer size&lt;/h3&gt;
&lt;p&gt;If we use a buffer that is &lt;em&gt;too small&lt;/em&gt; and the CPU has a lot of tasks to perform, there could be not enough time to come back to our thread and fill the next chunk of the buffer, so we&apos;d get popcorn, because the DAC would have to wait for the next chunk to be delivered.&lt;/p&gt;
&lt;p&gt;On the other hand, if we use a buffer that is &lt;em&gt;too large&lt;/em&gt;, that means we need to delay the output of audio for the time duration of that buffer while we fill it completely before we deliver it to the DAC — and there is latency again. For example, if we have a buffer that can hold 4096 samples and our sample rate is 44.1kHZ, that means we would have a ~93 ms delay from when the data comes in the processing pipe and when the DAC is able to consume it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/your-app-and-low-latency-audio-output/buffer-size-latency.png&quot; alt=&quot;Buffer size and latency tradeoff&quot;&gt;&lt;/p&gt;
&lt;p&gt;Yet again, we can ask the OS what is the best buffer size we should be using, to find what the optimal value is:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; audioManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSystemService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AUDIO_SERVICE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; AudioManager
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; framesPerBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; audioManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AudioManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PROPERTY_OUTPUT_FRAMES_PER_BUFFER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We will want then, to use a multiple of that value to determine how many samples we should put in each of the buffer&apos;s chunks, so that we can minimize the number of times the DAC needs to call us back for more data.&lt;/p&gt;
&lt;p&gt;For example, if the device reports a recommended buffer size of 208 samples and we make our chunks hold a non-multiple value of samples, like 160, we could have the following scenario:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/your-app-and-low-latency-audio-output/buffer-chunk-mismatch.png&quot; alt=&quot;Buffer chunk mismatch problem&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s say the first 160-samples chunk was consumed, and the DAC calls us before the buffer-filling thread is able to add more data. Since there is not enough data in the buffer to collect another chunk, the DAC will have to call us yet again later. In the meantime, the OS &lt;em&gt;scheduler&lt;/em&gt; might interrupt the DAC thread to give CPU access to another task. If that&apos;s the case, we run the risk of surpassing the time deadline to output audio, causing a glitch.&lt;/p&gt;
&lt;p&gt;Interruption of DAC&apos;s consumer thread is more likely than we would expect, given that the platform &lt;a href=&quot;https://android.googlesource.com/platform/system/media/+/c7369d04a4b94aca0462d0e0a38c243a80a945a9/opensles/libopensles/android_AudioPlayer.cpp#544&quot;&gt;uses locks around the buffer data structure&lt;/a&gt;, preventing concurrent reads and writes. So even if the DAC gets scheduled at the right time, but the filler thread is interrupted, it will have to wait for the lock to be released — surpassing the time deadline (i.e: glitch). It is something Google is aware, though: they recently released a new audio API for usage with Android Oreo and above that claims to improve that scenario.&lt;/p&gt;
&lt;h2&gt;In summary,&lt;/h2&gt;
&lt;p&gt;there are many factors that influence latency and the overall feeling we give users when doing audio playback, and some of these are even out of our control.&lt;/p&gt;
&lt;p&gt;For the aspects we do control, it is beneficial to have a broad perspective of the elements in play, and learning how the underlying operating system behaves when we interact with it is certainly very helpful.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://larsimmisch.github.io/pyalsaaudio/terminology.html&quot;&gt;PCM Terminology and Concepts - alsaaudio documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://gopinaths.gitlab.io/post/android-audio-framework-architecture/&quot;&gt;Android Audio Framework Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Extending the Web with Android Instant Apps]]></title><description><![CDATA[During I/O 2016, Google announced it was developing a new way to bridge the gap between the web and native apps, by making them as easy to access as a simple click in a link: Instant Apps, made of small data-chunks that can be temporarily loaded on the device with no extra going-to-the-Play-Store…]]></description><link>https://www.juliozynger.com/articles/extending-the-web-with-android-instant-apps/</link><guid isPermaLink="false">https://www.juliozynger.com/articles/extending-the-web-with-android-instant-apps/</guid><pubDate>Thu, 18 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During I/O 2016, Google announced it was developing a new way to bridge the gap between the web and native apps, by making them as easy to access as a simple click in a link: &lt;strong&gt;Instant Apps&lt;/strong&gt;, made of small data-chunks that can be temporarily loaded on the device with no extra going-to-the-Play-Store steps.&lt;/p&gt;
&lt;p&gt;Selected developers started adapting their apps to the new format shortly after: the feature rollout will happen in stages, beginning a small set of devices and starting on Android Marshmallow and above.&lt;/p&gt;
&lt;p&gt;In this post, I&apos;ll introduce a little of my experience with the Instant Apps SDK and provide some insights while tinkering with the code. Hopefully this will be helpful in migrating your existing application.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/9oaaecd7NpI&quot;&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Preparing the project&lt;/h2&gt;
&lt;p&gt;Earlier this year, Google released a &lt;a href=&quot;https://developer.android.com/topic/instant-apps/prepare.html&quot;&gt;list of steps&lt;/a&gt; to help you prepare your app to supporting Instant App experiences. The initial steps should be already covered by your application at this moment: support Android Marshmallow&apos;s runtime permissions and deep links to enable URL-based navigation (Android Studio can help with that!).&lt;/p&gt;
&lt;p&gt;Take into account that if your app requires logging in, you&apos;ll also have to integrate the &lt;a href=&quot;https://developers.google.com/identity/smartlock-passwords/android/&quot;&gt;SmartLock API for Passwords&lt;/a&gt;. Also you can use the new Payments API to a include checkout flow (Google currently has partnerships with Vantiv, Braintree and Stripe, but more to come).&lt;/p&gt;
&lt;p&gt;The most complicated step might be &lt;strong&gt;modularizing&lt;/strong&gt; your application as each downloaded module on a cold start should have less than 4MB, both for business (users on a mobile network might not be able to download bigger files) and technical reasons (Google&apos;s CDN is optimized to distributing files up to 4MB in size).&lt;/p&gt;
&lt;p&gt;You can also have 2 versions of your app: Full App &amp;#x26; Instant App. However, this is rather discouraged by Google. &lt;em&gt;Ideally&lt;/em&gt; your instant app should have a subset of features of your full app.&lt;/p&gt;
&lt;h2&gt;Features or APK splits&lt;/h2&gt;
&lt;p&gt;The Instant Apps SDK introduced the concept of APK splits (also called features - not to be confused with &lt;a href=&quot;https://developer.android.com/studio/build/configure-apk-splits.html&quot;&gt;this kind of split&lt;/a&gt;), that represents each module of your application. Android is able to download each of these features based on the accessed deep link — as long as there is at least one activity in the application that handles the URL.&lt;/p&gt;
&lt;p&gt;Those modules are kept in a device-wide shared LRU-cache for future usage. Eventually, if the user accesses an URL that is not present in-cache, then the new download gets triggered.&lt;/p&gt;
&lt;p&gt;If your app contains more than one single feature, then those will be bundled in a zip file also known as &lt;strong&gt;APK Bundle&lt;/strong&gt;, which basically wraps everything for easier publishing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/extending-the-web-with-android-instant-apps/apk-bundle-structure.png&quot; alt=&quot;APK Bundle structure&quot;&gt;&lt;/p&gt;
&lt;p&gt;Notice the size limit of 4 MB applies to the entire downloaded feature, including its dependencies. So if your base module (let&apos;s say, general-usage libraries and resources) already accounts for 3.5 MB, you&apos;re going to have a bad time delivering your app. Make sure your modules are lean: you can use one of &lt;a href=&quot;https://developer.android.com/topic/performance/reduce-apk-size.html&quot;&gt;many tools to help you out&lt;/a&gt; in this task, like ApkAnalyzer.&lt;/p&gt;
&lt;h3&gt;Building your first feature&lt;/h3&gt;
&lt;p&gt;In reality, features are just &quot;pointers to libraries with a naming convention for unique identification&quot;. Android infers the dependency graph as shown in the below example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.juliozynger.com/images/articles/extending-the-web-with-android-instant-apps/feature-dependency-graph.png&quot; alt=&quot;Feature dependency graph&quot;&gt;&lt;/p&gt;
&lt;p&gt;Notice how a feature might depend on one or more libraries, and the libraries themselves might depend on other libraries (&lt;a href=&quot;http://www.reactiongifs.com/wp-content/uploads/2013/10/tim-and-eric-mind-blown.gif&quot;&gt;#mindblown&lt;/a&gt;). All features of the application must depend on the base feature, as that is the module that will contain the wiring of the instant app and also the definition of the deep links&apos; URLs (more about that in the next section).&lt;/p&gt;
&lt;p&gt;For each of your features, you&apos;ll need to add a module to your project, containing just a manifest with your Activity declarations and a &lt;code class=&quot;language-text&quot;&gt;build.gradle&lt;/code&gt; file that applies the com.android.feature plugin (or you can do it via Android Studio&apos;s wizards):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;apply plugin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.android.feature&apos;&lt;/span&gt;

android &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;manifest&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.sample.instantsonar.artists&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;android&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://schemas.android.com/apk/res/android&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;activity&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;.ArtistsActivity&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;theme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@style/AppTheme&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;intent-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;action&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.intent.action.MAIN&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;category&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.intent.category.LAUNCHER&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;intent-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;intent-filter&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;autoVerify&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;order&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;action&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.intent.action.VIEW&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;category&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.intent.category.BROWSABLE&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;category&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.intent.category.DEFAULT&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;host&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;instantsonar.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;pathPrefix&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/artist&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;scheme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;scheme&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;intent-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta-data&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;default-url&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://instantsonar.com/artist&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;activity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the &lt;code class=&quot;language-text&quot;&gt;IntentFilter&lt;/code&gt; I have declared to the activity. This is the definition of the deep links that will be handled by the Instant App: the feature that contains ArtistsActivity will be downloaded when &lt;a href=&quot;https://instantsonar.com/artist&quot;&gt;https://instantsonar.com/artist&lt;/a&gt; (also for http) is accessed (as declared with the pathPrefix attribute, so it won&apos;t conflict with the other declared links).&lt;/p&gt;
&lt;p&gt;In case your app declares more than one activity to match a given URL, the &lt;code class=&quot;language-text&quot;&gt;android:order&lt;/code&gt; attribute is the one that will define which will handle it (that parameter is optional and defaults to zero): for example, if you have an activity that declares the URL path /artist and another with /artist/*, when receiving a request from &lt;a href=&quot;https://instantsonar.com/artist/42&quot;&gt;https://instantsonar.com/artist/42&lt;/a&gt;, Google Play will pick the activity with the lowest android:order value.&lt;/p&gt;
&lt;p&gt;Another important aspect of the manifest are the &lt;code class=&quot;language-text&quot;&gt;&amp;lt;metadata&gt;&lt;/code&gt; tags defining Default URLs. For each CATEGORY_LAUNCHER / ACTION_MAIN activity, you need to define a sibling default-url tag that will enable Play Store discovery and integration with Launchers.&lt;/p&gt;
&lt;h3&gt;Base feature&lt;/h3&gt;
&lt;p&gt;Your base library contains all the code necessary to run the core functionality of your application, so this is the place where you&apos;d set the shared resources/assets and dependencies on libraries (as the support library and your preferred libraries for networking, dependency injection, analytics, crash reporting, testing, etc). All your features must depend on this base one: &lt;strong&gt;always take&lt;/strong&gt; in account the size of those dependencies to not bloat your instant app&apos;s modules!&lt;/p&gt;
&lt;p&gt;It is also the place where the declaration of your full application will reside, thus propagating the &lt;code class=&quot;language-text&quot;&gt;applicationId&lt;/code&gt; to all features in the project.&lt;/p&gt;
&lt;p&gt;In order to differentiate the &lt;strong&gt;base feature&lt;/strong&gt; from the others, all you need to do is to set a gradle attribute. The base feature build.gradle file will look something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;apply plugin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &apos;com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;android&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;feature

android &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;...&lt;/span&gt;

    baseFeature &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  feature &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:feature1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  feature &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:feature2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And its manifest file will be very simple, just containing permission declarations and shared activities (if that&apos;s the case for your project). Later on, the &lt;a href=&quot;https://developer.android.com/studio/build/manifest-merge.html&quot;&gt;manifest merger tool&lt;/a&gt; will also aggregate all your module&apos;s manifest files:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;manifest&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.sample.instantsonar.base&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;android&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://schemas.android.com/apk/res/android&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;uses-permission&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;android.permission.INTERNET&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;application&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;allowBackup&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;@string/app_name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;android:&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;com.sample.instantsonar.SampleApplication&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- shared activities here (only if you need!) --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;APK Bundle&lt;/h3&gt;
&lt;p&gt;Building the APK Bundle is the simplest part of the process: all you need is to create a new module that contains a &lt;strong&gt;gradle file&lt;/strong&gt; that will instruct how your bundle will be assembled.&lt;/p&gt;
&lt;p&gt;Make sure you declare &lt;code class=&quot;language-text&quot;&gt;com.android.instantapp&lt;/code&gt; as the build plugin, and refer to all your features in the dependencies block.&lt;/p&gt;
&lt;p&gt;Generally, the file will have a similar structure as this one:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;apply plugin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;com.android.instantapp&apos;&lt;/span&gt;

dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  implementation &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:base&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  implementation &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:feature1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  implementation &lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:feature2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This article was written during the early days of Android Instant Apps. The technology has since evolved into &lt;a href=&quot;https://developer.android.com/guide/app-bundle&quot;&gt;Android App Bundles&lt;/a&gt; and &lt;a href=&quot;https://developer.android.com/guide/playcore/feature-delivery&quot;&gt;Dynamic Feature Modules&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Continuous Deployment no Android: como usar a Publishing API para automatizar seu release]]></title><link>https://medium.com/android-dev-br/continuous-deployment-no-android-f42b96ece80d</link><guid isPermaLink="false">https://medium.com/android-dev-br/continuous-deployment-no-android-f42b96ece80d</guid><pubDate>Tue, 10 May 2016 00:00:00 GMT</pubDate></item></channel></rss>