[Bug]: Continue tab takes long time to show up on Android Auto #1204

Open
opened 2026-04-24 23:53:32 +02:00 by adam · 11 comments
Owner

Originally created by @golinski on GitHub (May 11, 2025).

I have verified that the bug is not already awaiting release

Yes

What was the Problem?

I am relatively new user of the app (~2 months). I was very happy with the app, my only usability problem was that after attaching to Android Auto I had to navigate to the latest book starting with the Libraries tab. Well, I've always had just two tabs on the android auto screen: Libraries and Downloads. I've then prompted my wife to install the app on her phone. Imagine my surprise, when she got the "Continue" tab and could just resume listening with a single tap! For reference: my phone is a Pixel 6A, her is a Samsung A52s.

At that time I've used Tailscale VPN to connect with the phone and assumed that maybe it's a connectivity issue of some kind. But then I've downloaded the sources, connected through the Desktop Head Unit and did some digging.

The ApiHandler.getAllItemsInProgress function would take a lot of time to finish when stepping through with the debugger. I've added some logging inside the ItemInProgress.makeFromServerObject(jsobj) call and I've noticed that https://github.com/advplyr/audiobookshelf-app/blob/cf2f684e80545039d3dc9adaab8f3ecb5c63c6cd/android/app/src/main/java/com/audiobookshelf/app/data/ItemInProgress.kt#L23 takes over 100ms to parse an item. Given that the server returns 16 of them, it quickly adds up.

For comparison I've added Gson to dependencies and did a very quick deserialization test with that and Gson seems to be an order of magnitude faster. This is an excerpt from logcat while running on real device (Pixel 6A) with Desktop Head Unit showing the UI on PC:

2025-05-10 23:53:26.807  com.audiobookshelf.app.debug         E  processing items in progress
2025-05-10 23:53:26.807  com.audiobookshelf.app.debug         E  processing item 0
2025-05-10 23:53:26.808  com.audiobookshelf.app.debug         E  serialize to string
2025-05-10 23:53:26.809  com.audiobookshelf.app.debug         E  reading with Jackson
2025-05-10 23:53:27.319  com.audiobookshelf.app.debug         E  read item
2025-05-10 23:53:27.319  com.audiobookshelf.app.debug         E  reading with Gson
2025-05-10 23:53:27.344  com.audiobookshelf.app.debug         E  read item
2025-05-10 23:53:27.344  com.audiobookshelf.app.debug         E  processing item 1
2025-05-10 23:53:27.344  com.audiobookshelf.app.debug         E  serialize to string
2025-05-10 23:53:27.345  com.audiobookshelf.app.debug         E  reading with Jackson
2025-05-10 23:53:27.805  com.audiobookshelf.app.debug         E  read item
2025-05-10 23:53:27.805  com.audiobookshelf.app.debug         E  reading with Gson
2025-05-10 23:53:27.812  com.audiobookshelf.app.debug         E  read item

Quick look in the debugger suggests that both Jackson and Gson deserialized roughly the same data (so the Gson-derived object and its subobjects are not empty etc.)

I have no idea why it takes almost 500ms here, maybe because the screen on the device was off? Running the loop over 16 items in getAllItemsInProgress took over 7 seconds. I guess the real Android Auto in my car may just timeout.

I cannot imagine Jackson to be an order of magnitude slower than Gson in general, so something else is amiss here. Maybe some configuration issue?

Steps to Reproduce the Issue

  1. Connect the app to android auto.
  2. No Continue tab present on the car infotainment screen.

What was Expected?

Continue tab present to allow quick resume.

Phone Model

Pixel 6A

Phone OS

Android 15

Audiobookshelf App Version

Android App - 0.9.81

Installation Source

Other (List in "Additional Notes")

Additional Notes

Compiled from source, installed with Android Studio.

