Feedback required: Creating a programming language for App Inventor

Hi! I'm excited to present a new project that I've been working on since quite some time!
It's a new programming language aimed at the App Inventor ecosystem!


Wait, what?

I'm a very language enthusiast (as in making new languages and designing syntax).
For many months I've worked on creating a new programming language called Eia64.

Which was also recently featured on GitHub Education Newsletter :smiley:.

It's a purely interpreted typesafe language built on Kotlin.
I decided to adapt it to the App Inventor ecosystem.


Why?

It has the ability to fullfil many aspects of programming in App Inventor

  • Dynamic code execution
  • Full App Inventor interoperability
  • Context based UI rendering
  • Java interoperability: Provides dynamic access to runtime APIs that would otherwise require compiling new extensions or modification to App Inventor source code

Future aspects:

  • "This can prove to be the initial steps to something like copilot in app inventor!" — @Shreyash
    One of the aspects that hurdles something like integrating AI to App Inventor is lack of strong syntax base for AI to interact with.

  • I believe having a strong high level syntax representation of the visual blocks is a very win.

  • In future, just like how we use Java to make new extensions, it's possible to make the very same thing with Eia.


Language

Read the docs here: eia-appinventor-docs.vercel.app It also contains many code snippets.

GitHub Open Source: XomaDev/EiaAI2
space.themelon.eia4ai2.aix (87.5 KB)

  1. Runs a string of code and returns the result

    Run

  2. You can define a global variable with this block

    Define

  3. If you wanna clear memory each time you run

  4. Print output, lines printed out using the print() and printf() method.

    component_event


Complex examples

You can find many code snippets at eia-appinventor-docs.vercel.app.

Bluetooth chat app

I was able to rewrite the Bluetooth chat example by @Taifun in Eia.

BluetoothEia.aia (93.0 KB)

let boolServer = false

fun update(text: String) {
  lblResult.Text(text + '\n' + lblResult.Text())
}

fun init() {
  BluetoothServer1.AcceptConnection("")
  update("init")
}

fun connected(isTrue: Bool) {
  btnSend.Enabled(isTrue)
  lipConnect.Enabled(!isTrue)
  Clock1.TimerEnabled(isTrue)
  btnDisconnect.Enabled(isTrue)
}

fun disconnect() {
  update(txbNickname.Text() + " disconnected")
  if (boolServer) {
    BluetoothServer1.Disconnect()
  } else {
    BluetoothClient1.Disconnect()
  }
  connected(false)
  init()
}

fun send(message: String) {
  if (boolServer) {
    BluetoothServer1.SendText(message)
  } else {
    BluetoothClient1.SendText(message)
  }
  update(message)
}

Screen1:PermissionGranted(permissionName: String) {
  if (permissionName == "BLUETOOTH_CONNECT") {
    Screen1.AskForPermission("BLUETOOTH_SCAN")
  } else {
    TinyDB1.StoreValue("permissionsGranted", true)
  }
}

lipConnect:BeforePicking {
  lipConnect.Elements(BluetoothClient1.AddressesAndNames())
  if (!BluetoothClient1.Enabled()) {
    Notifier1.ShowAlert("Please enable bluetooth\nin your Device Settings")
  }
}

lipConnect:AfterPicking {
  update("Trying to connect to " + lipConnect.Selection())
  if (BluetoothServer1.IsAccepting()) {
    BluetoothServer1.StopAccepting()
  }
  if (BluetoothClient1.Connect(lipConnect.Selection())) {
    boolServer = false
    Screen1.Title("Bluetooth Chat, Role=Client")
    connected(true)
    send(txbNickname.Text() + " connected")
  } else {
    init()
  }
}

Clock1:Timer {
  if (boolServer) {
    if (BluetoothServer1.BytesAvailableToReceive() > 0) {
      update(BluetoothServer1.ReceiveText(BluetoothServer1.BytesAvailableToReceive()))
    }
  } else {
    if (BluetoothClient1.BytesAvailableToReceive() > 0) {
      update(BluetoothClient1.ReceiveText(BluetoothClient1.BytesAvailableToReceive()))
    }
  }
}

BluetoothServer1:ConnectionAccepted {
  update("Connection accepted")
  boolServer = true
  Screen1.Title("Bluetooth Chat, Role=Server")
  connected(true)
}

btnSend:Click {
  send(txbNickname.Text() + " said: " + txbMessage.Text())
}

btnExit:Click {
  disconnect()
  closeApp()
}

btnClear:Click {
  lblResult.Text("")
}

btnDisconnect:Click {
  disconnect()
}

Screen1:ErrorOccurred(component: Any, functionName: String, errorNumber: Int, message: String) {
  update("Error " + errorNumber + " " + functionName + ": " + message)
}

Screen1.Title("Bluetooth Chat, Role=undefined")
Clock1.TimerEnabled(false)
txbNickname.Text("User_" + rand(1000, 9999))

if (TinyDB1.GetValue("permissionsGranted", false)) {
  init()
} else {
  Screen1.AskForPermission("BLUETOOTH_CONNECT")
}

... many yet to come


Your feedback

Your feedback is very important to shape the vision of this project.
Share your thoughts, vision or how you want to see this project evolve.

