Create a new (Custom) Component Container that works for every component created by Reflection

For the last few days, I'm trying to create a Component container for components to create through Reflection. I couldn't.

How do you actually create a working (implemented) class of it?
The component container needs to Override things like $context and $form. I tried to create an extended class of all of them (Form and activity).

My code
class FForm extends Form {
        @Override
        public boolean canDispatchEvent(Component component, String str) {
            return true;
        }

        @Override
        public boolean dispatchEvent(Component component, String str, String str2, Object[] objArr) {
            Log.d(TAG, "Dispatch event: " + component.toString() + ", values: " + Arrays.toString(objArr) +  ", str one: " + str + ", str two: " + str2);
            getDispatchDelegate().dispatchEvent(component,str,str2,objArr);
            return true;
        }

        @Override
        public void dispatchGenericEvent(Component component, String str, boolean z, Object[] objArr) {
            Log.d(TAG, "Dispatch Generic Event: " + component.toString() + ", values: " + Arrays.toString(objArr) +  ", str one: " + str + ", boolean two: " + z);
            getDispatchDelegate().dispatchGenericEvent(component,str,z,objArr);
        }

        @Override
        public void onDestroy() {

        }

        @Override
        public void onPause() {

        }
    }

    static class AActivity extends Activity {
        public void init(Context context) {
            attachBaseContext(context);
        }
        
        @Override
        public String getLocalClassName() {
            return "Screen1";
        }

    }

    static class CComponentContainer implements ComponentContainer {
        private final BackgroundService service;

        public CComponentContainer(BackgroundService service){
            this.service = service;
        }

        @Override
        public Activity $context() {
            return service.activity;
        }

        @Override
        public Form $form() {
            return service.form;
        }

        @Override
        public void $add(AndroidViewComponent androidViewComponent) {

        }

        @Override
        public void setChildWidth(AndroidViewComponent androidViewComponent, int i) {

        }

        @Override
        public void setChildHeight(AndroidViewComponent androidViewComponent, int i) {

        }

        @Override
        public int Width() {
            return 1;
        }

        @Override
        public int Height() {
            return 1;
        }
    }

This is how I initialize it:

activity = new AActivity();
activity.init(getApplicationContext());
form = new FForm();
container = new CComponentContainer(this);

(some code is from my old extension)

I supply the container to the component as the constructor.
For some components it does work and for some, it doesn't.

Like, the LightSensor component. The component(s) failes to initialize and the error occurs.
This is the error of the component:

Caused by: java.lang.IllegalStateException: System services not available to Activities before onCreate()

Also, I'm trying to get the Form environment of it using the extended Form which is FForm in the code above.

java.lang.RuntimeException: Unable to destroy activity {appinventor.ai_bgpsdr.LocationDataSensor/appinventor.ai_bgpsdr.LocationDataSensor.Screen1}: java.lang.IllegalStateException: Unable to get form

I found these issues with the snippets that you have shared.

  1. Form is a subclass of Activity, so you should implement ComponentContainer interface there itself.
  2. Instantiating Activity doesn't work. Android requires an entry in the manifest.
  3. For non-visible components, the ComponentContainer is the screen or the Form class.

This means that you should call super.onCreate() beforehand.

2 Likes

I am consused do I need to implement ComponentContainer in Activity itself?

Now I've added the activity element and now the error (Caused by: java.lang.IllegalStateException: System services not available to Activities before onCreate()) seems to be gone.

So can I try to assign Form as the constructor for the Component object?

You should do something like this as you want to create a ComponentContainer instance at runtime

container = new ComponentContainer {
... // implement methods
}

If you are creating this within a component/extension, you could use the component's $activity and $form references to implement the ComponentContainer interface.

Otherwise, currently active Form can be accessed using Form.getActiveForm() and the same can be used as Activity.

Yes, as Form is a ComponentContainer at the root level.

1 Like

Thank you for your help Pavitra :slightly_smiling_face:

2 Likes

Hello Pavitra, I was able to create most of the components successfully but for components like Player however it did work work. As the player uses Form, it crashes at this line:

form.setVolumeControlStream(AudioManager.STREAM_MUSIC);
Source: https://github.com/mit-cml/appinventor-sources/blob/ffba5beaca12b9b443344d02e2241df8bdc27236/appinventor/components/src/com/google/appinventor/components/runtime/Player.java#L148

Do you know why does it happen? The form is null according to the app logs:

06-21 21:49:38.072 15310 15433 W System.err: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.Window.setVolumeControlStream(int)' on a null object reference
06-21 21:49:38.073 15310 15433 W System.err: at android.app.Activity.setVolumeControlStream(Activity.java:6288)
06-21 21:49:38.073 15310 15433 W System.err: at com.google.appinventor.components.runtime.Player.<init>(Player.java:148)

I also attach the base context for the form as did with activity.

1 Like

Seems like the Window is null and not the form. How do I get the main window and override it?

https://developer.android.com/reference/android/app/Activity#getWindow()

It could be because current Activity's onCreate() may not have been called yet.

Could you show the full stacktrace?

Here is the full stack trace:

06-22 09:08:31.621 14913 14945 W System.err: java.lang.reflect.InvocationTargetException
06-22 09:08:31.622 14913 14945 W System.err: at java.lang.reflect.Constructor.newInstance0(Native Method)
06-22 09:08:31.622 14913 14945 W System.err: at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
06-22 09:08:31.622 14913 14945 W System.err: at com.kumaraswamy.tasks.ActivityService.createComponent(ActivityService.java:259)
06-22 09:08:31.622 14913 14945 W System.err: at com.kumaraswamy.tasks.ActivityService.processComponentList(ActivityService.java:145)
06-22 09:08:31.622 14913 14945 W System.err: at com.kumaraswamy.tasks.ActivityService.processTasks(ActivityService.java:91)
06-22 09:08:31.622 14913 14945 W System.err: at com.kumaraswamy.tasks.ActivityService.access$000(ActivityService.java:34)
06-22 09:08:31.622 14913 14945 W System.err: at com.kumaraswamy.tasks.ActivityService$1$1.run(ActivityService.java:74)
06-22 09:08:31.622 14913 14945 W System.err: at java.util.TimerThread.mainLoop(Timer.java:562)
06-22 09:08:31.622 14913 14945 W System.err: at java.util.TimerThread.run(Timer.java:512)
06-22 09:08:31.626 14913 14945 W System.err: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.Window.setVolumeControlStream(int)' on a null object reference
06-22 09:08:31.626 14913 14945 W System.err: at android.app.Activity.setVolumeControlStream(Activity.java:6288)
06-22 09:08:31.626 14913 14945 W System.err: at com.google.appinventor.components.runtime.Player.<init>(Player.java:148)
06-22 09:08:31.626 14913 14945 W System.err: ... 9 more