12

We're building a Gmail Add-On however we wish to show a different card on depending on some business logic when the add-on's onTriggerFunction is called. This works fine for the first time the function runs when an email opens.

We have the conditional logic, but Gmail appears to cache the result of the initial call returning the first card. Going to another email and back to original, the onTriggerFunction is not called again, so the conditional logic is not run to change the initial card rendered.

Is there anyway to get the onTriggerFunction to run every time an email is opened, not just once the first time the email is opened?


Here's an example add-on with a trigger function that returns a single card displaying the current time:

Code.js

function buildAddOn(e) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle(new Date().toLocaleTimeString()));  

  return [card.build()];
}

appsscript.json

{
  "timeZone": "GMT",
  "dependencies": {
  },
  "oauthScopes": ["https://www.googleapis.com/auth/gmail.addons.execute"],
  "gmail": {
    "name": "Minimal example",
    "logoUrl": "https://www.gstatic.com/images/icons/material/system/2x/bookmark_black_24dp.png",
    "contextualTriggers": [{
      "unconditional": {
      },
      "onTriggerFunction": "buildAddOn"
    }],
    "primaryColor": "#4285F4",
    "secondaryColor": "#4285F4",
    "openLinkUrlPrefixes": ["https://mail.google.com/"],
    "version": "TRUSTED_TESTER_V2"
  }
}

When navigating to older conversations a spinner is displayed followed by the current time. However, when navigating back to previously displayed conversations the old value of the time is displayed instantly, suggesting some caching is taking place:

demo of the issue

Actions inside the add-on, or other activity in our app can affect what should be displayed when re-opening a previously displayed conversation. This means that redisplaying an old copy of the card results in unexpected behaviour for the user.

mikej
  • 65,295
  • 17
  • 152
  • 131
Tom Bell
  • 1,120
  • 2
  • 16
  • 33
  • Please post the code you're using. – Brian May 24 '18 at 18:35
  • Unfortunately I cannot post the exact code as its company code, however the issue will be for any code, as the function defined as the "onTriggerFunction" is never called after the first time for the email, unless you refresh Gmail, and navigate to that email again. – Tom Bell May 24 '18 at 20:35
  • 1
    @TomBell Don't post your company code, but a [minimal complete verifiable example](https://stackoverflow.com/help/mcve) to show what you're trying to do. Without any code, it's unlikely we can fully understand what you're doing or that we can help. – Basj May 28 '18 at 19:05
  • I'll get something sorted out for an example after I get back to work after my holiday. Just mentioning this so it doesn't look like I've forgotten this. – Tom Bell May 29 '18 at 23:08
  • @Brian I work on the same team with Tom and have updated the question with a minimal example and a bit more detail of why this causes an issue for the add-on we're building. Thanks! – mikej May 30 '18 at 16:50
  • 2
    Does using `setStateChanged` resolve this? Seems like if you do not say the state changes, then Google is free to cache things for improvement in UX. https://issuetracker.google.com/issues/80269537#comment3 – tehhowch May 31 '18 at 06:24
  • @tehhowch thanks! that does help with one common scenario. There's a couple of cases we've thought of 1) user performs an action in the add-on that affects what the content of the initial card should be for *that* conversation, 2) user performs an action that also affects what the content of the initial card should be for *other previously viewed conversations*, 3) user performs an action *outside of Gmail* that affects what the content of the initial card for a previously viewed conversation should be. We can use `setStateChanged` to resolve 1) but don't think it can help with the others. – mikej Jun 06 '18 at 12:22
  • @TomBell Read this once, this might help you https://stackoverflow.com/questions/48843209/gmail-add-on-trigger-criteria/50583457#50583457 – Jai Prak Jun 22 '18 at 09:40
  • @tehhowch - could you give an example of where to use setStateChanged ? – user2677034 Sep 28 '18 at 18:45

2 Answers2

1

Unfortunately there is no way at the moment (July 2019) to force the trigger to run every time the email is opened. You can use ActionResponseBuilder.setStateChanged(true) as a response to a button click to instruct Gmail to clear the cache, but it can't be sent along with a normal card response.

Eric Koleda
  • 12,420
  • 1
  • 33
  • 51
  • In my experience, `setStateChanged(true)` does absolutely nothing. All the cards in my add-on are still in their cached state when I navigate to them after calling this. The only way I've found is explained in my answer below. – paulwithap Oct 29 '19 at 17:29
0

There are a couple ways you can reload the UI of a card. Both of them have to happen in an ActionResponse.

The first is if you want to reload the current card as the result of some state change caused by a user action:

function buildCounterCard() {
  const card = CardService.newCardBuilder().setName('Counter');

  const count = CountApi.get(); // Or however you get this data

  const countWidget = CardService.newKeyValue().setTopLabel('Current Count').setContent(`${count}`);

  const incrementAction = CardService.newAction().setFunctionName('handleIncrement');
  const incrementButton = CardService.newTextButton().setText('Increment').setOnClickAction(incrementAction);

  const section = CardService.newCardSection().addWidget(countWidget).addWidget(incrementButton);

  card.addSection(section);

  return card.build();
}

function handleIncrement() {
  const actionResponse = CardService.newActionResponseBuilder();

  CountApi.update(); // Or however you update the data

  const nav = CardService.newNavigation().updateCard(buildCounterCard());
  actionResponse.setNavigation(nav);

  return actionResponse.build();
}

The only other way I've found so far is to update the previous card in the Navigation stack. Imagine the following scenario:

  1. User fills out a form and clicks "Save"
  2. Script posts the data to the API and on success pushes a "Success" card on to the stack.
  3. User sees the "Success" card with a message letting them know the data was saved and a button to go "Back" or "Home".

The action handler for that button could look something like this:

function handleBackClick(event) {
  const actionResponse = CardService.newActionResponseBuilder();

  clearPreviousCardState() // Otherwise the user sees the same data they filled out previously when they go back to the form

  const nav = CardService.newNavigation()
    .popCard()
    .updateCard(buildPreviousCard(event))
    .popToRoot();
  actionResponse.setNavigation(nav);

  return actionResponse.build();
}

For whatever reason this works, but updating multiple cards by chaining navigation steps along with updateCard() does not.

Seems like these are the only options right now. I find it pretty crazy that there's a Linear Optimization Service, but not a way to reload the UI. A commenter mentioned ActionResponseBuilder.setStateChanged(true), which sounds like what we want based on the documentation, but in my experience, it does absolutely nothing.

paulwithap
  • 678
  • 1
  • 7
  • 12