fix: handle empty date fields from subsonic json

This commit is contained in:
eddyizm 2025-10-13 21:09:27 -07:00
parent 2854ac6354
commit f091b3d248
No known key found for this signature in database
GPG key ID: CF5F671829E8158A
4 changed files with 62 additions and 5 deletions

View file

@ -39,7 +39,7 @@ Please note the two variants in the release assets include release/debug and 32/
As mentioned above, I am working towards a rebrand to get into app stores with a new name an icon. As mentioned above, I am working towards a rebrand to get into app stores with a new name an icon.
Moved details to [CHANGELOG.md](https://github.com/eddyizm/tempo/blob/main/CHANGELOG.md) Moved details to [CHANGELOG.md](CHANGELOG.md)
Fork [**sponsorship here**](https://ko-fi.com/eddyizm). Fork [**sponsorship here**](https://ko-fi.com/eddyizm).

View file

@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import android.util.Log;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.interfaces.DecadesCallback; import com.cappielloantonio.tempo.interfaces.DecadesCallback;
@ -31,14 +32,22 @@ public class AlbumRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) { if (response.isSuccessful()
&& response.body() != null
&& response.body().getSubsonicResponse().getAlbumList2() != null
&& response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
listLiveAlbums.setValue(response.body().getSubsonicResponse().getAlbumList2().getAlbums()); listLiveAlbums.setValue(response.body().getSubsonicResponse().getAlbumList2().getAlbums());
} else {
Log.e("AlbumRepository", "API Error on getAlbums. HTTP Code: " + response.code());
listLiveAlbums.setValue(new ArrayList<>());
} }
} }
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.e("AlbumRepository", "Network Failure on getAlbums: " + t.getMessage());
listLiveAlbums.setValue(new ArrayList<>());
} }
}); });

View file

@ -2,22 +2,28 @@ package com.cappielloantonio.tempo.subsonic
import com.cappielloantonio.tempo.App import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.subsonic.utils.CacheUtil import com.cappielloantonio.tempo.subsonic.utils.CacheUtil
import com.cappielloantonio.tempo.subsonic.utils.EmptyDateTypeAdapter
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class RetrofitClient(subsonic: Subsonic) { class RetrofitClient(subsonic: Subsonic) {
var retrofit: Retrofit var retrofit: Retrofit
init { init {
val gson = GsonBuilder()
.registerTypeAdapter(Date::class.java, EmptyDateTypeAdapter())
.setLenient()
.create()
retrofit = Retrofit.Builder() retrofit = Retrofit.Builder()
.baseUrl(subsonic.url) .baseUrl(subsonic.url)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())) .addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.client(getOkHttpClient()) .client(getOkHttpClient())
.build() .build()
} }

View file

@ -0,0 +1,42 @@
package com.cappielloantonio.tempo.subsonic.utils
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
// This adapter handles Date objects, returning null if the JSON string is empty or unparsable.
class EmptyDateTypeAdapter : JsonDeserializer<Date> {
// Define the date formats expected from the Subsonic server.
private val dateFormats: List<SimpleDateFormat> = listOf(
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") },
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") },
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") }
)
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Date? {
val jsonString = json.asString.trim()
if (jsonString.isEmpty()) {
return null
}
for (format in dateFormats) {
try {
return format.parse(jsonString)
} catch (e: ParseException) {
// Ignore and try the next format
}
}
return null
}
}