Listen for event dispatches in component through the extension

@ewpatton sir kindly have a look.

I did test it again, the component object is always null and produces and error with crash.

1 Like

pls explain me i cant understand the above codes

1 Like

@vknow360 I did some research and ended up failing with this code

static class EventComponentContainer implements ComponentContainer {
        public static EventForm form;
        public static ExtendedAct activity;

        public EventComponentContainer(){
            form = new EventForm();
            activity = new ExtendedAct();
        }

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

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

        @Override
        public void $add(AndroidViewComponent component) {

        }

        @Override
        public void setChildWidth(AndroidViewComponent component, int width) {

        }

        @Override
        public void setChildHeight(AndroidViewComponent component, int height) {

        }

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

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

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

            @Override
            public boolean dispatchEvent(Component component, String str, String str2, Object[] objArr) {
                getDispatchDelegate().dispatchEvent(component,str,str2,objArr);
                Log.e("EventDispatchedListener", str);
                return true;
            }

            @Override
            public void dispatchGenericEvent(Component component, String str, boolean z, Object[] objArr) {
                getDispatchDelegate().dispatchGenericEvent(component,str,z,objArr);
                Log.e("EventDispatchedListener", str);
            }

            public void context(Context context) {
                attachBaseContext(context);
            }
        }

        class ExtendedAct extends Activity {
            public void context(Context context) {
                attachBaseContext(context);
                Log.e("Notification", "Attached");
            }
        }
    }

If I pass this extended component container to the component constructor then the component is null. Changing $context and $form value to the default values, there is no error. I have still found the solution and reason why the component is null. From today morning I am searching for listening to all the method invokes in a component class. I did find a similar question asked in StackOverFlow and it was not really helpful:


