Sample app: Store invisible, permanent information when a button is pressed

Apps can store invisible information when a button is pressed, which is retained even when the app is closed. Our sample app uses this technique to enforce that a button is only pressed once per day.

We see a lot of apps built with Calcapp that simply collect information and send it back to the office. But what if the user should only be allowed to send the data once per day?

Prior to our last release, it wasn’t possible for an app to enforce such a rule. Now, though, you can use formula buttons, in conjunction with persistent fields, to make this a reality.

The technique described in this post is applicable to many other kinds of apps. We will demonstrate a technique enabling buttons to store information — any information — for subsequent use when the user comes back to the app, even after restarting their device.

You’re encouraged to view the app and its formulas as you read along. It’s available as a template when you create a new app under the Sample: Daily yield report name.

Usage

Without further ado, here’s the app:

The user is expected to fill out the yield once per day. When the button is pressed, the data is emailed back to the office and a message appears. If the user has never submitted the yield before, a simple message informs the user that the data has been submitted. Otherwise, the yield difference is also displayed:

The message that is displayed when the information is submitted successfully

If the user tries to submit the yield again the same day, this message appears:

The message that is displayed when an attempt is made to submit the data twice the same day

How it works

The key to the app is the use of a formula button, which runs formulas in response to being pressed. Refer to our blog post from August to learn more.

Action formulas have access to special functions. Here, we’ll use the EMAILREPORT function to email data back to the office.

Action formulas also have access to :=, which assigns values to properties. For instance, := can be used to change the values of fields.

The key to making this app work is that it has two hidden fields, which are also persistent. A persistent field stores its value to the user’s device, which is read back when the app is started again.

(Unfortunately, that means that the user is allowed to submit the yield on their cell phone and on their desktop computer, all on the same day.)

The field where the user enters the yield is named Yield. There’s also a hidden, persistent field named LastYield. A final hidden, persistent field is LastSubmissionDate.

The formula button is grayed-out if the user hasn’t filled in the Yield field. To achieve that, this formula is associated with the Enabled property of the button:

ISDEFINED(Yield)ISDEFINED(Yield)

Next, here’s part of the action formula associated with the OnPress property of the formula button:

IF(ISDEFINED(LastSubmissionDate) && (TODAY() <= LastSubmissionDate), ALERT("A yield has already been reported today, please
return tomorrow.", "Warning"), …)
IF(ISDEFINED(LastSubmissionDate) && (TODAY() <= LastSubmissionDate); ALERT("A yield has already been reported today, please
return tomorrow."; "Warning"); …)

Above, we’re only looking at a part of the formula. The elided part has been replaced with

The formula above checks the LastSubmissionDate field and its value. If it is defined (meaning that it holds a value) and it is equal to the return value of TODAY, or greater than it, a warning message is displayed using the ALERT function, warning the user that they have already pressed the button today.

The first time the app is started, LastSubmissionDate does not hold a value, meaning that ISDEFINED(LastSubmissionDate)ISDEFINED(LastSubmissionDate) returns FALSE and the message isn’t shown. (&& means “and”.)

Today’s date should be stored in the LastSubmissionDate field when the button is pressed. To do so, this formula fragment must be run by the button:

LastSubmissionDate := TODAY()LastSubmissionDate := TODAY()

The next time the button is pressed, ISDEFINED(LastSubmissionDate)ISDEFINED(LastSubmissionDate) returns TRUE and the next part of the formula, TODAY() <= LastSubmissionDateTODAY() <= LastSubmissionDate is evaluated. That part only returns TRUE if the value stored in LastSubmissionDate indicates that the button was last pressed on the same day (or in the future), which makes the button display the warning message.

We also need to store the yield when the button is pressed, so that we can show the change in the submission message:

LastYield := YieldLastYield := Yield

Here’s the full formula fragment that was elided with in the action formula above, which is run if the warning message isn’t displayed:

EMAILREPORT({ Yield }, "office@example.com"); ALERT("Submission successful." & IF(ISDEFINED(LastYield), NEWLINE() & "Yield
difference: " & FORMATNUMBER((Yield - LastYield) / Yield * 100, 0, 2) & "%"), "Success"); LastYield := Yield; LastSubmissionDate := TODAY()
EMAILREPORT({ Yield }; "office@example.com");; ALERT("Submission successful." & IF(ISDEFINED(LastYield); NEWLINE() & "Yield
difference: " & FORMATNUMBER((Yield - LastYield) / Yield * 100; 0; 2) & "%"); "Success");; LastYield := Yield;; LastSubmissionDate := TODAY()

EMAILREPORT({ Yield }, "office@example.com")EMAILREPORT({ Yield }; "office@example.com") sends email to the office, containing just the yield. (A real app should probably also collect the user’s identity and send that as part of the report.)

The ALERT part of the formula shows a message to the user, informing them that the submission was successful. It also displays the yield difference using this formula fragment:

FORMATNUMBER((Yield - LastYield) / Yield * 100, 0, 2) & "%"FORMATNUMBER((Yield - LastYield) / Yield * 100; 0; 2) & "%"

FORMATNUMBER above takes a number and formats it. In this case, it uses a minimum of zero decimal places, and a maximum of two decimal places.

& joins text strings together. Above, it adds a percentage sign to the end of the number displayed to the user.

Finally, the formula fragment assigns values to the hidden, persistent fields LastYield and LastSubmissionDate, which is the information that makes the app work.

Taking it all together, here’s the full action formula:

IF(ISDEFINED(LastSubmissionDate) && (TODAY() <= LastSubmissionDate), ALERT("A yield has already been reported today, please
return tomorrow.", "Warning"), EMAILREPORT({ Yield }, "office@example.com"); ALERT("Submission successful." & IF(ISDEFINED(LastYield), NEWLINE() & "Yield
difference: " & FORMATNUMBER((Yield - LastYield) / Yield * 100, 0, 2) & "%"), "Success"); LastYield := Yield; LastSubmissionDate := TODAY())
IF(ISDEFINED(LastSubmissionDate) && (TODAY() <= LastSubmissionDate); ALERT("A yield has already been reported today, please
return tomorrow."; "Warning"); EMAILREPORT({ Yield }; "office@example.com");; ALERT("Submission successful." & IF(ISDEFINED(LastYield); NEWLINE() & "Yield
difference: " & FORMATNUMBER((Yield - LastYield) / Yield * 100; 0; 2) & "%"); "Success");; LastYield := Yield;; LastSubmissionDate := TODAY())
« Feature: Streamlined experience for our Unlimited plan Tip: Calculate the elapsed time between two dates without DATEDIF »