diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..87c9c88
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,18 @@
+{
+ "git.detectSubmodules": true,
+ "java.project.referencedLibraries": [
+ "lib/**/*.jar"
+ ],
+ "java.compile.nullAnalysis.mode": "automatic",
+ "[java]": {
+ "editor.tabSize": 2,
+ "files.trimTrailingWhitespace": true,
+ "editor.trimAutoWhitespace": true
+ },
+ "[xml]": {
+ "editor.tabSize": 2
+ },
+ "indentRainbow.excludedLanguages": [
+ "java"
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index c4a3fc4..cce290f 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,10 @@
-![Icon](assets/icon.png)
+# Dynamic Components for AI2
-# DynamicComponents-AI2 `Extension`
+An extension for [MIT App Inventor 2](https://appinventor.mit.edu/) applications that allows to create components dynamically by its name at runtime with blocks.
-[![forthebadge](https://forthebadge.com/images/badges/its-not-a-lie-if-you-believe-it.svg)](https://forthebadge.com)
+It is based on Java's reflection feature, so this allows us to create instances of classes (components) by its name. Also, unlike other extensions that create components in runtime, this extension doesn't keep a list of all component names because it supports every component which is ever added to your App Inventor distribution by nature. So, not only can you dynamically create common components like `Button`, but you can also create `DatePicker` components.
-[![Maintainability](https://api.codeclimate.com/v1/badges/31e4cd31de1bd0e186c8/maintainability)](https://codeclimate.com/github/ysfchn/DynamicComponents-AI2/maintainability)
-
-Fully supported Dynamic Components extension for MIT App Inventor 2. It is based on Java's reflection feature, so it creates the components by searching for a class by just typing its name. So it doesn't have a limited support for specific components, because it supports every component which is ever added to your App Inventor distribution!
-
-So if you use Kodular, you will able to create all Kodular components, if you use App Inventor, you will able to create all App Inventor components and so on. Extension components are supported too!
-
-> ⚠ The `beta` branch will be reset after every release. So stay on the `main` branch if you don't know what you do.
-
----
-
-### Asynchronous support
-
-This extension can create components asynchronously or synchronously based on your choice. If you don't want to block the main app during creating a bunch of components, go to the Designer (after importing the extension) and select between "UI" (asynchronous) and "Main" (synchronous).
-
-
+So if you use Kodular, you will able to create all Kodular components, if you use App Inventor, you will able to create all App Inventor components and so on. Creating instances of other extensions are also supported.
## 🧩 Blocks
@@ -49,7 +35,7 @@ This extension can create components asynchronously or synchronously based on yo
-->
- Creates a new dynamic component. It supports all component that added to your current AI2 distribution.
+ Creates a new dynamic component. It supports all component that added to your current AI2 distribution. Note that you can't create components in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that.
componentName parameter can have these values:
@@ -68,6 +54,15 @@ This extension can create components asynchronously or synchronously based on yo
+
+
+
+
+
+
+ Creates a new dynamic component in given container (arrangement/canvas) and return it without saving it to the created components list, so it won't be attached to an ID. Note that you can't create components in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that.
+
+
@@ -131,7 +126,16 @@ This extension can create components asynchronously or synchronously based on yo
-->
- Removes the component with specified ID from screen/layout and the component list. So you will able to use its ID again as it will be deleted.
+ Removes the component with specified ID from screen and the component list. So you will able to use its ID again as it will be deleted.
+
+
+
+
+
+
+
+
+ Removes a component from the screen. It doesn't need to be created by this extension. But if the given component is dynamically created by this extension, this block will also de-register its ID so its ID can be reused for other components that are going to be created later.
@@ -431,6 +435,12 @@ This extension can create components asynchronously or synchronously based on yo
+### Asynchronous support
+
+This extension can create components asynchronously or synchronously based on your choice. If you don't want to block the main app during creating a bunch of components, go to the Designer (after importing the extension) and select between "UI" (asynchronous) and "Main" (synchronous).
+
+
+
## 🔨 Building
You will need:
@@ -438,7 +448,15 @@ You will need:
- Java 1.8 (either OpenJDK or Oracle)
- Ant 1.10 or higher
-Then execute `ant extensions` in the root of the repository.
+After cloning the repository, make sure to fetch submodules first:
+
+```
+git submodule update --init --recursive
+```
+
+Then execute `ant extensions` in the root of the repository to build the extension.
+
+> ⚠ The `beta` branch will be reset after every release. So stay on the `main` branch if you don't know what you do.
## 🏅 License
diff --git a/assets/blocks/method_createephemeral.png b/assets/blocks/method_createephemeral.png
new file mode 100644
index 0000000..d5e136c
Binary files /dev/null and b/assets/blocks/method_createephemeral.png differ
diff --git a/assets/blocks/method_removecomponent.png b/assets/blocks/method_removecomponent.png
new file mode 100644
index 0000000..e154e7f
Binary files /dev/null and b/assets/blocks/method_removecomponent.png differ
diff --git a/assets/icon.png b/assets/icon.png
deleted file mode 100644
index 1c688f6..0000000
Binary files a/assets/icon.png and /dev/null differ
diff --git a/lib/appinventor b/lib/appinventor
index 477586b..6380b04 160000
--- a/lib/appinventor
+++ b/lib/appinventor
@@ -1 +1 @@
-Subproject commit 477586b7e1901db30ed6b8e76cf524c4a05b2802
+Subproject commit 6380b04f69fd0ead8b8b5ca8a06287a020c6c967
diff --git a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java
index 17b437e..7e85043 100644
--- a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java
+++ b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java
@@ -1,8 +1,15 @@
package com.yusufcihan.DynamicComponents;
+import com.yusufcihan.DynamicComponents.classes.Utils;
+import com.yusufcihan.DynamicComponents.classes.Metadata;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.view.ViewGroup;
+
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
-import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
@@ -14,158 +21,75 @@
import com.google.appinventor.components.runtime.Component;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
-import com.google.appinventor.components.runtime.Form;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import com.google.appinventor.components.runtime.util.YailDictionary;
import com.google.appinventor.components.runtime.util.YailList;
import org.json.JSONArray;
+import org.json.JSONException;
import org.json.JSONObject;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.View;
-import android.view.ViewGroup;
-import gnu.lists.FString;
-import gnu.math.DFloNum;
-import gnu.math.IntNum;
-
-import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
@DesignerComponent(
- description = "Dynamic Components is an extension that creates any component in your App Inventor distribution programmatically, instead of having pre-defined components. Made with ❤️ by Yusuf Cihan.",
- category = ComponentCategory.EXTENSION,
- helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2/blob/main/README.md",
- iconName = "aiwebres/icon.png",
- nonVisible = true,
- version = 9,
- versionName = "2.2.2"
+ description =
+ "Create any component available in your App Inventor distribution and create instances of " +
+ "other extensions programmatically in runtime. Made with ❤️ by Yusuf Cihan.",
+ category = ComponentCategory.EXTENSION,
+ helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2/blob/main/README.md",
+ iconName = "aiwebres/icon.png",
+ nonVisible = true,
+ version = 10,
+ versionName = "2.3.0"
)
@SimpleObject(external = true)
public class DynamicComponents extends AndroidNonvisibleComponent {
- // Base package name for components
- private final String BASE = "com.google.appinventor.components.runtime.";
+ private static final String TAG = Utils.TAG;
// Whether component creation should happen on the UI thread
private boolean postOnUiThread = false;
// Components created with Dynamic Components
- private final HashMap COMPONENTS = new HashMap();
+ private final HashMap COMPONENTS = new HashMap<>();
// IDs of components created with Dynamic Components
- private final HashMap COMPONENT_IDS = new HashMap();
+ private final HashMap COMPONENT_IDS = new HashMap<>();
private Object lastUsedId = "";
- private ArrayList componentListeners = new ArrayList();
- private JSONArray propertiesArray = new JSONArray();
- private final Util UTIL_INSTANCE = new Util();
+ private final ArrayList componentListeners = new ArrayList<>();
public DynamicComponents(ComponentContainer container) {
super(container.$form());
}
interface ComponentListener {
- public void onCreation(Component component, String id);
+ void onCreation(Component component, String id);
}
- class Util {
- public boolean exists(Component component) {
- return COMPONENTS.containsValue(component);
- }
-
- public boolean exists(String id) {
- return COMPONENTS.containsKey(id);
- }
-
- public String getClassName(Object componentName) {
- String regex = "[^.$@a-zA-Z0-9]";
- String componentNameString = componentName.toString().replaceAll(regex, "");
-
- if (componentName instanceof String && componentNameString.contains(".")) {
- return componentNameString;
- } else if (componentName instanceof String) {
- return BASE + componentNameString;
- } else if (componentName instanceof Component) {
- return componentName.getClass().getName().replaceAll(regex, "");
- } else {
- throw new YailRuntimeError("Component is invalid.", "DynamicComponents");
- }
- }
-
- public Method getMethod(Method[] methods, String name, int parameterCount) {
- name = name.replaceAll("[^a-zA-Z0-9]", "");
- for (Method method : methods) {
- int methodParameterCount = method.getParameterTypes().length;
- if (method.getName().equals(name) && methodParameterCount == parameterCount) {
- return method;
- }
- }
-
- return null;
- }
-
- public void newInstance(Constructor> constructor, String id, AndroidViewComponent input) {
- Component mComponent = null;
-
- try {
- mComponent = (Component) constructor.newInstance((ComponentContainer) input);
- } catch(Exception e) {
- throw new YailRuntimeError(e.getMessage(), "DynamicComponents");
- } finally {
- if (!isEmptyOrNull(mComponent)) {
- String mComponentClassName = mComponent.getClass().getSimpleName();
- if (mComponentClassName == "ImageSprite" || mComponentClassName == "Sprite") {
- Invoke(mComponent, "Initialize", new YailList());
- }
-
- COMPONENT_IDS.put(mComponent, id);
- COMPONENTS.put(id, mComponent);
- this.notifyListenersOfCreation(mComponent, id);
- ComponentBuilt(mComponent, id, mComponentClassName);
- }
- }
- }
-
- public void parse(String id, JSONObject json) {
- JSONObject data = new JSONObject(json.toString());
- data.remove("components");
-
- if (!"".equals(id)) {
- data.put("in", id);
- }
-
- propertiesArray.put(data);
-
- if (json.has("components")) {
- for (int i = 0; i < json.getJSONArray("components").length(); i++) {
- this.parse(data.optString("id", ""), json.getJSONArray("components").getJSONObject(i));
- }
- }
- }
-
- public void notifyListenersOfCreation(Component component, String id) {
- for (ComponentListener listener : componentListeners) {
- listener.onCreation(component, id);
- }
- }
+ public boolean isCreatedComponent(String id) {
+ return COMPONENTS.containsKey(id);
}
- public boolean isEmptyOrNull(Object item) {
- if (item instanceof String) {
- String mItem = item.toString();
- mItem = mItem.replace(" ", "");
- return mItem.isEmpty();
+ public void notifyListenersOfCreation(Component component, String id) {
+ for (ComponentListener listener : componentListeners) {
+ listener.onCreation(component, id);
}
+ }
- return item == null;
+ private void dispatchEvent(final String name, final Object... parameters) {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ EventDispatcher.dispatchEvent(DynamicComponents.this, name, parameters);
+ }
+ });
}
@DesignerProperty(
@@ -175,343 +99,244 @@ public boolean isEmptyOrNull(Object item) {
)
@SimpleProperty(userVisible = false)
public void Thread(String thread) {
- postOnUiThread = (thread == "UI");
+ if (thread.equalsIgnoreCase("UI")) {
+ postOnUiThread = true;
+ } else if (thread.equalsIgnoreCase("Main")) {
+ postOnUiThread = false;
+ } else {
+ throw new YailRuntimeError("Unexpected value '" + thread + "'", TAG);
+ }
}
@SimpleEvent(description = "Is called after a component has been created.")
public void ComponentBuilt(final Component component, final String id, final String type) {
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- EventDispatcher.dispatchEvent(DynamicComponents.this, "ComponentBuilt", component, id, type);
- }
- });
+ dispatchEvent("ComponentBuilt", component, id, type);
}
@SimpleEvent(description = "Is called after a schema has/mostly finished component creation.")
public void SchemaCreated(final String name, final YailList parameters) {
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- EventDispatcher.dispatchEvent(DynamicComponents.this, "SchemaCreated", name, parameters);
- }
- });
+ dispatchEvent("SchemaCreated", name, parameters);
}
@SimpleFunction(description = "Assign a new ID to a previously created dynamic component.")
public void ChangeId(String id, String newId) {
- if (UTIL_INSTANCE.exists(id) && !UTIL_INSTANCE.exists(newId)) {
- for (String mId : UsedIDs().toStringArray()) {
+ if (checkBeforeReplacement(id, newId)) {
+ for (String mId : COMPONENTS.keySet()) {
if (mId.contains(id)) {
- Component mComponent = (Component) GetComponent(mId);
+ Component mComponent = COMPONENTS.get(mId);
String mReplacementId = mId.replace(id, newId);
COMPONENT_IDS.remove(mComponent);
COMPONENTS.put(mReplacementId, COMPONENTS.remove(mId));
COMPONENT_IDS.put(mComponent, mReplacementId);
}
}
- } else {
- throw new YailRuntimeError("The ID you used is either not a dynamic component, or the ID you've used to replace the old ID is already taken.", "DynamicComponents");
}
}
- @SimpleFunction(description = "Creates a new dynamic component.")
+ @SimpleFunction(description = "Replace an existing ID with a new one.")
+ public void ReplaceId(String id, String newId) {
+ if (checkBeforeReplacement(id, newId)) {
+ final Component component = COMPONENTS.get(id);
+ COMPONENTS.remove(id);
+ COMPONENT_IDS.remove(component);
+ COMPONENTS.put(newId, component);
+ COMPONENT_IDS.put(component, newId);
+ }
+ }
+
+ private boolean checkBeforeReplacement(String id, String newId) {
+ if (isCreatedComponent(id) && !isCreatedComponent(newId)) {
+ return true;
+ }
+ throw new YailRuntimeError(
+ "The ID you used is either not a dynamic component, or the ID you've used " +
+ "to replace the old ID is already taken.", TAG
+ );
+ }
+
+ @SimpleFunction(description =
+ "Creates a new dynamic component in given container (arrangement/canvas) and assign to an ID to reference " +
+ "the created component later. The 'ComponentBuilt' event will be invoked when the component has created. " +
+ "Note that you can't create components in Screen directly, you will need to have an arrangement beforehand " +
+ "inside a Screen to do that."
+ )
public void Create(final AndroidViewComponent in, Object componentName, final String id) throws Exception {
if (!COMPONENTS.containsKey(id)) {
lastUsedId = id;
-
- String mClassName = UTIL_INSTANCE.getClassName(componentName);
- Class> mClass = Class.forName(mClassName);
+ Class> mClass = Class.forName(Utils.getClassName(componentName));
final Constructor> mConstructor = mClass.getConstructor(ComponentContainer.class);
-
if (postOnUiThread) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
- UTIL_INSTANCE.newInstance(mConstructor, id, in);
+ Component mComponent = Utils.createInstance(mConstructor, in);
+ COMPONENT_IDS.put(mComponent, id);
+ COMPONENTS.put(id, mComponent);
+ notifyListenersOfCreation(mComponent, id);
+ ComponentBuilt(mComponent, id, mComponent.getClass().getSimpleName());
}
});
} else {
- UTIL_INSTANCE.newInstance(mConstructor, id, in);
+ Component mComponent = Utils.createInstance(mConstructor, in);
+ COMPONENT_IDS.put(mComponent, id);
+ COMPONENTS.put(id, mComponent);
+ notifyListenersOfCreation(mComponent, id);
+ ComponentBuilt(mComponent, id, mComponent.getClass().getSimpleName());
}
} else {
- throw new YailRuntimeError("Expected a unique ID, got '" + id + "'.", "DynamicComponents");
+ throw new YailRuntimeError("All component IDs must be unique, the component ID '" + id + "' has already used before.", TAG);
}
}
- @SimpleFunction(description = "Generates a random ID to create a component with.")
- public String GenerateID() {
- String id = "";
+ @SimpleFunction(description =
+ "Creates a new dynamic component in given container (arrangement/canvas) and return it without saving it to the " +
+ "created components list, so it won't be attached to an ID. Note that you can't create components " +
+ "in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that."
+ )
+ public Component CreateEphemeral(final AndroidViewComponent in, Object componentName) throws Exception {
+ Class> mClass = Class.forName(Utils.getClassName(componentName));
+ final Constructor> mConstructor = mClass.getConstructor(ComponentContainer.class);
+ return Utils.createInstance(mConstructor, in);
+ }
+ @SimpleFunction(description = "Generates a random UUID, can be useful to create components with random ID.")
+ public String GenerateID() {
+ String id;
do {
id = UUID.randomUUID().toString();
- } while (UTIL_INSTANCE.exists(id));
-
+ } while (isCreatedComponent(id));
return id;
}
- @SimpleFunction(description = "Returns the component associated with the specified ID.")
+ @SimpleFunction(description = "Returns the component associated with the specified ID. If not found, returns an empty string.")
public Object GetComponent(String id) {
- return COMPONENTS.get(id);
+ Component component = COMPONENTS.get(id);
+ if (component == null) {
+ return "";
+ }
+ return component;
}
@SimpleFunction(description = "Get meta data about the specified component.")
public YailDictionary GetComponentMeta(Component component) {
- Class> mClass = component.getClass();
- DesignerComponent mDesignerAnnotation = mClass.getAnnotation(DesignerComponent.class);
- boolean mHasDesigner = !isEmptyOrNull(mDesignerAnnotation);
- boolean mHasObject = false;
- SimpleObject mObjectAnnotation = mClass.getAnnotation(SimpleObject.class);
- YailDictionary mMeta = new YailDictionary();
- mHasObject = !isEmptyOrNull(mObjectAnnotation);
-
- if (mHasDesigner && mHasObject) {
- // Return all metadata
- mMeta.put("androidMinSdk", mDesignerAnnotation.androidMinSdk());
- mMeta.put("category", mDesignerAnnotation.category());
- mMeta.put("dateBuilt", mDesignerAnnotation.dateBuilt());
- mMeta.put("description", mDesignerAnnotation.description());
- mMeta.put("designerHelpDescription", mDesignerAnnotation.designerHelpDescription());
- mMeta.put("external", mObjectAnnotation.external());
- mMeta.put("helpUrl", mDesignerAnnotation.helpUrl());
- mMeta.put("iconName", mDesignerAnnotation.iconName());
- mMeta.put("nonVisible", mDesignerAnnotation.nonVisible());
- mMeta.put("package", mClass.getName());
- mMeta.put("showOnPalette", mDesignerAnnotation.showOnPalette());
- mMeta.put("type", mClass.getSimpleName());
- mMeta.put("version", mDesignerAnnotation.version());
- mMeta.put("versionName", mDesignerAnnotation.versionName());
- } else if (!mHasDesigner && mHasObject) {
- // Return some amount of metadata even if there is no
- // @DesignerComponent annotation provided
- mMeta.put("external", mObjectAnnotation.external());
- mMeta.put("package", mClass.getName());
- mMeta.put("type", mClass.getSimpleName());
- } else {
- // Return the least amount of metadata if no
- // annotation is provided
- mMeta.put("package", mClass.getName());
- mMeta.put("type", mClass.getSimpleName());
- }
-
- return mMeta;
+ return Metadata.getComponentCommonInfo(component);
}
@SimpleFunction(description = "Get meta data about events for the specified component.")
public YailDictionary GetEventMeta(Component component) {
- Method[] mMethods = component.getClass().getMethods();
- YailDictionary mEvents = new YailDictionary();
-
- for (Method mMethod : mMethods) {
- SimpleEvent mAnnotation = mMethod.getAnnotation(SimpleEvent.class);
- boolean mIsDeprecated = !isEmptyOrNull(mMethod.getAnnotation(Deprecated.class));
- String mName = mMethod.getName();
- YailDictionary mEventMeta = new YailDictionary();
-
- if (!isEmptyOrNull(mAnnotation)) {
- // Return all metadata
- mEventMeta.put("description", mAnnotation.description());
- mEventMeta.put("isDeprecated", mIsDeprecated);
- mEventMeta.put("userVisible", mAnnotation.userVisible());
- } else {
- // Return the least amount of metadata if no
- // annotation is provided
- mEventMeta.put("isDeprecated", mIsDeprecated);
- }
-
- mEvents.put(mName, mEventMeta);
+ try {
+ return Metadata.getComponentAnnotationInfo(component, SimpleEvent.class);
+ } catch (Exception e) {
+ String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage();
+ throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG);
}
-
- return mEvents;
}
@SimpleFunction(description = "Get meta data about functions for the specified component.")
public YailDictionary GetFunctionMeta(Component component) {
- Method[] mMethods = component.getClass().getMethods();
- YailDictionary mFunctions = new YailDictionary();
-
- for (Method mMethod : mMethods) {
- SimpleFunction mAnnotation = mMethod.getAnnotation(SimpleFunction.class);
- boolean mIsDeprecated = !isEmptyOrNull(mMethod.getAnnotation(Deprecated.class));
- String mName = mMethod.getName();
- YailDictionary mFunctionMeta = new YailDictionary();
-
- if (!isEmptyOrNull(mAnnotation)) {
- // Return all metadata
- mFunctionMeta.put("description", mAnnotation.description());
- mFunctionMeta.put("isDeprecated", mIsDeprecated);
- mFunctionMeta.put("userVisible", mAnnotation.userVisible());
- } else {
- // Return the least amount of metadata if no
- // annotation is provided
- mFunctionMeta.put("isDeprecated", mIsDeprecated);
- }
-
- mFunctions.put(mName, mFunctionMeta);
+ try {
+ return Metadata.getComponentAnnotationInfo(component, SimpleFunction.class);
+ } catch (Exception e) {
+ String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage();
+ throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG);
}
-
- return mFunctions;
}
- @SimpleFunction(description = "Returns the ID of the specified component.")
+ @SimpleFunction(description = "Returns the ID of the specified component. If not found, returns an empty string.")
public String GetId(Component component) {
- if (!isEmptyOrNull(component) || COMPONENT_IDS.containsKey(component)) {
- return COMPONENT_IDS.get(component);
- }
-
- return "";
- }
-
- @Deprecated
- @SimpleFunction(description = "Do NOT use this function. Use 'GetComponentMeta' as a replacement.")
- public String GetName(Component component) {
- return component.getClass().getName();
+ return COMPONENT_IDS.getOrDefault(component, "");
}
- @SimpleFunction(description = "Returns the position of the specified component according to its parent view. Index begins at one.")
+ @SimpleFunction(description =
+ "Returns the position of the specified component according to its parent component. " +
+ "Indexes begins at one. If there is no parent (which shouldn't happen, as the top-most parent is Screen) " +
+ "then return zero (0)."
+ )
public int GetOrder(AndroidViewComponent component) {
- View mComponent = (View) component.getView();
- int mIndex = 0;
- ViewGroup mParent = (!isEmptyOrNull(mComponent) ? (ViewGroup) mComponent.getParent() : null);
+ // (non null)
+ View mComponent = component.getView();
+ ViewGroup mParent = (ViewGroup) mComponent.getParent();
- if (!isEmptyOrNull(mComponent) && !isEmptyOrNull(mParent)) {
- mIndex = mParent.indexOfChild(mComponent) + 1;
+ if (Utils.isNotEmptyOrNull(mComponent) && Utils.isNotEmptyOrNull(mParent)) {
+ return mParent.indexOfChild(mComponent) + 1;
}
-
- return mIndex;
+ return 0;
}
- @SimpleFunction(description = "Get a properties value.")
+ @SimpleFunction(description =
+ "Get a property value of a component with given property name. The returned value can be " +
+ "any type of value, but if the property value is null, this block will return an empty string instead so " +
+ "it can be manipulated and compared with other App Inventor blocks."
+ )
public Object GetProperty(Component component, String name) {
- return Invoke(component, name, YailList.makeEmptyList());
+ Object returnedValue = Utils.callMethod(component, name, new Object[] { });
+ return returnedValue == null ? "" : returnedValue;
}
@SimpleFunction(description = "Get meta data about properties for the specified component, including their values.")
public YailDictionary GetPropertyMeta(Component component) {
- Method[] mMethods = component.getClass().getMethods();
- YailDictionary mProperties = new YailDictionary();
-
- for (Method mMethod : mMethods) {
- DesignerProperty mDesignerAnnotation = mMethod.getAnnotation(DesignerProperty.class);
- boolean mHasDesigner = !isEmptyOrNull(mDesignerAnnotation);
- boolean mHasProperty = false;
- SimpleProperty mPropertyAnnotation = mMethod.getAnnotation(SimpleProperty.class);
- String mName = mMethod.getName();
- YailDictionary mPropertyMeta = new YailDictionary();
- Object mValue = Invoke(component, mName, new YailList());
- mHasProperty = !isEmptyOrNull(mPropertyAnnotation);
-
- if (mHasProperty) {
- mPropertyMeta.put("description", mPropertyAnnotation.description());
- mPropertyMeta.put("category", mPropertyAnnotation.category());
-
- if (mHasDesigner) {
- YailDictionary mDesignerMeta = new YailDictionary();
- mDesignerMeta.put("defaultValue", mDesignerAnnotation.defaultValue());
- mDesignerMeta.put("editorArgs", mDesignerAnnotation.editorArgs());
- mDesignerMeta.put("editorType", mDesignerAnnotation.editorType());
- mPropertyMeta.put("designer", mDesignerMeta);
- }
-
- mPropertyMeta.put("isDeprecated", (!isEmptyOrNull(mMethod.getAnnotation(Deprecated.class))));
- mPropertyMeta.put("isDesignerProperty", mHasDesigner);
- mPropertyMeta.put("userVisible", mPropertyAnnotation.userVisible());
- mPropertyMeta.put("value", mValue);
- mProperties.put(mName, mPropertyMeta);
- }
+ try {
+ return Metadata.getComponentPropertyInfo(component);
+ } catch (Exception e) {
+ String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage();
+ throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG);
}
-
- return mProperties;
}
- @SimpleFunction(description = "Invokes a method with parameters.")
+ @SimpleFunction(description =
+ "Calls any method of a component by its name and given parameters, and returns its result. " +
+ "The returned value can be any type of value, but if the returned value is null, this block will return " +
+ "an empty string instead so it can be manipulated and compared with other App Inventor blocks."
+ )
public Object Invoke(Component component, String name, YailList parameters) {
- if (!isEmptyOrNull(component)) {
- Object mInvokedMethod = null;
- Method[] mMethods = component.getClass().getMethods();
-
- try {
- Object[] mParameters = parameters.toArray();
- Method mMethod = UTIL_INSTANCE.getMethod(mMethods, name, mParameters.length);
-
- Class>[] mRequestedMethodParameters = mMethod.getParameterTypes();
- ArrayList