<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.1.1">Jekyll</generator><link href="https://hugotunius.se/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hugotunius.se/" rel="alternate" type="text/html" /><updated>2024-01-20T16:55:44+00:00</updated><id>https://hugotunius.se/feed.xml</id><title type="html">Hugo Tunius - Blog</title><subtitle>Personal blog of Hugo Tunius. Developer, open source enthusiast, aspiring designer, and Hugo of all trades.
</subtitle><author><name>Hugo Tunius</name></author><entry><title type="html">Stop Using (only) GitHub Releases</title><link href="https://hugotunius.se/2024/01/20/stop-using-github-releases.html" rel="alternate" type="text/html" title="Stop Using (only) GitHub Releases" /><published>2024-01-20T00:00:00+00:00</published><updated>2024-01-20T00:00:00+00:00</updated><id>https://hugotunius.se/2024/01/20/stop-using-github-releases</id><content type="html" xml:base="https://hugotunius.se/2024/01/20/stop-using-github-releases.html">&lt;p&gt;The other day at work I, accidentally, roped myself into upgrading some dependencies in our Rust services. These were breaking changes, so not just a case of running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo update&lt;/code&gt;. I had to understand the changes and make the appropriate modifications to our code. Adopting breaking changes can be frustrating in the best of times, but it was particularly annoying this time because none of these projects kept a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CHANGELOG.md&lt;/code&gt; files, although they all had release notes on GitHub.&lt;/p&gt;

&lt;p&gt;GitHub’s &lt;a href=&quot;https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository&quot;&gt;releases feature&lt;/a&gt; allows you to combine a git tag with release notes, metadata, and files(binaries, source code etc). While useful, GitHub’s releases have many downsides and consuming them adds friction to the task of understanding changes in a project. They can be a useful supplement to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CHANGELOG.md&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HISTORY.md&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RELEASES.md&lt;/code&gt; file but &lt;strong&gt;should not&lt;/strong&gt; be the only place where release notes are recorded.&lt;/p&gt;

&lt;p&gt;The problems with exclusively using GitHub’s releases feature are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination&lt;/strong&gt;, which makes it hard to search across the full releases and makes cross-referencing between different versions cumbersome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not truly being apart of the repository&lt;/strong&gt;, which means, if you want to look at the source checked out, you end up having to read release notes on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt; and the source in your editor(reading the code on GitHub is not ergonomic at all).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External system risk&lt;/strong&gt;, which means that an integral part of the project, the release notes, live in an external system to the code itself. While GitHub is showing no signs of waning at the moment, it’s not going to be around forever and when it does eventually fall out of favour the release notes of many projects will be lost to history. Migrating the git repository itself to a new host is trivial and necessary, but few projects will take the time to migrate release notes. Of course, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CHAGNELOG.md&lt;/code&gt; file(already being apart of the repository) migrates along the source code.&lt;/p&gt;

&lt;p&gt;If you are involved with an open source project, please do &lt;a href=&quot;https://keepachangelog.com/en/1.1.0/&quot;&gt;keep a changelog&lt;/a&gt;, but make it an actual file in the repository not just the releases section on GitHub. If you provide release notes on GitHub in addition to the file that’s great too!&lt;/p&gt;

&lt;p&gt;This idea generalises beyond just release notes. Always try to make the source of truth files in git, rather than data in the databases of external system. Don’t relegate the details of why a change was made to an external ticketing system, that will eventually be lost to history, put them in the commit message. Don’t put your documentation in an external system that will get lost when the business changes its mind about documentation practices for the hundredth time, put it in markdown files in a folder. If something is related to development of a project and can be expressed as files in a folder, it should be files in a folder.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If something is related to development of a project and can be expressed as files in a folder, it should be files in a folder.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Hugo Tunius</name></author><category term="programming" /><category term="culture" /><category term="git" /><category term="vsc" /><category term="ux" /><category term="opinion" /><summary type="html">The other day at work I, accidentally, roped myself into upgrading some dependencies in our Rust services. These were breaking changes, so not just a case of running cargo update. I had to understand the changes and make the appropriate modifications to our code. Adopting breaking changes can be frustrating in the best of times, but it was particularly annoying this time because none of these projects kept a CHANGELOG.md files, although they all had release notes on GitHub.</summary></entry><entry><title type="html">The Great Pendulum</title><link href="https://hugotunius.se/2023/07/09/the-great-pendulum.html" rel="alternate" type="text/html" title="The Great Pendulum" /><published>2023-07-09T00:00:00+01:00</published><updated>2023-07-09T00:00:00+01:00</updated><id>https://hugotunius.se/2023/07/09/the-great-pendulum</id><content type="html" xml:base="https://hugotunius.se/2023/07/09/the-great-pendulum.html">&lt;p&gt;17 odd years ago when I stared programming, PHP was all the rage. Javascript was steadily gaining traction. Django and Ruby on Rails were in their infancy, but promised greatly increased productivity. A few years later, inspired by Ruby’s fame, Coffeescript became a mainstay in the Javascript ecosystem. Statically compiled, typed languages, used to build monolithic web applications, were rapidly falling out of favour. In 2023 the trend is reversing, static compilation and types are cool again. Monoliths are making a comeback. &lt;em&gt;The pendulum is turning.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first serious web application I built used an emerging pattern, AJAX(&lt;strong&gt;A&lt;/strong&gt;synchronous &lt;strong&gt;J&lt;/strong&gt;avascript &lt;strong&gt;a&lt;/strong&gt;nd &lt;strong&gt;X&lt;/strong&gt;ML), where the server didn’t just return HTML, but also Javascript that could fetch further data and update the HTML later. Several years later, when many started complaining about the high cost of React and SPAs I started thinking in terms of the pendulum. Entirely server rendered applications is one extreme of this particular pendulum, entirely client side rendered applications being the other. In the pursuit of web applications that delivered snappier user experiences the industry, arguably, overshot past the equilibrium point. &lt;a href=&quot;https://htmx.org/&quot;&gt;HTMX&lt;/a&gt; and &lt;a href=&quot;https://hotwired.dev/&quot;&gt;Hotwired&lt;/a&gt; are examples of the pendulum starting to swing back, whether the industry will again overshoot is an open question.&lt;/p&gt;

&lt;p&gt;There are many pendulums in our industry, here are some that I’ve noticed over the years:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Static typing&lt;/strong&gt; vs &lt;strong&gt;Dynamic typing&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Monoliths&lt;/strong&gt; vs &lt;strong&gt;Microservices&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cloud&lt;/strong&gt; vs &lt;strong&gt;On-prem&lt;/strong&gt;, I think on-prem is having a bit of a moment after the industry indexed heavily on cloud in the past decade.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Statically compiled languages&lt;/strong&gt; vs &lt;strong&gt;Interpreted languages&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Server Side Rendering&lt;/strong&gt; vs &lt;strong&gt;Client Side Rendering&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those who have spent longer than me in the industry have, undoubtedly, spotted even more than I have. If you have a suggestion I would love to hear it.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#conclusion&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In most instances the correct position lies somewhere near the equilibrium point, but we tend to overshoot it in each swing. Maybe, like a real pendulum, the amplitude of these pendulums will decrease over time and they will come to rest at the equilibrium. Or maybe we’ll continue to overshoot, the memories of the previous swing faded by time. I’m somewhat hopeful that we will learn from each period and the former will prove to be more true than the latter.&lt;/p&gt;</content><author><name>Hugo Tunius</name></author><category term="opinion" /><category term="web" /><summary type="html">17 odd years ago when I stared programming, PHP was all the rage. Javascript was steadily gaining traction. Django and Ruby on Rails were in their infancy, but promised greatly increased productivity. A few years later, inspired by Ruby’s fame, Coffeescript became a mainstay in the Javascript ecosystem. Statically compiled, typed languages, used to build monolithic web applications, were rapidly falling out of favour. In 2023 the trend is reversing, static compilation and types are cool again. Monoliths are making a comeback. The pendulum is turning.</summary></entry><entry><title type="html">NFTs, How Do They Work?</title><link href="https://hugotunius.se/2022/01/16/nfts-how-do-they-work.html" rel="alternate" type="text/html" title="NFTs, How Do They Work?" /><published>2022-01-16T00:00:00+00:00</published><updated>2022-01-16T00:00:00+00:00</updated><id>https://hugotunius.se/2022/01/16/nfts-how-do-they-work</id><content type="html" xml:base="https://hugotunius.se/2022/01/16/nfts-how-do-they-work.html">&lt;p&gt;Freaking &lt;del&gt;magnets&lt;/del&gt;  NFTs, how do they work? In this post I’ll try to explain NFTs in a way that’s mostly accurate, but requires minimal technical understanding. I’m going to assume the reader is familiar with excel style software and &lt;a href=&quot;https://www.google.co.uk/sheets/about/&quot;&gt;Google Sheets&lt;/a&gt; in particular.&lt;/p&gt;

&lt;p&gt;At its core every NFT project is like a single Google Sheet. It has a creator who has some special permission to modify the sheet.&lt;/p&gt;

&lt;p&gt;An NFT within the project is like a single row in the sheet. Each row contains only two things: a name and an id. For example, if my NFT has 10,000 unique tokens then I could use the ids from 1 through 10,000 to identify each token.&lt;/p&gt;

&lt;p&gt;&lt;span aria-hidden=&quot;true&quot;&gt;
&lt;a href=&quot;/img/nfts-how-do-they-work/empty-sheet.png&quot;&gt;&lt;img src=&quot;/img/nfts-how-do-they-work/empty-sheet.png?1705769745&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;To begin with the sheet is empty. The creator might assign themselves a few tokens by creating new rows with their own name and some ids. Then they invite other people to “mint” new tokens and gain rows with their name in them. In order to mint, the user has to pay the creator a small fee(for example via Venom). In return for payment a row is added to the sheet containing the user’s name and a randomly selected unassigned id. In addition the user is given permission to change who owns the id they were allocated, this facilitates selling their token. Eventually all 10,000 tokens run out and there are no longer unassigned ids. The creator will now refuse to mint more tokens.&lt;/p&gt;

&lt;p&gt;&lt;span aria-hidden=&quot;true&quot;&gt;
&lt;a href=&quot;/img/nfts-how-do-they-work/minted-sheet.png&quot;&gt;&lt;img src=&quot;/img/nfts-how-do-they-work/minted-sheet.png?1705769745&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Users can sell their row in the sheet to other people. When they do, they update the name in the row to the buyer’s name and in so doing loses the ability to change it.&lt;/p&gt;

&lt;p&gt;Now you might be saying: but aren’t NFTs, like, ugly pictures of monkeys? Yes they can be and often are images, but they don’t have to be. Further, what’s actually stored is just an id as I’ve described.&lt;/p&gt;

&lt;p&gt;So how do you get the picture of your ugly monkey to show off to your friends? You use a special URL, which is stored in a cell somewhere in the sheet, and then append your token’s id to it. For example, the URL might be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ugly-monkeys.com/&lt;/code&gt; and if you own monkey #54 then its image can be found at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ugly-monkeys.com/54&lt;/code&gt;. Depending on how the creator has set things up they might be able to change the URL in the sheet. Maybe tomorrow your ugly monkey will become a zebra, because the creator changed the URL to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ugly-zebras.com&lt;/code&gt;, exciting!&lt;/p&gt;

&lt;p&gt;Now instead of a Google Sheet imagine all this data lives on a Blockchain, for example &lt;a href=&quot;https://ethereum.org/en/&quot;&gt;Ethereum&lt;/a&gt;, and that’s mostly how it all works.&lt;/p&gt;

