• HelpOpenFL
  • A simple example of passing data in StackNavigator

You need to pass the name submitted by the user when you dispatch the event form_submitHandler in RegistrationPage:

private function form_submitHandler(event:FormEvent):Void 
{
    if(textInput.text.trim()=="") textInput.errorString="Empty";
    else dispatchEvent(new Event(Event.CHANGE));
}

So, you'd create a subclass of Event, similar to ContactEvent in stack-navigator-pass-data-between-views, and dispatch it there instead of new Event(Event.CHANGE). I'll leave creating that event subclass up to you. Let's assume that you call it something like RegistrationEvent.

Then, you need to pass the name from the RegistrationEvent to the HomePage, which you'd do by modifying the event map here:

var registrationPage = StackItem.withClass(RegistrationPage.ID, RegistrationPage, [
	Event.CHANGE => Push(HomePage.ID)
]);

Similar to stack-navigator-pass-data-between-views, you'd use NewAction action to create a custom Push action that includes passing the data to HomePage

var registrationPage = StackItem.withClass(RegistrationPage.ID, RegistrationPage, [
	RegistrationEvent.REGISTER => NewAction((event:RegistrationEvent) -> {
		var name = event.name;
		return Push(HomePage.ID, (target: HomePage) -> {
			target. registeredName = name;
		});
	})
]);

You'll need to modify HomePage to add a new property to store the name, which might be called something like registeredName.

public var registeredName(default, set):String = null;

private function set_registeredName(value:String):String {
	if (this.registeredName == value) {
		return this.registeredName;
	}
	this.registeredName = value;
	this.setInvalid(InvalidationFlag.DATA);
	return this.registeredName;
}

Then, you can access the name in an override of update in HomePage.

Thanks for reply. So here is the code for ContactEvent class:

package events;

import valueObjects.Contact;
import openfl.events.Event;

class ContactEvent extends Event {
	public static final REQUEST_CONTACT:String = "requestContact";
	public static final CHOOSE_CONTACT:String = "chooseContact";

	public function new(type:String, ?contact:Contact) {
		super(type, false, false);
		this.contact = contact;
	}

	public var contact:Contact;
}

Now I need to create RegistrationEvent similar to this. I have some questions in regard to this code:

  1. It is importing the Contact class. Do I need to import the Registration class similar to this?
  2. Two constants namely REQUEST_CONTACT and CHOOSE_CONTACT have been defined inside this class. Do I need to define similar constants like REQUEST_REGISTRATION and CHOOSE_REGISTRATION inside my own class?
  3. Why the first argument passed to the new function is type? From where its value will come?
  4. Why we need to pass three arguments to super function?

Please clarify these points and then I will define the class RegistrationEvent.

  1. It depends on how you want to store the data. There is no Registration class unless you create it yourself. If you only store the name, you can probably just use String. If you add other fields to the registration form in the future, then it might make sense to create a Registration class to store all of them in a single object.
  2. You need to define at least one constant for your custom event. In stack-navigator-pass-data-between-views, two constants were needed. One for requesting a contact from ComposeMessageView and one for returning the selected contact from ChooseContactView. In my example code from the previous reply, you'll notice that I referenced a hypothetical REGISTER constant that you might create for your custom event. That might be the only constant you need. That's for you to determine based on the needs of your app.
  3. The value of type comes from the constants defined by your custom event. So, if you create a REGISTER constant, that's what you'd pass to the constructor: new RegisterEvent(RegisterEvent.REGISTER, /* possibly more arguments here */).
  4. All events extend openfl.events.Event, and its constructor has three arguments in its signature. The event type, whether the event bubbles on the display list, and whether the event may be cancelled with preventDefault(). In most of the custom events I've created over the years, I usually pass false for both bubbles and cancelable.

If you're not very familiar with OpenFL's event system, you may also want to read through OpenFL Developer’s Guide: Chapter 1: Handling Events.

OK. I have added two more fields to my registration form - for email and password. So my registration form is having three variables:
nameInput
emailInput
passwordInput

Then I have defined a class with name Registration whose code is as follows:

package objects;

