🏃‍♂️ Fast : An Efficient Way to Build Extensions

Perfect, thanks, I already updated fast to the latest version.

1 Like

As we know, we can have several classes extending AndroidNonvisibleComponent in an extension, which gives the effect of several extensions in one. But unfortunately we only have one Manifest file. Annotations allowed to add different entries to the manifest from different classes. It would be nice to be able to create more manifest files assigned to different classes. The component_build_infos.json file allows this.

2 Likes

Certainly, the multi-component manifest declaration is already supported by FAST. I will share the detailed process with you shortly.

2 Likes

Here is the example of how could you define the component specific manifest elements:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.jewel.imagegenerator">

  <component name="ImageGenerator">
    <application>
      <meta-data android:name="io.sentry.ndk.scope-sync.enable" android:value="true" />
      <meta-data android:name="io.sentry.auto-init" android:value="false" />
    </application>
  
    <uses-permission android:name="android.permission.INTERNET" />
  </component>

  <component name="DemoAds">
    <application>
      <meta-data android:name="com.startapp.sdk.RETURN_ADS_ENABLED" android:value="false" />
      <activity android:name="com.facebook.ads.AudienceNetworkActivity" 
        android:configChanges="keyboardHidden|orientation|screenSize" 
        android:exported="false" 
        android:theme="@android:style/Theme.Translucent.NoTitleBar" />
      <activity
        android:name="io.bidmachine.nativead.view.VideoPlayerActivity"
       android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" />
      <receiver
        android:name="com.appodeal.ads.AppodealPackageAddedReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
          <action android:name="android.intent.action.PACKAGE_ADDED" />
          <data android:scheme="package" />
        </intent-filter>
      </receiver>
    </application>

    <queries>
      <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
         <data android:scheme="https" />
      </intent>
    </queries>
  
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
  </component>
</manifest>
4 Likes

Thanks. It works.

2 Likes

:mega: An update is available v1.2.3

Date built: 07.11.24.11.31


  • A few compilation bugs have been fixed.
  • Optimized the Java compiler.
  • Optimized the Kotlin comliler.
  • Optimized the desugar process.
  • Issue regarding space has been fixed.
  • Terminal prints style has changed.
5 Likes

Can I develop an extension using FAST, the extension is to read and download files from gmail? Can I develop by using basic computer which has no advanced cpu/gpu?

2 Likes

FAST doesn't need a powerful PC. Even mobile users can run it easily.

2 Likes

What are the additional requirements needed in addition to FAST to create codes/programs and turn it into blocks finally? (I am a beginner in programming).

1 Like

Has anyone tried to create an extension with helper blocks? The extension compiles successfully. In the block editor, the helper blocks display correctly, but the application fails to compile when we use the helper block. The problem seems to be similar to what we had in Rush2.0 at the beginning.

I used both the latest version and 1.1.5. I installed the latest version to test. Since version 1.1.5 there have been many updates that were supposed to optimize compilations, but with the latest version my extension compiles in over 4 minutes, and in the old version 1.1.5 it takes a maximum of 1.5 minutes. The output file size for the latest version of Fast has increased by a few kB. So I'm going back to 1.1.5.

2 Likes

I’ve successfully compiled an extension using red helper blocks and build the app without issues. However, I’m not certain of the exact version I used; it was around version 1.0.x

I've already tested 5-10 extensions with helper blocks and those are working fine as expected.

Issues might be in your source or dependencies.

It should not be happening. I'll request you to provide some debug info to resolve the issue.

Is it because of using ProGuard or without ProGuard?

If you still believe that there is an issue with FAST, please provide some additional information so that I can investigate the matter further.

:mega: An update available v1.2.4

Date built: 08.11.24.22.50


  • Added a new feature that allows to select a language when creating a new project.
  • Changes in terminal prints.
1 Like

Do I need to use any special proguard rules for helper classes?

1 Like

The latest version of FAST keeps helper classes by default. And a new compilation process was introduced from 1.1.6 to make it compatible with older devices, such as Windows 7. It might be a issue to take a bit long time to compile sources. I'll investigate more on this matter.

1 Like

