Issue #370

Define response model

import com.squareup.moshi.Json

data class Response(
    @field:Json(name="data") val data: ResponseData
)

data class ResponseData(
    @field:Json(name="posts") val posts: Posts
)

data class Posts(
    @field:Json(name="edges") val edges: List<Edge>
)

data class Edge(
    @field:Json(name="node") val node: Item
)

data class Item(
    @field:Json(name="id") val id: String,
    @field:Json(name="name") val name: String,
    @field:Json(name="url") val url: String,
    @field:Json(name="tagline") val tagline: String,
    @field:Json(name="featuredAt") val featuredAt: String,
    @field:Json(name="votesCount") val votesCount: Int,
    @field:Json(name="commentsCount") val commentsCount: Int,
    @field:Json(name="thumbnail") val thumbnail: Thumbnail
)

data class Thumbnail(
    @field:Json(name="url") val ur: String
)

Here is the query

{
  posts {
    edges {
      node {
        id
        name
        url
        tagline
        featuredAt
        votesCount
        commentsCount
        thumbnail {
          url
        }
      }
    }
  }
}

Here’s how request looks in Insomnia

> POST /v2/api/graphql HTTP/1.1
> Host: api.producthunt.com
> User-Agent: insomnia/6.6.2
> Cookie: __cfduid=d9a588136cbb286b156d8e4a873d52a301566795296
> Accept: application/json
> Content-Type: application/json
> Authorization: Bearer 068665d215cccad9123449841463b1248da07123418915a192a1233dedfd23b2
> Content-Length: 241

| {"query":"{\n  posts {\n    edges {\n      node {\n        id\n        name\n        url\n        tagline\n        featuredAt\n        votesCount\n        commentsCount\n        thumbnail {\n          url\n        }\n      }\n    }\n  }\n}"}

To post as json, need to use object for Moshi to convert

data class GetTopBody(
    @field:Json(name="query") val queryString: String
)

interface Api {
    @Headers(
        "Content-Type: application/json",
        "Accept: application/json",
        "Authorization: Bearer 068665d215cccad9123449841463b1248da07123418915a192a1233dedfd23b2",
        "Host: api.producthunt.com",
        "User-Agent: insomnia/6.6.2"
    )

    @POST("./")
    suspend fun getTop(
        @Body body: GetTopBody
    ): Response
}

And consume it in ViewModel. Use multiline string interpolation. No need to set contentLength

class ViewModel(val repo: Repo): ViewModel() {
    val items = liveData {
        val queryString = """
{
    posts {
        edges {
            node {
                id
                name
                url
                tagline
                featuredAt
                votesCount
                commentsCount
                thumbnail {
                    url
                }
            }
        }
    }
}
        """.trimIndent()

        val body = GetTopBody(queryString)

        try {
            val response = repo.api().getTop(body)
            val items = response.data.posts.edges.map { it.node }
            emit(items.toCollection(ArrayList()))
        } catch (e: Exception) {
            emit(arrayListOf<Item>())
        }

    }
}

The response looks like

{
  "data": {
    "posts": {
      "edges": [
        {
          "node": {
            "id": "158359",
            "name": "Toast",
            "url": "https://www.producthunt.com/posts/toast-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+PH+API+Explorer+%28ID%3A+9162%29",
            "tagline": "Organise tabs into organised sessions",
            "featuredAt": "2019-08-25T07:00:00Z",
            "votesCount": 318,
            "commentsCount": 16,
            "thumbnail": {
              "url": "https://ph-files.imgix.net/a169654a-850d-4b1c-80ba-be289f973fb7?auto=format&fit=crop"
            }
          }
        },
        {
          "node": {
            "id": "165621",
            "name": "Tree",
            "url": "https://www.producthunt.com/posts/tree-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+PH+API+Explorer+%28ID%3A+9162%29",
            "tagline": "Write documents in tree-like organisation with Markdown",
            "featuredAt": "2019-08-25T09:10:53Z",
            "votesCount": 227,
            "commentsCount": 11,
            "thumbnail": {
              "url": "https://ph-files.imgix.net/68b1f007-e630-4c79-8a27-756ec364343f?auto=format&fit=crop"
            }
          }
        }
      ]
    }
  }
}

Map

Instead of using an object, we can use Map. If using HashMap, I get

Unable to create @Body converter for java.util.HashMap<java.lang.String, java.lang.String>

@POST("./")
suspend fun getTop(
    @Body body: Map<String, String>
): Response

val body = mapOf("query" to queryString)

Troubleshooting

Use Network Profiler to inspect failure View > Tool Windows > Profiler

Read more