Originally created by @golinski on GitHub (May 11, 2025). ### I have verified that the [bug is not already awaiting release](https://github.com/advplyr/audiobookshelf-app/issues?q=is%3Aissue%20label%3A%22awaiting%20release%22) Yes ### What was the Problem? I am relatively new user of the app (~2 months). I was very happy with the app, my only usability problem was that after attaching to Android Auto I had to navigate to the latest book starting with the Libraries tab. Well, I've always had just two tabs on the android auto screen: Libraries and Downloads. I've then prompted my wife to install the app on her phone. Imagine my surprise, when she got the "Continue" tab and could just resume listening with a single tap! For reference: my phone is a Pixel 6A, her is a Samsung A52s. At that time I've used Tailscale VPN to connect with the phone and assumed that maybe it's a connectivity issue of some kind. But then I've downloaded the sources, connected through the Desktop Head Unit and did some digging. The `ApiHandler.getAllItemsInProgress` function would take a lot of time to finish when stepping through with the debugger. I've added some logging inside the `ItemInProgress.makeFromServerObject(jsobj)` call and I've noticed that https://github.com/advplyr/audiobookshelf-app/blob/cf2f684e80545039d3dc9adaab8f3ecb5c63c6cd/android/app/src/main/java/com/audiobookshelf/app/data/ItemInProgress.kt#L23 takes over 100ms to parse an item. Given that the server returns 16 of them, it quickly adds up. For comparison I've added Gson to dependencies and did a very quick deserialization test with that and Gson seems to be an order of magnitude faster. This is an excerpt from logcat while running on real device (Pixel 6A) with Desktop Head Unit showing the UI on PC: ``` 2025-05-10 23:53:26.807 com.audiobookshelf.app.debug E processing items in progress 2025-05-10 23:53:26.807 com.audiobookshelf.app.debug E processing item 0 2025-05-10 23:53:26.808 com.audiobookshelf.app.debug E serialize to string 2025-05-10 23:53:26.809 com.audiobookshelf.app.debug E reading with Jackson 2025-05-10 23:53:27.319 com.audiobookshelf.app.debug E read item 2025-05-10 23:53:27.319 com.audiobookshelf.app.debug E reading with Gson 2025-05-10 23:53:27.344 com.audiobookshelf.app.debug E read item 2025-05-10 23:53:27.344 com.audiobookshelf.app.debug E processing item 1 2025-05-10 23:53:27.344 com.audiobookshelf.app.debug E serialize to string 2025-05-10 23:53:27.345 com.audiobookshelf.app.debug E reading with Jackson 2025-05-10 23:53:27.805 com.audiobookshelf.app.debug E read item 2025-05-10 23:53:27.805 com.audiobookshelf.app.debug E reading with Gson 2025-05-10 23:53:27.812 com.audiobookshelf.app.debug E read item ``` Quick look in the debugger suggests that both Jackson and Gson deserialized roughly the same data (so the Gson-derived object and its subobjects are not empty etc.) I have no idea why it takes almost 500ms here, maybe because the screen on the device was off? Running the loop over 16 items in `getAllItemsInProgress` took over 7 seconds. I guess the real Android Auto in my car may just timeout. I cannot imagine Jackson to be an order of magnitude slower than Gson in general, so something else is amiss here. Maybe some configuration issue? ### Steps to Reproduce the Issue 1. Connect the app to android auto. 2. No Continue tab present on the car infotainment screen. ### What was Expected? Continue tab present to allow quick resume. ### Phone Model Pixel 6A ### Phone OS Android 15 ### Audiobookshelf App Version Android App - 0.9.81 ### Installation Source Other (List in "Additional Notes") ### Additional Notes Compiled from source, installed with Android Studio.
adam added the bug label 2026-04-24 23:53:32 +02:00
Author
Owner

@nichwall commented on GitHub (May 11, 2025):

Looks like gson has been in maintenance mode for several years (with some stack overflow and forum posts incorrectly thinking gson was deprecated back in 2014). https://github.com/google/gson

Their readme also says proper Kotlin support will likely not be added. With that said, it is interesting that it takes so long to deserialize the data. To clarify, the difference in time is fully within the app, not a speed difference in the server response time?

Are you and your wife listening to podcasts or books? Podcast progress can add up a lot quicker due to there being many episodes versus a longer book, so that could be a difference between the two of you as well.

@nichwall commented on GitHub (May 11, 2025): Looks like gson has been in maintenance mode for several years (with some stack overflow and forum posts incorrectly thinking gson was deprecated back in 2014). https://github.com/google/gson Their readme also says proper Kotlin support will likely not be added. With that said, it is interesting that it takes so long to deserialize the data. To clarify, the difference in time is fully within the app, not a speed difference in the server response time? Are you and your wife listening to podcasts or books? Podcast progress can add up a lot quicker due to there being many episodes versus a longer book, so that could be a difference between the two of you as well.
Author
Owner

@golinski commented on GitHub (May 11, 2025):

We listen to books and Gson was meant only as a quick comparison, not a solution. Probably Moshi is a more current alternative. Later I will take a deeper look at this. Just skimming through the code, maybe this takes longer than expected:

https://github.com/advplyr/audiobookshelf-app/blob/cf2f684e80545039d3dc9adaab8f3ecb5c63c6cd/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt#L15

@golinski commented on GitHub (May 11, 2025): We listen to books and Gson was meant only as a quick comparison, not a solution. Probably Moshi is a more current alternative. Later I will take a deeper look at this. Just skimming through the code, maybe this takes longer than expected: https://github.com/advplyr/audiobookshelf-app/blob/cf2f684e80545039d3dc9adaab8f3ecb5c63c6cd/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt#L15
Author
Owner

@golinski commented on GitHub (May 19, 2025):

