App Component Reuse Solution

1. Summary

  • We would like to take a subset (Data, UX and Behavior) of an App and be able to reuse this subset into another existing App quickly and reliably. (See example in figures 1 and 2).
  • We propose a solution which is much faster and safer than replicating each single change by hand. (See Figures 3 and 4).
  • The solution is intentionally simple but it is powerful, flexible and fairly easy to implement. There is no hidden magic, the App Author is in control of every step.

 

3X_3_2_32bae2f698527c51320a0deca6f56a54e7bf726a.png

 


 

3X_3_e_3e72ff3520d4c00f15b0ffb240e88f179973fcc1.png

 


3X_c_4_c42da0e38a70852bcd90b854fbd4b501d866860e.png


2. The Problem (Why we need this)

2.1 Description of the problem

  • It is not possible to take a subset of the Data, UX and Behavior of an App and reuse this subset in another existing App without redoing all changes manually in the App Editor.

2.2 Examples of the problem

2.2.1 Replicating a complex feature in another App is a lot of work

  • We have two existing Apps, App A and App B. We need to add in App B a complex feature that we already have in App A. Since this feature consists of a set of interrelated tables, columns, views and actions, the only way at present is to open App B with the App Editor and to manually enter all these objects and their properties by looking in detail at what we did in App A. This is time consuming and error prone. (See an example in Figures 1 and 2)

2.2.2 One general App VS Many specific Apps dilemma

  • When we use AppSheet to build Apps, we eventually end up with a set of Apps for a given Line of Business. Often there are some common features needed by the Apps. Example of such common features are : People and Organizations, Access Control, Projects and Tasks, Approval Workflows, Notifications, etc. Once we have built such a feature in an App and need to add it to other Apps we are faced with a dilemma. One choice is to merge all the specific Apps into a single general App that has all the common features; this causes negative issues : performance degradation because of the large number of tables and also difficulty to maintain a big App. Another choice is to recreate the common feature into each of the specific Apps; this is tedious and error-prone, especially that we will have to redo it in all apps every time we make a change of logic to a common feature.

2.2.3 App Templates do not help in the long run

  • App Templates that include common features may help save time when you first create an App, but when business rules require changes in a common feature, you still have to manually make the same changes in all the Apps that were derived from the template.

2.2.4 Two authors cannot work in parallel on an App and merge their work

  • It is often beneficial for an organization when two people work on an App because it fosters sharing ideas for business process improvement and ensures that more than one person understand the internals of an App. Ideally each author would work on his own copy of the App to build a feature, then the co-author can review it before the feature is merged into the main version. At present, an author can Upgrade an App based on another App copy, but the Upgrade takes the complete App. Therefore the other author has to wait his turn to make his own copy and start to work on his feature.

3. The Goal (What we need)

  • We define a “Component” in an AppSheet App as a set of Objects that work together to provide a feature.
  • The Object types that can be put in a Component are : a Table’s Set of Columns, an Action, a Slice, a View, a Format Rule, a Workflow Rule, a Report Rule.
  • To determine the contents of a Component, we want to be able to Tag Objects as soon as we add or modify them in the App Editor.
  • We would like to take a Component of an App and be able to add or update this Component into another existing App quickly and reliably, without having to enter the properties of the Objects.

4. The Solution (THIS SECTION WAS EDITED ON 2023-02-07)

When I posted this idea in January 2021, I proposed a solution that could be implemented in the App Editor, so that it could be available within a few months of the idea being approved. However after many attempts to promote this idea, it was assigned the "Not Planned" status in November 2022. I think that my detailed specification of the solution with examples did more harm than good, so I deleted this section.

5. References

This June 2020 topic presents many Reusability Ideas as well as a list of related topics in the Community

The following Feature Request is a long overdue outline I have of having Reusable Items in Appsheet. It is part of a response in part @tony request: This is just the initial outline. I will elaborate further in the future as and when I work on my Apps where it will trigger further thoughts. 1. What would be Reused and then Shared? The basic concept of reuse could be based on table which is reused (for example the Person Table with all the details of a Person like Name, Email, Phone) where …

6. Final thoughts

  • This is a long post which has too much detail for a Feature Request, but I feel a Modularity / Reusability solution is long overdue. I really care about AppSheet.
  • I welcome comments from the AppSheet Community about this Proposed Feature. In particular, I have probably forgotten some details and many heads are better than one.
Status Not Planned
52 35 2,560
35 Comments
Grant_Stead
Silver 5
Silver 5

When you add a “real” data source at the account level, like a database. We were talking about going one level deeper into the actual tables themselves so that at the account level you could define the column structure. Then when you add that table in your individual apps it would initialize the definition. (Further we discussed locking it to the global definition.)

aucforum
Bronze 3
Bronze 3

Sounds like some progress.

Grant_Stead
Silver 5
Silver 5

Talking isn’t real progress.

Steven_Aung
Silver 2
Silver 2

Related feature

aucforum
Bronze 3
Bronze 3

Grant, are they still talking or has there been some progress made?

