Local Variable not refreshing inside forEach

Certified Associate Developer

Ok so, inside a forEach statement I define a local variable like so:

a!localVariables(
  local!routingChoices: a!refreshVariable(
    value: if(
      a!isNotNullOrEmpty(ri!inputData[local!currentIndex].routingAction[fv!index]),
      a!match(
        value: ri!inputData[local!currentIndex].routingAction[fv!index],
        equals: 6,
        then: {1,2},
        equals: 7,
        then: {1,3},
        equals: 8,
        then: {2,4},
        equals: 9,
        then: {2,3},
        equals: 10,
        then: {3,4},
        equals: 11,
        then: {1,2,3},
        equals: 12,
        then: {2,3,4},
        default: fv!value
      ),
      null()
    ),
    refreshOnReferencedVarChange: false(),
    refreshOnVarChange: local!sealCount
  ),

local!sealCount stores an integer and the forEach is set up to run "enumerate(local!sealCount)" times (so if local!sealCount = 2 the forEach runs twice).

Then, farther down in the interface, I have a dynamic link that acts as a "delete" button (it subtracts 1 from local!sealCount and deletes the data in ri!inputData that corresponds to the index being deleted).

I also have an "add" button that is also created with a dynamic link that adds 1 to local!sealCount and appends a null() value to the fields in ri!inputData.

The problem I'm running into is that, when I add a component with the add button, put some data into local!routingChoices (via a card selection component), delete that component with the delete button, and then add a component again, the data that I put into local!routingChoices is still there. However, I expected it to be null() because ri!inputData[local!currentIndex].routingAction[fv!index] = null() and local!sealCount changed so the local variable should re-evaluate.

Here's a simplified chain of events:

   1) Click "add" button (sealCount = 2 now)

    2) Save data to routingChoices and inputData

    3) Click "delete" button for that component (sealCount = 1 now)

    4) Click "add" button again (sealCount = 2 now) <-- routingChoices expected to be null() but instead retains values from step 2

Can anyone help me understand what is going on here and how to fix it so that, when the "add" button is pressed routingChoices will be null()?

(Also, if you need to ask for clarification on something definitely feel free)

