Six repos, all Java, none of them finished
Between December 2022 and March 2023, I created six Android repos on GitHub. I was in the middle of college. I had never written an Android app before. Here is the list, in the order I made them.
Clicker-android(Dec 11, 2022): a button, a number, and a reset. That is the whole app.Android-Firebase-cloud-messaging(Dec 14, 2022): tap a button, fetch an FCM token, print it viaSystem.out.println, do nothing with it.Spoter(Dec 21, 2022 to Jan 8, 2023): the first serious attempt. A Spotify-coloured login screen, an activity that embeds the YouTube player, asongmodel class with four fields.Spoter_android(Jan 18, 2023): the same app, redone with Firestore wiring bolted on.android-project(Mar 30, 2023): another restart, this time withMediaPlayerfor local audio and a voting activity.AndroidSwipeableCardStack-master(Mar 30, 2023): a clone of an open-source card-stack library I was reading to understand custom views.
None of them shipped. Two of them do not even compile in a fresh Android Studio today, because Gradle 7 has moved on and the YouTube player library I depended on has been deprecated. That is fine. The point of these repos was not to ship. The point was to learn what an Android app is.
Why Java, when everyone was saying Kotlin
By late 2022 Google had been telling developers to use Kotlin for four years. The official docs had been Kotlin-first since 2019. Every blog post from a Google I/O talk was Kotlin. And I picked Java anyway.
The reason was boring. The books my college library had were all Java. The YouTube tutorials I could find that started from zero and did not skip the manifest file were mostly Java. Stack Overflow answers older than two years, which is where most of the actual signal lives when you are stuck at 1am on a NullPointerException, were Java. Android Studio in 2022 could still generate a Java project from the new-project wizard, but the templates were quietly getting worse. The activity_main.xml scaffold worked. The Kotlin equivalents came with more magic that I did not understand yet.
So I chose the language with the deeper corpus of beginner material, and paid for it later when I had to translate every modern coroutine tutorial back into AsyncTask and callback pyramids in my head. It was the wrong language for the right reason. If I had started fresh six months later I would have gone Kotlin and been faster overall. But you have to make the call with the information you have.
Clicker was the smallest possible thing
Clicker-android/MainActivity.java has one integer field, one count(View v) handler that increments it and writes it back into a TextView, and one reset handler. That is all.
public void count(View v) {
View x = findViewById(R.id.textView);
i += 1;
TextView m = (TextView) x;
m.setText(Integer.toString(i));
}I remember writing this and being annoyed at how many concepts you have to hold at once to make one button do one thing. You need the activity lifecycle, at least the onCreate half. You need the XML layout language with its constraint syntax. You need findViewById and the R class and the resource compiler. You need the manifest to declare the activity. You need Gradle to build it. You need an emulator or a real device to run it. Then you get to see a number go up.
Compare that to writing a Python script that does the same thing. Two lines. count = int(input()) + 1; print(count). The value of the Android version is that you had to know a whole stack to produce it. The value of the Python version is that you did not.
Both are real. But they teach different things.
Spoter tried to be an app
Spoter is where I actually reached. The premise was a room-scale voting app for songs: you and your friends are at a party, everyone opens the app, a YouTube video plays, and everyone votes on what plays next. It has a login screen with Spotify's #191414 background and #1DB954 green buttons, hardcoded because I did not know what a theme file was yet. It has a polling activity that extends YouTubeBaseActivity and calls youTubePlayer.loadVideo("qAHMCZBwYo4"). That is a specific video ID I picked because it was the first thing that came up when I searched a chill lofi track.
The API key for the YouTube player is checked into the repo. This is fine, it was a personal experiment and I revoked it a long time ago, but at the time I did not know that keys checked into public GitHub get scraped inside of five minutes. GitHub itself sent me the email that flagged it, which was the first time I learned that GitHub scans public commits for secrets.
The second version, Spoter_android, added Firebase. There is a Map<String, Object> user = new HashMap<>(); and a db.collection("users").add(user) call that literally writes {"first": "Ada", "last": "Lovelace", "born": 1815} to Firestore. I copied it verbatim from the Firebase docs and never changed it. If you inspect that project today, Ada Lovelace is the only user my Spoter database ever knew about.
What building an app teaches that a script does not
Here is what I actually got out of these six repos.
First, the sheer weight of an app forces you to learn tooling. A Python script is one file. An Android project is a Gradle module inside a Gradle project, with a manifest, a resource tree, a build variant matrix, a signing config, and a dependency graph that resolves against a Maven repository. You cannot escape it. By the third project I could read a build.gradle file and understand what implementation, androidTestImplementation, and debugImplementation all meant, because I had watched them fail in different ways.
Second, the emulator loop is slower than the REPL loop. This changes how you write code. On a script, you run, you see the error, you fix it, you run again. Two seconds. On an early Android app, you build, wait, deploy to emulator, wait, click through to the screen you are debugging, then reproduce the bug. Twenty seconds if the emulator is warm, two minutes if it is cold. This teaches you to think before you build, and to write more of the code correctly the first time. I got a lot better at reading code before running it, because running it was expensive.
Third, an app is a shipped surface. A script is code you read. An app is a thing your friend can install on their phone and use, or fail to use, and then tell you about. Even at the toy scale of Clicker, I sent the APK to a friend, watched them tap the button, and got real feedback that the number was too small to read on their phone. You do not get that from a Colab notebook.
The rest of the class
The three I did not deep-read here are close variants. Android-Firebase-cloud-messaging is a small FCM token fetcher I wrote to figure out how push notifications actually get delivered, a single button that calls FirebaseMessaging.getInstance().getToken() and prints the result. android-project is a third rewrite of Spoter that swapped YouTube for local MediaPlayer on raw resources. AndroidSwipeableCardStack-master is a cloned open-source card-stack library I was reading source code from, not writing.
Two years later I would use everything from these six repos to write BloomCycle in Kotlin, with Room, Hilt, Compose, and SQLCipher, and I would ship it to Google Play. But the reason I could is that I already knew what findViewById and R.layout.activity_main meant, and had watched a Gradle sync fail forty times.
The wrong language, in the right order, still moves you forward.
See also
- /blog/bloomcycle-kotlin-android, the later Android app, Kotlin instead of Java.
- /blog/firebase-free-tier-base64-android, Firebase for the college Fine Arts Club app from a similar era.