Grant_Stead
Silver 5
Silver 5

Talk, as far as I can tell…

lamontfr
Silver 1
Silver 1

At least the votes are slowly increasing !

Ed_Cottrell
Bronze 5
Bronze 5

This is an epic post and fantastic work - obviously!

My only addition is a vote, but also to implore the AppSheet team to aim for small improvements in this direction at a time (if they are indeed listening to this topic)…

The idea of re-using an app sub-set of tables, actions etc, is amazing; and described simply and clearly at the pseudo-level. I’m certain however that it is an immensely challenging task at the ‘under-the-hood / code’ level.

The related request to be simply able to copy a table definition from one app to another where by the table source is the identical database table (or sheet!) is an example small step in this direction; would generate huge efficiencies in building apps and not having to re-build the same thing over and over again (albeit such requirement does provide an opportunity to build it better each time!!).

We are currently building a multi-tenant, multi-app platform, and while AppSheet is great for prototyping changes quickly, the repeat work is subtracting in a big way!

Go well everyone, and keep safe.

lamontfr
Silver 1
Silver 1

@Ed_Cottrell Thanks for your excellent comments.

In terms of implementation complexity, the solution I propose requires only changes to the App Editor, mainly for the Tag, the Export and the Import functionalities, but no changes to the interpreter runtime.

bonameblisto
Silver 3
Silver 3

Perfetto!
“I am not interested in your questions, I need suggestions for solving the problem”

Good work!
Many thanks

aucforum
Bronze 3
Bronze 3

Has there been any update on this?

lamontfr
Silver 1
Silver 1

@aucforum

All I can say is that we got 33 votes in 9 months. There is hope !

lamontfr
Silver 1
Silver 1

My original post is just over one year old and has gathered enough votes to show that the proposal serves a real need. I think it will be useful to give an example of the contents of a Component stored as a json file.

Here is a json file (containing a small subset of Objects to avoid excessive length), that shows how we can fully describe the Objects in a App Component :

 