I'll try it out soon, I'm using Windows 11.

If the system version affects compilation, then perhaps it would be possible to somehow detect what system Fast is running on and use a different compilation method depending on the system version.

1 Like

I am also using Windows 11 and have not encountered any significant compilation time differences. May I know the approximate number of source classes present in your project? If necessary, I can consider implementing different compilation systems for various device types after further investigation.

Are you asking only about my java sources or also classes from libraries? 12 java files and a dozen or so R files for androidx. But the whole extension has a lot of androidx, guava and gson libraries. Without proguarding it will not compile the extension because there are too many classes for one DEX file. But as I mentioned version 1.1.5 compiles it in max 1.5 min, sometimes a bit faster. Proguarding takes the longest. In the version I tested yesterday I tried twice and it took over 4 min. The size of the output file has hardly changed, it has only increased by a few kB.

I compiled the extension with the latest version of Fast. I get this when compiling the APK in app inventor.

Build failed! [GenerateClasses] ERROR: Kawa compile has failed. [GenerateClasses] ERROR: Can't find class file for Screen 'Screen1'

ERROR: appinventor/ai_froniu84a/extension_test/Screen1.yail line 207: caught exception in inliner for #<procedure static-field> - java.lang.VerifyError: Expecting a stackmap frame at branch target 115

Exception Details:
Location:
pl/patryk_f/extension/helpers/MyIcon.<clinit>()V @88: if_icmpge
Reason:
Expected stackmap frame at this location.
Bytecode:
0000000: bb00 0a59 1202 0303 b700 16b3 000d bb00
0000010: 0a59 1203 0404 b700 16b3 000e bb00 0a59
0000020: 1201 0505 b700 16b3 000c 06bd 000a 5903
0000030: b200 0d53 5904 b200 0e53 5905 b200 0c53
0000040: b300 0bbb 0008 59b7 0015 b300 0fb8 0018
0000050: 594b be3c 033d 1c1b a200 1b2a 1c32 4eb2
0000060: 000f 2db6 0017 2db9 001a 0300 5784 0201
0000070: a7ff e6b1

