Apollo sends subscription as GET - android

my tech setup is as follows:
GraphQl via Apollo
AWS as backend
Native Android App written in Kotlin
GraphQl queries and mutations work without any problem.
I have the following Issue:
When creating a subscription the execution produces a GET instead of a POST. Resulting in a Backend Error saying:
<-- 400 Bad Request aws.url/graphql (152ms)
Content-Type: application/json;charset=UTF-8
Content-Length: 135
Connection: close
Date: Wed, 05 Jun 2019 12:40:04 GMT
x-amzn-RequestId: id
x-amzn-ErrorType: MalformedHttpRequestException
X-Cache: Error from cloudfront
Via: 1.1 cloudfront.url (CloudFront)
X-Amz-Cf-Pop: pop
X-Amz-Cf-Id: id
{
"errors" : [ {
"message" : "Invalid request, `query` can't be null.",
"errorType" : "MalformedHttpRequestException"
} ]
}
Here is my code:
Client creation:
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.HEADERS
val blogger = HttpLoggingInterceptor()
blogger.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(blogger)
.authenticator(get())
.build()
ApolloClient.builder()
.serverUrl(BuildConfig.backendUrl)
.okHttpClient(client)
.subscriptionTransportFactory(WebSocketSubscriptionTransport.Factory(BuildConfig.backendUrl, client))
.build() as ApolloClient
Subscription
override suspend fun subscribeToVehiclePosition(vehicleId: String, listener: DataRegistration.Listener<SubscribeToVehicleSubscription.Data>): DataRegistration {
val registration = RemoteDataRegistration()
authenticatedClient.subscribe(SubscribeToVehicleSubscription.builder().id(vehicleId).build()).execute(object: ApolloSubscriptionCall.Callback<SubscribeToVehicleSubscription.Data> {
override fun onFailure(e: ApolloException) {
listener.onClose(e)
}
override fun onResponse(response: Response<SubscribeToVehicleSubscription.Data>) {
val data = response.data()
if (data != null) {
listener.onData(data)
}
}
override fun onTerminated() {
listener.onClose(IllegalStateException("Connection Terminated!!"))
}
override fun onCompleted() {
listener.onCompleted()
}
})
return registration
}
The graphql definition
subscription subscribeToVehicle($id: String!) {
subscribeToCreateVehiclePositionLog(vehicleId: $id) {
lat
lng
date
tripId
}
}
The schema
{
"kind": "OBJECT",
"name": "Subscription",
"description": null,
"fields": [
{
"name": "subscribeToCreateVehiclePositionLog",
"description": null,
"args": [
{
"name": "vehicleId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VehiclePositionLog",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
I´m a bit lost why the subscription is called as a GET call.
Can anybody help me / does know what´s wrong here?

Related

GraphQL JSON schema to Apollo Client Schema

I have a JSON schema file that looks something like this
{
"data": {
"__schema": {
"queryType": { "name": "Query" },
"mutationType": { "name": "Mutation" },
"subscriptionType": null,
"types": [
{
"kind": "OBJECT",
"name": "Query",
"description": "",
"fields": [
{
"name": "planningSearch",
"description": "",
"args": [
{
"name": "search",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "PlanningSearchInput",
"ofType": null
}
However this won't work in my Android studio project, I'm following the Apollo Kotlin GraphQL tutorial and the schema they are using and tell you to add to your project looks like this:
type Query {
launches("The number of results to show. Must be >= 1. Default = 20" pageSize: Int, "If you add a cursor here, it will only return results _after_ this cursor" after: String): LaunchConnection!
launch(id: ID!): Launch
me: User
totalTripsBooked: Int
}
type LaunchConnection {
cursor: String!
hasMore: Boolean!
launches: [Launch]!
}
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
type Mission {
name: String
missionPatch(size: PatchSize): String
}
type User {
id: ID!
email: String!
profileImage: String
trips: [Launch]!
token: String
}
type Mutation {
bookTrips(launchIds: [ID]!): TripUpdateResponse!
cancelTrip(launchId: ID!): TripUpdateResponse!
login(email: String): User
}
type TripUpdateResponse {
success: Boolean!
message: String
launches: [Launch]
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
What can I do to convert the JSON schema to the expected one?
What you are expected is called GraphQL SDL schema format, and there's some npm tools like graphql-introspection-json-to-sdl, graphql-json-to-sdl or some online web site - json2sdl can easily to do this.

Expected BEGIN_OBJECT but was NUMBER at line 1 column 40 path $.result.loggedUser

I'm trying to consume an API in kotlin and this is the first time I'm working with API's and especially with retrofit, I checked online for a solution but I couldn't understand how to go about with this.
this is the API link
https://connect.managedcoder.com/api/leaves
inorder to get the response I have to pass a token as part of the parameter so the link has to be :
https://connect.managedcoder.com/api/leaves?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InNoYW5lLnBlcmVpcmFAc2ppbm5vdmF0aW9uLmNvbSIsInBhc3N3b3JkIjoicGFzc3dvcmQxMjMifQ.ncwng1CK8Y2N4z7ZofgB94ZVxJ8V5L8fk1JKGLnNr2s
however, the API fails and it returns the above error and goes into the on failure method.
I'm not sure if the way I have constructed the URL while calling the API is correct and if that is the cause of the issue, or it is due to some other reason.
This is my response to the API :
{
"status": "OK",
"result": {
"loggedUser": {
"id": 119,
"first_name": "Shane",
"last_name": "Pereira",
"emp_id": "Goa6",
"personal_email": "shanepereira30#gmail.com",
"office_email": "shane.pereira#sjinnovation.com",
"present_address": "Hno. 172 Church Street Cortalim Goa 403710",
"permanent_address": "Hno. 172 Church Street Cortalim Goa 403710",
"mobile_number": "2147483647",
"alternate_number": "2147483647",
"emergency_number": "2147483647",
"country": "India",
"office_location": "GOA",
"gender": 1,
"birth_date": "1970-01-01T00:00:00",
"maritial_status": null,
"identity_proof": "",
"blood_group": "",
"bank_name": "",
"bank_account_number": "",
"salary": null,
"tax_bracket": "",
"languages": "",
"max_qualification": "--",
"designation_id": 33,
"shift_type": "09:00 - 18:00",
"department_id": null,
"reporting_team": "",
"reporting_manager": "",
"reporting_manager_responsibilities": "0",
"mentor": "",
"date_of_joining": "2019-01-03T00:00:00",
"source_of_hire": "",
"referred_by": "",
"employment_status": "1",
"work_phone": "8390429861",
"employment_type": "full-time",
"confirmation_date": "2019-06-03T00:00:00",
"increment_date": "1970-01-01T00:00:00",
"resignation_date": "1970-01-01T00:00:00",
"last_working_date": "1970-01-01T00:00:00",
"notice_period": "2 Months ",
"reason": "",
"blacklisted": "",
"notes": "",
"knowledge": "--",
"role_id": 4,
"is_manager": 0,
"created": "2019-03-18T11:40:48",
"modified": "2019-03-18T11:40:48",
"profile_pic": "",
"is_pm": 0,
"hubstaff_name": null
},
"error": "No Leave days has been assigned. Please contact with HR.",
"success": false
}
}
this is my get request
#GET("api/leaves?")
fun getLeaveData(
#Query("token")token:String
):Call<LoginResponse>
this is my retrofit instance :
object RetrofitClient {
private val AUTH = "Basic"
private const val BASE_URL = "https://connect.managedcoder.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor{chain ->
val original = chain.request()
val RequestBuilder = original.newBuilder()
.addHeader("Authorization", "")
.method(original.method(),original.body())
val request = RequestBuilder.build()
chain.proceed(request)
}.build()
val instance: Api by lazy {
val retrofit= Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(Api::class.java)
}
}
and I have 3 classes which are LoginResponse, Result, and User
the LoginResponse contains the result and status of the response, the result contains the token, success and holds the logged user details from the user class which contains the id, name, and email
Login response:
data class LoginResponse ( val status: String, val result:Result) {
}
Result:
data class Result (val token:String, val success: String,val loggedUser: User){
}
User:
data class User(val id:Int?, val office_email: String?, val first_name: String?)
and here is where I call the API
if(token!=""){
RetrofitClient.instance.getLeaveData(token)
.enqueue(object :retrofit2.Callback<LoginResponse> {
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Toast.makeText(context,"error"+t.message, Toast.LENGTH_LONG).show()
Log.d("xxerror response xxxxx", t.message)
}
override fun onResponse(
call: Call<LoginResponse>,
response: Response<LoginResponse>
) {
// Toast.makeText(context,response.body()?.result?.success, Toast.LENGTH_LONG).show()
if (response.body()?.status=="OK"){
Toast.makeText(context,"mellooooo"+response.body()?.result?.loggedUser?.first_name, Toast.LENGTH_LONG).show()
}else{
Toast.makeText(context,"api failed to load"+response.body()?.status,
Toast.LENGTH_LONG).show()
}
}
})
}else{
Toast.makeText(context,"token is empty",
Toast.LENGTH_LONG).show()
val i= Intent(activity, LoginActivity::class.java)
startActivity(i)
SharedPrefManager.getInstance(this.requireContext()).clear()
}
For the GET request provided above, below is the response.
{"status":"OK","result":{"loggedUser":0,"error":"No Leave days has been assigned. Please contact with HR.","success":false}}
You can see that loggedUser is 0 instead of null
When the success=false the User Model expected is JsonObject and but response is an Int thus an error. Reason why it does not fall into onError block because it's not an HTTP error but a business logic error.
You can manually serialise the object based on success using Gson or ask the server to send null instead of 0

Get facebook posts attachments return NullPointerException

I'm trying to get page posts attachments. This is a JSON which I try to get:
{
"attachments": {
"data": [
{
"media": {
"image": {
"height": 720,
"src": "url",
"width": 720
}
}
}
]
},
"id": "post_id"
}
My #GET request:
#GET("{post_id}")
fun getPostsAttachments(#Path("post_id") postId : String?,
#Query("access_token") authToken: String?,
#Query("fields") media : String?)
:Observable<AttachmentsList>
Observable:
var getPostsAttachments: Observable<AttachmentsList> = facebook.getPostsAttachments(postId, "app_token", "attachments")
getPostsAttachments.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeBy(
onNext = { result ->
imgUrl?.addAll(result.data)
Log.d("TAG_NEXT", "$result")
},
onError = { throwable -> Log.d("TAG_ERROR", throwable.toString()) },
onComplete = { Log.d("TAG_COMPLETE", "$imgUrl") }
)
And it return D/TAG_NEXT: AttachmentsList(data=null) and D/TAG_COMPLETE: null.
How can I fix it ? Maybe my link request is wrong ?
From what I can see on https://developers.facebook.com/docs/graph-api/reference/v2.11/attachment your path for the call is wrong. Try changing the Retrofit call annotation to #GET("{post_id}/attachments").

Using Retrofit + OkHttp + Gson the api returns html though postman return json

I tested my api in postman and it returns json
just a plain get api
baseurl/ecom-services/api/mobile/tasks/user/tokenhere_/upcoming
but on Android
#GET("mobile/tasks/user/{id}/upcoming")
fun jobList(#Path("id") id: String): retrofit2.Call<Job>
this returns an html
onError returns this:
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
this is the call
fun fetchData() {
apiService.jobList(getUserId()).enqueue(object : retrofit2.Callback<Job>{
override fun onFailure(call: Call<Job>?, t: Throwable?) {
Timber.v(t!!.message.toString())
}
override fun onResponse(call: Call<Job>?, response: Response<Job>?) {
Timber.v(response!!.body().toString())
}
})
Job = data
json:
{
"type": "success",
"text": "200",
"data": [
{
"requestId": "5a31fc2b9af83128136f8fb3",
"taskDetailRequestId": null,
"clientEmail": null,
"imageBase64": null,
"imageLogoWidth": 0,
"imageLogoHeight": 0,
"additionalEmails": [],
"clientId": "5891d6b01d58301538b578ea",
"clientServiceAddress": {
"id": "5891d6b01d58301538b578eb",
"_refs": null,
"name": "tony chew",
"address1": null,
"address2": null,
"pinCode": "123123",
"createdUserId": null,
"lastUpdatedUserId": null,
"latitude": 0,
"longitude": 0,
"organizationId": null,
"addressType": null
},
"startDate": 1513311600000,
"endDate": 1513312500000,
"userRegId": "5891d7521d58301538b578fd",
"requestDate": 1513225200000,
"serviceName": "Service 1 SImple",
"serviceRequest": null,
"userTaskDetailsId": "5a31fc319af83128136f8fb9",
"fromSubclient": false,
"requestStatus": "ASSIGNED",
"serviceRequestType": "SERVICE",
"contractPayments": null
}
]
}
Job.kt
data class Job(
#Expose #SerializedName("type") val type: String, //success
#Expose #SerializedName("text") val text: String, //200
#Expose #SerializedName("data") val data: List<Data>
)
Possibly you need to provide the Accept header
http://square.github.io/retrofit/#api-declaration
#Headers("Accept: application/json")
#GET("mobile/tasks/user/{id}/upcoming")
fun jobList(#Path("id") id: String): retrofit2.Call<Job>
But it's also possible that your request is failing for other reasons like auth and getting redirected somewhere. Try the accept header first though. But you don't provide enough detail in your question to answer definitively.

How to parse dynamically named jsonarray using Gson and retrofit2?

I have a JSON response, which looks like this:
{
"equipment_layer": [
{
"id": 2,
"name": "Gateway",
"detail": "All gateways"
},
{
"id": 3,
"name": "Node",
"detail": "All Nodes"
},
{
"id": 1,
"name": "Miscellaneous",
"detail": "All miscellaneous assets"
},
{
"id": 4,
"name": "Sensors",
"detail": "All Sensors"
},
{
"id": 5,
"name": "IRM",
"detail": "Installation required material"
},
{
"id": 6,
"name": "Communication",
"detail": "All communication devices such as Cellular Router, ETU etc. which are purely communication"
}
],
"data": {
"1": [
{
"equipment_id": 353,
"item_quantity": 1,
"name": "DC Current Transformer (20mm) (Old)",
"shortcode": "SNS-DCI-CT20m-R1A",
"part_number": "718,804,805,",
"equipment_layer_id": 1,
"equipment_layer_name": "Miscellaneous"
},
{
"equipment_id": 357,
"item_quantity": 1,
"name": "Fuel Sensor - B4 (Old)",
"shortcode": "SNS-FUL-PSR-R1A",
"part_number": "718,810,811",
"equipment_layer_id": 1,
"equipment_layer_name": "Miscellaneous"
}
],
"2": [
{
"equipment_id": 345,
"item_quantity": 1,
"name": "RTU (Old)",
"shortcode": "RAN-RTU-PMCL-R1A",
"part_number": "787,788,789",
"equipment_layer_id": 2,
"equipment_layer_name": "Gateway"
}
],
"3": [
{
"equipment_id": 356,
"item_quantity": 1,
"name": "Battery Analyzer (Product) (Old)",
"shortcode": "RAN-BAM-PMCL-R1A",
"part_number": "787,808,809",
"equipment_layer_id": 3,
"equipment_layer_name": "Node"
}
],
"4": [
{
"equipment_id": 346,
"item_quantity": 1,
"name": "DC Current Transformer (30mm) (Old)",
"shortcode": "SNS-CT-DCI-R1A",
"part_number": "718,792,793",
"equipment_layer_id": 4,
"equipment_layer_name": "Sensors"
},
{
"equipment_id": 350,
"item_quantity": 1,
"name": "AC Block CT (Old)",
"shortcode": "SNS-ACI-BLK-R1A",
"part_number": "718,790,791",
"equipment_layer_id": 4,
"equipment_layer_name": "Sensors"
}
]
}
}
Now the part after the "data" label is dynamic, in a response I can have subarrays of "1", "2" but not of "3" or "4".The POJO of the data inside is same as you can see. So how can I parse this data? I'm using Rerofit2 with Gson.converterfactory. I've tried jsonchema2pojo as well but the data inside the "data" object is not showing up.
I've tried to follow this method:
Parsing Retrofit2 result using Gson with different JSON structures but I can't seem to trigger the UnrwapConverter.
This is my converterfactory implementation:
internal class UnwrappingGsonConverterFactory private constructor(private val gson: Gson) : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *> ?{
if (!needsUnwrapping(annotations)) {
return super.responseBodyConverter(type, annotations, retrofit)
}
val typeAdapter = gson.getAdapter(TypeToken.get(type))
return UnwrappingResponseConverter(typeAdapter)
}
private class UnwrappingResponseConverter (private val typeAdapter: TypeAdapter<*>) : Converter<ResponseBody, Any> , AnkoLogger{
#Throws(IOException::class)
override fun convert(responseBody: ResponseBody): Any? {
responseBody.use { responseBody ->
JsonReader(responseBody.charStream()).use({ jsonReader ->
// Checking if the JSON document current value is null
val token = jsonReader.peek()
if (token === JsonToken.NULL) {
return null
}
// If it's an object, expect `{`
jsonReader.beginObject()
var value: Any? = null
// And iterate over all properties
while (jsonReader.hasNext()) {
val data = jsonReader.nextName()
debug("Unwrap Stuff: $data")
when (data) {
"1", "2", "3", "4", "5", "6" -> value = typeAdapter.read(jsonReader)
else ->jsonReader.skipValue()
}
}
// Consume the object end `}`
jsonReader.endObject()
return value
})
}
}
}
companion object {
fun create(gson: Gson): Converter.Factory {
return UnwrappingGsonConverterFactory(gson)
}
private fun needsUnwrapping(annotations: Array<Annotation>): Boolean {
for (annotation in annotations) {
if (annotation is Unwrap) {
return true
}
}
return false
}
}
}
And the interface:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class Unwrap
My data classes are these:
data class ActivityNodes(#SerializedName("equipment_layer") val equipmentLayer: List<EquipmentLayer>,
#SerializedName("data") val data: nodeData)
data class nodeData (#SerializedName("1") val nodeList: List<dataItem>) <-- this is where I need someway to tell SerializedName that the value can be anything from 1 to 6
data class dataItem(#SerializedName("equipment_id") val equipmentId: Int,
#SerializedName("item_quantity") val itemQuantity: Int,
#SerializedName("name") val name: String,
#SerializedName("shortcode") val shortCode: String,
#SerializedName("part_number") val partNumber: String,
#SerializedName("equipment_layer_id") val equipmentLayerId: Int,
#SerializedName("equipment_layer_name") val equipmentLayerName: String,
var isScanned: Boolean = false )
data class EquipmentLayer(#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("detail") val details: String)
For the dynamic JSON, you have to parse the JSON string manually. To get JSON string from retrofit you have to use ScalarsConverterFactory instead of GsonConverterFactory.
Add this dependency:
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
Create Adapter like this:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://echo.jsontest.com")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
Create request method with ResponseBody
public interface MyService {
#GET("/key/value/one/two")
Call<ResponseBody> getData();
}
You can get Json String like this:
MyService service = retrofit.create(MyService.class);
Call<ResponseBody> result = service.getData();
result.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
e.printStackTrace();
}
});
Now you have to parse the JSON string manually to get your data from JSON.
Hope it helps:)
use below for the part of "data" of json:
Type mapType = new TypeToken<Map<String, List<EqupmentDetail.class>>>() {}.getType(); // define generic type
Map<String, List<EqupmentDetail.class>> result= gson.fromJson(new InputStreamReader(source), mapType);
here define EqipmentDetails class same as your refence
this will definitely work

Categories

Resources