0

i try to build a ActionsOnGoogle App with the ActionsSDK, Firebase Functions and Firebase Firestore.

I build a fulfillment that haves to intents. The mainIntent when starting the Actions App and the answerIntent when the users answers the question from the mainIntent. Getting the text in the mainIntent from the firestore database and ask the users with the wrapped text work very fine. But then inside the function where new data is wrapped from the database array get filled. After executing this function the array ist empty.

This is my code:

    function answerIntent(app){
    console.log('DEBUG: answerIntent acsess');
    console.log('DEBUG: ' + partAwnserRef);

    var rawInput = app.getRawInput();

    var answerCollection = db.collection(partAwnserRef);
    var answers = answerCollection.get().then(collection => {
      console.log('DEBUG: Collection size: ' + collection.size);
      collection.forEach(document => {
        if(document.exists){
          console.log('DEBUG: document ID: ' + document.id + ' = ' + document.data());
          answerDocumentArray.push(document);
          console.log('DEBUG: ArraySize: ' + answerDocumentArray.length);
          //HERE MY ARRAY WAS FILLED CORRECTLY
        }
        else{
          console.log('DEBUG: Dokument existiert nicht;');
          app.tell('Es liegt ein Fehler vor!');
        }
      });
    })
    .catch(err => {
      console.log('DEBUG: Es ist ein Fehler aufgetretten ' + err); 
      app.tell('Es liegt ein Fehler vor!');
    });

    //HERE THE OUTPOT IN THE LOG SAY THE ARRAY IS EMPTY
    console.log("DEBUG: lenght " + answerDocumentArray.length);

    for(var i = 0; i < answerDocumentArray.length; i++){
      var propertyNameArray = [];

      for(var propertyName in answerDocumentArray[i]){
        propertyNameArray.push(propertyName);
        console.log('DEBUG: propertyName: ' + propertyName);
      }

      for(var j = 0; j < propertyNameArray.length; j++){
        if(propertyNameArray[j].includes('answer')){
          if(rawInput.includes(answerDocumentArray[i].propertyNameArray[j])){
            app.tell(answerDocumentArray[i].propertyNameArray[j]);
          }
          else{
            var inputPrompt = app.buildInputPrompt(false, 'Ich habe die leider nicht verstanden!', ['Kannst du deine Antwort wiederholen?']);
            app.ask(inputPrompt); 
          }
        }
      }
    }
   };

When i look inside the firebase logs is see my self coded logs. And there i get the following. First log ist latest.

DEBUG: lenght 0

DEBUG: Collection size: 2

DEBUG: document ID: answer1 = [object Object]

DEBUG: ArraySize: 1

DEBUG: document ID: answer2 = [object Object]

DEBUG: ArraySize: 2

DasLukas
  • 151
  • 12
  • Your app crashes, therefore the function will not execute. At line 32 you have declared something that is undefined. – J. Doe Oct 30 '17 at 17:01
  • Okay, i find another mistake. A for loop that makes an extra loop. Now i have the problem that the answerDocumentArray inside the database function is filled right. But after this function the array is empty and so the for loop after this code doesn't starts because the array is empty. Why is the array outside the function empty, its an globale variable. – DasLukas Oct 30 '17 at 17:29
  • If you made a change and still have a problem, can you make sure you've updated the original question with the updates? Not just posted a description in the comments. It helps us figure out exactly what the current state of the problem is so we can help you. – Prisoner Oct 30 '17 at 18:18
  • Okay Updated my question. – DasLukas Oct 30 '17 at 18:32

1 Answers1

4

You have two problems, both of them fundamentally involve understanding how results get passed back from what you're working on:

Problem 1: Working with Promises

The reason answerDocumentArray is empty at the DEBUG: lenght line is because the call to answerCollection.get() does not actually return the values. It returns a JavaScript Promise object that will eventually resolve to the values.

Until it does, however, it will keep executing the code, and the next line in the code is the debugging line.

When it does resolve to those values, the function in the then() clause will be called and will start processing the collection returned. But this may be executed after the lines following the debugging line. When the function completes, it should return another Promise with any results indicating what needs to be further evaluated. (In this case - you're not returning anything, which ends up as a null.)

What you should likely do is have another then() clause that chains off the first one that is passed the answerDocumentArray and works with it. Something roughly like this:

var answers = answerCollection.get().then( collection =>{
    console.log( 'DEBUG: Collection size: ' + collection.size );
    collection.forEach( document =>{
      if( document.exists
      ){
        console.log( 'DEBUG: document ID: ' + document.id + ' = ' + document.data() );
        answerDocumentArray.push( document );
        console.log( 'DEBUG: ArraySize: ' + answerDocumentArray.length );
        //HERE MY ARRAY WAS FILLED CORRECTLY
      }
      else{
        console.log( 'DEBUG: Dokument existiert nicht;' );
        app.tell( 'Es liegt ein Fehler vor!' );
      }
    } )
    return Promise.resolve( answerDocumentArray );
    ;
  } )
  .then( answers => {
    for( var i = 0; i < answers.length; i++ ){
      // ... Do stuff with the answers here
    }
  })
  .catch( err =>{
    console.log( 'DEBUG: Es ist ein Fehler aufgetretten ' + err );
    app.tell( 'Es liegt ein Fehler vor!' );
  } )
  ;

(I may have mangled the explanation about Promises, but do yourself a favor - learn JavaScript Promises. They're still a pain, but they make JavaScript callbacks worlds easier.)

But what you do with the "do stuff" part... leads to the second problem:

Problem 2: Returning values through Actions on Google

This code block attempts to return values to the Google Assistant inside a loop (actually, inside a double-loop, but even one loop is bad enough).

Unless you can guarantee only one result, you're going to try and call either app.ask() or app.tell() more than once, assuming you can return one response for each document that matches the conditions that you meet.

for( var j = 0; j < propertyNameArray.length; j++ ){
  if( propertyNameArray[j].includes( 'answer' ) ){
    if( rawInput.includes( answerDocumentArray[i].propertyNameArray[j] ) ){
      app.tell( answerDocumentArray[i].propertyNameArray[j] );
    }
    else{
      var inputPrompt = app.buildInputPrompt( false, 'Ich habe die leider nicht verstanden!', ['Kannst du deine Antwort wiederholen?'] );
      app.ask( inputPrompt );
    }
  }
}

Actions on Google doesn't work like this, however. You can only send a single response to the user for each time your Intent is called. That response must be in a single ask() or a single tell(). You can't call them multiple times. If you try calling it multiple times, the results are undefined, but at best, you'll only get the first result. (At worst, it will crash.)

If you need to return multiple results, you need to collect them as you go through the loop and then only say something based on the result of what you've collected.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • 1. Thank you. Now i get i feeling for what i made wrong. I start studying Promises. 2. With this App i try to move the logic from the Action Package into this function with a database behind. So my idea ist that the mainIntent starts with a text. When the users answers, the answerIntent starts and wrapping the answer keywords from the text before. If this keywords correct, the text of the responding answer is wrapped from the database and then the function is asking. After the user answers, the answerIntent is start again. Is there a way to realize this? – DasLukas Oct 30 '17 at 19:32
  • (2) If you can guarantee that you'll only get one match through those loops, then you'll be ok. But you're trying to do a very complicated task (Natural Language Processing and state management) that has other tools that may help better. Using tools like Dialogflow.com to build and manage your phrases and turn those into Intents is generalyl a better solution since it separates your VUI from your logic design. – Prisoner Oct 30 '17 at 19:35
  • Okay thank you for helping me. I starts thinking about my goals and how to reach them with the right way. – DasLukas Oct 30 '17 at 19:38