java.base/java.lang.Class.getDeclaredFields0(Native Method)
java.base/java.lang.Class.privateGetDeclaredFields(Unknown Source)
java.base/java.lang.Class.getDeclaredFields(Unknown Source)
gnu.bytecode.ClassType.addFields(ClassType.java:606)
gnu.bytecode.ClassType.getFields(ClassType.java:506)
gnu.bytecode.ClassType.getDeclaredField(ClassType.java:520)
gnu.bytecode.ClassType.getField(ClassType.java:539)
gnu.kawa.reflect.SlotGet.lookupMember(SlotGet.java:213)
gnu.kawa.reflect.CompileReflect.validateApplySlotGet(CompileReflect.java:144)
jdk.internal.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.base/java.lang.reflect.Method.invoke(Unknown Source)
gnu.expr.InlineCalls.maybeInline(InlineCalls.java:467)
gnu.expr.QuoteExp.validateApply(QuoteExp.java:150)
gnu.expr.ReferenceExp.validateApply(ReferenceExp.java:191)
gnu.kawa.functions.CompilationHelpers.validateApplyToArgs(CompilationHelpers.java:66)
jdk.internal.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.base/java.lang.reflect.Method.invoke(Unknown Source)
gnu.expr.InlineCalls.maybeInline(InlineCalls.java:467)
gnu.expr.QuoteExp.validateApply(QuoteExp.java:150)
gnu.expr.ReferenceExp.validateApply(ReferenceExp.java:191)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:119)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:28)
gnu.expr.ApplyExp.visit(ApplyExp.java:410)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visit(InlineCalls.java:28)
gnu.expr.ExpVisitor.visitAndUpdate(ExpVisitor.java:161)
gnu.expr.ExpVisitor.visitExps(ExpVisitor.java:175)
gnu.expr.ApplyExp.visitArgs(ApplyExp.java:415)
gnu.expr.LambdaExp.validateApply(LambdaExp.java:1737)
gnu.expr.ReferenceExp.validateApply(ReferenceExp.java:191)
gnu.kawa.functions.CompilationHelpers.validateApplyToArgs(CompilationHelpers.java:66)
jdk.internal.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.base/java.lang.reflect.Method.invoke(Unknown Source)
gnu.expr.InlineCalls.maybeInline(InlineCalls.java:467)
gnu.expr.QuoteExp.validateApply(QuoteExp.java:150)
gnu.expr.ReferenceExp.validateApply(ReferenceExp.java:191)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:119)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:28)
gnu.expr.ApplyExp.visit(ApplyExp.java:410)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:272)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:28)
gnu.expr.BeginExp.visit(BeginExp.java:156)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visitLetExp(InlineCalls.java:317)
gnu.expr.InlineCalls.visitLetExp(InlineCalls.java:28)
gnu.expr.LetExp.visit(LetExp.java:207)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visit(InlineCalls.java:28)
gnu.expr.LambdaExp.visitChildrenOnly(LambdaExp.java:1664)
gnu.expr.LambdaExp.visitChildren(LambdaExp.java:1651)
gnu.expr.InlineCalls.visitScopeExp(InlineCalls.java:279)
gnu.expr.InlineCalls.visitLambdaExp(InlineCalls.java:349)
gnu.expr.InlineCalls.visitLambdaExp(InlineCalls.java:28)
gnu.expr.LambdaExp.visit(LambdaExp.java:1640)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:55)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visitSetExpValue(InlineCalls.java:363)
gnu.expr.InlineCalls.visitSetExpValue(InlineCalls.java:28)
gnu.expr.ExpVisitor.visitSetExp(ExpVisitor.java:114)
gnu.expr.InlineCalls.visitSetExp(InlineCalls.java:369)
gnu.expr.InlineCalls.visitSetExp(InlineCalls.java:28)
gnu.expr.SetExp.visit(SetExp.java:406)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:55)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:272)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:28)
gnu.expr.BeginExp.visit(BeginExp.java:156)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:272)
gnu.expr.InlineCalls.visitBeginExp(InlineCalls.java:28)
gnu.expr.BeginExp.visit(BeginExp.java:156)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visit(InlineCalls.java:28)
gnu.expr.ExpVisitor.visitAndUpdate(ExpVisitor.java:161)
gnu.expr.ExpVisitor.visitExps(ExpVisitor.java:175)
gnu.expr.ApplyExp.visitArgs(ApplyExp.java:415)
gnu.kawa.functions.CompileMisc.validateApplyAppendValues(CompileMisc.java:139)
jdk.internal.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
java.base/java.lang.reflect.Method.invoke(Unknown Source)
gnu.expr.InlineCalls.maybeInline(InlineCalls.java:467)
gnu.expr.QuoteExp.validateApply(QuoteExp.java:150)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:119)
gnu.expr.InlineCalls.visitApplyExp(InlineCalls.java:28)
gnu.expr.ApplyExp.visit(ApplyExp.java:410)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:55)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.visit(InlineCalls.java:28)
gnu.expr.LambdaExp.visitChildrenOnly(LambdaExp.java:1664)
gnu.expr.LambdaExp.visitChildren(LambdaExp.java:1651)
gnu.expr.InlineCalls.visitScopeExp(InlineCalls.java:279)
gnu.expr.InlineCalls.visitLambdaExp(InlineCalls.java:349)
gnu.expr.InlineCalls.visitLambdaExp(InlineCalls.java:28)
gnu.expr.ExpVisitor.visitModuleExp(ExpVisitor.java:103)
gnu.expr.ModuleExp.visit(ModuleExp.java:482)
gnu.expr.ExpVisitor.visit(ExpVisitor.java:51)
gnu.expr.InlineCalls.visit(InlineCalls.java:46)
gnu.expr.InlineCalls.inlineCalls(InlineCalls.java:33)
gnu.expr.Compilation.walkModule(Compilation.java:994)
gnu.expr.Compilation.process(Compilation.java:1965)
gnu.expr.ModuleInfo.loadByStages(ModuleInfo.java:330)
gnu.expr.ModuleInfo.loadByStages(ModuleInfo.java:315)
gnu.expr.ModuleInfo.loadByStages(ModuleInfo.java:315)
gnu.expr.ModuleInfo.loadByStages(ModuleInfo.java:315)
kawa.repl.compileFiles(repl.java:783)
kawa.repl.processArgs(repl.java:412)
kawa.repl.main(repl.java:827)
[GenerateClasses] ERROR: Can't find class file for Screen 'Screen1'
[GenerateClasses] Task errored in 2.394 seconds