I've tried adding some tests to run relevant code on a PC during development and converting everything to use kotlinx.serialization. There are some problems with how the model classes derive from one another (particularly the LibraryItem and LibraryItemWrapper). So this isn't a short task of adding some annotations and renaming some calls and might not yield a PR you would be willing to merge.

In the meantime I'll try to create some performance comparisons relevant for this use-case. Everything I've found online takes a look either at very short, toy-like classes with relatively flat JSON files or uses warm-up rounds, making the results irrelevant.

Maybe like @advplyr suggested in #1577 a solution might be a new endpoint, some leaner data classes for deserializing the existing data or even postponing data mapping entirely with deserializing relevant portions of JSObject by hand on the android auto startup. In the end getting a subset of relevant data from json, even a very deeply nested one, shouldn't be slower in Kotlin than in JS. And the web part runs very smoothly.

@golinski commented on GitHub (May 19, 2025): I've tried adding some tests to run relevant code on a PC during development and converting everything to use kotlinx.serialization. There are some problems with how the model classes derive from one another (particularly the `LibraryItem` and `LibraryItemWrapper`). So this isn't a short task of adding some annotations and renaming some calls and might not yield a PR you would be willing to merge. In the meantime I'll try to create some performance comparisons relevant for this use-case. Everything I've found online takes a look either at very short, toy-like classes with relatively flat JSON files or uses warm-up rounds, making the results irrelevant. Maybe like @advplyr suggested in #1577 a solution might be a new endpoint, some leaner data classes for deserializing the existing data or even postponing data mapping entirely with deserializing relevant portions of JSObject by hand on the android auto startup. In the end getting a subset of relevant data from json, even a very deeply nested one, shouldn't be slower in Kotlin than in JS. And the web part runs very smoothly.
Author
Owner

@golinski commented on GitHub (May 20, 2025):

I created an instrumented test at https://github.com/golinski/audiobookshelf-app/blob/kotlinx-serialization/android/app/src/androidTest/kotlin/com/audiobookshelf/app/SerializationInstrumentedTest.kt

The two tests in succession do the following

  • First test
    • deserialize a single personalized library json (to see the cold start time with class loading etc.)
    • deserialize another personalized library json of roughly comparable size (to compare performance on the second run)
  • Second test
    • deserialize json for items in progress

All json files taken directly from my server.

This was meant mostly to simulate what happens when "Recent" and "Continue" tabs are loaded on connecting to Android Auto.
I've created fresh data classes with the relevant annotations, particularly the shelves inheritance needed to be changed a bit to accommodate all the libraries. Tested libraries are kotlinx.serialization, Jackson and Moshi. Results in ms from my Pixel 6A:

2025-05-20 00:46:21.138 25095-25121 serialization c.a.a.debug         W  kotlinx.serialization 207
2025-05-20 00:46:21.179 25095-25121 serialization c.a.a.debug         W  kotlinx.serialization 41
2025-05-20 00:46:22.008 25095-25121 serialization c.a.a.debug         W  Jackson 773
2025-05-20 00:46:22.049 25095-25121 serialization c.a.a.debug         W  Jackson 41
2025-05-20 00:46:22.213 25095-25121 serialization c.a.a.debug         W  Moshi 125
2025-05-20 00:46:22.233 25095-25121 serialization c.a.a.debug         W  Moshi 20
2025-05-20 00:46:22.263 25095-25121 serialization c.a.a.debug         W  kotlinx.serialization 17
2025-05-20 00:46:22.361 25095-25121 serialization c.a.a.debug         W  Jackson 97
2025-05-20 00:46:22.377 25095-25121 serialization c.a.a.debug         W  Moshi 9

All three give equal objects as result, which is tested as well.

@golinski commented on GitHub (May 20, 2025): I created an instrumented test at https://github.com/golinski/audiobookshelf-app/blob/kotlinx-serialization/android/app/src/androidTest/kotlin/com/audiobookshelf/app/SerializationInstrumentedTest.kt The two tests in succession do the following * First test * deserialize a single personalized library json (to see the cold start time with class loading etc.) * deserialize another personalized library json of roughly comparable size (to compare performance on the second run) * Second test * deserialize json for items in progress All json files taken directly from my server. This was meant mostly to simulate what happens when "Recent" and "Continue" tabs are loaded on connecting to Android Auto. I've created fresh data classes with the relevant annotations, particularly the shelves inheritance needed to be changed a bit to accommodate all the libraries. Tested libraries are kotlinx.serialization, Jackson and Moshi. Results in ms from my Pixel 6A: ``` 2025-05-20 00:46:21.138 25095-25121 serialization c.a.a.debug W kotlinx.serialization 207 2025-05-20 00:46:21.179 25095-25121 serialization c.a.a.debug W kotlinx.serialization 41 2025-05-20 00:46:22.008 25095-25121 serialization c.a.a.debug W Jackson 773 2025-05-20 00:46:22.049 25095-25121 serialization c.a.a.debug W Jackson 41 2025-05-20 00:46:22.213 25095-25121 serialization c.a.a.debug W Moshi 125 2025-05-20 00:46:22.233 25095-25121 serialization c.a.a.debug W Moshi 20 2025-05-20 00:46:22.263 25095-25121 serialization c.a.a.debug W kotlinx.serialization 17 2025-05-20 00:46:22.361 25095-25121 serialization c.a.a.debug W Jackson 97 2025-05-20 00:46:22.377 25095-25121 serialization c.a.a.debug W Moshi 9 ``` All three give equal objects as result, which is tested as well.
Author
Owner

