
Overview
Demodulate is a no-frills radio app, powered by React Native. There’s a search box and local stations list, favorites and history, and the minimal player UI. That’s it.
Demodulate has a simple yet beautiful user interface based on Material 3. The UI has large elements to make it easy to use on the go or in a car, even in the split-screen mode.
Demodulate can reliably do things many other apps can’t. It will show current track info, play in the background, handle headset buttons, restore playback after a call, and recover from many types of network failures.
Demodulate will cache favorites and history info and switch between available server instances automatically. You can send your playback history if you’d like to make the local station list better for everyone, or if you just want to get updated stream URLs.
Why a new app?
There’s a community-maintained database of internet radio stations at radio-browser.info. There’s also a companion app for Android, named RadioDroid.
Unfortunately, RadioDroid doesn’t seem to be maintained anymore. It’s gone from Play Store, the latest version on F-Droid is 2 years old, and there are many bugs and annoyances:
- The UI feels “crowded” and is impossible to use safely when docked in a car.
- Sometimes the UI is stuck and shows outdated track / station info.
- Sometimes skipping to the next station does not work, and you’re stuck with ads.
- Can’t automatically resume playback after a call (even a missed one).
- It won’t work at all if the randomly selected API instance is down.
Demodulate keeps the familiar user interface of RadioDroid, but it’s implemented from scratch and has only the essential features. No alarms, recording, track history, MPD support, etc.
Why not a web app?
Modern web browsers do not fully support HLS (adaptive bitrate streaming) yet, and do not allow for HTTP traffic on HTTPS websites at all. There are CORS issues because your browser is protecting you from potentially malicious websites. There are caching issues, where stale content is sometimes played. The notification support is incomplete. The browser UI gets in the way (and Progressive Web Apps, PWA for short, is not a solution). It’s easy to close the browser tab by accident, and you can’t open two tabs side by side on mobile. The list goes on and on.
On the other hand, I don’t want to learn Kotlin for a single project. I’ve created a sample project, and it already seems to be way too convoluted.
I know some React, so React Native will be a piece of cake, right? Right?
Demodulate uses react-native-paper
for Material 3, react-native-track-player
for actual streaming, axios
for accessing the API, zustand
for managing
state, and some extra libraries for icons, edge-to-edge layout, localization,
country detection, persistence, etc. There’s also a little bit of native Kotlin
code for audio effects, RTL support, and event logging.
Not so native
When working on a webpage, you need to learn HTML, CSS and JS, understand the Document Object Model, get familiar with some JavaScript APIs and development tools, and that’s it. This is true for any modern web browser, it’s been true since the early days of the Internet.
JSX itself is where React Native really shines. You can do impressive things and work around some limitations if you know React basics and best practices. You can use many of the available JavaScript libraries. Sometimes you would have to get your hands dirty and write some Kotlin code, but it’s not really an issue.
First surprise, most components have a flexbox-like layout by default. Some have
platform-specific behavior, and, for example, I couldn’t change the z-index
without adding a shadow (“elevation”) first. Then, elevation does not look good
unless you add an explicit solid background color. You can’t insert text freely,
it must always be contained in <Text>
. It has some custom properties, such as
dataDetectorType
(for parsing links) or numberOfLines
(for multi-line text),
which is useful. Do not expect full CSS support, though.
React Native doesn’t have any built-in persistence mechanisms, no LocalStorage or IndexedDB. You can’t access Android’s SharedPreferences out of the box either. But you can install a third-party library to store data in SQLite files, which gets the job done.
The JavaScript event loop is stopped when the app is moved to background, and sometimes the UI can get unmounted as well. So don’t try to use debounce with backgrounds code, and you better have your state always stored somewhere.
All network requests are processed by the native OkHttp
library, even if you
use axios
or fetch
on the JS side. This may be a significant challenge,
because axios
will give you a useless “NetworkError” code instead of a more
specific one. Basic HTTP communication with custom headers works fine, but I
haven’t tested authentication, large payloads, WebSockets.
A lot of things could go better
The official RN documentation tries to convince you to use Expo framework, which is like Next.js of React Native. Well, none of the code examples provided there would load in Firefox (not even the source code), and hot reload crashed randomly. Maybe I was unlucky, but you only get one chance to make a good first impression. Metro (official bundler) without Expo has been much better, but I had to hunt down libraries that would be taken for granted.
React Native Track Player has good docs and seems to be very reliable. Still, a lot of code is necessary to make it user-proof… and Android-proof. For example, I have to add two fake objects to a playback queue so the previous and next buttons are correctly displayed on older Android devices. Sadly, the library doesn’t support “new architecture” yet, so I’m stuck with RN 0.79.
Same for target SDK version. I can’t set it to 36
, Android 16, otherwise I run
into weird UI bugs such as broken dialog windows (can’t be closed with a back
gesture) and list items (not being highlighted at random). SDK version 35
is
better, but I had to make some interactions delayed to work around broken touch
events. That’s right, React Native is more than 10 years old, and no one has
figured out how to make scrolling reliable. One can hope it gets fixed…
eventually.
To add insult to injury, Android Studio is slow. It doesn’t support JSX and asks you to buy IntelliJ instead. Its file manager is trash and can’t be used if there are any background tasks running. Its emulator is broken and randomly stops playing sounds. But you need Studio for managing SDK and virtual devices, and then you need another IDE for React. It’s a total mess.
Why no Play Store?
Speaking of Google…
Demodulate is not published in Play Store because of Google’s hostility towards open source software, which they have recently manifested by going to require all application developers to submit their government-issued ID to make their software installable at all, even if published in a third-party app store or distributed via APK file, in the name of user “safety” — as if Play Protect warnings weren’t annoying enough. But we all know the truth.
If I wanted to publish Demodulate to Play Store, I would have to either act as an independent contractor, do all the paperwork, and have my home address publicly available in the app listing. Or find 20 people who will test the app for 14 consecutive days, and hope for the best. And if the app gets declined for some reason, then, well, $25 goes down the drain.
You don’t have to install F-Droid store app. You can simply download the APK file from F-Droid website, but you will miss out on the auto-updates. Or you can build the app from source.