An Overview of the App Inventor Sources -- Components

Apr 10, 2023 evan's Blog


In our first installment of An Overview of the App Inventor Sources, we will be looking at the Components modules.

App Inventor components are provided in two modules: components, which provides the Android implementation, and components-ios, which provides the iOS implementation. The components module contains two main packages. The main package, which we will be focusing on, is com.google.appinventor.components. The other package is com.google.zxing, which is bundled version of the Zebra Crossing library we use for the QR code scanner functionality.

In the main package, there are four subdivisions: annotations, common, runtime, and scripts.

Annotations

The annotations package defines the Java annotations used to describe the components to the rest of the system. The key annotations that one needs to be familiar with when working on App Inventor components are @SimpleObject, @DesignerComponent, @SimpleProperty, @DesignerProperty, @SimpleFunction, and @SimpleEvent.

Let’s briefly look at each of these annotations.

SimpleObject

The @SimpleObject annotation indicates to App Inventor that this object should be considered for analysis by the system. If you fail to include this annotation on your class, no processing will happen on it.

Historical note: App Inventor originally targeted a language called Simple. It eventually was moved to target YAIL (the Young Android Intermediate Language), but retains the Simple monicker in many cases.

DesignerComponent

Simply annotating a class with @SimpleObject is sufficient for it to be included in the system. If you want an object to appear in the App Inventor designer, it must also be annotated with @DesignerComponent.

SimpleProperty

The @SimpleProperty annotation is used to indicate that a method defines a property getter or setter interface. A getter interface should take no arguments and return non-void. A setter interface should take a single argument and return void. Currently, the App Inventor system does not allow for method overloading. That is, you cannot have void Property(TypeA object) and void Property(TypeB object) where TypeA != TypeB.

This annotation only applies to a single method and it will be interpreted based on the method signature. If you plan to make a property both readable and writeable, you should annotate both the setter and getter methods.

DesignerProperty

The @DesignerProperty annotation indicates to the system that the property in question should be presented in the designer properties panel. The two important fields to specify here are the editorType and defaultValue. The former specifies what editor is show in the properties panel and the latter specifies what the default should be. The default is always specified as a case-sensitive string, and in the case of booleans should be "True" or "False".

SimpleFunction

The @SimpleFunction annotation indicates to the system that a method on the component should be shown as a method block in the blocks editor. Methods that return void will appear as statement blocks (connectors at the top and bottom of the block) whereas non-void methods will appear as value blocks (plug on the leading edge of the block).

SimpleEvent

The @SimpleEvent annotation indicates to the system that a method on the component should be shown as an event block. All events should return void and call EventDispatcher.dispatchEvent with the component (typically, this), the event name exactly matching the method name, and the arguments in the same order as the method signature.

Additional Annotations

Beyond the annotations above, you may need to include some other items for a component. To do so, you can use the following annotations:

A complete rundown of all of the annotations in the system is available here.

Common

The common module defines a few key pieces of the system, mainly focused on categorization and versioning of components and their functions.

ComponentCategory

ComponentCategory enumerates the different categories that components in which components can appear. For extensions, only the EXTENSION category is allowed. There are also the special categories UNINITIALIZED and INTERNAL. The UNINITIALIZED category is the default. The INTERNAL category indicates that the component is internal to App Inventor and the indicated component will only be shown if the showInternalComponentsCategory feature is enabled.

PropertyTypeConstants

The PropertyTypeConstants class defines the different property editors available to the system. Use these constants to group properties in the properties panel based on their use.

YaVersion

The YaVersion class defines all of the version numbers for components and includes a historical record of the changes made to each component. When updating a component, the version number in YaVersion must be incremented and versioning code must be written for upgrading the component in YoungAndroidFormUpgrader (part of the appengine module) and versioning.js (in the blocklyeditor module).

Runtime

The runtime package in components provides the Android implementation of the App Inventor components (see also iOS Components). In addition to the component implementations, it defines the following supporting subpackages.

Collect

The collect package contains collections useful for working with native Java collections. Components that need to work with the blocks language list and dictionary types should make use of the YailList and YailDictionary classes in the utils package.

Errors

The errors package defines error types used by App Inventor. Of note is the DispatchableError type, which can be used to report errors that will be reported via the ErrorOccurred event on the screen.

Multidex

The multidex package is used for processing multidex apps. This functionality is only used on Android versions prior to 5.0.

Utils

The utils package contains utility classes. This package includes the types YailDictionary and YailList. It also includes a number of utility classes to handle different behaviors at various Android SDK levels.

View

The view package contains additional views that are important to the App Inventor components but not directly instantiable by users. This contains the zoom controls for the Map component, for example, because these views are needed if the ShowZoom option is enabled on the Map but the zoom controls are not present as a component level item.

Scripts

The scripts package defines the annotation processors used during the build process to take the definitions of the components in the runtime package and produce metadata used by the appengine and blocklyeditor modules to present the components and their functions to the users. Here’s a short description of each item:

iOS Components

The iOS versions of components exist in the directory components-ios. Components for iOS are primarily written in Swift.

Components in Swift are structured similarly to their Java counterparts wherever possible. However, there are a few key things to keep in mind when authoring components in Swift.

First, App Inventor for iOS relies on using Objective-C’s dynamic dispatch mechanism to communicate between the code generated by the App Inventor blocks editor and the component implementations.

// Bad: Missing @objc will prevent this component from being usable
open class CustomComponent: NonvisibleComponent {
  // implementation
}

// Good: @objc ensures the class is available in the Objective-C runtime
@objc open class CustomComponent: NonvisibleComponent {
  // implementation
}

Second, any class that is intended to be used as a component must be annotated with the @objc attribute. Any variables or functions representing block implementations must also be annotated as @objc and arguments, if any, must be unnamed.

// Bad: This function won't be visible in Objective-C, so App Inventor can't call it
open func DoSomething() {
}

// Bad: This translates into DoSomethingWithFoo:bar:, which App Inventor won't recognize
@objc open func DoSomething(foo: String, bar: Int32) {
}

// Good: This translates into DoSomething::, which App Inventor can recognize
@objc open func DoSomething(_ foo: String, _ bar: Int32) {
}

Third, avoid retain cycles. Swift (and Objective-C) use automated reference counting to manage memory. Therefore, when translating any Java code to Swift you must take particular care to not create reference cycles that would prevent unused objects from being collected. A common error would be to hold a strong reference to the Form from another component. Since Form holds strong references to all of its descendants, having a descendant strongly reference Form creates a cycle that will never be garbage collected. This can eventually cause the app to crash due to running out of memory if many such cycles (or memory intensive cycles) are created.


In the next installment, we will discuss the Runtime module that defines App Inventor’s target language, YAIL, the Young Android Intermediate Language.

Go back to the introduction