class Registration
{
    public function new(name:String, email:String, password:String)
    {
        this.name=name;
        this.email=email;
        this.password=password;
    }
    public var name:String;
    public var email:String;
    public var password:String;
}

As I have placed this code inside the objects folder and so I have used package objects; at the top of the code.

Then I have defined RegistrationEvent class whose code is as follows:

package events;

import objects.Registration;
import openfl.events.Event;

class RegistrationEvent extends Event {
	public static final REGISTER:String = "register";

	public function new(type:String, ?registration:Registration) {
		super(type, false, false);
		this.registration = registration;
	}

	public var registration:Registration;
}

As I have placed this code inside the events folder and so I written package events; in the beginning of the code.

Then I have used this RegistrationEvent class with the StackItem.withClass function as follows:

		var registrationPage = StackItem.withClass(RegistrationPage.ID, RegistrationPage, [
			RegistrationEvent.REGISTER => NewAction((event:RegistrationEvent) -> {
				var registration = event.registration;
				return Push(HomePage.ID, (target: HomePage) -> {
					target.registeredRegistration = registration;
				});
			})
		]);

Here I would like to know what is the role of NewAction function? Can you please explain it?

Then inside the HomePage class I have added the following code:

	public var registeredRegistration(default, set):Registration = null;

	private function set_registeredRegistration(registration:Registration):Registration {
		if (this.registeredRegistration == registration) {
			return this.registeredRegistration;
		}
		this.registeredRegistration = registration;
		this.setInvalid(InvalidationFlag.DATA);
		return this.registeredRegistration;
	}

Here my question is when the variable registeredRegistration is defined then why we writing it as registeredRegistration(default, set)? What is the meaning of default and set in this case?

Furthermore what is meaning of the following code:

this.setInvalid(InvalidationFlag.DATA);

Why it is setting it as invalid?