:(

It would be really great and helpful if you say something about this topic if it's possible or not @ewpatton. :disappointed_relieved:

2 Likes

what i want is i register a component i need to find when will all the event in a component occurs like

public void EventOccured(Component com, String eventName, List variableNames, List variable Values)

Is there any update on this? Is it possible to listen for event dispatches in a component? What's the conclusion?


This sounds good, but does it work?

1 Like

Are you trying to listen for dispatches on an existing component or for one you've created dynamically?

1 Like

I am trying to listen for dispatches on components created dynamically. I had tried serval times to create a new ComponentContainer but the Component object created is null and that crashes the app.

I think it might be possible but it is definitely non-trivial. I'll have to think about it. I may have time to explore this a bit on Wednesday.

3 Likes

thanks for looking into it!! :blush:

it does not work i just told that i need function like that if they can

1 Like

Hello @ewpatton can you please look here? :blush:

I think you can only override event dispatches when you override the form.

can you give me an example?

Here is an example that I wrote:

// -*- mode: java; c-basic-offset: 2; -*-
// Copyright © 2021 MIT, All rights reserved.
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package edu.mit.appinventor.extensions.event_example;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.Component;
import com.google.appinventor.components.runtime.EventDispatcher;
import com.google.appinventor.components.runtime.Form;
import com.google.appinventor.components.runtime.OnClearListener;
import gnu.mapping.Environment;
import gnu.mapping.ProcedureN;
import gnu.mapping.SimpleSymbol;
import gnu.mapping.Symbol;
import gnu.mapping.Values;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The DynamicComponents extension demonstrates how to create a component at runtime and attach
 * event listeners.
 *
 * @author Evan W. Patton (ewpatton@mit.edu)
 */
@DesignerComponent(version = 1, nonVisible = true, category = ComponentCategory.EXTENSION)
@SimpleObject(external = true)
public class DynamicComponents extends AndroidNonvisibleComponent implements OnClearListener {

  /**
   * A mapping of component names to the component instances they represent.
   */
  private final Map<String, Component> components = new HashMap<>();

  /**
   * A mapping of qualified event name symbols to the implementation of the corresponding handler.
   */
  private final Map<Symbol, EventHandler> eventHandlers = new HashMap<>();

  /**
   * The {@code EventHandler} class provides the basis for dispatching an event. In the normal
   * operation of App Inventor, the Scheme code would generate a lambda function that would be
   * invoked during the event handling.
   */
  class EventHandler extends ProcedureN {

    /**
     * The component to which the event handler is bound.
     */
    private final Component component;

    /**
     * The name of the event to which this handler corresponds.
     */
    private final String name;

    EventHandler(Component component, String name) {
      this.component = component;
      this.name = name;
    }

    @Override
    public Object applyN(Object[] objects) {
      EventFired(component, name, Arrays.asList(objects));
      return Values.empty;
    }
  }

  public DynamicComponents(Form form) {
    super(form);
  }

  ///region Methods

  /**
   * Creates a new component with the given name of the given type.
   *
   * @param type the type for the component, either an unqualified name in the App Inventor runtime
   *             or a fully qualified name for an extension.
   * @param name the name of the component, as the user might call it.
   * @return an instance of the component type, if the type exists and is a valid component type.
   */
  @SimpleFunction
  public Component CreateComponent(String type, String name) {
    if (components.containsKey(name)) {
      return components.get(name);
    }
    if (type.indexOf('.') == -1) {
      type = "com.google.appinventor.components.runtime." + type;
    }
    Component result = makeComponent(type);
    getFormEnvironment().put(new SimpleSymbol(name), result);
    components.put(name, result);
    return result;
  }

  /**
   * Registers a handler for the given component name and the given event name. Returns true if
   * the event was registered successfully, otherwise false.
   *
   * @param componentName the component name
   * @param eventName the event name
   * @return true if the event was registered successfully, otherwise false.
   */
  @SimpleFunction
  public boolean RegisterForEvent(String componentName, String eventName) {
    Component component = components.get(componentName);
    if (component == null) {
      return false;
    }
    Symbol eventSymbol = SimpleSymbol.valueOf(componentName + "$" + eventName);
    if (eventHandlers.containsKey(eventSymbol)) {
      return false;
    }
    Environment environment = getFormEnvironment();
    EventHandler h = new EventHandler(component, eventName);
    eventHandlers.put(eventSymbol, h);
    environment.put(eventSymbol, h);
    EventDispatcher.registerEventForDelegation(form, componentName, eventName);
    return true;
  }

  ///endregion

  ///region Events

  /**
   * The EventFired event is raised when a registered event handler fires of the given event name
   * for the given component previously registered with RegisterForEvent.
   *
   * @param component the component that raised the event
   * @param event the name of the event raised
   * @param arguments any additional arguments related to the event. this will always be a list,
   *                  but the contents will vary from event to event.
   */
  @SimpleEvent
  public void EventFired(Component component, String event, List<Object> arguments) {
    EventDispatcher.dispatchEvent(this, "EventFired", component, event, arguments);
  }

  ///endregion

  ///region OnClearListener implementation

  @Override
  public void onClear() {
    components.clear();
  }

  ///endregion

  ///region Utilities

  /**
   * Utility function to create a new component of the given type.
   *
   * @param type the type of the component to create
   * @return an instance of the component type
   * @throws IllegalArgumentException if type does not implement Component
   * @throws IllegalStateException if the type does not represent a Java Class object
   */
  private Component makeComponent(String type) {
    try {
      Class<?> t = Class.forName(type);
      if (!Component.class.isAssignableFrom(t)) {
        throw new IllegalArgumentException(type + " does not correspond to a Component type");
      }
      for (Constructor<?> constructor : t.getConstructors()) {
        Class<?>[] parameters = constructor.getParameterTypes();
        if (parameters.length == 1 && parameters[0].isAssignableFrom(Form.class)) {
          return (Component) constructor.newInstance(form);
        }
      }
    } catch (ReflectiveOperationException e) {
      throw new IllegalStateException("Unable to instantiate component of type " + type, e);
    }
    throw new IllegalStateException("Unable to instantiate component of type " + type);
  }

  /**
   * Gets the form environment via reflection.
   *
   * @return the form environment
   * @throws IllegalStateException if the form's environment cannot be retrieved via reflection
   */
  private Environment getFormEnvironment() {
    try {
      Field f = form.getClass().getField("form$Mnenvironment");
      return (Environment) f.get(form);
    } catch (ReflectiveOperationException e) {
      throw new IllegalStateException("Unable to get form environment", e);
    }
  }

  ///endregion
}

And here is an example of how you could create a Clock and listen for its Timer event:

Note that I've only tested this in the companion. In theory it should work in compiled apps as well but I make no guarantees.

6 Likes

Really thanks @ewpatton! I am very exited to test it! Can you please post the project file if possible?

EventTest.aia (10.3 KB)

3 Likes

It works perfect! Really thanks for spending time to look into it! This was what I was waiting for! :smiling_face_with_three_hearts:

This works in the compiled app too!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.