You may also help with technical testing and feedback on it. There are many examples listed in the docs (eia-appinventor-docs.vercel.app)


Acknowledgement

I would like to thank @Shreyash and @Kanishka_Developer. Without their encouragement I wouldn't be motivated to work on this project for App Inventor.


Thank you,
Kumaraswamy B G

16 Likes

Creating android widgets is also possible? For example TextView, EditText, ImageView with their properties?

Do you envision a blocks image input precompiler for those really blocks-heavy projects needing some kind of relief?

Android does not allow you to natively use the views that are dynamically inflated at the runtime. It requires you to provide the resource Id of a layout XML. Additionally android requires you to pre-declare many XML files which isnt possible right now.

But with time, it could be possible :wink:

2 Likes

Yes, the first step would be to map the Blockly code to Eia code. It may also help with performance benefits.

1 Like

I've got a question, if anyone in the App Inventor development team could advice me on this.

I would like to map the Blockly XML code. When we download blocks or export projects, the Blockly XML is within those files.

Is there a resource or common format that would help me parse these XMLs?
I looked at the Blockly JS here in the source code but could'nt figure out how these Blocks are serialized.

1 Like

I tried to compile xml to custom java code to reduce blocks.

xmlToJava.html
xmlToJava.js
conditions.html
conditions.js

These are latest files. Other files are just previous versions

2 Likes

Pretty cool, did you refer to any resource to parse the XMLs in a structured manner?

1 Like

I've been using this methods for all of my big projects to reduce blocks.

Codes

package com.hridoy.eenotifierhelper;

import android.util.Log;
import com.google.appinventor.components.runtime.AndroidViewComponent;
import com.google.appinventor.components.runtime.Component;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import com.google.appinventor.components.runtime.util.YailDictionary;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.regex.Pattern;

public class InvokeUtils {
// Extension log tag
public static final String TAG = "EENotifierHelper";
// Base package name for components
private static final Pattern methodNamePattern = Pattern.compile("[^a-zA-Z0-9]");

public static boolean isNotEmptyOrNull(Object item) {
    return item instanceof String ? !((String) item).replace(" ", "").isEmpty() : item != null;
}

/*
    Find a method from list of methods by name and parameter count.
    Return null if not found.
*/
public static Method getMethod(Object object, String name, int parameterCount) {
    String nameString = methodNamePattern.matcher(name).replaceAll("");
    for (Method method : object.getClass().getMethods()) {
        int methodParameterCount = method.getParameterTypes().length;
        if (method.getName().equals(nameString) && methodParameterCount == parameterCount) {
            return method;
        }
    }
    return null;
}

/*
    Invoke a method of an object by its name and get its return value.
*/
public static Object callMethod(Object object, String name, Object[] parameters) {
    if (!isNotEmptyOrNull(object)) {
        throw new YailRuntimeError("Component cannot be null.", TAG);
    }
    try {
        Method mMethod = getMethod(object, name, parameters.length);
        Class<?>[] mRequestedMethodParameters = mMethod.getParameterTypes();

        for (int i = 0; i < mRequestedMethodParameters.length; i++) {
            final String value = String.valueOf(parameters[i]);

            switch (mRequestedMethodParameters[i].getName()) {
                case "int":
                    parameters[i] = Integer.parseInt(value);
                    break;
                case "float":
                    parameters[i] = Float.parseFloat(value);
                    break;
                case "double":
                    parameters[i] = Double.parseDouble(value);
                    break;
                case "java.lang.String":
                    parameters[i] = value;
                    break;
                case "boolean":
                    parameters[i] = Boolean.parseBoolean(value);
                    break;
            }
        }
        Object mInvokedMethod = mMethod.invoke(object, parameters);
        return mInvokedMethod;
    } catch (InvocationTargetException e) {
        String errorMessage = e.getCause().getMessage() == null ? e.getCause().toString() : e.getCause().getMessage();
        throw new YailRuntimeError("Got an error inside the invoke: " + errorMessage, TAG);
    } catch (Exception e) {
        String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage();
        throw new YailRuntimeError("Couldn't invoke: " + errorMessage, TAG);
    }
}

public static Object Invoke(Object component, String methodName, Object[] parameters) {
    Object returnedValue = InvokeUtils.callMethod(component, methodName, parameters);
    return returnedValue == null ? "" : returnedValue;
}

public static Object GetProperty(Object component, String name) {
    Object returnedValue = InvokeUtils.callMethod(component, name, new Object[] { });
    return returnedValue == null ? "" : returnedValue;
}
public static void SetProperties(Object component, YailDictionary properties) throws Exception {
    for (Map.Entry<Object, Object> pair : properties.entrySet()) {
        InvokeUtils.callMethod(component, (String)pair.getKey(), new Object[] { pair.getValue() });
    }
}
public static void SetProperty(Object component, String name, Object value) {
    InvokeUtils.callMethod(component, name, new Object[] { value });
}
public static AndroidViewComponent GetViewForRV(Object recyclerView, AndroidViewComponent root, String tag){
    Component viewForRV = (Component) Invoke(recyclerView,"GetComponent",new Object[]{root,tag});
    return (AndroidViewComponent) viewForRV;
}

public static Object GetViewForDynamicComponent(Object DynamicComponent, String id){
    return Invoke(DynamicComponent,"GetComponent",new Object[]{id});
}



/**
 * Writes an error message to the Android system log. See the Google Android documentation for
 * how to access the log.
 *
 * @param message the error message
 */
public static void LogError(String message) {
    Log.e(TAG, message);
}

/**
 * Writes a warning message to the Android log. See the Google Android documentation for how to
 * access the log.
 *
 * @param message the warning message
 */
public static void LogWarning(String message) {
    Log.w(TAG, message);
}

/**
 * Writes an information message to the Android log.
 *
 * @param message the information message
 */
public static void LogInfo(String message) {
    Log.i(TAG, message);
}

}