{
  "All Columns": [
    {
      "For this table": "member",
      "Table - Descriptive comment": "#Component_RBAC",
      "Columns": [
        {
          "Column name": "member_code",
          "Show?": "ON",
          "Type": "Text",
          "Type Details": {
            "Maximum length": "6",
            "Minimum length": "6"
          },
          "Data Validity": {
            "Valid if": [
              "COUNT(",
              "  SELECT(",
              "    member[member_id],",
              "    AND(",
              "      [member_id] <> [_THISROW].[member_id],",
              "      [member_code] = [_THISROW].[member_code] )))",
              "= 0"
            ],
            "Invalid value error": "The member code must be unique",
            "Require?": "ON"
          },
          "Auto Compute": {
            "App formula": "",
            "Initial value": "",
            "Suggested values": "",
            "Spreadsheet formula": ""
          },
          "Update Behavior": {
            "Key": "OFF",
            "Editable": "ON"
          },
          "Display": {
            "Label": "OFF",
            "Display name": "Member Code",
            "Description": ""
          },
          "Other Properties": {
            "Searchable": "ON",
            "Scannable": "OFF",
            "NFC Scannable": "OFF",
            "Sensitive data": "OFF"
          }
        }
      ]
    },
    {
      "For this table": "memberrole",
      "Table - Descriptive comment": "#Component_RBAC",
      "Columns": [
        {
          "Column name": "memberrole_member_id",
          "Show?": "ON",
          "Type": "Ref",
          "Type Details": {
            "Source table": "member",
            "Is a part of?": "OFF",
            "External relationship name": "",
            "Input mode": "Auto"
          },
          "Data Validity": {
            "Valid if": "",
            "Invalid value error": "",
            "Require?": "ON"
          },
          "Auto Compute": {
            "App formula": "",
            "Initial value": "",
            "Suggested values": "",
            "Spreadsheet formula": ""
          },
          "Update Behavior": {
            "Key": "OFF",
            "Editable": [
              "CONTEXT(\"ViewType\") <> \"Form\""
            ],
            "Reset on edit?": "OFF"
          },
          "Display": {
            "Label": "OFF",
            "Display name": "",
            "Description": ""
          },
          "Other Properties": {
            "Searchable": "OFF",
            "Scannable": "OFF",
            "NFC Scannable": "OFF",
            "Sensitive data": "OFF"
          }
        }
      ]
    }
  ],
  "All Slices": [
    {
      "For this source table": "memberrole",
      "Slices": [
        {
          "Slice name": "s_memberrole",
          "Row filter condition": "",
          "Slice Columns": [
            "_RowNumber",
            "memberrole_id",
            "memberrole_member_id",
            "memberrole_role_id",
            "memberrole_v_header"
          ],
          "Slice Actions": [
            "a_memberrole_delete",
            "a_memberrole_viewref_member",
            "a_memberole_export_csv"
          ],
          "Update mode": [
            "Updates",
            "Adds",
            "Deletes"
          ],
          "Descriptive comment": "#Component_RBAC"
        }
      ]
    }
  ],
  "All Views": [
    {
      "For this data": "s_memberrole (slice)",
      "Views": [
        {
          "View name": "v_memberrole_inline",
          "View type": "table",
          "Position": "ref",
          "View Options": {
            "Sort by": [
              {
                "Ascending": "memberrole_type"
              },
              {
                "Ascending": "memberrole_name"
              }
            ],
            "Group by": [],
            "Group aggregate": "NONE",
            "Column order": [
              "memberrole_type",
              "memberrole_name",
              "memberrole_date_given"
            ],
            "Column width": "Narrow",
            "Enable QuickEdit (beta)": "OFF"
          },
          "Display": {
            "Icon": "list-ul",
            "Display name": "Member has these roles",
            "Show If": ""
          },
          "Behavior": {
            "Event Actions": "**auto**"
          },
          "Documentation": {
            "Descriptive comment": "#Component_RBAC"
          }
        }
      ]
    }
  ],
  "All Format Rules": [
    {
      "For this data": "member",
      "Format Rules": [
        {
          "Rule name": "Member_is_inactive",
          "If this condition is true": [
            "[member_state] <> \"active\""
          ],
          "Format these columns and actions": [
            "member_code",
            "member_name",
            "member_date_created"
          ],
          "Visual Format": {
            "Icon": "",
            "Highlight color": "",
            "Text color": "red"
          },
          "Text Format": {
            "Text size": "1",
            "Underline": "OFF",
            "Bold": "ON",
            "Italic": "OFF",
            "Uppercase": "OFF",
            "Strikethrough": "OFF"
          },
          "Workflow template format": {
            "Image format": ""
          },
          "Documentation": {
            "Descriptive comment": "#Component_RBAC"
          }
        }
      ]
    }
  ],
  "All Actions": [
    {
      "For a record of this table": "member",
      "Actions": [
        {
          "Action name": "a_member_grp",
          "Do this": "Grouped: execute a sequence of actions",
          "Actions": [
            "a_member_role_all",
            "a_member_memberrole_all",
            "a_member_exec_rights"
          ],
          "Appearance": {
            "Display name": "Update rights",
            "Action icon": "chart-network",
            "Prominence": "Display prominently"
          },
          "Behavior": {
            "Only if this condition is true": [
              "AND(",
              "  CONTEXT(\"ViewType\") = \"Detail\"",
              "  CONTAINS(",
              "    ANY(",
              "      SELECT(",
              "        member[member_rights],",
              "        [member_email] = USEREMAIL() )),",
              "    \"has_role_update_all\" ))"
            ],
            "Needs confirmation": "OFF",
            "Confirmation message": ""
          },
          "Documentation": {
            "Descriptive comment": "#Component_RBAC"
          }
        },
        {
          "Action name": "a_member_memberrole_add",
          "Do this": "App: go to another view within this app",
          "Target": [
            "LINKTOFORM(",
            "  \"v_memberrole_form\",",
            "  \"memberrole_member_id\", [member_id] )"
          ],
          "Appearance": {
            "Display name": "Give role",
            "Action icon": "compress-alt",
            "Prominence": "Display prominently"
          },
          "Behavior": {
            "Only if this condition is true": [
              "AND(",
              "  CONTEXT(\"ViewType\") = \"Detail\"",
              "  CONTAINS(",
              "    ANY(",
              "      SELECT(",
              "        member[member_rights],",
              "        [member_email] = USEREMAIL() )),",
              "    \"has_role_add\" ))"
            ],
            "Needs confirmation": "OFF",
            "Confirmation message": ""
          },
          "Documentation": {
            "Descriptive comment": "#Component_RBAC"
          }
        }
      ]
    }
  ]
}

 

Notes about the json file :

  1. The hashtag (here #Component_RBAC) is present in the "Descriptive comment" property of the Objects contained in the Component. It is present in each Table, Slice, View, Format Rule and Action. This tag is used by the EXPORT mechanism to select the Objects in the Component.
  2. As mentioned in section 4.2 of the original post, there is no Table object in the Component, since the tables are always created by adding them "manually" in the App Editor. The Component has a Columns Object which will override the default column properties that have been assigned when the table was added in the Editor.
  3. A json Component file should not be kept for the long term as the "Master" of a Component, because the evolution of the AppSheet platform may change the properties of some Objects. The "Master" must be kept in the "template" App itself and the json Component file is EXPORTed just in time when one wants to IMPORT it in another App.
  4. A json Component file is human readable and can be used as part of the technical documentation of an App.
  5. Text editors can show a json file as an outline and allow tree folding. Also the Firefox Browser natively supports viewing json files when their path is entered in the address bar. Here is an screenshot of the Firefox json outliner :

Screenshot from 2022-01-30 16-34-05.png

Status changed to: Open
Pratyusha
Community Manager
Community Manager
 
Tom_Stevens
Silver 2
Silver 2

Dear AppSheet,

Pretty please, with sugar on top, make this a reality... Please

Thank you,