&lt;h2 id=&quot;caveats&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#caveats&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Caveats&lt;/h2&gt;

&lt;p&gt;This section contains some caveats about analogy above. They aren’t super important, but if you are interested read on.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Almost all of the steps outlined above are executed by the sheet(it’s actually a &lt;a href=&quot;https://en.wikipedia.org/wiki/Smart_contract&quot;&gt;smart contract&lt;/a&gt;) itself. The creator creates the contract and they can have special permission to withdraw funds stored in the contract and change some properties of it, but the minting and selling process is handled by the contract.&lt;/li&gt;
  &lt;li&gt;The contact is running on a decentralised network of computers that power the Blockchain in question. This is very unlike Google Sheets(which is centralised), were Google are ultimately in control and could change the sheet at will.&lt;/li&gt;
  &lt;li&gt;The example of monkeys turning into zebras above is not made up. This is possible in real NFTs, for example in the most famous NFT project: &lt;a href=&quot;https://boredapeyachtclub.com/#/&quot;&gt;Bored Ape Yacht Club&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;In the case of Ethereum(as of February 2022) running this decentralised network of computers consumes an absurd amount of energy and generates massive electronic waste. This isn’t just because the network contains a lot of computers, at its core Blockchains like Ethereum are intentionally extremely wasteful.&lt;/li&gt;
&lt;/ol&gt;</content><author><name>Hugo Tunius</name></author><category term="nfts" /><category term="ethereum" /><category term="opinion" /><summary type="html">Freaking magnets NFTs, how do they work? In this post I’ll try to explain NFTs in a way that’s mostly accurate, but requires minimal technical understanding. I’m going to assume the reader is familiar with excel style software and Google Sheets in particular.</summary></entry><entry><title type="html">They Are Just Links</title><link href="https://hugotunius.se/2022/01/13/they-are-just-links.html" rel="alternate" type="text/html" title="They Are Just Links" /><published>2022-01-13T00:00:00+00:00</published><updated>2022-01-13T00:00:00+00:00</updated><id>https://hugotunius.se/2022/01/13/they-are-just-links</id><content type="html" xml:base="https://hugotunius.se/2022/01/13/they-are-just-links.html">&lt;p&gt;NFTs exploded into mainstream popularity in the latter half of 2021 and if you follow me on Twitter you’ll know I’m not a fan. In “crypto”-speak I’m &lt;a href=&quot;https://blog.libertasbella.com/glossary/ngmi/&quot;&gt;NGMI(not gonna make it)&lt;/a&gt;. But what are NFTs anyway?&lt;/p&gt;

&lt;p&gt;The common meme is that NFTs are kind of like receipts or, more charitably, certificates of ownership. The ugly monkey you buy is actually a piece of state maintained in a smart contract on a Blockchain, typically Ethereum. Ethereum based NFTs implement the &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-721&quot;&gt;EIP-721&lt;/a&gt; standard. This standard describes the behaviour that a smart contract should implement to be an NFT. NFTs aren’t exclusively visual art, but that’s the most common form so let’s roll with it.&lt;/p&gt;

&lt;p&gt;Let’s use the &lt;a href=&quot;https://boredapeyachtclub.com/#/&quot;&gt;ugly monkeys&lt;/a&gt; as an example. The &lt;a href=&quot;https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d&quot;&gt;contract&lt;/a&gt; supports the metadata extension for EIP-271, which specifies the method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-solidity&quot; data-lang=&quot;solidity&quot;&gt;&lt;span class=&quot;n&quot;&gt;tokenURI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint256&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;external&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;returns&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Given a token id return a URI that points to its metadata. For the ugly monkeys the URI looks like this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://us-central1-bayc-metadata.cloudfunctions.net/api/tokens/{token_id}&lt;/code&gt;. That domain doesn’t look particularly decentralised, in fact it’s part of Google cloud. If Google goes out of business or otherwise lose access to this domain who knows what your ugly monkey will be pointing to in the future. To boot Google are &lt;a href=&quot;https://killedbygoogle.com/&quot;&gt;notorious&lt;/a&gt; for sun setting products. With GCP maybe they’ll not be as aggressive, but it’s not hard to imagine a future were Google changes the structure of cloud function URLs. A change like that would have a long grace period and most users would easily migrate since changing a URL is not that hard. Except if you have locked the URL inside an immutable smart contract on the Ethereum Blockchain that is.&lt;/p&gt;

&lt;p&gt;Here’s a concrete example with ugly monkey &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#5465&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl https://us-central1-bayc-metadata.cloudfunctions.net/api/tokens/5465&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;image&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://ipfs.io/ipfs/Qmbijgmi1APqH2UaMVPkwoAKyNiBEHUjap54s3MAifKta6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Background&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Gray&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Clothes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Stunt Jacket&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Fur&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Dark Brown&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Mouth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Phoneme  ooo&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Short Mohawk&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trait_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Eyes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Crazy&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The image of the ugly monkey is stored on &lt;a href=&quot;https://en.wikipedia.org/wiki/InterPlanetary_File_System&quot;&gt;IPFS&lt;/a&gt; which is at least decentralised, although the ipfs.io gateway isn’t.&lt;/p&gt;

&lt;h2 id=&quot;its-all-about-ownership&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#its-all-about-ownership&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;It’s all About Ownership&lt;/h2&gt;

&lt;p&gt;NFT &lt;del&gt;shillers&lt;/del&gt; proponents will tell you NFTs are all about proving ownership. What does a given NFT actually prove? Mostly that there’s some state locked in an Ethereum contract where your wallet is recorded as the owner.&lt;/p&gt;

&lt;p&gt;Here’s a pop quiz: Which of these two is the BAYC contract and which is my own NFT UMBC(Ugly Monkey Boat Cabal)?&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0xBC4CA0EdA9647A8aB7C2061c2E118A18a936f13D&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you guessed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; congrats, you correctly identified my superior NFT, UMBC.&lt;/p&gt;

&lt;p&gt;UMBC doesn’t exist, I would never actually create an NFT. However, there’s an interesting conundrum here: If I duplicate the BAYC contract and deploy it, how can someone tell the difference between mine and the original?&lt;/p&gt;

&lt;p&gt;This question came up when Twitter launched support for &lt;a href=&quot;https://help.twitter.com/en/using-twitter/twitter-blue-fragments-folder/nft&quot;&gt;NFT profile pictures&lt;/a&gt;, which are displayed as hexagons. People were angry at Twitter for not launching with the concept of “verified collections”. BAYC would be a verified collection, UMBC would not.&lt;/p&gt;

&lt;p&gt;However, the whole notion of a “verified collection” hints at a bigger problem: who does the verifying? It’s weird for an, ostensibly, decentralised concept to become useless without introducing a centralised entity like a verifier. &lt;a href=&quot;opensea.io&quot;&gt;OpenSea&lt;/a&gt;, the leading NFT marketplace, is rapidly filling the role of verifier and central authority in the NFT space. It has banned several NFTs from its platform, effectively dooming those projects. The centralisation on OpenSea is already so significant that OpenSea having an outage &lt;a href=&quot;https://www.vice.com/en/article/g5qjej/people-cant-see-some-nfts-in-crypto-wallets-after-opensea-goes-down&quot;&gt;took down Twitter’s NFT feature&lt;/a&gt;. So much for the promise of decentralisation.&lt;/p&gt;

&lt;h2 id=&quot;wait-its-not-2007&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#wait-its-not-2007&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Wait, It’s Not 2007?!&lt;/h2&gt;

&lt;p&gt;If you squint, NFTs are awfully similar to torrents and there are interesting parallels with the legal battles between The Pirate Bay and the entertainment industry circa 2007.&lt;/p&gt;

&lt;p&gt;Back in those olden days a common defence employed by pirates was that the torrents are just links, they don’t actually contain any content and thus aren’t and couldn’t be infringing on copyright. The point that both Google and The Pirate Bay could be used to find torrent files was a frequent argument.&lt;/p&gt;

&lt;p&gt;You can make a very similar argument about my hypothetical UMBC NFT described above. It would simply contain links, perhaps the same ones that BAYC uses, to metadata and artwork. I’m not a lawyer, but I don’t see how that can be construed as a copyright violation, they’re just links after all. Of course UMBC would get immediately banned from OpenSea, effectively killing the project.&lt;/p&gt;

&lt;h2 id=&quot;the-decentralisation-that-wasnt&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#the-decentralisation-that-wasnt&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;The Decentralisation That Wasn’t&lt;/h2&gt;

&lt;p&gt;It turns out NFTs aren’t particularly decentralised. The most popular NFT directly depends on Google’s cloud and the links it stored on the Ethereum Blockchain could all stop resolving tomorrow. Knowing the NFT space such an event would, paradoxically, increase the value of the ugly monkeys.&lt;/p&gt;

&lt;p&gt;Even if you can, in theory, mint anything as an NFT, good luck getting anywhere when you end up banned from OpenSea. Your project might as well not exist at that point.&lt;/p&gt;

&lt;p&gt;NFTs remain a solution in search of a problem. A speculative, hype-driven scam were neither the art nor the decentralisation is important, making a quick buck by not being the greater fool is.&lt;/p&gt;</content><author><name>Hugo Tunius</name></author><category term="nfts" /><category term="ethereum" /><category term="opinion" /><summary type="html">NFTs exploded into mainstream popularity in the latter half of 2021 and if you follow me on Twitter you’ll know I’m not a fan. In “crypto”-speak I’m NGMI(not gonna make it). But what are NFTs anyway?</summary></entry><entry><title type="html">How to Delete All your Tweets</title><link href="https://hugotunius.se/2021/04/05/how-to-delete-all-your-tweets.html" rel="alternate" type="text/html" title="How to Delete All your Tweets" /><published>2021-04-05T00:00:00+01:00</published><updated>2021-04-05T00:00:00+01:00</updated><id>https://hugotunius.se/2021/04/05/how-to-delete-all-your-tweets</id><content type="html" xml:base="https://hugotunius.se/2021/04/05/how-to-delete-all-your-tweets.html">&lt;p&gt;A while back I had to re-activate my deactivated Facebook account to participate in a Messenger group chat. I wasn’t exactly happy about this, but being an absolutist about these things is not worthwhile either. After re-activating my account I decided it would make me slightly happier about the situation if I wiped all the content from my account. A digital detox if you will. Ever since then I’ve had a nagging feeling I should expand this idea to other platforms. This blog post is about how I deleted all my tweets on Twitter.&lt;/p&gt;

&lt;p&gt;Thanks to the EU and the GDPR I can ask Twitter to delete all my data and they have to comply with my request. Unfortunately, my goal isn’t to delete my Twitter account, I just want to delete all my tweets. Twitter does have an API which should make it trivial to enumerate and delete all of my tweets. There are many scripts available online which do just this. Not to mention many websites that offer the same service, although I’d stay away from them.&lt;/p&gt;

&lt;p&gt;I used &lt;a href=&quot;https://gist.github.com/chrisalbon&quot;&gt;Chris Albon&lt;/a&gt;’s &lt;a href=&quot;https://gist.github.com/chrisalbon/b9bd4a6309c9f5f5eeab41377f27a670&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweet_deleting_script.py&lt;/code&gt;&lt;/a&gt; as the base of my implementation. Before running it I made a few tweaks, namely injecting the relevant secrets via the environment.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;from_env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;f&quot;Env variable &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; must be provided&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Chris’s script fetches all tweets for the account in question using Twitter’s &lt;a href=&quot;https://developer.twitter.com/en/docs/twitter-api/tweets/timelines/introduction&quot;&gt;timeline API endpoint&lt;/a&gt; and then, unsurprisingly, deletes each of them in turn. Chris also added some filtering criteria to keep certain Tweets around, but since I was going nuclear I removed that code.&lt;/p&gt;

&lt;p&gt;That’s that then? Run the script and wait? You’d be forgiven for thinking this was the end of this post and that it makes for a rather dull post. However, we live in the year 2021 of exciting web scale services and distributed systems and Twitter’s timeline API only returns the last 3200 tweets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Twitter’s docs:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The user Tweet timeline endpoint is a REST endpoint that receives a single path parameter to indicate the desired user (by user ID). The endpoint can return the 3,200 most recent Tweets, Retweets, replies and Quote Tweets posted by the user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you spend your time on better things than tweeting and thus have fewer than 3200 tweets I applaud you. The script above is all you need, just run it an enjoy your digital detox. If you, like me, waste lots of time tweeting let me tell you about how to waste even more time deleting said tweets.&lt;/p&gt;

&lt;p&gt;When I first read about the 3200 tweets limitation I assumed that after deleting the most recent 3200 tweets I could just run the script again, but no such luck. After deleting 3200 tweets the timeline endpoint returns nothing, regardless of the number of remaining undeleted tweets. In my case I had about 5000 tweets left after running Chris’s script.&lt;/p&gt;

&lt;p&gt;The crux of the matter is that we need the tweets, well their ids anyway, in order to delete them, a bit of a catch 22. I dug through the Twitter API documentation and some discussion online but there doesn’t seem to exist a programmatic way to access all the tweets for a given account. One neat trick I found used browser automation to search for tweets from the given account day by day, but this would have required 3285(9 years) searches for my account.&lt;/p&gt;

&lt;p&gt;I was about to give up, but then I realised that Twitter has to give me all the data they hold about me if I ask them(thank you EU!). I &lt;a href=&quot;https://twitter.com/settings/download_your_data&quot;&gt;asked Twitter for an archive of my data&lt;/a&gt; and waited. It took a couple of days but eventually Twitter sent me a comprehensive archive of everything I had ever done on Twitter, including every tweet and their ids.&lt;/p&gt;

&lt;p&gt;In the archive there’s a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/tweets.js&lt;/code&gt; which starts with the line:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YTD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;part0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then follows every tweet I have ever tweeted. Simply deleting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.YTD.tweet.part0 =&lt;/code&gt; part of this line makes the file valid JSON. With some tweaks to Chris’s script I used this new source of tweets to delete every single one of my tweets. Ah bliss, digital detoxing feels good.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/delete-tweets/and-then-there-was-none.png&quot;&gt;&lt;img src=&quot;/img/delete-tweets/and-then-there-was-none.png?1705769745&quot; alt=&quot;Screenshot of my Twitter profile. Below my display name is the string &amp;quot;25 Tweets&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twitter still thinks I have 25 tweets and I have been unable to locate these tweets. I suppose in the age of distributed systems 0 is just a mirage.&lt;/p&gt;

&lt;p&gt;If you feel like doing a digital detox I’ve published the relevant technical details in the following &lt;a href=&quot;https://gist.github.com/k0nserv/d8c6f110f001ee960a5684357431f033&quot;&gt;GitHub Gist&lt;/a&gt;.&lt;/p&gt;</content><author><name>Hugo Tunius</name></author><category term="web" /><category term="privacy" /><category term="python" /><summary type="html">A while back I had to re-activate my deactivated Facebook account to participate in a Messenger group chat. I wasn’t exactly happy about this, but being an absolutist about these things is not worthwhile either. After re-activating my account I decided it would make me slightly happier about the situation if I wiped all the content from my account. A digital detox if you will. Ever since then I’ve had a nagging feeling I should expand this idea to other platforms. This blog post is about how I deleted all my tweets on Twitter.</summary></entry><entry><title type="html">The Apps That Are Listening to You</title><link href="https://hugotunius.se/2021/01/10/the-apps-that-listen-to-you.html" rel="alternate" type="text/html" title="The Apps That Are Listening to You" /><published>2021-01-10T00:00:00+00:00</published><updated>2021-01-10T00:00:00+00:00</updated><id>https://hugotunius.se/2021/01/10/the-apps-that-listen-to-you</id><content type="html" xml:base="https://hugotunius.se/2021/01/10/the-apps-that-listen-to-you.html">&lt;p&gt;An oft discussed hypothesis is that certain apps, usually Facebook, listens to and analyses your surroundings for ad targeting purposes. It has never been conclusively proven that Facebook does this, but there are plenty of people on the internet with anecdotal stories of ads appearing for products they’ve only discussed IRL. In iOS 14 Apple added indicators to highlight when an app is using the device’s microphone or camera. Since I have access to a decently sized collection of app privacy details I decided to have a look if any apps admit to this behaviour.&lt;/p&gt;

&lt;p&gt;This is an extension of my previous work on &lt;a href=&quot;https://hugotunius.se/2021/01/03/an-analysis-of-privacy-on-the-app-store.html&quot;&gt;analysing privacy on the app store&lt;/a&gt;, I’d recommend reading that post before this one.&lt;/p&gt;

&lt;p&gt;In this post I am looking at apps that collect &lt;em&gt;“Audio Data”&lt;/em&gt; under the &lt;em&gt;“User Content”&lt;/em&gt; category for third party tracking use i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_USED_TO_TRACK_YOU&lt;/code&gt;. Apple defines audio data as &lt;em&gt;The user’s voice or sound recordings&lt;/em&gt;, thus it’s not definite if these apps listen to your microphone or use some other type of sound recording.&lt;/p&gt;

&lt;p&gt;My data set contains 22 812 apps, of which about half have provided privacy details. Of these apps there are nine that confess to collecting audio data for third party tracking purposes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id836215269&quot;&gt;Chime - Mobile Banking&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id972909677&quot;&gt;Periscope Live Video Streaming&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1382123330&quot;&gt;Scary chat stories, text games&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1068187100&quot;&gt;Football Index - Bet &amp;amp; Trade&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id421254504&quot;&gt;Magic Piano by Smule&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1443813253&quot;&gt;Paxful Bitcoin Wallet&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1484587490&quot;&gt;Millionaire Match: Upscale App&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1104213750&quot;&gt;Min Doktor – läkare i mobilen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/id1525138651&quot;&gt;Monifi - Mobile Banking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;update-history&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#update-history&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Update History&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Date&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Changes&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;2020-01-10&lt;/td&gt;
      &lt;td&gt;Initial publication&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2020-01-17&lt;/td&gt;
      &lt;td&gt;Updated with larger data set&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2020-04-27&lt;/td&gt;
      &lt;td&gt;Refreshed data&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</content><author><name>Hugo Tunius</name></author><category term="privacy" /><category term="ios" /><summary type="html">An oft discussed hypothesis is that certain apps, usually Facebook, listens to and analyses your surroundings for ad targeting purposes. It has never been conclusively proven that Facebook does this, but there are plenty of people on the internet with anecdotal stories of ads appearing for products they’ve only discussed IRL. In iOS 14 Apple added indicators to highlight when an app is using the device’s microphone or camera. Since I have access to a decently sized collection of app privacy details I decided to have a look if any apps admit to this behaviour.</summary></entry><entry><title type="html">An Analysis of Privacy on the App Store</title><link href="https://hugotunius.se/2021/01/03/an-analysis-of-privacy-on-the-app-store.html" rel="alternate" type="text/html" title="An Analysis of Privacy on the App Store" /><published>2021-01-03T00:00:00+00:00</published><updated>2021-01-03T00:00:00+00:00</updated><id>https://hugotunius.se/2021/01/03/an-analysis-of-privacy-on-the-app-store</id><content type="html" xml:base="https://hugotunius.se/2021/01/03/an-analysis-of-privacy-on-the-app-store.html">&lt;p&gt;In iOS 14.3, Apple added their new &lt;a href=&quot;https://developer.apple.com/app-store/app-privacy-details/&quot;&gt;app privacy details&lt;/a&gt; to App Store listings. App privacy details, which are sometimes compared to the nutritional labels on foodstuff, are details about the data an app collects and the purposes and use of such data. What can we learn by analysing this data?&lt;/p&gt;

&lt;p&gt;From the 14&lt;sup&gt;th&lt;/sup&gt; of December 2020, all new apps and app updates have to provide information on the data the app collects. This is used to power the app privacy details labelling. On Twitter, videos scrolling through the privacy listing for Facebook circulated immediately after the 14.3 release.&lt;/p&gt;

&lt;p&gt;This system is somewhat flawed, because app developers can, at least in theory, lie about the data they collect. Some apps that profess to collect no data, actually turn out to collect a bunch if you read their privacy policy. However, the punishment for being caught lying, removal from the App Store, is a strong deterrent and it’s safe to assume most developers will have been truthful in their accounts.&lt;/p&gt;

&lt;p&gt;An interesting side-effect of this, is that Apple has now made available the same data that can be found in terse and hard to parse privacy policies as simple and structured data that can be parsed and analysed. In this post I will do just that i.e. collect and analyse the privacy details for thousands of the most popular apps on the App Store.&lt;/p&gt;

&lt;h2 id=&quot;collecting-the-data&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#collecting-the-data&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Collecting the Data&lt;/h2&gt;

&lt;p&gt;If you just want to read the juicy details feel free to skip to the &lt;a href=&quot;#analysis&quot;&gt;analysis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apple makes the privacy labelling data available for each app on the App Store via an API used by the App Store apps. By reverse engineering the App Store apps I’ve figured out how to make the API divulge this data on a per app basis.&lt;/p&gt;

&lt;p&gt;This only gets me the privacy data for a single app, but I want to analyse popular apps. A good source of popular apps are the charts the App Store provides on a per app category basis. An example of this is “Top Free” apps in “Education”. These listings contain up to 200 apps per category and price point(i.e. free or paid).&lt;/p&gt;

&lt;p&gt;On the UK store, which is the store I’ve used for all this analysis, there are 25 categories. Each of which have top charts with up to 200 paid and 200 free apps. This means the theoretical total number of apps is 10 000. However, because some apps occupy chart positions in multiple categories and because the charts also contain app bundles the actual number is lower.&lt;/p&gt;

&lt;p&gt;The full list of categories is:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Book&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Business&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Developer Tools&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Education&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Entertainment&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Finance&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Food &amp;amp; Drink&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Games&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Graphics &amp;amp; Design&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Health &amp;amp; Fitness&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Lifestyle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Magazines &amp;amp; Newspapers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Medical&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Music&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Navigation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;News&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Photo &amp;amp; Video&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Productivity&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reference&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shopping&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Social Networking&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sports&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Travel&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Utilities&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Weather&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;structure-of-the-data&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#structure-of-the-data&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Structure of the Data&lt;/h2&gt;

&lt;p&gt;If you don’t care about the exact details and structure of the data feel free to skip to the &lt;a href=&quot;#analysis&quot;&gt;analysis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The structure of the data returned by the App Store API is&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;number&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;apps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;href&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;privacyDetails&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;managePrivacyChoicesUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;privacyTypes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;privacy-types&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;privacy-types&amp;gt;&lt;/code&gt; section of this document is the important bit. It’s an array where each item has the following structure.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;privacyType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;human-readable-description&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;string-identifier&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;human-readable-description&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dataCategories&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;data-categories&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;purposes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;data-purposes&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;string-identifier&amp;gt;&lt;/code&gt; is one of&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Identifier&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;DATA_LINKED_TO_YOU&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DATA_NOT_COLLECTED&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DATA_NOT_LINKED_TO_YOU&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DATA_USED_TO_TRACK_YOU&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_NOT_COLLECTED&lt;/code&gt; is used as a marker in which case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataCategories&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;purposes&lt;/code&gt; are both empty and this is the only element in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;privacyDetails&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_USED_TO_TRACK_YOU&lt;/code&gt; contains details on data used to track you across websites and apps owned by other companies, Apple’s description is &lt;em&gt;The following data may be used to track you across apps and websites owned by other companies:&lt;/em&gt;. For this entry &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;purposes&lt;/code&gt; will be empty and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataCategories&lt;/code&gt; contain the different data types that are tracked across apps and websites owned by other companies.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_LINKED_TO_YOU&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_NOT_LINKED_TO_YOU&lt;/code&gt; both contain data types with purposes specific granularity. This means that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataCategories&lt;/code&gt; will be empty and the different data types are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;purposes&lt;/code&gt;. Apple’s description for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_LINKED_TO_YOU&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_NOT_LINKED_TO_YOU&lt;/code&gt; are &lt;em&gt;The following data, which may be collected and linked to your identity, may be used for the following purposes:&lt;/em&gt; and &lt;em&gt;The following data, which may be collected but is not linked to your identity, may be used for the following purposes:&lt;/em&gt; respectively.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;data-purposes&amp;gt;&lt;/code&gt; is an array of purposes with the following structure:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;purpose&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;human-readable-purpose&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;purpose-identifier&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dataCategories&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;data-categories&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The different values for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;purpose-identifier&amp;gt;&lt;/code&gt; are:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ANALYTICS&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;APP_FUNCTIONALITY&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;These are described by Apple in their &lt;a href=&quot;https://developer.apple.com/app-store/app-privacy-details/#data-type-usage&quot;&gt;documentation&lt;/a&gt;, but I’ve added them here for completeness.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Purpose&lt;/th&gt;
      &lt;th&gt;Definition&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Third-Party Advertising&lt;/td&gt;
      &lt;td&gt;Such as displaying third-party ads in your app, or sharing data with entities who display third-party ads&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Developer’s Advertising or Marketing&lt;/td&gt;
      &lt;td&gt;Such as displaying first-party ads in your app, sending  marketing communications directly to your users, or sharing data with  entities who will display your ads&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Analytics&lt;/td&gt;
      &lt;td&gt;Using data to evaluate user behavior, including to  understand the effectiveness of existing product features, plan new  features, or measure audience size or characteristics&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Product Personalization&lt;/td&gt;
      &lt;td&gt;Customizing what the user sees, such as a list of recommended products, posts, or suggestions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;App Functionality&lt;/td&gt;
      &lt;td&gt;Such as to authenticate the user, enable features,  prevent fraud, implement security measures, ensure server up-time,  minimize app crashes, improve scalability and performance, or perform  customer support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Other Purposes&lt;/td&gt;
      &lt;td&gt;Any other purposes not listed&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Lastly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;data-categories&amp;gt;&lt;/code&gt; is an array of objects with the following structure:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dataCategory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;human-readable-purpose&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;data-category-identifier&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dataTypes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;human-readable-data-type&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The full list of data types and the categories they belong to is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;IDENTIFIERS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;User ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Device ID&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;USAGE_DATA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other Usage Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Advertising Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Product Interaction&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;DIAGNOSTICS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Performance Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other Diagnostic Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Crash Data&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;CONTACT_INFO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other User Contact Info&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Phone Number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Email Address&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Physical Address&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;PURCHASES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Purchase History&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;LOCATION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Coarse Location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Precise Location&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;USER_CONTENT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other User Content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Photos or Videos&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Audio Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Emails or Text Messages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Customer Support&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Gameplay Content&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;CONTACTS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Contacts&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;OTHER&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other Data Types&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;BROWSING_HISTORY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Browsing History&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;SEARCH_HISTORY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Search History&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;HEALTH_AND_FITNESS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Health&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Fitness&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;FINANCIAL_INFO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Credit Info&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Payment Info&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Other Financial Info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;SENSITIVE_INFO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sensitive Info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s do some analysis of this data&lt;/p&gt;

&lt;h2 id=&quot;analysis&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#analysis&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Analysis&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Last Updated:&lt;/strong&gt; 7&lt;sup&gt;th&lt;/sup&gt; of January 2020. Added data for Games, which was previously missing and extended analysis of all apps to larger data set.&lt;/p&gt;

&lt;p&gt;The data set I’ve collected contains 9477 combinations of apps and a position in a given category chart. In total there are 9435 unique apps in this data set.&lt;/p&gt;

&lt;p&gt;Most charts contain 200 or nearly 200 apps, however &lt;strong&gt;Graphics &amp;amp; Design(Paid)&lt;/strong&gt;, &lt;strong&gt;Developer Tools(Paid)&lt;/strong&gt;, and &lt;strong&gt;Magazines &amp;amp; Newspapers(Paid)&lt;/strong&gt; all have fewer than 90 apps so I’m dropping them from further analysis.&lt;/p&gt;

&lt;p&gt;Because the privacy details have only been required for new apps and updates since mid December, not all apps contain information about privacy details. After removing those apps 3370 apps remain in the data set. Breaking this down by chart, several charts have less than 25 apps so I am dropping them from further analysis too. This leaves 3233 apps in the data set.&lt;/p&gt;

&lt;p&gt;In total the following charts have been dropped:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Education(Paid)&lt;/li&gt;
  &lt;li&gt;Navigation(Paid)&lt;/li&gt;
  &lt;li&gt;Sports(Paid)&lt;/li&gt;
  &lt;li&gt;Business(Paid)&lt;/li&gt;
  &lt;li&gt;Food &amp;amp; Drink(Paid)&lt;/li&gt;
  &lt;li&gt;Shopping(Paid)&lt;/li&gt;
  &lt;li&gt;Medical(Paid)&lt;/li&gt;
  &lt;li&gt;Magazines &amp;amp; Newspapers(Paid)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the analysis there are a few different data points that are interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Apps that collect data this is linked to the user and how many such data types they collect.&lt;sup&gt;*&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Apps that collect no data.&lt;/li&gt;
  &lt;li&gt;Third Party tracking, i.e. tracking users across apps and websites owned by other companies and how many(max 32) such data types they collect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;* Data that is linked to the user for the purpose of supporting app functionality, that is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP_FUNCTIONALITY&lt;/code&gt; purpose, is legitimate and will be exclude from the following analysis. This leaves 160 data types spread across 5 purposes.&lt;/p&gt;

&lt;p&gt;I am excluding data that is collected but not linked to the user, in part to keep down the length of the analysis and in part because it’s the least interesting. I’ll probably do a follow up post on it later.&lt;/p&gt;

&lt;p&gt;The questions I’ll be looking at for this analysis are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#free-vs-paid&quot;&gt;Do free apps collect more data?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#worst-charts&quot;&gt;Which are the worst charts?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#worst-apps&quot;&gt;Which apps in the whole data set are the worst?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#oxymorons&quot;&gt;Which apps lie subtly about the nature of data they collect?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But first let’s have a quick look at the data set.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The images in this post can be clicked to show larger versions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/data-collected-histogram-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/data-collected-histogram-plot.svg?1705769745&quot; alt=&quot;Histogram plot of data types collected. The apps that collect zero such data types dominate&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see here, most apps collect no data outside of that which supports the app’s functionality. To get a better view of the apps that do collect data, let’s remove the majority of apps that don’t.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/data-collected-histogram-non-zero-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/data-collected-histogram-non-zero-plot.svg?1705769745&quot; alt=&quot;Histogram plot of data types collected with zero values removed.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still the amount of data collected is fairly low, but there’s a curious set of outliers somewhere around 120 data types collected. All of those outliers have something in common, see if you can figure it out before I reveal the answer later in the post.&lt;/p&gt;

&lt;p&gt;How about third party tracking?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/ttp-histogram-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/ttp-histogram-plot.svg?1705769745&quot; alt=&quot;Histogram plot of third party tracking data types collected. The apps that collect zero such data types dominate&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again most apps don’t collect any data types for third party tracking. Let’s repeat the process from above by removing those that do no tracking.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/ttp-histogram-non-zero-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/ttp-histogram-non-zero-plot.svg?1705769745&quot; alt=&quot;Histogram plot of third party tracking data types collected with zero values removed.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have an overview of the data let’s move on to answer the questions posed above.&lt;/p&gt;

&lt;h3 id=&quot;free-vs-paid&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#free-vs-paid&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Free vs Paid&lt;/h3&gt;

&lt;p&gt;A fairly common meme in the discourse around free apps is: “if you’re not paying for the product, you are the product”. Facebook is probably the quintessential example of this business model. Facebook makes money not from users paying them, but from advertisers paying to show hyper targeted ads to Facebook’s users. So is there truth to the meme? Do free apps track more than paid ones? I asked my Twitter followers this &lt;a href=&quot;https://twitter.com/K0nserv/status/1344412528823173120&quot;&gt;question&lt;/a&gt;, most people thought so.&lt;/p&gt;

&lt;p&gt;As previously established we’ll look at a few different data points to determine this. First of all, do free apps collect more data types that are linked to the user for non-app functionality purposes?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/free-vs-paid-box-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/free-vs-paid-box-plot.svg?1705769745&quot; alt=&quot;Box Plot comparing free vs paid apps. The median number of data points collect is 2 for paid apps and 8 for free apps&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes they certainly do. The median number of such data types for free apps is 3 and for paid apps it’s 0. The mean is impacted by outliers in the free category and is ~8.1 for free apps and ~0.5 for paid apps.&lt;/p&gt;

&lt;p&gt;If we look at the number of apps that don’t collect data, as a percentage. It’s also clear that paid apps are much less likely to collect data.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Percentage&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;# Apps&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;# Apps that don’t collect&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;~9.1%&lt;/td&gt;
      &lt;td&gt;2628&lt;/td&gt;
      &lt;td&gt;240&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Paid&lt;/td&gt;
      &lt;td&gt;~53.9%&lt;/td&gt;
      &lt;td&gt;605&lt;/td&gt;
      &lt;td&gt;326&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Lastly do free apps collect more data types that are used to track the users across other apps and websites i.e. data categories with the identifier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATA_USED_TO_TRACK_YOU&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/free-vs-paid-tracking-box-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/free-vs-paid-tracking-box-plot.svg?1705769745&quot; alt=&quot;Box Plot comparing free vs paid apps. The median number of data types collect is 1 for paid apps and 3 for free apps&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes they do, the median number of data types used to track users across other apps and websites is 1 for free apps and 0 for paid apps. The mean is ~2.3 for free apps, but only ~0.2 for paid apps.&lt;/p&gt;

&lt;p&gt;For all three metrics considered, it turns out that my Twitter followers were correct. Free apps do collect more data than paid ones.&lt;/p&gt;

&lt;h3 id=&quot;worst-charts&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#worst-charts&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Worst Charts&lt;/h3&gt;

&lt;p&gt;Of the 40 remaining charts in the data set which are the worst? Let’s again start by considering the number of data types collected and linked to the user for non-app functionality purposes.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/worsts-charts-box-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/worsts-charts-box-plot.svg?1705769745&quot; alt=&quot;Box Plot with the top 5 worst charts measured by their median: &amp;quot;Shopping(Free)&amp;quot;, &amp;quot;Travel(Free)&amp;quot;, &amp;quot;Sports(Free)&amp;quot;, &amp;quot;Business(Free)&amp;quot;, and &amp;quot;Health &amp;amp; Fitness(Free)&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Median&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Games(Free)&lt;/td&gt;
      &lt;td&gt;13.668639&lt;/td&gt;
      &lt;td&gt;8.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shopping(Free)&lt;/td&gt;
      &lt;td&gt;11.938931&lt;/td&gt;
      &lt;td&gt;8.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Travel(Free)&lt;/td&gt;
      &lt;td&gt;10.486486&lt;/td&gt;
      &lt;td&gt;6.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sports(Free)&lt;/td&gt;
      &lt;td&gt;9.410000&lt;/td&gt;
      &lt;td&gt;5.5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Business(Free)&lt;/td&gt;
      &lt;td&gt;11.558559&lt;/td&gt;
      &lt;td&gt;5.0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here &lt;em&gt;Games(Free)&lt;/em&gt; is the clear winner, perhaps because the number of free games that are financed entirely by third party ads combined with the high cost of making games. &lt;em&gt;Shopping(Free)&lt;/em&gt; is a close second presumably due to analytics data collected to optimise purchases and checkout experiences.&lt;/p&gt;

&lt;p&gt;Another interesting observation here is that worst 24 charts, sorted by median, are all free. The first paid chart is &lt;em&gt;Games(Paid)&lt;/em&gt; at position 25.&lt;/p&gt;

&lt;p&gt;When considering the percentage of apps in each chart that don’t collect any data there’s commonality with the above. &lt;em&gt;Health &amp;amp; Fitness(Free)&lt;/em&gt;, &lt;em&gt;Shopping(Free)&lt;/em&gt;, and &lt;em&gt;Travel(Free)&lt;/em&gt; all show up again.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Percentage&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;#Apps&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;#Apps that don’t collect&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Games (Free)&lt;/td&gt;
      &lt;td&gt;~0.6%&lt;/td&gt;
      &lt;td&gt;169&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Health &amp;amp; Fitness(Free)&lt;/td&gt;
      &lt;td&gt;~2%&lt;/td&gt;
      &lt;td&gt;151&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;News(Free)&lt;/td&gt;
      &lt;td&gt;~2.8%&lt;/td&gt;
      &lt;td&gt;108&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shopping(Free)&lt;/td&gt;
      &lt;td&gt;~3.1%&lt;/td&gt;
      &lt;td&gt;131&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Travel(Free)&lt;/td&gt;
      &lt;td&gt;~3.6&lt;/td&gt;
      &lt;td&gt;111&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The same divide between paid and free apps occur again here. The first paid app shows up only at position 24(it’s Games again, &lt;em&gt;Games(Paid)&lt;/em&gt; at ~24.3%).&lt;/p&gt;

&lt;p&gt;When considering tracking across other apps and websites this is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/app-privacy/worsts-charts-tpt-box-plot.svg&quot;&gt;&lt;img src=&quot;/img/app-privacy/worsts-charts-tpt-box-plot.svg?1705769745&quot; alt=&quot;Box Plot with the top 5 worst measured by their median data types collected for third party tracking: &amp;quot;News(Free)&amp;quot;, &amp;quot;Entertainment(Free)&amp;quot;, &amp;quot;Sports(Free)&amp;quot;, &amp;quot;Shopping(Free)&amp;quot;, and &amp;quot;Health &amp;amp; Fitness(Free)&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Median&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Games(Free)&lt;/td&gt;
      &lt;td&gt;6.088757&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;News(Free)&lt;/td&gt;
      &lt;td&gt;2.731481&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shopping(Free)&lt;/td&gt;
      &lt;td&gt;3.488550&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sports(Free)&lt;/td&gt;
      &lt;td&gt;3.020000&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Entertainment(Free)&lt;/td&gt;
      &lt;td&gt;2.390000&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Health &amp;amp; Fitness(Free)&lt;/td&gt;
      &lt;td&gt;2.125828&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Again &lt;em&gt;Games(Free)&lt;/em&gt; is the worst, as I speculate above the reason is surely the amount of third party advertising used in games for monetisation.&lt;/p&gt;

&lt;p&gt;The trend with paid vs free apps repeats again, the first paid chart is, you guessed it, &lt;em&gt;Games(Paid)&lt;/em&gt; at position 24.&lt;/p&gt;

&lt;h3 id=&quot;worst-apps&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#worst-apps&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Worst Apps&lt;/h3&gt;

&lt;p&gt;Let’s now focus on individual apps, which are the absolute worst apps? While I was fetching the data, I tweeted some &lt;a href=&quot;https://twitter.com/K0nserv/status/1344051828602916877&quot;&gt;preliminary results&lt;/a&gt; based on a shallow analysis of response size. By this metric, all of Facebook’s apps were extremely data hungry. Let’s see if that conclusion holds up to more rigorous analysis. This data is based on a larger data set of 5274 apps collected from both UK and US stores rather than the smaller data set used for the analysis so far.&lt;/p&gt;

&lt;p&gt;Let’s start by again considering data collected and linked to the user for non-app functionality purposes. Here are the top 25 apps by this measure.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;#Data Types Collected&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Facebook Gaming&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Instagram&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Facebook Business Suite&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Facebook&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Messenger&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Oculus&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Portal from Facebook&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Boomerang from Instagram&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Facebook Adverts Manager&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Threads from Instagram&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Layout from Instagram&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Creator Studio from Facebook&lt;/td&gt;
      &lt;td&gt;128.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LinkedIn: Job Search &amp;amp; News&lt;/td&gt;
      &lt;td&gt;91.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Scrabble® GO - New Word Game&lt;/td&gt;
      &lt;td&gt;60.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Football Index - Bet &amp;amp; Trade&lt;/td&gt;
      &lt;td&gt;56.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Klarna | Shop now. Pay later.&lt;/td&gt;
      &lt;td&gt;56.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Draw a Line: Tricky Brain Test&lt;/td&gt;
      &lt;td&gt;55.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Telegraph News&lt;/td&gt;
      &lt;td&gt;55.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ovia Parenting &amp;amp; Baby Tracker&lt;/td&gt;
      &lt;td&gt;54.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ovia Pregnancy Tracker&lt;/td&gt;
      &lt;td&gt;54.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ovia Fertility &amp;amp; Cycle Tracker&lt;/td&gt;
      &lt;td&gt;54.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nectar: Shop &amp;amp; Collect Points&lt;/td&gt;
      &lt;td&gt;53.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Full Guide for Cyberpunk 2077&lt;/td&gt;
      &lt;td&gt;53.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NFL&lt;/td&gt;
      &lt;td&gt;52.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ring - Always Home&lt;/td&gt;
      &lt;td&gt;51.0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Can you spot the pattern? The top 12 apps are all from the same company, Facebook. All of Facebook’s apps collect an ungodly amount of data, the nearest other app is LinkedIn which collects 37 fewer data types. Keep in mind that the maximum number of data types an app can collect and link to the user outside of app functionality is 160(32 data types, across 5 purposes), Facebook manages to get almost all the way there at 128. All of Facebook’s apps declare the same set of data types collected. It’s easier to look at the data Facebook &lt;strong&gt;does not collect&lt;/strong&gt; than the data they do collect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data types not collected by Facebook:&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Data Type&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ANALYTICS&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Credit Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ANALYTICS&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Emails or Text Messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Credit Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Payment Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Fitness&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Health&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;SENSITIVE_INFO&lt;/td&gt;
      &lt;td&gt;Sensitive Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Audio Data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Customer Support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DEVELOPERS_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Emails or Text Messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Credit Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Payment Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Fitness&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Health&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;SENSITIVE_INFO&lt;/td&gt;
      &lt;td&gt;Sensitive Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Audio Data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OTHER_PURPOSES&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Emails or Text Messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Credit Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Payment Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Fitness&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Health&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Audio Data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Customer Support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRODUCT_PERSONALIZATION&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Emails or Text Messages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Credit Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;FINANCIAL_INFO&lt;/td&gt;
      &lt;td&gt;Payment Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Fitness&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;HEALTH_AND_FITNESS&lt;/td&gt;
      &lt;td&gt;Health&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;SENSITIVE_INFO&lt;/td&gt;
      &lt;td&gt;Sensitive Info&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Audio Data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Customer Support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;THIRD_PARTY_ADVERTISING&lt;/td&gt;
      &lt;td&gt;USER_CONTENT&lt;/td&gt;
      &lt;td&gt;Emails or Text Messages&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Data types shows up several times here, but remember we care about the combination of data type and purpose.&lt;/p&gt;

&lt;p&gt;The complete set of data they collect and link to users for non-app functionality purposes is scarier. &lt;a href=&quot;https://gist.github.com/k0nserv/8198a365f776c3a2c41a0e0cd259c1f5&quot;&gt;Yikes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is how the data breaks down for Facebook’s apps:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Purpose/Category&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;#Data Types&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Used to Track(3rd Party)&lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linked To You(Analytics)&lt;/td&gt;
      &lt;td&gt;30.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linked To You(Developer Advertising)&lt;/td&gt;
      &lt;td&gt;24.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linked To You(Other Purposes)&lt;/td&gt;
      &lt;td&gt;25.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linked To Your(Product Personalisation)&lt;/td&gt;
      &lt;td&gt;25.0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linked To You(Third Party Advertising)&lt;/td&gt;
      &lt;td&gt;24.0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;What about apps that track you across apps and websites used by other companies?&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;#Data Types Tracked&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Priceline - Hotel, Car, Flight&lt;/td&gt;
      &lt;td&gt;23&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Paxful Bitcoin Wallet&lt;/td&gt;
      &lt;td&gt;23&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chime - Mobile Banking&lt;/td&gt;
      &lt;td&gt;21&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nordstrom Rack&lt;/td&gt;
      &lt;td&gt;21&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Draw Coliseum&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nordstrom&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;M&amp;amp;S - Fashion, Food &amp;amp; Homeware&lt;/td&gt;
      &lt;td&gt;19&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bubble Pop! Puzzle Game Legend&lt;/td&gt;
      &lt;td&gt;18&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Block! Triangle puzzle:Tangram&lt;/td&gt;
      &lt;td&gt;18&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Bellingham Herald News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fresno Bee News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Football Index - Bet &amp;amp; Trade&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The State News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Miami Herald News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bradenton Herald News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Charlotte Observer News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Raleigh News &amp;amp; Observer&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Lexington Herald-Leader News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Telegraph News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Kansas City Star News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fort Worth Star-Telegram News&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Yelp: Local Food &amp;amp; Services&lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MLB Ballpark&lt;/td&gt;
      &lt;td&gt;16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;onX Backcountry GPS Trail Maps&lt;/td&gt;
      &lt;td&gt;16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;onX Hunt: GPS Tracking Tools&lt;/td&gt;
      &lt;td&gt;16&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here Facebook’s apps aren’t showing up, after all they are the people who facilitate the tracking across apps and websites. Unsurprisingly, all of the above apps are free.&lt;/p&gt;

&lt;h3 id=&quot;oxymorons&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#oxymorons&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Oxymorons&lt;/h3&gt;

&lt;p&gt;Astute readers will have noticed that some of the data types are linked to the user by their very nature and as such the combination of the data category &lt;em&gt;Data Not Linked to You&lt;/em&gt; and these data types are an oxymoron.&lt;/p&gt;

&lt;p&gt;For example your phone number, email address, name, or physical address are always linked to you even if the data isn’t attached to an identifier. Further, as numerous news outlets — including &lt;a href=&quot;https://www.nytimes.com/interactive/2019/12/19/opinion/location-tracking-cell-phone.html&quot;&gt;The New York Times&lt;/a&gt; — have reported, precise location can be re-identified with fair ease, by identifying locations such as homes and offices. Recovering a user’s identity from the contents of their text messages and emails is also trivial.&lt;/p&gt;

&lt;p&gt;Let’s look at apps that collect one of the above data types for the category &lt;em&gt;Data Not Linked to You&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this data set there are 740 apps that collect such oxymoron data types. The worst three offenders, by count, are &lt;em&gt;KFC: Online food delivery&lt;/em&gt;(20), &lt;em&gt;myCricket App&lt;/em&gt;(12), and &lt;em&gt;FootballNet QPR&lt;/em&gt;(12) although neither collect precise location which is reassuring. &lt;em&gt;The Weather Network&lt;/em&gt;, &lt;em&gt;OpenSnow&lt;/em&gt;, &lt;em&gt;Yanosik&lt;/em&gt;, &lt;em&gt;imo video calls and chat&lt;/em&gt;, and &lt;em&gt;MyRadar Weather Radar&lt;/em&gt; are examples of apps that collect precise locations for third party advertising purposes. In fact &lt;em&gt;imo video calls and chat&lt;/em&gt;, and &lt;em&gt;MyRadar Weather Radar&lt;/em&gt; collect precise location for every single one of the six different purposes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Taimi: LGBTQ+ Dating, Chat&lt;/em&gt; is an LGBTQ+ dating app that collects precise location for analytics purposes. This is of particular note because in many countries LGBTQ+ people face significant risk if their status is exposed.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#conclusion&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We’ve learned that a fairly large number of apps collect none or very little data that is linked to the user for non-app functionality purposes. However there are extreme outliers, not the least of which is Facebook.&lt;/p&gt;

&lt;p&gt;Free apps collect significantly more data than paid ones and anyone who cares about their privacy should opt to pay for the apps they use.&lt;/p&gt;

&lt;p&gt;Games are especially bad as category and especially with in third party tracking they stand out.&lt;/p&gt;

&lt;p&gt;The data at this stage is sparse, because only about 1/3 of the apps in the data set have added privacy details. In the future this will reach close to 100% and I will redo this analysis.&lt;/p&gt;

&lt;p&gt;If you have other questions about the data that you’d like me to cover please DM me on &lt;a href=&quot;https://twitter.com/K0nserv&quot;&gt;Twitter&lt;/a&gt; and I’ll try to add them to this post, or if there are a lot of questions I’ll do a follow up post.&lt;/p&gt;

&lt;p&gt;Now, if you’ll excuse me, I’m off to uninstall Instagram.&lt;/p&gt;</content><author><name>Hugo Tunius</name></author><category term="privacy" /><category term="ios" /><summary type="html">In iOS 14.3, Apple added their new app privacy details to App Store listings. App privacy details, which are sometimes compared to the nutritional labels on foodstuff, are details about the data an app collects and the purposes and use of such data. What can we learn by analysing this data?</summary></entry><entry><title type="html">Flutter Web: A Fractal of Bad Design</title><link href="https://hugotunius.se/2020/10/31/flutter-web-a-fractal-of-bad-design.html" rel="alternate" type="text/html" title="Flutter Web: A Fractal of Bad Design" /><published>2020-10-31T00:00:00+00:00</published><updated>2020-10-31T00:00:00+00:00</updated><id>https://hugotunius.se/2020/10/31/flutter-web-a-fractal-of-bad-design</id><content type="html" xml:base="https://hugotunius.se/2020/10/31/flutter-web-a-fractal-of-bad-design.html">&lt;p&gt;The web has a long and rich history dating back to the nineties at CERN. Back then &lt;a href=&quot;https://twitter.com/timberners_lee&quot;&gt;Tim Berners-Lee&lt;/a&gt; laid the foundation of HTML that is still around today. There have been attempts to replace it with varying success but none have been successful, for good reason. HTML and the later invention of CSS are a remarkably powerful set of tools to build all kinds of experiences on the web. People are still trying to replace HTML, which brings us to the topic of this post: Flutter Web.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://flutter.dev/web&quot;&gt;Flutter Web&lt;/a&gt; is part of Google’s &lt;a href=&quot;https://flutter.dev/&quot;&gt;Flutter&lt;/a&gt; framework for building cross platform UI. Hailed by many developers as the best thing since sliced bread, my opinion of it lacks the rose coloured glasses. I haven’t looked at Flutter for other platforms than web so I cannot comment on it other than that the general principle of Flutter is a terrible idea. Flutter works by throwing away the native UI toolkits provided by the platform and rendering everything from scratch using OpenGL et al. This translates extremely poorly to the web platform in particular. It’s worth noting that Flutter for Web is currently in beta and the problems I am about to detail could be addressed. However, I believe these issues are fundamental to Flutter’s design choices so I feel confident in my criticism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update(March 2021):&lt;/strong&gt; I have had a fair amount of feedback about how this piece is unfair because Flutter Web was in beta at the time it was written. Now, &lt;a href=&quot;https://medium.com/flutter/flutter-web-support-hits-the-stable-milestone-d6b84e83b425&quot;&gt;Flutter Web is stable&lt;/a&gt;, yet all these issues remain unfixed.&lt;/p&gt;

&lt;h2 id=&quot;semantic-html&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#semantic-html&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Semantic HTML&lt;/h2&gt;

&lt;p&gt;Anyone learning HTML these days would have encountered the term “Semantic HTML” because it is such an important part of modern web. &lt;a href=&quot;https://www.petelambert.com/&quot;&gt;Pete Lambert&lt;/a&gt; describes why this is important in the excellent blog post &lt;a href=&quot;https://www.petelambert.com/journal/html-is-the-web&quot;&gt;HTML is the Web&lt;/a&gt;. In short, the visible portion of a website, i.e. presentation, is only half the story. To take an example Pete used, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt; with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onClick&lt;/code&gt; handler might be clickable and can be styled to look like a button, but that doesn’t make it a button. The semantic structure of a document matters because that’s how machines, not humans, understand the web. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt; with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onClick&lt;/code&gt; handler doesn’t look like a link or a button to a screen reader, search engine crawler, or accessibility extension, it looks like a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Most importantly, semantic HTML is key for accessibility and other tools that let a user experience the web as they wish.&lt;/p&gt;

&lt;h2 id=&quot;a-fractal-of-bad-design&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#a-fractal-of-bad-design&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;A Fractal of Bad Design&lt;/h2&gt;

&lt;p&gt;Does Flutter Web generate semantic HTML? Not even close. It generates a patchwork of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canvas&lt;/code&gt; elements, custom elements, and a few other HTML elements. In the demo app &lt;a href=&quot;https://gallery.flutter.dev/#/reply&quot;&gt;Reply&lt;/a&gt;, how many buttons and links are there? If you guessed &lt;strong&gt;zero&lt;/strong&gt;, congratulations you are as jaded and cynical as me. Let me reiterate that, an email app with no buttons and links! Because there are no links in particular, features like cmd/ctrl clicking to open a new tab, hovering links to see the URL, and using the context menu do not work. For clarity, my understanding is that the Flutter Web demos at &lt;a href=&quot;https://gallery.flutter.dev&quot;&gt;galler.flutter.dev&lt;/a&gt; use the DOMCanvas backend rather than the CanvasKit backend. Of the two DOMCanvas should generate better HTML. CanvasKit renders everything from scratch with WebGL and should have all the issues I am about to describe and many more.&lt;/p&gt;

&lt;p&gt;I use the browser extension &lt;a href=&quot;https://vimium.github.io/&quot;&gt;Vimium&lt;/a&gt; to navigate the web, it’s an amazingly powerful tool that relies on, you guessed it, semantic HTML. Does it work on pages built with Flutter Web? Fuck no. It doesn’t work because it tries to find things that are semantically clickable, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; elements, of which, as we have established, Flutter Web generates none. Vimium works on almost all websites I use because most developers, thankfully, don’t just stick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onClick&lt;/code&gt; handlers on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt;s. However, whatever crazy shit Flutter Web does, doesn’t look clickable. Things being clickable that don’t look clickable is a good proxy for poor accessibility. For example, screen readers can navigate by landmarks, such as headings, &lt;strong&gt;links&lt;/strong&gt;, forms, and other semantic elements, does any of this work with Flutter Web? Fuck no.&lt;/p&gt;

&lt;p&gt;Even worse, unless you use special “selectable” text, Flutter Web doesn’t even support selecting text. No joke, their own code examples have a “copy all” button to get around this.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/flutter-web-a-fractal-of-bad-design/code-example.png&quot;&gt;&lt;img src=&quot;/img/flutter-web-a-fractal-of-bad-design/code-example.png?1705769745&quot; alt=&quot;Screenshot of Flutter's Navigation Bar component with preview, code, and &amp;quot;copy all&amp;quot; button&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How did anyone look at this and say “yeah nah, selecting text isn’t an important use case on the web”? Why does selecting text matter you ask? Some people use it to aid with reading by selecting the text they are currently reading, other people use it to(and I know this is wild) copy parts of the text, people with dyslexia use tools that read out selected portions of the text to help them read. Does any of this work with Flutter’s default, unselectable text? Fuck no!&lt;/p&gt;

&lt;p&gt;While we are talking about dyslexia, another useful feature that help people who suffer from it is the ability to change the fonts of web pages to one they find easier to read, such as &lt;a href=&quot;https://www.opendyslexic.org/&quot;&gt;OpenDyslexic&lt;/a&gt;. There are many tools that help with this and they all rely on the ability to inject custom CSS in a web page, to my surprise this actually looked to work when I tried it on some of the Flutter Web demos. However, looks deceive and while the font does apply it causes text to get cut off in almost all instances because of the terrible HTML Flutter generates. For example, in the “Reply” email client here’s the tag for the word “Reply” in the upper right&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;font-size: 18px; font-weight: normal; font-family: WorkSans_regular, -apple-system, BlinkMacSystemFont, sans-serif; color: rgb(255, 255, 255); letter-spacing: 0px; position: absolute; white-space: pre-wrap; overflow-wrap: break-word; overflow: hidden; height: 27px; width: 54px; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 59, 3.5); left: 0px; top: 0px;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;REPLY&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cleaned up a bit and these are the attributes&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;font-size: 18px;
font-weight: normal;
font-family: WorkSans_regular, -apple-system, BlinkMacSystemFont, sans-serif; color: rgb(255, 255, 255);
letter-spacing: 0px;
position: absolute;
white-space: pre-wrap;
overflow-wrap: break-word;
overflow: hidden;
height: 27px;
width: 54px;
transform-origin: 0px 0px 0px;
transform: matrix(1, 0, 0, 1, 59, 3.5);
left: 0px;
top: 0px;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Flutter Web generates absolutely positioned fixed sized HTML which instead of adopting to the text layout specified by the browser just cuts the text off.&lt;/p&gt;

&lt;p&gt;Another useful feature that is common in accessibility extensions is making links stand out more. This works by injecting global CSS rules that target &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; tags on the page.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/flutter-web-a-fractal-of-bad-design/link-underlines.png&quot;&gt;&lt;img src=&quot;/img/flutter-web-a-fractal-of-bad-design/link-underlines.png?1705769745&quot; alt=&quot;Screenshot of links with link emphasis turned on which underlines them in multiple colours&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does this work with Flutter Web (this questions is starting to feel redundant because the answer is almost always “No”)? Fuck no! In general, users use custom stylesheets for a multitude of reasons not limited to accessibility. As a “fun” exercise try this &lt;a href=&quot;https://ssb22.user.srcf.net/css/&quot;&gt;low vision stylesheet&lt;/a&gt; on one of the &lt;a href=&quot;https://gallery.flutter.dev/&quot;&gt;Flutter Web demos&lt;/a&gt; and see how readable the content is.&lt;/p&gt;

&lt;p&gt;Another one of the &lt;a href=&quot;https://gallery.flutter.dev/#/fortnightly&quot;&gt;demos&lt;/a&gt; in the Flutter Web Gallery is a news site. On news sites, I like to use the built-in “reader mode” in my browser to get a reading experience that is free from clutter and better suits me. For example, when reading in bed late at night (which I know I shouldn’t do) it’s nice to have a soft, dark mode experience instead of the glaring black text on pure white that many publications use. Does reader mode work with Flutter Web website? Nope. It doesn’t work because, you guessed it, it relies on semantic HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/flutter-web-a-fractal-of-bad-design/enable-accessibility.png&quot;&gt;&lt;img src=&quot;/img/flutter-web-a-fractal-of-bad-design/enable-accessibility.png?1705769745&quot; alt=&quot;Screenshot of Firefox accessibility inspector showing a single button with the text &amp;quot;Enable accessibility&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like any good writer, I’ve saved the best for last: the screen reader experience with Flutter Web. When you first focus on  one of the Flutter Web demos with a screen reader you are greeted with a “button” that says “Enable accessibility”. Admittedly, when you click this button there is a resemblance of screen reader content but it’s terrible. In the “Reply” app things are read out with unnecessarily high detail in the list view, things that can be clicked aren’t identified as such, you cannot even get to the menu as far as I can tell, and as previously identified there are almost no landmarks which are used for navigation.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#conclusion&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Flutter is a misguided attempt to achieve the impossible: quality cross platform experiences. Flutter Web in particular is fundamentally flawed and needs to be rebuilt from the ground up if it has any hopes of being viable tech that generates semantic, accessible, and modern web experiences. I have serious doubt that when Flutter Web leaves beta any of this will be addressed properly, unless the whole approach is reconsidered. If you see Flutter Web, turn around and run in the opposite direction.&lt;/p&gt;

&lt;p&gt;Developers, designers, and product people all love cross platform solutions because it saves them time and energy while achieving the “same” outcome as the costlier alternatives. Flutter Web nicely illustrates that the outcomes aren’t the same, the visible part of a product is only one part of the puzzle.&lt;/p&gt;

&lt;p&gt;I’m merely an accessibility novice and I didn’t even mention SEO in this post. I didn’t stop writing because I stopped finding flaws, but because this post was getting too long and I have other things to do. I’m sure accessibility users and experts can find even more issues than I have presented here(feel free to DM me on &lt;a href=&quot;https://twitter.com/k0nserv&quot;&gt;Twitter&lt;/a&gt; and I’ll include them).&lt;/p&gt;

&lt;p&gt;To end I’d like to leave you with a &lt;a href=&quot;https://www.youtube.com/watch?v=mRNX6XJOeGU&quot;&gt;quote&lt;/a&gt; from &lt;a href=&quot;https://www.imdb.com/title/tt0107290/characters/nm0000156?ref_=tt_cl_t3&quot;&gt;Dr. Ian Malcolm&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Hugo Tunius</name></author><category term="cross-platform" /><category term="web" /><category term="html" /><category term="css" /><category term="opinion" /><summary type="html">The web has a long and rich history dating back to the nineties at CERN. Back then Tim Berners-Lee laid the foundation of HTML that is still around today. There have been attempts to replace it with varying success but none have been successful, for good reason. HTML and the later invention of CSS are a remarkably powerful set of tools to build all kinds of experiences on the web. People are still trying to replace HTML, which brings us to the topic of this post: Flutter Web.</summary></entry><entry><title type="html">The Compiler Pain Index</title><link href="https://hugotunius.se/2020/10/18/the-compiler-pain-index.html" rel="alternate" type="text/html" title="The Compiler Pain Index" /><published>2020-10-18T00:00:00+01:00</published><updated>2020-10-18T00:00:00+01:00</updated><id>https://hugotunius.se/2020/10/18/the-compiler-pain-index</id><content type="html" xml:base="https://hugotunius.se/2020/10/18/the-compiler-pain-index.html">&lt;p&gt;One day when &lt;a href=&quot;https://en.wikipedia.org/wiki/Doomscrolling&quot;&gt;doomscrolling&lt;/a&gt; on Twitter, I saw this &lt;a href=&quot;https://twitter.com/mountain_ghosts/status/1301878824737611778&quot;&gt;tweet&lt;/a&gt; from &lt;a href=&quot;http://jcoglan.com/&quot;&gt;James Coglan&lt;/a&gt; and decided it would be a good topic for a long form writeup.&lt;/p&gt;

&lt;p&gt;For completeness here’s the full tweet:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;gonna keep thinking about how rust pulled off something that shouldn’t work on paper: making devs “do more work” with more static modelling and a very hard-to-please compiler, and ended up with anyone going “this is actually great”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While I am sure James’ tweet is at least partially sarcastic, it did get me thinking about why people do put up with Rust’s compiler. This post is a long form version of &lt;a href=&quot;https://twitter.com/K0nserv/status/1301902296595431425&quot;&gt;my thoughts in response&lt;/a&gt; to James’ tweet.&lt;/p&gt;

&lt;p&gt;Let’s talk about the concept of a “compiler”. Throughout this post I’ll use the term loosely and sometimes incorrectly to mean “interpreter” too. I will talk about a compiler accepting your program, which strictly speaking interpreters, like python, almost always do. However they might immediately crash with runtime errors. A python program that runs without syntax errors is considered “accepted”, for compiled languages a programs is accepted if the compiler compiles it successfully.&lt;/p&gt;

&lt;p&gt;So why does anyone put up with the Rust compiler? My short answer is: the level of input effort required to please the Rust compiler pays off in the output program. To make this clearer let’s define “input effort” and “compiler output”.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Input effort&lt;/strong&gt; means the time and mental energy required to make the compiler accept your program.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Compiler output&lt;/strong&gt; means the likelihood that the resulting program works correctly, is safe, and performant. The &lt;em&gt;“If it Compiles it Works”&lt;/em&gt;-factor if you will.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Rust specifically the &lt;strong&gt;input effort&lt;/strong&gt; is quite high. The compiler is strict about a lot of things and will not accept programs that don’t follow certain rules. For example, the Rust compiler has to be able to prove that references to values follow the rules of ownership; i.e. no immutable and mutable references to a value can exists at the same time. However, by enforcing these rules the &lt;strong&gt;compiler output&lt;/strong&gt; can have certain guarantees. For example, programs accepted by the Rust compiler are &lt;em&gt;mostly&lt;/em&gt; free from race conditions because of these ownership rules. I put up with the Rust compiler because when my program is accepted by the compiler, I have high confidence that it will be correct. Said another way, Rust has a high &lt;em&gt;“If it Compiles it Works”&lt;/em&gt;-factor.&lt;/p&gt;

&lt;h2 id=&quot;the-compiler-pain-index&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#the-compiler-pain-index&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;The Compiler Pain Index&lt;/h2&gt;

&lt;p&gt;With this concept of &lt;strong&gt;input effort&lt;/strong&gt; and how it translates to &lt;strong&gt;compiler output&lt;/strong&gt; we can plot different languages on a graph which gives us the compiler pain index. The index is the ratio of input effort to compiler output. None of this is particularly scientific, but it doesn’t have to be to get the idea across.&lt;/p&gt;

&lt;svg class=&quot;svg-illustration&quot; viewBox=&quot;0 0 665 500&quot; version=&quot;1.1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-role=&quot;img&quot; aria-label=&quot;The compiler pain index. The ratio between input effort and compiler output&quot;&gt;
    &lt;title&gt;The compiler pain index&lt;/title&gt;
    &lt;desc&gt;The ratio between compiler input effort and compiler output.&lt;/desc&gt;
    &lt;g id=&quot;compiler-pain-index-dark-mode&quot; stroke=&quot;none&quot; stroke-width=&quot;1&quot; fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;
        &lt;line x1=&quot;41&quot; y1=&quot;459&quot; x2=&quot;645&quot; y2=&quot;20&quot; class=&quot;line&quot; stroke-opacity=&quot;0.92&quot; stroke=&quot;#FFFFFF&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;square&quot; stroke-dasharray=&quot;8,20&quot;&gt;&lt;/line&gt;
        &lt;polyline id=&quot;Path-2&quot; stroke=&quot;#999999&quot; stroke-width=&quot;2&quot; points=&quot;645 459 41 459 41 20&quot;&gt;&lt;/polyline&gt;
        &lt;text id=&quot;Input-Effort&quot; transform=&quot;translate(18.500000, 240.500000) rotate(270.000000) translate(-18.500000, -240.500000) &quot; font-size=&quot;18&quot; font-weight=&quot;normal&quot; fill=&quot;#FFFFFF&quot; fill-opacity=&quot;0.9&quot;&gt;
            &lt;tspan x=&quot;-23.5&quot; y=&quot;247&quot;&gt;Input Effort&lt;/tspan&gt;
        &lt;/text&gt;
        &lt;text id=&quot;Compiler-Output&quot; font-size=&quot;18&quot; font-weight=&quot;normal&quot; fill=&quot;#FFFFFF&quot; fill-opacity=&quot;0.9&quot;&gt;
            &lt;tspan x=&quot;270&quot; y=&quot;488&quot;&gt;Compiler Output&lt;/tspan&gt;
        &lt;/text&gt;
        &lt;g id=&quot;Rust&quot; transform=&quot;translate(484.000000, 163.000000)&quot; fill-opacity=&quot;0.92&quot;&gt;
            &lt;circle class=&quot;oval&quot; fill=&quot;#11A8CA&quot; cx=&quot;4&quot; cy=&quot;10&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;
            &lt;text font-size=&quot;16&quot; font-weight=&quot;normal&quot; fill=&quot;#FFFFFF&quot;&gt;
                &lt;tspan x=&quot;12&quot; y=&quot;15&quot;&gt;Rust&lt;/tspan&gt;
            &lt;/text&gt;
        &lt;/g&gt;
        &lt;g id=&quot;Java&quot; transform=&quot;translate(343.000000, 173.000000)&quot; fill-opacity=&quot;0.92&quot;&gt;
            &lt;circle class=&quot;oval&quot; fill=&quot;#11A8CA&quot; cx=&quot;4&quot; cy=&quot;10&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;
            &lt;text font-size=&quot;16&quot; font-weight=&quot;normal&quot; fill=&quot;#FFFFFF&quot;&gt;
                &lt;tspan x=&quot;12&quot; y=&quot;15&quot;&gt;Java&lt;/tspan&gt;
            &lt;/text&gt;
        &lt;/g&gt;
        &lt;g id=&quot;Python&quot; transform=&quot;translate(132.000000, 383.000000)&quot; fill-opacity=&quot;0.92&quot;&gt;
            &lt;circle class=&quot;oval&quot; fill=&quot;#11A8CA&quot; cx=&quot;4&quot; cy=&quot;10&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;
            &lt;text font-size=&quot;16&quot; font-weight=&quot;normal&quot; fill=&quot;#FFFFFF&quot;&gt;
                &lt;tspan x=&quot;12&quot; y=&quot;15&quot;&gt;Python&lt;/tspan&gt;
            &lt;/text&gt;
        &lt;/g&gt;
    &lt;/g&gt;
&lt;/svg&gt;

&lt;p&gt;A language like Python is different from Rust. The &lt;strong&gt;input effort&lt;/strong&gt; required to please the compiler is very low, the code needs to be valid Python syntax, but in return we get very little in terms of &lt;strong&gt;compiler output&lt;/strong&gt;. There are few guarantees about the resulting program and my confidence that it will work is much lower than with Rust. Python has a low &lt;em&gt;“If it Compiles it Works”&lt;/em&gt;-factor and that’s okay because with Python the level of &lt;strong&gt;input effort&lt;/strong&gt; feels appropriate for the &lt;strong&gt;compiler output&lt;/strong&gt;. A bad language would be one that required unjustifiably high &lt;strong&gt;input effort&lt;/strong&gt; for the resulting &lt;strong&gt;compiler output&lt;/strong&gt;. By comparison, a good language would be one where the &lt;strong&gt;input effort&lt;/strong&gt; is low compared to the resulting &lt;strong&gt;compiler output&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every programmer will have different ideas about which languages are “good” and “bad” using this metric. Personally, I feel like Java requires too much &lt;strong&gt;input effort&lt;/strong&gt; to justify the &lt;strong&gt;compiler output&lt;/strong&gt; it provides.&lt;/p&gt;

&lt;p&gt;Of course, this is just a single metric to measure a programming language by and, as I’ve said, programming is about tradeoffs. Java might require too much &lt;strong&gt;input effort&lt;/strong&gt; for the &lt;strong&gt;compiler output&lt;/strong&gt;, but it has significant other upsides which often makes it a good choice.&lt;/p&gt;

&lt;style type=&quot;text/css&quot;&gt;
svg .oval {
  fill: var(--primary-color, #000);
}

svg text {
  fill: var(--text-color, #000);
}

svg .line {
  stroke: var(--text-color, #000);
}
&lt;/style&gt;</content><author><name>Hugo Tunius</name></author><category term="programming" /><category term="rust" /><summary type="html">One day when doomscrolling on Twitter, I saw this tweet from James Coglan and decided it would be a good topic for a long form writeup.</summary></entry><entry><title type="html">Why Svelte is Like Rust</title><link href="https://hugotunius.se/2020/08/15/why-svelte-is-like-rust.html" rel="alternate" type="text/html" title="Why Svelte is Like Rust" /><published>2020-08-15T00:00:00+01:00</published><updated>2020-08-15T00:00:00+01:00</updated><id>https://hugotunius.se/2020/08/15/why-svelte-is-like-rust</id><content type="html" xml:base="https://hugotunius.se/2020/08/15/why-svelte-is-like-rust.html">&lt;p&gt;If you’ve ever met me or read this blog, you know that I have a soft spot for the &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust programming language&lt;/a&gt;. I’m also excited about and interested in &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;, a declarative and reactive web framework. In this blog post I will explain how these two completely different technologies are actually quite similar and why these similarities are what make them great.&lt;/p&gt;

&lt;p&gt;Let’s start by talking about Rust. I could write several blog post about all of Rust’s excellent features, in fact I am planning to, but for now let’s focus on its most important feature: &lt;a href=&quot;https://doc.rust-lang.org/rust-by-example/scope/lifetime.html&quot;&gt;lifetimes&lt;/a&gt;. Lifetimes are Rust’s most unique and innovative feature and they are the solution to several problems that are nearly as old as programming itself. These problems all relate to managing memory and safe access to that memory. To understand why lifetimes are so important we need to understand how memory has been managed historically.&lt;/p&gt;

&lt;p&gt;The earliest programming languages left memory management up to the programmer, but it didn’t take long for systems to be created that took this responsibility out of the programmer’s hands. Lisp was the language that introduced the concept of an &lt;a href=&quot;https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)&quot;&gt;Automatic Garbage Collector&lt;/a&gt;, GC for short, and since its inception different forms of GC has been staple features of most popular programming languages. GC is an important innovation but it’s not free. All GCs come with a nontrivial runtime cost. This has made them, and the languages that use them, undesirable for many tasks, such as operating systems, games, and other realtime systems. For these task the indeterministic nature and high runtime cost are unacceptable; for example we cannot afford to stop the world for several milliseconds when the task at hand requires sub-millisecond precision. For these use cases C, C++, and other languages with manual memory management still reign supreme. It turns out, despite what some programmers say, that humans are terrible at manually managing memory. The consequence of errors in manual memory management range from crashes and data corruption, to information leaks and remote code execution. Microsoft has found that about &lt;a href=&quot;https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/&quot;&gt;70% of their security bugs&lt;/a&gt; are due to errors in manual memory management.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/why-svelte-is-like-rust/memory-safe-venn-diagram.png&quot;&gt;&lt;img src=&quot;/img/why-svelte-is-like-rust/memory-safe-venn-diagram.png?1705769745&quot; alt=&quot;Venn Diagram with two circles, the first labelled &amp;quot;Memory Safe&amp;quot; and the second labelled &amp;quot;Deterministic&amp;quot;. The overlap between them is labelled &amp;quot;Rust&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So back to lifetimes. They enable the compiler to determine &lt;strong&gt;at compile time&lt;/strong&gt; when memory can be &lt;strong&gt;safely&lt;/strong&gt; freed and reused. After compilation Rust code ends up kind of like C code written by a perfect programmer who makes no mistakes and knows exactly where to insert calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt;. The compiler can also use lifetimes to ensure that the code is correct and avoids common concurrency bugs in the presence of multiple threads. In conclusion, Rust solves the complex problem of memory management, while retaining the deterministic runtime performance required for many tasks that we typically associate with C and C++.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In conclusion, Rust solves the complex problem of memory management, while retaining the deterministic runtime performance required for many tasks that we typically associate with C and C++.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s switch gears and talk about the web a bit. When I started working as web developer Javascript heavy applications where just starting to gain popularity. Back then, one would write applications in “vanilla Javascript”(&lt;a href=&quot;https://en.wikipedia.org/wiki/ECMAScript#5th_Edition&quot;&gt;ES5&lt;/a&gt;) usually with &lt;a href=&quot;https://jquery.com/&quot;&gt;jQuery&lt;/a&gt;. As complexity moved from the server side to the client, a class of bugs started occurring with the underlying cause of application state ending up out of sync with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Document_Object_Model&quot;&gt;DOM&lt;/a&gt;. This hadn’t been a problem in server side rendered applications, because every state change involved rendering a completely new page from the current state. With client side applications one would write code to alter the DOM based on every state change, add a class here, remove an element over there etc. This approach was fraught with issues and almost always caused the afore mentioned bugs.&lt;/p&gt;

&lt;p&gt;Several client side libraries started popping up to try and tackle the issue of keeping state and UI in sync. &lt;a href=&quot;https://knockoutjs.com/&quot;&gt;Knockout&lt;/a&gt;, &lt;a href=&quot;https://backbonejs.org/&quot;&gt;Backbone&lt;/a&gt;, and &lt;a href=&quot;https://emberjs.com/&quot;&gt;Ember&lt;/a&gt; where some of these libraries. &lt;a href=&quot;https://angularjs.org/&quot;&gt;Angular&lt;/a&gt; from Google was also released around the same time. In common these libraries had the idea of two-way data bindings which would automatically keep the DOM in sync with changes to the underlying state and vice-versa. Still, these libraries didn’t solve the problem completely.&lt;/p&gt;

&lt;p&gt;It would be great if we could, like on the server side, simply re-render our whole application in response to every single state change, but this would be extremely computationally expensive and would result in unacceptable performance. This notion of re-rendering in response to every state change is the idea that underpins &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt;, which was released in 2013. React got around the performance issues inherent in re-rendering the whole application with &lt;a href=&quot;https://reactjs.org/docs/faq-internals.html&quot;&gt;Virtual DOM&lt;/a&gt;. Virtual DOM is an in memory representation of the DOM itself which React uses to determine a small set of changes that can be applied to the real DOM in response to every state change. The Virtual DOM was the crucial invention that made React a huge step forward for the web ecosystem. As an application developer one could “re-render” the whole application in response to every single state change and React would take care of making the desired changes to the underlying DOM.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// With React UI could
// be treated as a pure function of state
state =&amp;gt; UI&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;React is of course not entirely free, to determine the small set of changes to make to the DOM it has to &lt;a href=&quot;https://reactjs.org/docs/reconciliation.html&quot;&gt;reconcile&lt;/a&gt; two Virtual DOMs on the client side. If we squint a bit, automatic garbage collection and the Virtual DOM are kind of similar, they are both runtime solutions to problems programmers just weren’t up to solving correctly and consistently. So what’s the &lt;em&gt;lifetimes&lt;/em&gt; of web frameworks? You’ve probably guessed it, it’s Svelte!&lt;/p&gt;

&lt;p&gt;Svelte takes a similar approach to React and other frameworks that use a Virtual DOM, it’s both reactive and declarative. However, just like Rust does automatic memory management at compile time, Svelte determines the possible changes to DOM based on the possible state changes at compile time. With Svelte we get the benefits of React et al. without the downside of client side reconciliation that taxes performance.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/img/why-svelte-is-like-rust/client-side-state-venn-diagram.png&quot;&gt;&lt;img src=&quot;/img/why-svelte-is-like-rust/client-side-state-venn-diagram.png?1705769745&quot; alt=&quot;Venn Diagram with two circles, the first labelled &amp;quot;Free from state bugs&amp;quot; and the second labelled &amp;quot;Optimal performance&amp;quot;. The overlap between them is labelled &amp;quot;Svelte&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;hard-problems-at-compile-time&quot;&gt;&lt;a class=&quot;heading-anchor&quot; aria-hidden=&quot;true&quot; href=&quot;#hard-problems-at-compile-time&quot;&gt;&lt;img src=&quot;/assets/icons/link.svg?1705769745&quot; /&gt;&lt;/a&gt;Hard Problems at Compile Time&lt;/h2&gt;

&lt;p&gt;So why is Svelte like Rust? Svelte is like Rust because it solves a problem that programmers are clearly not capable of solving themselves &lt;strong&gt;at compile time&lt;/strong&gt;. It’s not all sunshine and rainbows however. As I mentioned at the top I haven’t yet used Svelte, but with Rust long compiles times are a significant problem. When we solve complex problems at compile time the complexity of compilers naturally increases and we pay the price of longer compile times for our runtime gains. Luckily there’s lots of &lt;a href=&quot;https://blog.mozilla.org/nnethercote/2019/10/11/how-to-speed-up-the-rust-compiler-some-more-in-2019/&quot;&gt;work&lt;/a&gt; happening to improve the compile times of the Rust compiler.&lt;/p&gt;</content><author><name>Hugo Tunius</name></author><category term="web" /><category term="rust" /><category term="svelte" /><summary type="html">If you’ve ever met me or read this blog, you know that I have a soft spot for the Rust programming language. I’m also excited about and interested in Svelte, a declarative and reactive web framework. In this blog post I will explain how these two completely different technologies are actually quite similar and why these similarities are what make them great.</summary></entry></feed>