After adding your own rules also the same:

-keep class pl.patryk_f.extension.helpers.MyIcon { *; }
-keepclassmembers enum pl.patryk_f.extension.helpers.MyIcon {
    public static **[] values();
    public static ** fromUnderlyingValue(java.lang.Integer);
    public Integer toUnderlyingValue(); 
}

In the output file, helper classes are preserved and in the correct place.

The components.json file looks correct:

"params": [
          {
            "helper": {
              "data": {
                "defaultOpt": "Icon1",
                "underlyingType": "java.lang.Integer",
                "options": [
                  {
                    "deprecated": "false",
                    "name": "Icon1",
                    "description": "Option for Icon1",
                    "value": "1"
                  },
                  {
                    "deprecated": "false",
                    "name": "Icon2",
                    "description": "Option for Icon2",
                    "value": "2"
                  },
                  {
                    "deprecated": "false",
                    "name": "Icon3",
                    "description": "Option for Icon3",
                    "value": "3"
                  },
                  {
                    "deprecated": "false",
                    "name": "Icon4",
                    "description": "Option for Icon4",
                    "value": "4"
                  },
                  {
                    "deprecated": "false",
                    "name": "Icon5",
                    "description": "Option for Icon5",
                    "value": "5"
                  }
                ],
                "className": "pl.patryk_f.extension.helpers.MyIcon",
                "tag": "MyIcon",
                "key": "MyIcon"
              },
              "type": "OPTION_LIST"
            },
            "name": "icon",
            "type": "number"
          }
        ],

Class before compilation:

package pl.patryk_f.extension.helpers;

import java.util.HashMap;
import java.util.Map;
import com.google.appinventor.components.common.*;


public enum MyIcon implements OptionList<Integer> {
  Icon1(1),
  Icon2(2),
  Icon3(3),
  Icon4(4),
  Icon5(5);
  
  private final int icon;

  MyIcon(int value) {
    this.icon = value;
  }

  public Integer toUnderlyingValue() {
    return icon;
  }

  private static final Map<Integer, MyIcon> lookup = new HashMap<>();

  static {
    for (MyIcon value : MyIcon.values()) {
      lookup.put(value.toUnderlyingValue(), value);
    }
  }

  public static MyIcon fromUnderlyingValue(Integer value) {
    return lookup.get(value);
  }
}

Decompiled class:

package pl.patryk_f.extension.helpers;

import com.google.appinventor.components.common.OptionList;
import java.util.HashMap;
import java.util.Map;

public enum MyIcon implements OptionList<Integer> {
   Icon1(1),
   Icon2(2),
   Icon3(3),
   Icon4(4),
   Icon5(5);

   private final int icon;
   private static final Map<Integer, MyIcon> lookup = new HashMap();

   private MyIcon(int var3) {
      this.icon = var3;
   }

   public final Integer toUnderlyingValue() {
      return this.icon;
   }

   public static MyIcon fromUnderlyingValue(Integer var0) {
      return lookup.get(var0);
   }

   static {
      MyIcon[] var0;
      int var1 = (var0 = values()).length;

      for(int var2 = 0; var2 < var1; ++var2) {
         MyIcon var3 = var0[var2];
         lookup.put(var3.toUnderlyingValue(), var3);
      }

   }
}

Use:

@SimpleFunction
public String GetIconUri(@Options(MyIcon.class) int icon) {
return Icons.getIconUri(icon, context).toString();
}

Everything seems to be correct but there is an error.

After decompiling DEX the helper class also looks correct.

I built a simple extension with a helpers block and it compiles fine. I don't know why practically the same helpers cause a bad compilation in my large extension.

Perhaps this is some internal ai2 error with larger DEX files?