5

I've been working on this issue for some time now, and I can't seem to figure out what exactly the issue is. In Dart(2) the json.encode() does not seem to be giving me the result I am aiming for.

I am passing in a Map<String, dynamic> that looks like this:

_data = <String, dynamic>{
    'uid': newProfileCreationModel.accountID,
    'displayName': newProfileCreationModel.profileName,
    'greeting': newProfileCreationModel.profileBody ?? '',
    'avatarSrc': newProfileCreationModel.avatarSrc,
    'heroSrc': newProfileCreationModel.heroSrc,
    'accountType': newProfileCreationModel.accountType,
    'cityID': newProfileCreationModel.cityID,
    'cityName': newProfileCreationModel.cityName,
    'country': newProfileCreationModel.countryCode,
    'state': newProfileCreationModel.stateAbv,
  };

and using this to convert it to JSON

final String _jsonData = json.encode(_data);

Then I am sending it to a google cloud function in the following way.

final Uri _uri = Uri.parse(functionUrl);
final String _jsonData = json.encode(_data);
final String _userToken = await AuthenticationService.getUserToken();

HttpClient client = new HttpClient();
HttpClientRequest request = await client.postUrl(_uri);
request.headers.set('Authorization', 'Bearer ' + _userToken);
request.headers.set('Content-Type', 'application/json; charset=utf-8');
print('Encoded: ' + _jsonData);
request.write(_jsonData);

HttpClientResponse response = await request.close();
if(response.statusCode != HttpStatus.OK){ ...

The line where I print the encoded string outputs this to the console:

05-04 18:52:57.902 26146-26200/com.app.name I/flutter: Encoded: {"uid":'123456789',"displayName":"James","greeting":"My Greetings!","avatarSrc":"http://cdn.free.com/someImage.jpg","heroSrc":"http://cdn.free.com/someImage.jpg","accountType":"per","cityID":1,"cityName":"Eugene","country":"US","state":"OR"}

However the request.write(_jsonData) fails with the following firebase console log error Request Body Missing Data the response that looks like this in the

Firebase Console Log.

Request body is missing data.  { 
    uid: '123456789',
    displayName: 'James',
    greeting: 'My Greetings!',
    avatarSrc: 'http://cdn.free.com/someImage.jpg',
    heroSrc: 'http://cdn.free.com/someImage.jpg',   accountType: 'per',   
    cityID: 1,
    cityName: 'Eugene',   
    country: 'US',
    state: 'OR' 
}


Invalid request IncomingMessage {
  _readableState: 
   ReadableState {
     objectMode: false,
     highWaterMark: 16384,
     buffer: BufferList { head: null, tail: null, length: 0 },
     length: 0,
     pipes: null,
     pipesCount: 0,
     flowing: true,
     ended: true,
     endEmitted: true,
     reading: false,
     sync: false,
     needReadable: false,
     emittedReadable: false,
     readableListening: false,
     resumeScheduled: false,
     defaultEncoding: 'utf8',
     ranOut: false,
     awaitDrain: 0,
     readingMore: false,
     decoder: null,
     encoding: null },
  readable: false,
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,
  socket: 
   Socket {
     connecting: false,
     _hadError: false,
     _handle: 
      TCP {
        bytesRead: 13285,
        _externalStream: {},
        fd: 14,
        reading: true,
        owner: [Circular],
        onread: [Function: onread],
        onconnection: null,
        writeQueueSize: 0,
        _consumed: true },
     _parent: null,
     _host: null,

The interesting part is that the data is getting through as is clearly displayed in the firebase console log, however it won't recognize it as the body.

Raw Data Approach

When I try sending a raw JSON object through the request.write() as

request.write({'hello':'universe'});

I get a totally different kind of error in the firebase console.

SyntaxError: Unexpected token h in JSON at position 1
    at Object.parse (native)
    at parse (/var/tmp/worker/node_modules/body-parser/lib/types/json.js:84:17)
    at /var/tmp/worker/node_modules/body-parser/lib/read.js:102:18
    at IncomingMessage.onEnd (/var/tmp/worker/node_modules/raw-body/index.js:149:7)
    at IncomingMessage.g (events.js:292:16)
    at emitNone (events.js:86:13)
    at IncomingMessage.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:974:12)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

on the firebase side I am using a function using a callable, this is where the firebase console logs are being recorded from

export const finalizeProfile = functions.https.onCall((data, context) => { 
    //CODE 
});

Is anyone able to spot what I may be doing incorrectly?

SeaFuzz
  • 1,177
  • 9
  • 28
  • For the first version it is possible you are not setting headers for content type correctly, try request.headers.contentType = new ContentType("application", "json", charset: "utf-8"); On the second example, if you pass an object to a request sink it calls toString on it. That won't give you the json representation. I don't know if these will solve your issues but it might help – Jonah Williams May 05 '18 at 01:18
  • Thanks @JonahWilliams I tried this as well to no avail, the problem was actually the way the body data was structured. I've posed the answer that solved the issue. Without an cloud function SDK for flutter / dart yet, gotta trod through this piece by piece. – SeaFuzz May 05 '18 at 01:38

2 Answers2

11

The solution was hidden in plain sight. The issue had to do with a missing field. for Firebase Cloud Functions, the contents 'body', as referred to in the error message body is missing data, literally means it requires a key named data which contains the object of data you are passing.

The google docs are not(ish) crystal clear on this, as they are missing an example https://firebase.google.com/docs/functions/callable-reference#request_body

This is how the data must be sent to functions.https.onCall(), note the Data Field as compared to the original question which did not include this:

{
   "data":{ 
      "uid":"123456789",
      "displayName":"James",
      "greeting":"My Greeting!",
      "avatarSrc":"http://cdn.free.com/someImage.jpg",
      "heroSrc":"http://cdn.free.com/someImage.jpg",
      "accountType":"per",
      "cityID":1,
      "cityName":"Eugene",
      "country":"OR",
      "state":"AL"
   }
}

The resulting code that now works looks as follows:

// Helper function to format any map to be used for Firebase
Map prepMapForFirebaseJSON(Map<String, dynamic> mapData){
    Map<String, Map<String, dynamic>> _fireJson = {
      'data' : mapData
    };
    return _fireJson;
}

// Process HTTP request
final Uri _uri = Uri.parse(functionUrl);
final String _jsonData = json.encode(prepMapForFirebaseJSON(mapData));
final String _userToken = await AuthenticationService.getUserToken();

HttpClient client = new HttpClient();
HttpClientRequest request = await client.postUrl(_uri);
request.headers.set('Authorization', 'Bearer ' + _userToken);
request.headers.set('Content-Type', 'application/json; charset=utf-8');
request.write(_jsonData);
request.close();
SeaFuzz
  • 1,177
  • 9
  • 28
0

I think closing the request is missing:

request.write(_jsonData);
var response = await request.close();
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I am revising my post to include this. It is already in my code but was not included in the code of my original post. – SeaFuzz May 04 '18 at 22:42