To the Home Page

Demodulate, the radio app for Android

A no-frills internet radio app based on the data from radio-browser.info service, with a minimal user interface, basic search features, and playback history.

Get it on F-Droid Not available in Play Store Source code

Search view and split screen view
Search view and split screen view

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:

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.

Check out other blog posts: