A few days ago, while working on a side project, I got the point that I needed to create a feature that would allow the user to Add
something to my app.
The package showing the already existing objects of the app was declared as follows: appName.objectName.list
so I decided to add an appName.objectName.add
package to include all the components needed to build that specific part of my app: screens (I'm building this app using Jetpack Compose), composable functions, view models, models, view state definition etc.
Little did I know, I was making my future self-life as hard as I could.
So I went ahead and used the right-click of my mouse to show the pop-up menu to create a new package:
I then proceeded to insert the new
keyword for my new package:
Unfortunately, I completely missed the warning you can see above, as I simply typed new and pressed Enter
as soon as I was done.
I missed the hint of the Android Studio editor:
That shows something was different with that folder (yep, not a package!).
I went ahead and created my screen, my various composable, and my ViewModel class inside of that package, and ran the app on my device. Initially, the ViewModel had no dependencies so there were no problems.
Kept coding for a while and started adding external dependencies to the ViewModel, to perform the action of adding my objects to the app, with all that it comes with: defined a view state that the screen observes and updates accordingly, created various functions to update that state, from the screen to the view model, etc...
I'd say I spent an hour coding the entire screen, something like that.
And so, as we all do, after finishing coding I re-ran the app to check that everything was working as expected. The app crashed as soon as I tried to reach that screen.
As I was using Room
for local storage, and did a few updates on the classes, I thought I'd forgotten about bumping the Database version - so the first thing I did, without looking at the crash log, was to go to the definition of the database and update the version. But that had already been increased by past-me, what a surprise! For once I did the right thing.
So I re-ran the app and looked at the Logcat output to check what was causing my app to crash.
The crash was:
FATAL EXCEPTION: main
Process: com.lucanicoletti.app, PID: 31649
java.lang.RuntimeException: Cannot create an instance of class com.lucanicoletti.app.screens.intervals.new.ViewModel
[...]
Caused by: java.lang.NoSuchMethodException: com.lucanicoletti.app.screens.intervals.new.ViewModel.<init> []
That felt weird. I was sure I had set up the ViewModel correctly as the first time I tried reaching that screen, the instance (despite missing external dependencies) was created without problems. I double-checked that the ViewModel had the correct @HiltViewModel
annotation, that the (only) activity it was contained within had the correct @AndroidEntryPoint
annotation, and that the dependencies it relied upon were set up correctly with either @Bind
or @Provide
annotation from a @Module
. Everything was set up accurately, or so it looked like so.
So, what was I doing wrong?
I started searching for possible solutions on the web, checking StackOverflow for questions with similar errors and crashes, asking ChatGPT for a possible solution, and reaching out to groups of developers I belong to.
All I was able to find were solutions that didn't apply to my case: missing annotations on the ViewModel (or wrong, outdated one), dependencies not set up the correct way, missing annotation in the activity hosting the ViewModel, or other things I already checked in my app or that upon checking, were made precisely.
My app at that point had 3 screens (counting the new one I was creating) and the only difference between those three screens was how they were added and reached from the Navigation component in Jetpack Compose. I had two nested graphs: one containing the two main screens (the two reachable from a bottom bar in my app) and a nested one (the one I just created and was causing trouble). So I thought it was due to the nested-ness of the newest screen, perhaps Hilt (dependency injection) wasn't able to retrieve an instance of the ViewModel due to that. Perhaps there was a specific configuration to set up to have nested graphs and ViewModels injectable. So I went ahead and created this question on StackOverflow.
I got a few comments and an answer, but none was pointing me in the right direction - they couldn't know, since they didn't know the noobie mistake I started with.
After a few days of doing extensive Google searches every search query had only purple results on the first two pages (yes, I spent around 2 days looking at every possible link Google gave me with different queries pointing at my problem).
I was close to giving up: I tried replicating the problem in a smaller (smaller than 3 screens and 3 dependencies, can you imagine?!?) project, but since for time-seek I didn't create the same package structure, the problem was not happening in the replica project. I didn't know what to do, and then a friend asked me for a complete stack trace of the crash (I only posted parts of it, removing namespaces, and so, to hide my app name, screen name etc..).
While copy-pasting the errors in the chat with him, I don't know for what reason, my eyes took a peak at the package structure in AndroidStudio Project Structure
and noticed (image above) that the package I was in was missing a small circle on the folder's icon. I wondered why. So I moved the files outside of that package and re-ran the app. It didn't crash.
I deleted the package and re-created it, giving it the same name it had from the beginning: new
. The app crashed again. So I renamed the package once more, with add
instead of new
and the app was running smoothly without crashing.
I was astonished by this situation, it was surely one that no one would have been able to debug for me without having access to the project file(s). But even with that at hand, it might not have been so easy to spot the problem. The warning (second image) is only momentary and doesn't stop you from creating a package with a reserved name my simply hitting Enter
on your keyboard.
I would love to get a dialog prompt warning me about the mistake I am making in this scenario, perhaps it would be worth raising a ticket on Android Studio? What do you think? Should I create one? Would it be helpful to everyone in the future to prevent being in my same situation?