@nichwall commented on GitHub (May 20, 2025):

Nice! To clarify, you mean the number at the end of the line is the number of milliseconds for the test, correct?

@nichwall commented on GitHub (May 20, 2025): Nice! To clarify, you mean the number at the end of the line is the number of milliseconds for the test, correct?
Author
Owner

@golinski commented on GitHub (May 20, 2025):

Yes, one line for a single deserialization operation. The first six lines are from the two personalized libraries, the last three from the items in progress.

@golinski commented on GitHub (May 20, 2025): Yes, one line for a single deserialization operation. The first six lines are from the two personalized libraries, the last three from the items in progress.
Author
Owner

@golinski commented on GitHub (May 29, 2025):

I took a quick shot at changing everything to Moshi and failed on the first try. Kotlin serilization seemed nicer, as more problems with the conversion might be caught at compilation time, but I encountered a limitation early when making serializable classes clashes with how some classes in the app are defined. So Moshi won as a path of least resistance.

My first try failed, as I've changed some classes hierarchies slightly and Paper db did not like it in the end. I will be more careful the second time. Moshi can do most stuff automatically with a single annotation, the thing that will need to be done in a custom code are situations like the actual subtype of MediaTypeMetadata to use depending on the mediaType field in the parent object. I think the only way with builtin Moshi strategies for polymorphic hierarchies is having a field inside the object itself to tell Moshi which subclass to use. It's certainly doable without changing any backend behaviour, only needs more custom code.

@advplyr Would it be a big deal to add a suitable field on the backend, so that it's possible to easily tell the correct subclass by looking only at a single string property in json (so no looking at the parent nor descendant objects)?

@golinski commented on GitHub (May 29, 2025): I took a quick shot at changing everything to Moshi and failed on the first try. Kotlin serilization seemed nicer, as more problems with the conversion might be caught at compilation time, but I encountered a limitation early when making serializable classes clashes with how some classes in the app are defined. So Moshi won as a path of least resistance. My first try failed, as I've changed some classes hierarchies slightly and Paper db did not like it in the end. I will be more careful the second time. Moshi can do most stuff automatically with a single annotation, the thing that will need to be done in a custom code are situations like the actual subtype of `MediaTypeMetadata` to use depending on the `mediaType` field in the parent object. I think the only way with builtin Moshi strategies for polymorphic hierarchies is having a field inside the object itself to tell Moshi which subclass to use. It's certainly doable without changing any backend behaviour, only needs more custom code. @advplyr Would it be a big deal to add a suitable field on the backend, so that it's possible to easily tell the correct subclass by looking only at a single string property in json (so no looking at the parent nor descendant objects)?
Author
Owner

@advplyr commented on GitHub (Sep 6, 2025):

The fix in v0.10.1 may have helped to improve this issue. We still need to fix the slow deserialization and I want to setup android auto specific server endpoints.

@advplyr commented on GitHub (Sep 6, 2025): The fix in v0.10.1 may have helped to improve this issue. We still need to fix the slow deserialization and I want to setup android auto specific server endpoints.
Author
Owner

@Madj42 commented on GitHub (Sep 19, 2025):

Just an fyi, I have not seen any improvements in this with 0.10.1. I still see only the downloads option in AA and that is it. No other options ever show.

@Madj42 commented on GitHub (Sep 19, 2025): Just an fyi, I have not seen any improvements in this with 0.10.1. I still see only the downloads option in AA and that is it. No other options ever show.
Author
Owner

@tali055 commented on GitHub (Oct 5, 2025):

Just an fyi, I have not seen any improvements in this with 0.10.1. I still see only the downloads option in AA and that is it. No other options ever show.

Can confirm, issue still persists

@tali055 commented on GitHub (Oct 5, 2025): > Just an fyi, I have not seen any improvements in this with 0.10.1. I still see only the downloads option in AA and that is it. No other options ever show. Can confirm, issue still persists
Author
Owner

@Madj42 commented on GitHub (Nov 30, 2025):

Has there been any developments on this?

@Madj42 commented on GitHub (Nov 30, 2025): Has there been any developments on this?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf-app#1204