How to use Product Hunt GraphQL API with Retrofit

Issue #370

Define response model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
posts {
edges {
node {
id
name
url
tagline
featuredAt
votesCount
commentsCount
thumbnail {
url
}
}
}
}
}

Here’s how request looks in Insomnia

1
2
3
4
5
6
7
8
9
10
> 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"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>

1
2
3
4
5
6
@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

query

Read more

Comments