Thank you!

  Discussion posts and replies are publicly visible

  • 0
    Certified Lead Developer

    are you able to provide full sale code for that interface which help to understand all your local variable values. 

  • 0
    Certified Associate Developer
    in reply to Naresh

    Naresh,

    That would not be practical as the interface has about 1100 lines of code. However, I can explain all the local variables in the code block that I did not explain in the question:

    local!currentIndex: the forEach that I mentioned in the question is actually inside another forEach, and currentIndex saves the value of fv!index for the outer forEach so I can reference it in the inner forEach. In this situation its value is 1.

    local!routingChoices: stores an array of integers that are selected in a card choice component. It could be {1,2}, {3,4}, {3}, {1,3,4}, ect. In the saveInto of the card choice component I convert each possible combination into a single number that represents it and store it in ri!inputData[local!currentIndex].routingAction[fv!index]. In the match statement shown in my code, I am unconverting it from the rule input back to an array to store in the local variable and display in the card choice component.

    Hope this helps!

  • 0
    Certified Lead Developer

    Without seeing local!sealCount code, it is difficult to suggest anything. If you share the code snippet Instead of writing a paragraph, it would be easy for other people to find the issue is and suggest any possible fix.

  • 0
    Certified Associate Developer
    in reply to Abhay Dalsaniya

    Abhay,

    I have attached all of the code snippets where local!sealCount is defined, used, or saved into. However, I don't think that local!sealCount is the problem as everything else in the forEach that uses that local variable works as expected, but maybe you'll be able to see something that I can't.

    a!forEach( /*This is the outer for each statement*/
        enumerate(local!disCount),
        a!localVariables(
          local!sealCount,
          local!currentIndex: fv!index,
          ...
    <-- Outer forEach statement

    a!forEach( /*This is the inner forEach that is inside the previous forEach*/
      enumerate(local!sealCount),
      ...
      <-- Inside the outer forEach statement

    a!dynamicLink(
        value: local!sealCount - 1,
        saveInto: {
          a!save(
            ri!inputData[local!currentIndex].controlNum,
            remove(ri!inputData[local!currentIndex].controlNum, fv!index)
          ),
          if(
            a!isNotNullOrEmpty(ri!inputData[local!currentIndex].sealDesc),
            a!save(
              ri!inputData[local!currentIndex].sealDesc,
              remove(ri!inputData[local!currentIndex].sealDesc, fv!index)
            ),
            null()
          ),
          if(
            a!isNotNullOrEmpty(ri!inputData[local!currentIndex].routingAction),
            a!save(
              ri!inputData[local!currentIndex].routingAction,
              remove(ri!inputData[local!currentIndex].routingAction, fv!index)
            ),
            null()
          ),
          if(
            a!isNotNullOrEmpty(ri!inputData[local!currentIndex].comments),
            a!save(
              ri!inputData[local!currentIndex].comments,
              remove(ri!inputData[local!currentIndex].comments, fv!index)
            ),
            null()
          ),
          if(
            a!isNotNullOrEmpty(ri!inputData[local!currentIndex].trackingTagNum),
            a!save(
              ri!inputData[local!currentIndex].trackingTagNum,
              remove(ri!inputData[local!currentIndex].trackingTagNum, fv!index)
            ),
            null()
          ),
          if(
            a!isNotNullOrEmpty(ri!trackingData),
            {
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].tagNum),
                a!save(
                  ri!trackingData[local!currentIndex].tagNum,
                  remove(ri!trackingData[local!currentIndex].tagNum, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].partNum),
                a!save(
                  ri!trackingData[local!currentIndex].partNum,
                  remove(ri!trackingData[local!currentIndex].partNum, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].equipNum),
                a!save(
                  ri!trackingData[local!currentIndex].equipNum,
                  remove(ri!trackingData[local!currentIndex].equipNum, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].storeNum),
                a!save(
                  ri!trackingData[local!currentIndex].storeNum,
                  remove(ri!trackingData[local!currentIndex].storeNum, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].workNum),
                a!save(
                  ri!trackingData[local!currentIndex].workNum,
                  remove(ri!trackingData[local!currentIndex].workNum, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].installDate),
                a!save(
                  ri!trackingData[local!currentIndex].installDate,
                  remove(ri!trackingData[local!currentIndex].installDate, fv!index)
                ),
                null()
              ),
              if(
                a!isNotNullOrEmpty(ri!trackingData[local!currentIndex].removeDate),
                a!save(
                  ri!trackingData[local!currentIndex].removeDate,
                  remove(ri!trackingData[local!currentIndex].removeDate, fv!index)
                ),
                null()
              )
            },
            null()
          ),
          local!sealCount,
          a!save(
            ri!inputData[local!currentIndex].controlNum,
            if(
              local!currentIndex = 1,
              {
                a!forEach(
                  enumerate(local!sealCount),
                  local!lastControlNum + fv!index
                )
              },
              {
                a!localVariables(
                  local!lastItControlNum: ri!inputData[local!currentIndex-1].controlNum[length(ri!inputData[local!currentIndex-1].controlNum)],
                  a!forEach(
                    enumerate(local!sealCount),
                    local!lastItControlNum + fv!index
                  )
                )
              }
            )
          ),
    <-- in the inner forEach statement

    a!dynamicLink(
        value: if(
          a!isNullOrEmpty(local!sealCount),
          1,
          local!sealCount + 1
        ),
        saveInto: {
          local!sealCount,
          a!save(
            ri!inputData[fv!index].controlNum,
            if(
              fv!isFirst,
              {
                a!forEach(
                  enumerate(local!sealCount),
                  local!lastControlNum + fv!index
                )
              },
              {
                a!localVariables(
                  local!lastItControlNum: ri!inputData[fv!index-1].controlNum[length(ri!inputData[fv!index-1].controlNum)],
                  a!forEach(
                    enumerate(local!sealCount),
                    local!lastItControlNum + fv!index
                  )
                )
              }
            )
          ),
          a!save(
            ri!inputData[local!currentIndex].sealDesc,
            append(ri!inputData[local!currentIndex].sealDesc, null())
          ),
          a!save(
            ri!inputData[local!currentIndex].routingAction,
            append(ri!inputData[local!currentIndex].routingAction, null())
          ),
          a!save(
            ri!inputData[local!currentIndex].comments,
            append(ri!inputData[local!currentIndex].comments, null())
          ),
          a!save(
            ri!inputData[local!currentIndex].trackingTagNum,
            append(ri!inputData[local!currentIndex].trackingTagNum, null())
          )
        }
      ),
    <-- in the outer forEach statement below the inner forEach

  • 0
    Certified Lead Developer
    in reply to Jack Ferguson

    Appian expressions is a functional language with immutable variables. Inside interfaces, variables can only change on user interaction.

    Code like the below snippet will not work. A foreach return an output item for each input item. It cannot change the value of any existing variable.

    a!localVariables(
      local!lastItControlNum: ri!inputData[fv!index-1].controlNum[length(ri!inputData[fv!index-1].controlNum)],
      a!forEach(
        enumerate(local!sealCount),
          local!lastItControlNum + fv!index
        )
      )
    )

    And I would not want to be the poor soul who has to maintain that code in the long run. Did you think about components?

  • 0
    Certified Associate Developer
    in reply to Stefan Helzle

    Stefan,

    That a!save statement does give me the values that I expect from it. For instance, if local!lastControlNum = 8600, and local!sealCount = 3, ri!inputData[fv!index].controlNum will equal {8601,8602,8603}.

    From what I've observed from the behavior of this interface, it seems like Appian creates a new instance of the local variable for each iteration of the forEach loop. So in my original question, the forEach loop is creating local!routingAction(1) with the values entered in loop 1 and local!routingAction(2) with the values entered in loop 2. But when the second loop is removed local!routingAction(2) still exists, so when loop 2 is re-added it displays the values from local!routingAction(2). This probably isn't what Appian is actually doing, but that's how it appears to be behaving.

    And yes, I definitely agree with the maintenance problem, this has quickly become more complicated than I expected. I am going to try to break things up into separate rules/interfaces, as I think this may solve the issue that I'm having (in addition to increasing maintainability).

  • 0
    Certified Lead Developer
    in reply to Jack Ferguson

    Check out my blog for how data in interfaces work.

    appian.rocks/.../