package com.hridoy.eenotifierhelper;

import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.Component;
import com.google.appinventor.components.runtime.util.YailList;
import kawa.standard.Scheme;

import java.awt.*;
import java.lang.reflect.Field;

import static com.hridoy.eenotifierhelper.InvokeUtils.*;

public class EENotifierHelper extends AndroidNonvisibleComponent {
public Component ToastParent,NotifierParent,DynamicComponents,DynamicClickUtil,ShapeableView,SVGImages,RelativeView,ComponentTools,AdvancedDecoration,ThMLT,AlphaDialog,Phase;
public int ScreenWidth = 0;
public boolean toastInitialized,SuccessToastInitialized,ErrorToastInitialized,WarningToastInitialized,InfoToastInitialized;
public static boolean isRepl = false;
public EENotifierHelper(ComponentContainer container) {
super(container.$form());
if (form instanceof ReplForm) {
isRepl = true;
}
}

//---------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------
@SimpleProperty(description = "")
public void SetScreenWidth(int width){
    ScreenWidth = width;
}
@SimpleProperty(description = "")
public int GetScreenWidth(){
    return ScreenWidth;
}

//---------------------------------------------------------------------------
// Events
//---------------------------------------------------------------------------

@SimpleEvent(description = "")
public void ErrorOccurred(String errorFrom, String errorMessage) {
    EventDispatcher.dispatchEvent(this, "ErrorOccurred", errorFrom, errorMessage);
}
//---------------------------------------------------------------------------
// Methods
//---------------------------------------------------------------------------
@SimpleFunction(description = "Notifier helper must be initialized first\n" +
      "Components Needed : \n" +
      "----------------\n" +
      "   - DynamicComponents\n" +
      "   - DynamicClickUtil\n" +
      "   - ShapeableView\n" +
      "   - SVGImages\n" +
      "   - RelativeView\n" +
      "   - ComponentTools\n" +
      "   - AdvancedDecoration\n" +
      "   - ThMLT\n" +
      "   - AlphaDialog\n" +
      "   - Phase\n" +
      "----------------\n" +
      "paramValues : \n" +
      "   1 - Toast Parent\n" +
      "   2 - Notifier Parent\n" +
      "   3 - Toast\n" +
      "   4 - Message Dialog")
public void Initialize(YailList paramValues){

    DynamicComponents = (Component) GetComponentByName("DynamicComponents1");
    DynamicClickUtil = (Component) GetComponentByName("DynamicClickUtil1");
    ShapeableView = (Component) GetComponentByName("ShapeableView1");
    SVGImages = (Component) GetComponentByName("SVGImages1");
    RelativeView = (Component) GetComponentByName("RelativeView1");
    ComponentTools = (Component) GetComponentByName("ComponentTools1");
    AdvancedDecoration = (Component) GetComponentByName("AdvancedDecoration1");
    ThMLT = (Component) GetComponentByName("ThMLT1");
    AlphaDialog = (Component) GetComponentByName("AlphaDialog1");
    Phase = (Component) GetComponentByName("Phase1");

    //Storing notifier schema templates for further use
    ToastParent = (Component) GetComponentByName("ToastScreen");

}
//------------------------------------------
// Toast Functions
//------------------------------------------
private void CreateToast(String toastName){

    // Adding styling
    SetProperty(ToastParent,"Visible",true);
    int toastHeight = (int) Math.floor((ScreenWidth * 0.8 * 0.24) + 30);
    int toastWidth = (int) Math.floor(ScreenWidth * 0.8);

    if (toastName.equals("success")){

        if (!SuccessToastInitialized){
            Object successToastParent = GetComponentByName("SuccessToast");
            Object ST_Icon = GetComponentByName("ST_Icon");

            SetProperty(GetComponentByName("ST_Child1"),"BackgroundColor",Invoke(AdvancedDecoration,"HexToInt", new Object[]{"#2D6A4F"}));
            SetProperty(ST_Icon,"Visible",true);

            ApplyCutCorner(GetComponentByName("ST_Child1"),"0,13,13,0",-1,280);
            Invoke(ComponentTools,"MoveVertically",new Object[]{30,ST_Icon,0,"ST_Icon"});
            Invoke(ComponentTools,"MoveHorizontally",new Object[]{30,ST_Icon,0,"ST_Icon"});
            Invoke(AdvancedDecoration,"SetPadding", new Object[]{GetComponentByName("ST_Child2"),"5,10,5,5"});

            Invoke(SVGImages, "LoadFromString", new Object[]{GetComponentByName("ST_Blob"),"<svg width=\"84\" height=\"75\" viewBox=\"0 0 84 75\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <circle cx=\"20.6075\" cy=\"9.29547\" r=\"9.29547\" fill=\"#004440\"/> <circle cx=\"80.0986\" cy=\"47.7167\" r=\"3.71819\" fill=\"#004440\"/> <path d=\"M79.4438 11.0253C82.4971 18.5483 78.8737 27.1221 71.3507 30.1754C70.5208 30.5122 69.6781 30.7678 68.8315 30.9458C64.1204 31.9366 58.8591 33.2841 56.3382 37.3855C53.3951 42.1741 55.0036 48.3927 59.3496 51.9571C68.015 59.0642 75.0268 68.4315 79.3829 79.6187C92.9059 114.348 75.7149 153.464 40.9856 166.987C6.25636 180.51 -32.8599 163.319 -46.3829 128.59C-59.9059 93.8607 -42.7149 54.7445 -7.98562 41.2214C7.18342 35.3148 23.1894 35.2678 37.5341 39.9824C42.7299 41.69 48.6536 40.072 51.5174 35.4125L52.5823 33.68C54.694 30.2441 53.7172 25.8191 52.2006 22.0823C49.1473 14.5592 52.7707 5.98544 60.2937 2.93215C67.8167 -0.121136 76.3906 3.5023 79.4438 11.0253Z\" fill=\"#004440\"/> </svg>"});
            Invoke(SVGImages, "LoadFromString", new Object[]{ST_Icon,"<svg width=\"78\" height=\"78\" viewBox=\"0 0 78 78\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M39.0002 68.64C57.9546 68.64 73.3202 53.2744 73.3202 34.32C73.3202 15.3656 57.9546 0 39.0002 0C20.0458 0 4.68018 15.3656 4.68018 34.32C4.68018 44.9926 9.55174 54.5274 17.1925 60.822C17.2047 60.832 17.1939 60.8515 17.1789 60.8464C17.1698 60.8433 17.1602 60.8501 17.1602 60.8598V71.5042C17.1602 74.4834 20.3004 76.4171 22.9607 75.076L34.9722 69.0212C35.6356 68.6867 36.3788 68.5494 37.1207 68.5894C37.743 68.623 38.3696 68.64 39.0002 68.64Z\" fill=\"#004440\"/> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M50.3012 24.3464C51.3603 25.1585 51.5625 26.6745 50.7531 27.7357L38.7428 43.4829C38.3193 44.0382 37.6774 44.3832 36.9817 44.4295C36.2861 44.4758 35.6044 44.2189 35.1114 43.7246L27.4884 36.0815C26.5456 35.1363 26.5456 33.6064 27.4884 32.6612C28.4346 31.7124 29.9715 31.7124 30.9177 32.6612L36.5784 38.3368L46.9033 24.7994C47.7155 23.7344 49.2382 23.5314 50.3012 24.3464Z\" fill=\"white\"/> </svg>"});

            Invoke(RelativeView, "Create",new Object[]{successToastParent});
            Invoke(RelativeView, "AddView", new Object[]{ST_Icon,GetProperty(RelativeView,"Left")});

            SetProperty(successToastParent,"Height",toastHeight);
            SetProperty(successToastParent,"Width",toastWidth);

            Invoke(ThMLT,"TranslateApp",new Object[]{successToastParent});
            SuccessToastInitialized = true;
            LogInfo("SuccessToast initialized");

        }

    }else if (toastName.equals("error")){

        if (!ErrorToastInitialized){

            Object errorToastParent = GetComponentByName("ErrorToast");
            Object ET_Icon = GetComponentByName("ET_Icon");

            SetProperty(GetComponentByName("ET_Child1"),"BackgroundColor",Invoke(AdvancedDecoration,"HexToInt", new Object[]{"#F05454"}));
            SetProperty(ET_Icon,"Visible",true);

            ApplyCutCorner(GetComponentByName("ET_Child1"),"0,13,13,0",-1,280);
            Invoke(ComponentTools,"MoveVertically",new Object[]{30,ET_Icon,0,"ET_Icon"});
            Invoke(ComponentTools,"MoveHorizontally",new Object[]{30,ET_Icon,0,"ET_Icon"});
            Invoke(AdvancedDecoration,"SetPadding", new Object[]{GetComponentByName("ET_Child2"),"5,10,5,5"});

            Invoke(SVGImages, "LoadFromString", new Object[]{GetComponentByName("ET_Blob"),"<svg width=\"93\" height=\"87\" viewBox=\"0 0 93 87\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <circle cx=\"33.9429\" cy=\"36.8292\" r=\"6.82918\" fill=\"#AF2D2D\"/> <circle cx=\"79\" cy=\"27\" r=\"13\" fill=\"#AF2D2D\"/> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M32.5511 49.4389C32.4191 48.7782 31.9357 48.2428 31.2918 48.0442C30.5686 47.8212 29.7827 48.0647 29.3124 48.6576L26.6097 52.0648C24.7585 54.3984 21.7904 55.5017 18.8235 55.2372C11.5519 54.5888 4.04597 55.1701 -3.42021 57.1407C-37.9518 66.255 -58.5567 101.637 -49.4425 136.169C-40.3282 170.7 -4.9463 191.305 29.5853 182.191C64.1169 173.077 84.7218 137.695 75.6076 103.163C70.3813 83.3617 56.5177 68.1397 39.13 60.4748C36.2342 59.1983 34.0008 56.6968 33.3809 53.5935L32.5511 49.4389Z\" fill=\"#AF2D2D\"/> <circle cx=\"21.5475\" cy=\"4.5476\" r=\"3.49096\" transform=\"rotate(-22.0902 21.5475 4.5476)\" fill=\"#AF2D2D\"/> </svg>"});
            Invoke(SVGImages, "LoadFromString", new Object[]{ET_Icon,"<svg width=\"78\" height=\"78\" viewBox=\"0 0 78 78\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M47.8057 67.6794C48.1346 67.4706 48.4935 67.3124 48.8667 67.2006C63.012 62.9621 73.3202 49.8448 73.3202 34.32C73.3202 15.3656 57.9546 0 39.0002 0C20.0458 0 4.68018 15.3656 4.68018 34.32C4.68018 47.8227 12.478 59.5042 23.816 65.1068C23.8288 65.1132 23.8236 65.1326 23.8093 65.1315C23.8002 65.1308 23.7932 65.1396 23.796 65.1483L26.904 74.9903C27.6949 77.4949 30.644 78.5705 32.8616 77.1631L47.8057 67.6794Z\" fill=\"#AF2D2D\"/> <path d=\"M48.8137 27.6379C49.6421 26.8095 49.6421 25.4664 48.8137 24.638L48.3622 24.1865C47.5338 23.3581 46.1907 23.3581 45.3623 24.1865L39.0002 30.5486L32.6381 24.1865C31.8097 23.3581 30.4666 23.3581 29.6382 24.1865L29.1867 24.638C28.3583 25.4664 28.3583 26.8095 29.1867 27.6379L35.5488 34L29.1867 40.362C28.3583 41.1904 28.3583 42.5336 29.1867 43.362L29.6382 43.8134C30.4666 44.6419 31.8097 44.6419 32.6381 43.8134L39.0002 37.4514L45.3623 43.8134C46.1907 44.6418 47.5338 44.6419 48.3622 43.8134L48.8137 43.362C49.6421 42.5336 49.6421 41.1904 48.8137 40.362L42.4516 34L48.8137 27.6379Z\" fill=\"white\"/> </svg>"});


            Invoke(RelativeView, "Create",new Object[]{errorToastParent});
            Invoke(RelativeView, "AddView", new Object[]{ET_Icon,GetProperty(RelativeView,"Left")});

            SetProperty(errorToastParent,"Height",toastHeight);
            SetProperty(errorToastParent,"Width",toastWidth);

            Invoke(ThMLT,"TranslateApp",new Object[]{errorToastParent});
            ErrorToastInitialized = true;
            LogInfo("ErrorToast initialized");

        }

    }else if (toastName.equals("warning")){

        if (!WarningToastInitialized){

            Object warningToastParent = GetComponentByName("WarningToast");
            Object WT_Icon = GetComponentByName("WT_Icon");

            SetProperty(GetComponentByName("WT_Child1"),"BackgroundColor",Invoke(AdvancedDecoration,"HexToInt", new Object[]{"#FCA652"}));
            SetProperty(WT_Icon,"Visible",true);

            ApplyCutCorner(GetComponentByName("WT_Child1"),"0,13,13,0",-1,280);
            Invoke(ComponentTools,"MoveVertically",new Object[]{30,WT_Icon,0,"WT_Icon"});
            Invoke(ComponentTools,"MoveHorizontally",new Object[]{30,WT_Icon,0,"WT_Icon"});
            Invoke(AdvancedDecoration,"SetPadding", new Object[]{GetComponentByName("WT_Child2"),"5,10,5,5"});

            Invoke(SVGImages, "LoadFromString", new Object[]{GetComponentByName("WT_Blob"),"<svg width=\"104\" height=\"86\" viewBox=\"0 0 104 86\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <circle cx=\"83\" cy=\"11\" r=\"11\" fill=\"#CC561E\"/> <circle cx=\"63.2049\" cy=\"43.1187\" r=\"4.15994\" fill=\"#CC561E\"/> <circle cx=\"11.992\" cy=\"6.77329\" r=\"2.77329\" fill=\"#CC561E\"/> <path d=\"M78.8706 73.138C109.931 100.957 112.559 148.688 84.7401 179.749C56.9211 210.809 9.18992 213.437 -21.8706 185.618C-52.9311 157.799 -55.559 110.068 -27.7401 79.0074C-14.3975 64.1102 3.52554 55.7534 21.993 54.1594C26.2912 53.7885 29.8346 50.3474 29.8264 46.0332L29.8232 44.3418C29.8192 42.2738 28.4959 40.4885 26.936 39.1309C22.4553 35.2312 21.9843 28.4375 25.8839 23.9568C29.7836 19.4761 36.5772 19.0051 41.0579 22.9047C45.5387 26.8044 46.0097 33.598 42.11 38.0787C41.7779 38.4603 41.4248 38.8129 41.054 39.1361C38.4051 41.4445 35.5739 44.264 35.5806 47.7776C35.5875 51.4237 38.3736 54.4312 41.9619 55.0778C55.255 57.4733 68.0706 63.4651 78.8706 73.138Z\" fill=\"#CC561E\"/> </svg>"});
            Invoke(SVGImages, "LoadFromString", new Object[]{WT_Icon,"<svg width=\"78\" height=\"78\" viewBox=\"0 0 78 78\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M39.0002 68.64C57.9546 68.64 73.3202 53.2744 73.3202 34.32C73.3202 15.3656 57.9546 0 39.0002 0C20.0458 0 4.68018 15.3656 4.68018 34.32C4.68018 43.2429 8.08537 51.3705 13.6671 57.4741C14.3864 58.2607 14.8202 59.2749 14.8202 60.3408V73.1872C14.8202 76.1365 17.9032 78.0717 20.5592 76.7894L37.4155 68.6519C37.4252 68.6472 37.425 68.6332 37.4151 68.6288C37.4023 68.623 37.4069 68.6037 37.421 68.6043C37.9444 68.628 38.4709 68.64 39.0002 68.64Z\" fill=\"#CC561E\"/> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M35.8696 36.5652C35.8696 38.222 37.2128 39.5652 38.8696 39.5652H39.1305C40.7874 39.5652 42.1305 38.222 42.1305 36.5652L42.1305 23.0869C42.1305 21.4301 40.7874 20.0869 39.1305 20.0869H38.8696C37.2128 20.0869 35.8696 21.4301 35.8696 23.0869V36.5652ZM39.0001 47.913C40.729 47.913 42.1305 46.5115 42.1305 44.7826C42.1305 43.0537 40.729 41.6521 39.0001 41.6521C37.2712 41.6521 35.8696 43.0537 35.8696 44.7826C35.8696 46.5115 37.2712 47.913 39.0001 47.913Z\" fill=\"white\"/> </svg>"});


            Invoke(RelativeView, "Create",new Object[]{warningToastParent});
            Invoke(RelativeView, "AddView", new Object[]{WT_Icon,GetProperty(RelativeView,"Left")});

            SetProperty(warningToastParent,"Height",toastHeight);
            SetProperty(warningToastParent,"Width",toastWidth);

            Invoke(ThMLT,"TranslateApp",new Object[]{warningToastParent});
            WarningToastInitialized = true;
            LogInfo("WarningToast initialized");

        }

    }else if (toastName.equals("info")){

        if (!InfoToastInitialized){

            Object infoToastParent = GetComponentByName("InfoToast");
            Object IT_Icon = GetComponentByName("IT_Icon");

            SetProperty(GetComponentByName("IT_Child1"),"BackgroundColor",Invoke(AdvancedDecoration,"HexToInt", new Object[]{"#B8B5FF"}));
            SetProperty(IT_Icon,"Visible",true);

            ApplyCutCorner(GetComponentByName("IT_Child1"),"0,13,13,0",-1,280);
            Invoke(ComponentTools,"MoveVertically",new Object[]{30,IT_Icon,0,"IT_Icon"});
            Invoke(ComponentTools,"MoveHorizontally",new Object[]{30,IT_Icon,0,"IT_Icon"});
            Invoke(AdvancedDecoration,"SetPadding", new Object[]{GetComponentByName("IT_Child2"),"5,0,5,5"});

            Invoke(SVGImages, "LoadFromString", new Object[]{GetComponentByName("IT_Blob"),"<svg width=\"140\" height=\"88\" viewBox=\"0 0 140 88\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <circle cx=\"18\" cy=\"40\" r=\"11\" fill=\"#7868E6\"/> <circle cx=\"77.3911\" cy=\"32.3911\" r=\"8.39107\" fill=\"#7868E6\"/> <circle cx=\"60.5\" cy=\"76.5\" r=\"11.5\" fill=\"#7868E6\"/> <circle cx=\"45.1955\" cy=\"18.1955\" r=\"4.19554\" fill=\"#7868E6\"/> <circle cx=\"22.4715\" cy=\"2.09777\" r=\"2.09777\" fill=\"#7868E6\"/> <circle cx=\"31.523\" cy=\"142.523\" r=\"76.1453\" transform=\"rotate(-48.1512 31.523 142.523)\" fill=\"#7868E6\"/> </svg>"});
            Invoke(SVGImages, "LoadFromString", new Object[]{IT_Icon,"<svg width=\"78\" height=\"78\" viewBox=\"0 0 78 78\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M45.5712 68.4064C46.0379 68.0508 46.5792 67.8055 47.1494 67.6667C62.1699 64.0087 73.3202 50.4665 73.3202 34.32C73.3202 15.3656 57.9546 0 39.0002 0C20.0458 0 4.68018 15.3656 4.68018 34.32C4.68018 47.018 11.5762 58.1053 21.8274 64.0412C22.5606 64.4658 23.1681 65.0833 23.5396 65.8447L28.2814 75.5654C29.3879 77.8339 32.293 78.5231 34.3006 76.9935L45.5712 68.4064Z\" fill=\"#6155A6\"/> <ellipse cx=\"38.8215\" cy=\"46.8696\" rx=\"2.43478\" ry=\"2.43478\" fill=\"white\"/> <path d=\"M43.6304 20.4316C41.8412 19.5492 39.8304 19.2173 37.8525 19.4777C35.8745 19.7381 34.0182 20.5791 32.5183 21.8945C31.5445 22.7486 30.7493 23.7776 30.1699 24.9218C29.6493 25.9501 30.3284 27.1095 31.4417 27.4078L32.1136 27.5878C33.2269 27.8861 34.3448 27.1813 35.0532 26.272C35.2544 26.0137 35.4805 25.7737 35.729 25.5557C36.5048 24.8753 37.465 24.4403 38.4881 24.3056C39.5111 24.1709 40.5512 24.3426 41.4767 24.799C42.4022 25.2554 43.1715 25.976 43.6875 26.8696C44.2034 27.7633 44.4428 28.7899 44.3753 29.8196C44.3078 30.8492 43.9365 31.8358 43.3083 32.6545C42.6801 33.4731 41.8233 34.0871 40.8462 34.4188C40.0433 34.6957 39.8273 34.6957 38.9998 34.6957L38.3042 34.6957C37.1516 34.6957 36.2172 35.6301 36.2172 36.7827V40.2609C36.2172 41.4135 37.1516 42.3479 38.3042 42.3479H38.9999C40.1524 42.3479 41.0868 41.4135 41.0868 40.2609C41.0868 39.7503 41.4568 39.3184 41.9473 39.1763C42.0974 39.1329 42.2518 39.0841 42.4114 39.03C44.3006 38.3887 45.9571 37.2016 47.1716 35.6189C48.3861 34.0361 49.104 32.1288 49.2344 30.138C49.3649 28.1473 48.9021 26.1626 47.9046 24.4348C46.9071 22.7071 45.4197 21.314 43.6304 20.4316Z\" fill=\"white\"/> </svg>"});


            Invoke(RelativeView, "Create",new Object[]{infoToastParent});
            Invoke(RelativeView, "AddView", new Object[]{IT_Icon,GetProperty(RelativeView,"Left")});

            SetProperty(infoToastParent,"Height",toastHeight);
            SetProperty(infoToastParent,"Width",toastWidth);

            Invoke(ThMLT,"TranslateApp",new Object[]{infoToastParent});
            InfoToastInitialized = true;
            LogInfo("InfoToast initialized");

        }

    }else {
        LogError("Invalid toast type");
    }
     SetProperty(ToastParent,"Visible",false);


}

private void ShowToast(String toast, String title, String description){

  CreateToast(toast);
  Object selectedToast = GetComponentByName("SuccessToast");
  String[] toastParentIds = ("SuccessToast,ErrorToast,WarningToast,InfoToast").split(",");
  for (String toastParentId:toastParentIds){
      SetProperty(GetComponentByName(toastParentId),"Visible",false);
  }

  if (toast.equals("success")){
      LogInfo("Showing Success toast");
      SetProperty(GetComponentByName("SuccessToast"),"Visible",true);
      SetProperty(GetComponentByName( "ST_Title"),"Text",title);
      SetProperty(GetComponentByName( "ST_Description"),"Text",description);
      selectedToast = GetComponentByName("SuccessToast");

  } else if (toast.equals("error")) {
      LogInfo("Showing Error toast");
      SetProperty(GetComponentByName("ErrorToast"),"Visible",true);
      SetProperty(GetComponentByName( "ET_Title"),"Text",title);
      SetProperty(GetComponentByName( "ET_Description"),"Text",description);
      selectedToast = GetComponentByName("ErrorToast");
  } else if (toast.equals("warning")) {
      LogInfo("Showing warning toast");
      SetProperty(GetComponentByName("WarningToast"),"Visible",true);
      SetProperty(GetComponentByName("WT_Title"),"Text",title);
      SetProperty(GetComponentByName( "WT_Description"),"Text",description);
      selectedToast = GetComponentByName("WarningToast");
  } else if (toast.equals("info")) {
      LogInfo("Showing info toast");
      SetProperty(GetComponentByName("InfoToast"),"Visible",true);
      SetProperty(GetComponentByName( "IT_Title"),"Text",title);
      SetProperty(GetComponentByName( "IT_Description"),"Text",description);
      selectedToast = GetComponentByName("InfoToast");
  }

  Invoke(AlphaDialog,"ShowToast",new Object[]{ToastParent,GetProperty(AlphaDialog,"LongLength")});
  Invoke(Phase,"CancelAnimation",new Object[]{});
  Invoke(Phase,"AnimateComponent",new Object[]{120,selectedToast,GetProperty(Phase,"FadeInUp"),0,300,0});
  Invoke(Phase,"AnimateComponent",new Object[]{121,selectedToast,GetProperty(Phase,"FadeOutDown"),3200,300,0});

}

@SimpleFunction(description = "")
public void ShowSuccessToast(String title, String description){
  ShowToast("success",title,description);
}

@SimpleFunction(description = "")
public void ShowErrorToast(String title, String description){
  ShowToast("error",title,description);
}

@SimpleFunction(description = "")
public void ShowWarningToast(String title, String description){
    ShowToast("warning",title,description);
}

@SimpleFunction(description = "")
public void ShowInfoToast(String title, String description){
    ShowToast("info",title,description);
}



//------------------------------------------
// Private methods
//------------------------------------------
private void ApplyCutCorner(Object layout, String cutValues, int height, int width) {
    // Convert layout to string and create id
    String id = String.valueOf(layout);

    // Create cutList from cutValues
    String[] cutList = cutValues.split(",");

    // Invoke Create method
    Invoke(ShapeableView, "Create", new Object[] { id, layout, GetProperty(ShapeableView, "CutCorner") });

    // Invoke SetCornerCutSize method
    Object[] cornerCutSizeParams = new Object[5];
    cornerCutSizeParams[0] = id;
    System.arraycopy(cutList, 0, cornerCutSizeParams, 1, 4);
    Invoke(ShapeableView, "SetCornerCutSize", cornerCutSizeParams);

    // Invoke SetHeight method
    Invoke(ShapeableView, "SetHeight", new Object[] { id, height });

    // Invoke SetWidth method
    Invoke(ShapeableView, "SetWidth", new Object[] { id, width });
}

private Object lookupComponentInRepl(String componentName) {
    Scheme lang = Scheme.getInstance();
    try {
        // Invoke the Scheme interpreter to get the method.
        Object result = lang.eval("(begin (require <com.google.youngandroid.runtime>)(get-component " +
                componentName + "))");
        if (result instanceof Component) {
            return result;
        } else {
            String errorMessage = "Component - '" + componentName + "' Wanted a Component, but got a " +
                    (result == null ? "null" : result.getClass().toString());
            LogError(errorMessage);
        }
    } catch (Throwable throwable) {
        LogError(String.valueOf(throwable));
    }
    return ""; // Returning an empty string if no valid Component is found
}

private Object lookupComponentInForm(String componentName) {
    try {
        // Get the field by name
        Field field = form.getClass().getField(componentName);
        // Get the field's value
        Object component = field.get(form);
        if (component instanceof Component) {
            return component;
        } else {
            String errorMessage = "Component - '" + componentName + "' Wanted a Component, but got a " +
                    (component == null ? "null" : component.getClass().toString());
            LogError(errorMessage);
        }
    } catch (NoSuchFieldException | IllegalAccessException e) {
        LogError("Error accessing component: " + componentName);
    }
    return ""; // Returning an empty string if no valid Component is found
}

@SimpleFunction(description = "")
public Object GetComponentByName(String componentName) {
    Object mComponent;
    if (isRepl) {
        mComponent = lookupComponentInRepl(componentName);
    } else {
        mComponent = lookupComponentInForm(componentName);
    }
    if (mComponent.equals("")){
        // Log and report error before returning
        String errorMessage = "Component - '" + componentName + "' not found or invalid type";
        LogError(errorMessage);
        ErrorOccurred("GetComponentByName", errorMessage);
    }
    return mComponent;

}

}

