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
.
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.
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.
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
.
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.
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"
.
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).
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.
Beyond the annotations above, you may need to include some other items for a component. To do so, you can use the following annotations:
@UsesActivities
- Include additional activities in the app manifest when building an app containing the component@UsesAssets
- Include additional assets in the app package when building an app using the component@UsesLibraries
- Include additional libraries when building an app with the component@UsesPermissions
- Include additional permissions in the app manifest when building an app using the component@UsesServices
- Include additional services in the app manifest when building an app containing the componentA complete rundown of all of the annotations in the system is available here.
The common
module defines a few key pieces of the system, mainly focused on categorization and versioning of components and their functions.
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.
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.
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).
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.
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.
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.
The multidex
package is used for processing multidex apps. This functionality is only used on Android versions prior to 5.0.
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.
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.
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:
@SimpleObject(external = true)
.docs/markdown/
.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.