Please reply to my these three questions and then I will move further.

    Hi @joshtynjala
    Can you please clarify my three points as written in previous post? Here are the points:

    1. What is the role of NewAction function?
    2. What is the meaning of InvalidationFlag.DATA? From where it is coming?
    3. Why it is being set as Invalid?

    I am waiting for your reply.

    1. NewAction is one of the possible values on the StackAction enum. It's purpose is to select one of the other actions, such as Push or Pop, using data stored on the event that the view dispatched. In this case, it's being used to create a Push action that includes passing the value of event.registration to the new view.
    2. InvalidationFlag is an enum used by Feathers UI components to indicate that something needs to be updated before the component is rendered again. There are a number of possible values, like DATA, STYLES, SELECTION, etc. to help ensure that the component updates only what it needs to update, which can help with performance.
    3. When a property of a UI component changes, the UI component should call setInvalid(). When a UI component is invalid, its update() method will be called automatically before the next frame is rendered by OpenFL. For more detail, check out Feathers UI: UI Component Lifecycle in the documentation.

    ccpplinux when the variable registeredRegistration is defined then why we writing it as registeredRegistration(default, set)? What is the meaning of default and set in this case?

    The default means that there is no get_registeredRegistration getter function (otherwise, it would be get instead). The set means that there is a set_registeredRegistration function.

    See Haxe Documentation: Property for more details.

    Thanks for the clarifications. In between, I have added some code to my program. Now when the flow of control is going on the Home Page then the text of label is being updated from "Home Page" to "Welcome Hello". But I also want to display the name, email and password submitted through the registration form on the home page. And I am facing problem in this. When I using the following code:

    this.label.text = "Welcome Hello "+this.registeredRegistration.name;

    then I am getting following error:

    Uncaught TypeError: Cannot read properties of null (reading 'name')

    This implies that the value of this.registeredRegistration is null. But why it is null?

    Here is my code:

    import objects.Registration;
    import events.RegistrationEvent;
    import feathers.controls.Application;
    import feathers.controls.Label;
    import feathers.controls.navigators.StackNavigator;
    import feathers.controls.LayoutGroup;
    import feathers.controls.navigators.StackItem;
    import openfl.events.Event;
    import feathers.events.TriggerEvent;
    import feathers.controls.Button;
    import feathers.layout.VerticalLayout;
    import feathers.controls.Form;
    import feathers.controls.FormItem;
    import feathers.controls.TextInput;
    import feathers.events.FormEvent;
    import feathers.skins.RectangleSkin;
    import feathers.core.InvalidationFlag;
    
    using StringTools;
    
    class RegistrationForm extends Application 
    {
    	public function new() 
    	{
    		super();
    
    		var navigator = new StackNavigator();
    		/*
    		var registrationPage = StackItem.withClass(RegistrationPage.ID, RegistrationPage, [
    			Event.CHANGE => Push(HomePage.ID)]);
    		*/
    
    		var registrationPage = StackItem.withClass(RegistrationPage.ID, RegistrationPage, [
    			RegistrationEvent.REGISTER => NewAction((event:RegistrationEvent) -> {
    				var registration = event.registration;
    				return Push(HomePage.ID, (target: HomePage) -> {
    					target.registeredRegistration = registration;
    				});
    			})
    		]);
    
    		navigator.addItem(registrationPage);
    		
    		var homePage = StackItem.withClass(HomePage.ID, HomePage, [
    			//Event.COMPLETE => Pop()
    			//Event.COMPLETE => Push(ViewA.ID)
    			Event.CHANGE => Push(RegistrationPage.ID)]);
    		navigator.addItem(homePage);
    		
    		navigator.rootItemID = RegistrationPage.ID;
    		addChild(navigator);
    	}
    }
    class RegistrationPage extends LayoutGroup 
    {
        public static final ID = "registration-page";
    	public var nameInput:TextInput;
    	public var emailInput:TextInput;
    	public var passwordInput:TextInput;
    
    	public var registeredRegistration(default, set):Registration = null;
    
    	private function set_registeredRegistration(registration:Registration):Registration {
    		if (this.registeredRegistration == registration) {
    			return this.registeredRegistration;
    		}
    		this.registeredRegistration = registration;
    		this.setInvalid(InvalidationFlag.DATA);
    		return this.registeredRegistration;
    	}
    
        public function new() 
    	{
            super();
            layout = new VerticalLayout();
    
    		var form = new Form();
    
    		var nameItem = new FormItem();
    		nameItem.text = "Name:";
    		nameInput = new TextInput();
    		nameInput.prompt = "Your name:";
    		nameInput.autoSizeWidth = true;
    		nameItem.content = nameInput;
    		nameItem.required = true;
    		form.addChild(nameItem);
    
    		nameItem.textPosition = LEFT;
    		nameItem.paddingTop = 5.0;
    		nameItem.paddingRight = 8.0;
    		nameItem.paddingBottom = 5.0;
    		nameItem.paddingLeft = 8.0;
    		nameItem.gap = 10.0;
    		
    		var emailItem = new FormItem();
    		emailItem.text = "Email:";
    		emailInput = new TextInput();
    		emailInput.prompt = "Your email:";
    		emailInput.autoSizeWidth = true;
    		emailItem.content = emailInput;
    		emailItem.required = true;
    		form.addChild(emailItem);
    
    		emailItem.textPosition = LEFT;
    		emailItem.paddingTop = 5.0;
    		emailItem.paddingRight = 8.0;
    		emailItem.paddingBottom = 5.0;
    		emailItem.paddingLeft = 8.0;
    		emailItem.gap = 10.0;
    
    		var passwordItem = new FormItem();
    		passwordItem.text = "Password:";
    		passwordInput = new TextInput();
    		passwordInput.prompt = "Your password:";
    		passwordInput.autoSizeWidth = true;
    		passwordInput.displayAsPassword = true;
    		passwordItem.content = passwordInput;
    		passwordItem.required = true;
    		form.addChild(passwordItem);
    
    		passwordItem.textPosition = LEFT;
    		passwordItem.paddingTop = 5.0;
    		passwordItem.paddingRight = 8.0;
    		passwordItem.paddingBottom = 5.0;
    		passwordItem.paddingLeft = 8.0;
    		passwordItem.gap = 10.0;
    
    		var sendButton = new Button();
    		sendButton.text = "Register";
    		form.addChild(sendButton);
    		form.submitButton = sendButton;
    
    		var skin = new RectangleSkin();
    		skin.border = SolidColor(1.0, 0x999999);
    		skin.fill = SolidColor(0xcccccc);
    		skin.width = 16.0;
    		skin.height = 16.0;
    		form.backgroundSkin = skin;
    
    		form.addEventListener(FormEvent.SUBMIT, form_submitHandler);
    
    		addChild(form);
        }
    
        private function form_submitHandler(event:FormEvent):Void 
    	{
            if(nameInput.text.trim()=="" || emailInput.text.trim()=="" || passwordInput.text.trim()=="")
    		{
    			if(nameInput.text.trim()=="") nameInput.errorString="Empty";
    			if(emailInput.text.trim()=="") emailInput.errorString="Empty";
    			if(passwordInput.text.trim()=="") passwordInput.errorString="Empty";
    		}
    		else dispatchEvent(new RegistrationEvent(RegistrationEvent.REGISTER, this.registeredRegistration));
        }
    }
    class HomePage extends LayoutGroup 
    {
        public static final ID = "home-page";
    
    	public var registeredRegistration(default, set):Registration = null;
    
    	private function set_registeredRegistration(registration:Registration):Registration {
    		if (this.registeredRegistration == registration) {
    			return this.registeredRegistration;
    		}
    		this.registeredRegistration = registration;
    		this.setInvalid(InvalidationFlag.DATA);
    		return this.registeredRegistration;
    	}
    
    	private var label:Label;
    
        public function new() 
    	{
            super();
        }
    
    	override private function initialize():Void 
    	{
    		super.initialize();
    
            layout = new VerticalLayout();
    
            this.label = new Label();
            this.label.text = "Home Page";
            this.addChild(this.label);
    
            var button = new Button();
            button.text = "Go To Registration Page";
            button.addEventListener(TriggerEvent.TRIGGER, button_triggerHandler);
            this.addChild(button);
    
    	}
    
    	override private function update():Void {
    		var dataInvalid = this.isInvalid(InvalidationFlag.DATA);
    		trace(dataInvalid);
    		if (dataInvalid) {
    			this.label.text = "Welcome Hello "+this.registeredRegistration.name;
    			//this.label.text = "Welcome Hello ";
    		}
    
    		super.update();
    	}
    
    
        private function button_triggerHandler(event:TriggerEvent):Void 
    	{
            //dispatchEvent(new Event(Event.COMPLETE));
    		dispatchEvent(new Event(Event.CHANGE));
        }
    }

    I can't seem to find any part of your code that calls new Registration() and populates the values. Perhaps you meant to do that in form_submitHandler?

    Thanks a LOT for giving me a hint about creating new object of Registration class in the form_submitHandler. Now the code of form_submitHandler is as follows:

    private function form_submitHandler(event:FormEvent):Void 
    {
        if(nameInput.text.trim()=="" || emailInput.text.trim()=="" || passwordInput.text.trim()=="")
    	{
    		if(nameInput.text.trim()=="") nameInput.errorString="Empty";
    		if(emailInput.text.trim()=="") emailInput.errorString="Empty";
    		if(passwordInput.text.trim()=="") passwordInput.errorString="Empty";
    	}
    	else
    	{
    		this.registeredRegistration = new Registration(nameInput.text, emailInput.text, passwordInput.text);
    		
    		dispatchEvent(new RegistrationEvent(RegistrationEvent.REGISTER, this.registeredRegistration));
    	}
    }

    And I glad to let you know that the value entered through form are being transferred to home page. This is really a great achievement for me. I am very much thankful to you for the helping me so far.

    One thing more. Following code is written in both classes - RegistrationPage and HomePage:

    public var registeredRegistration(default, set):Registration = null;
    
    private function set_registeredRegistration(registration:Registration):Registration {
    	if (this.registeredRegistration == registration) {
    		return this.registeredRegistration;
    	}
    	this.registeredRegistration = registration;
    	this.setInvalid(InvalidationFlag.DATA);
    	return this.registeredRegistration;
    }

    So when we need to establish communication between two classes then we need to write same code in both classes? Is there any way that we write this code only one time both classes make use of them?

      Furthermore when I tried to convert it to Android application then after running the command openfl build android I received the following error message:

      Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
      Use '--warning-mode all' to show the individual deprecation warnings.
      See https://docs.gradle.org/5.6.3/userguide/command_line_interface.html#sec:command_line_warnings

      BUILD SUCCESSFUL in 21s
      53 actionable tasks: 53 executed

      Although the build was successful but with some warning.

      When I installed the apk file on mobile phone then after starting it, I am getting following error message:

      SDL Error
      
      An error occurred
      while trying to start the
      application. Please try
      again and/or reinstall.
      
      Error: dlopen failed: cannot
      locate symbol "__atomic
      _compare_exchange_4"
      referenced by "/data/app/
      ~~00StYuOpjArYIYKmNvZ
      YZg==/com.example.Regis
      trationForm-jouBTAKoHkV
      ytMB17b5dUQ==/lib/arm/
      libApplicationMain.so"...

      Can you please help me in fixing these errors?

        ccpplinux Although the build was successful but with some warning.

        This warning is safe to ignore.

        ccpplinux When I installed the apk file on mobile phone then after starting it, I am getting following error message:

        Which versions of the Android NDK do you have installed? Which model of Android phone are you testing with?

          ccpplinux One thing more. Following code is written in both classes - RegistrationPage and HomePage:
          So when we need to establish communication between two classes then we need to write same code in both classes? Is there any way that we write this code only one time both classes make use of them?

          Looking over your code, RegistrationPage probably does not need a public registeredRegistration property because you never set this value from outside of RegistrationPage. I think that you could remove it and then change your RegistrationEvent dispatch to look like this instead:

          var registeredRegistration = new Registration(nameInput.text, emailInput.text, passwordInput.text);
          		
          dispatchEvent(new RegistrationEvent(RegistrationEvent.REGISTER, registeredRegistration));

            joshtynjala OK. I have removed the extra code from RegistrationPage class and changed the code of RegistrationEvent dispatch as per your suggestions. And it is working fine. Thanks for suggestion.

            joshtynjala Which versions of the Android NDK do you have installed? Which model of Android phone are you testing with?

            My Android NDK version is 21.4.7075529. I am using a Samsung Galaxy M13 5G phone. It is having Android version 13.

              Actually when I am running the command openfl test html5 then I also getting lot of warning message like below:

              /usr/share/haxe/lib/openfl/9,2,0/src/openfl/display/_internal/IBitmapDrawableType.hx:3: characters 1-7 : Warning : (WDeprecated) @:enum abstract is deprecated in favor of enum abstract
              /usr/share/haxe/lib/openfl/9,2,0/src/openfl/display3D/Context3DMipFilter.hx:12: characters 1-7 : Warning : (WDeprecated) @:enum abstract is deprecated in favor of enum abstract
              /usr/share/haxe/lib/openfl/9,2,0/src/openfl/display3D/Context3DTextureFilter.hx:12: characters 1-7 : Warning : (WDeprecated) @:enum abstract is deprecated in favor of enum abstract

              To hide these warning messages I have written the following code in project.xml:

              <haxedef name="no-deprecation-warnings" unless="debug" />

              Then no such warning are appearing after running the command openfl test html5.

              Is there effect of this on Android APK file generation?

              The @:enum abstract warnings are new in Haxe 4.3. They are safe to ignore. However, you can get rid of them by updating to Lime 8.0.2 and OpenFL 9.2.2, which we just recently released.

              ccpplinux When I installed the apk file on mobile phone then after starting it, I am getting following error message:

              I've never encountered this error message before. I can't seem to find any info on Google either.

              ccpplinux BUILD SUCCESSFUL in 21s

              This seems very fast. Perhaps you should try a clean build to ensure that nothing is cached in a bad state.

              openfl build android -clean

              ccpplinux My Android NDK version is 21.4.7075529.

              This is the same NDK that I typically use. On rare occassions, I have found that I need to use 15.2.4203891 instead.

              I have updated feathersui. But after that I am not able to create a new project. When I am creating new project then I am getting following error message:

              /usr/share/haxe/lib/hxargs/3,0,2/hxargs/Args.hx:92: characters 14-16 : Reification $v is not allowed outside of macro expression

              What could be the reason? How to fix it? Please help me.