Repository: tuskyapp/Tusky (language: kotlin)

Issue:
Trending posts no longer load
Trending posts stopped loading in the beta.  They still load fine in browser for my instance ![Screenshot_20250315-204814_Tusky.jpg](https://github.com/user-attachments/assets/26a06e98-6795-4d44-881d-1b899fdc6098)


App details:

Tusky 28.0 beta 1

Device:

asus ASUS_AI2401_H
Android version: 15
SDK version: 35

Account:

@vwbusguy@mastodon.online
Version: 4.4.0-nightly.2025-03-14-security


Discussion hints:
There is a post where the preview card has an invalid `published_at` date currently trending https://github.com/mastodon/mastodon/issues/34182
@cbeyls Do you have an idea what a good client side fix for this is? Combining `GuardedAdapter` with `Rfc3339DateJsonAdapter` doesn't seem to work :(
`GuardedAdapter` skips the value on exception, but the date adapter already read it.
@connyduck it's an ordering issue, in NetworkModule you need to add GuardedAdapter before the date adapter so it will be called first and delegate to it.

Pull request: fix loading posts with invalid `published_at` in preview card
closes #4992

Test command: ./gradlew :app:testBlueDebugUnitTest --tests "com.keylesspalace.tusky.json.GuardedAdapterTest"

Fix patch:
diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
index 2a81d55ebd..d7d16fcf2d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
@@ -82,8 +82,8 @@ object NetworkModule {
     @Provides
     @Singleton
     fun providesMoshi(): Moshi = Moshi.Builder()
-        .add(Date::class.java, Rfc3339DateJsonAdapter())
         .add(GuardedAdapter.ANNOTATION_FACTORY)
+        .add(Date::class.java, Rfc3339DateJsonAdapter())
         // Enum types with fallback value
         .add(
             Attachment.Type::class.java,
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/PreviewCard.kt b/app/src/main/java/com/keylesspalace/tusky/entity/PreviewCard.kt
index c90b025f20..5168f74210 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/PreviewCard.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/PreviewCard.kt
@@ -15,6 +15,7 @@
 
 package com.keylesspalace.tusky.entity
 
+import com.keylesspalace.tusky.json.Guarded
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import java.util.Date
@@ -27,7 +28,8 @@ data class PreviewCard(
     val authors: List<PreviewCardAuthor> = emptyList(),
     @Json(name = "author_name") val authorName: String? = null,
     @Json(name = "provider_name") val providerName: String? = null,
-    @Json(name = "published_at") val publishedAt: Date?,
+    // sometimes this date is invalid https://github.com/tuskyapp/Tusky/issues/4992
+    @Json(name = "published_at") @Guarded val publishedAt: Date?,
     val image: String? = null,
     val type: String,
     val width: Int = 0,
diff --git a/app/src/main/java/com/keylesspalace/tusky/json/GuardedAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/json/GuardedAdapter.kt
index 11cb1f3af2..579d58d12b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/json/GuardedAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/json/GuardedAdapter.kt
@@ -17,8 +17,8 @@
 
 package com.keylesspalace.tusky.json
 
+import android.util.Log
 import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.JsonDataException
 import com.squareup.moshi.JsonReader
 import com.squareup.moshi.JsonWriter
 import com.squareup.moshi.Moshi
@@ -35,10 +35,12 @@ class GuardedAdapter<T> private constructor(
 
     override fun fromJson(reader: JsonReader): T? {
         return try {
-            delegate.fromJson(reader)
-        } catch (e: JsonDataException) {
-            reader.skipValue()
+            reader.peekJson().use { delegate.fromJson(it) }
+        } catch (e: Exception) {
+            Log.w("GuardedAdapter", "failed to read json", e)
             null
+        } finally {
+            reader.skipValue()
         }
     }
 


Test patch:
diff --git a/app/src/test/java/com/keylesspalace/tusky/json/GuardedAdapterTest.kt b/app/src/test/java/com/keylesspalace/tusky/json/GuardedAdapterTest.kt
index 87ae8e5f59..5dfaef991a 100644
--- a/app/src/test/java/com/keylesspalace/tusky/json/GuardedAdapterTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/json/GuardedAdapterTest.kt
@@ -1,8 +1,11 @@
 package com.keylesspalace.tusky.json
 
+import com.keylesspalace.tusky.entity.PreviewCard
 import com.keylesspalace.tusky.entity.Relationship
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.adapter
+import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
+import java.util.Date
 import org.junit.Assert.assertEquals
 import org.junit.Test
 
@@ -11,6 +14,7 @@ class GuardedAdapterTest {
 
     private val moshi = Moshi.Builder()
         .add(GuardedAdapter.ANNOTATION_FACTORY)
+        .add(Date::class.java, Rfc3339DateJsonAdapter())
         .build()
 
     @Test
@@ -131,4 +135,53 @@ class GuardedAdapterTest {
             moshi.adapter<Relationship>().fromJson(jsonInput)
         )
     }
+
+    @Test
+    fun `should deserialize PreviewCard when attribute 'published_at' is invalid`() {
+        // https://github.com/tuskyapp/Tusky/issues/4992
+        val jsonInput = """{
+
+            "url": "https://www.cbc.ca/amp/1.7484477",
+            "title": "Canada reconsidering F-35 purchase amid tensions with Washington, says minister",
+            "description": "Canada is looking at cancelling a major portion of its purchase of U.S.-built F-35 stealth fighters and plans on opening talks with rival aircraft makers, Defence Minister Bill Blair said.",
+            "language": "en",
+            "type": "link",
+            "author_name": "Murray Brewster",
+            "author_url": "",
+            "provider_name": "CBC",
+            "provider_url": "",
+            "html": "",
+            "width": 0,
+            "height": 0,
+            "image": "https://files.mastodon.social/cache/preview_cards/images/137/231/445/original/0f63297db3ac7362.jpg",
+            "image_description": "",
+            "embed_url": "",
+            "blurhash": "U74#eeXoK9nLrVWZS+nfXVaenKkXTOjErobx",
+            "published_at": "57171-08-04T06:31:30.000Z",
+            "authors": [
+            {
+                "name": "Murray Brewster",
+                "url": "",
+                "account": null
+            }
+            ]
+
+        }"""
+        assertEquals(
+            PreviewCard(
+                url = "https://www.cbc.ca/amp/1.7484477",
+                title = "Canada reconsidering F-35 purchase amid tensions with Washington, says minister",
+                description = "Canada is looking at cancelling a major portion of its purchase of U.S.-built F-35 stealth fighters and plans on opening talks with rival aircraft makers, Defence Minister Bill Blair said.",
+                type = "link",
+                authorName = "Murray Brewster",
+                providerName = "CBC",
+                image = "https://files.mastodon.social/cache/preview_cards/images/137/231/445/original/0f63297db3ac7362.jpg",
+                width = 0,
+                height = 0,
+                blurhash = "U74#eeXoK9nLrVWZS+nfXVaenKkXTOjErobx",
+                publishedAt = null,
+            ),
+            moshi.adapter<PreviewCard>().fromJson(jsonInput)
+        )
+    }
 }