This a simple example how i use the converted methods

Not all views require additional resources. The ones I listed, and many more, do not require them, and what's more, appinventor components are based on them. Maybe not so much to create new views, but for example to extract a view from a component and modify it, adding properties that are not in app inventor, as compiled extensions do now.

1 Like

I was saying that in order to create widgets, the views for those widgets must have to be from the resource XML files.


Yep, it's one aspect where this project might be helpful :smiley:

1 Like

I am still trying to grasp the potential impact of this. For instance this statement.

How would this look? Would Eia compile into an aix or would you paste the code into the Eia extension and let it execute there?

I don't think it will ever be possible to do the same thing as with compiled extensions. But we'll be able to do a lot using code instead of blocks.

In the extension I can easily create a view e.g. Button button = new Button(context) and add it e.g. to the arrangement without additional xml resources. So I don't know what you mean by writing about XML.

Eia64 will not natively compile to Java. We would setup a custom build system that would produce parsed intermediary output that would be executed on the device when accessed through blocks.

Just for sake of imagination, here's how you would create a block defining it in Eia:

block ComputeAverage(numbers: List) {
  let sum = 0
  for (num in numbers) {
    sum += num
  }
  let avg = sum / len(numbers)
  Notifier1.ShowAlert("Average is " + avg)
}

and boom! When you build it, it should produce a new extension with a block ComputeAverage.

I supposed you were referring to Android Widgets which appears on the home screen.

I am also thinking about iOS. Apple doesn't allow extensions inside apps. Does your system also work for that? If you would have a system like this, your extension as a standard component and users would paste code inside your block, it wouldn´t be considered an extension. Just brainstorming, crazy thoughts, etc, etc, :crazy_face:

3 Likes

Yeah I was thinking about it too :grin: The project should be adopted in either Swift or C++. I'm not sure with how feasible it would be on Swift.

In theory it should be possible to manipulate App Inventor components at runtime with Eia running on iOS but I'm not sure if Swift is as open as Java when it comes to interacting with Swift/iOS libraries.

1 Like