{"action":"created","issue":{"url":"https://api.github.com/repos/codenameone/CodenameOne/issues/2472","repository_url":"https://api.github.com/repos/codenameone/CodenameOne","labels_url":"https://api.github.com/repos/codenameone/CodenameOne/issues/2472/labels{/name}","comments_url":"https://api.github.com/repos/codenameone/CodenameOne/issues/2472/comments","events_url":"https://api.github.com/repos/codenameone/CodenameOne/issues/2472/events","html_url":"https://github.com/codenameone/CodenameOne/issues/2472","id":339297864,"node_id":"MDU6SXNzdWUzMzkyOTc4NjQ=","number":2472,"title":"TEST CASE for UIBinding BUG and Validator BUG","user":{"login":"jsfan3","id":1997316,"node_id":"MDQ6VXNlcjE5OTczMTY=","avatar_url":"https://avatars3.githubusercontent.com/u/1997316?v=4","gravatar_id":"","url":"https://api.github.com/users/jsfan3","html_url":"https://github.com/jsfan3","followers_url":"https://api.github.com/users/jsfan3/followers","following_url":"https://api.github.com/users/jsfan3/following{/other_user}","gists_url":"https://api.github.com/users/jsfan3/gists{/gist_id}","starred_url":"https://api.github.com/users/jsfan3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsfan3/subscriptions","organizations_url":"https://api.github.com/users/jsfan3/orgs","repos_url":"https://api.github.com/users/jsfan3/repos","events_url":"https://api.github.com/users/jsfan3/events{/privacy}","received_events_url":"https://api.github.com/users/jsfan3/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"shannah","id":2677562,"node_id":"MDQ6VXNlcjI2Nzc1NjI=","avatar_url":"https://avatars3.githubusercontent.com/u/2677562?v=4","gravatar_id":"","url":"https://api.github.com/users/shannah","html_url":"https://github.com/shannah","followers_url":"https://api.github.com/users/shannah/followers","following_url":"https://api.github.com/users/shannah/following{/other_user}","gists_url":"https://api.github.com/users/shannah/gists{/gist_id}","starred_url":"https://api.github.com/users/shannah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shannah/subscriptions","organizations_url":"https://api.github.com/users/shannah/orgs","repos_url":"https://api.github.com/users/shannah/repos","events_url":"https://api.github.com/users/shannah/events{/privacy}","received_events_url":"https://api.github.com/users/shannah/received_events","type":"User","site_admin":false},"assignees":[{"login":"shannah","id":2677562,"node_id":"MDQ6VXNlcjI2Nzc1NjI=","avatar_url":"https://avatars3.githubusercontent.com/u/2677562?v=4","gravatar_id":"","url":"https://api.github.com/users/shannah","html_url":"https://github.com/shannah","followers_url":"https://api.github.com/users/shannah/followers","following_url":"https://api.github.com/users/shannah/following{/other_user}","gists_url":"https://api.github.com/users/shannah/gists{/gist_id}","starred_url":"https://api.github.com/users/shannah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shannah/subscriptions","organizations_url":"https://api.github.com/users/shannah/orgs","repos_url":"https://api.github.com/users/shannah/repos","events_url":"https://api.github.com/users/shannah/events{/privacy}","received_events_url":"https://api.github.com/users/shannah/received_events","type":"User","site_admin":false}],"milestone":{"url":"https://api.github.com/repos/codenameone/CodenameOne/milestones/12","html_url":"https://github.com/codenameone/CodenameOne/milestone/12","labels_url":"https://api.github.com/repos/codenameone/CodenameOne/milestones/12/labels","id":2611363,"node_id":"MDk6TWlsZXN0b25lMjYxMTM2Mw==","number":12,"title":"Version 5.0","description":"","creator":{"login":"codenameone","id":2353641,"node_id":"MDQ6VXNlcjIzNTM2NDE=","avatar_url":"https://avatars1.githubusercontent.com/u/2353641?v=4","gravatar_id":"","url":"https://api.github.com/users/codenameone","html_url":"https://github.com/codenameone","followers_url":"https://api.github.com/users/codenameone/followers","following_url":"https://api.github.com/users/codenameone/following{/other_user}","gists_url":"https://api.github.com/users/codenameone/gists{/gist_id}","starred_url":"https://api.github.com/users/codenameone/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codenameone/subscriptions","organizations_url":"https://api.github.com/users/codenameone/orgs","repos_url":"https://api.github.com/users/codenameone/repos","events_url":"https://api.github.com/users/codenameone/events{/privacy}","received_events_url":"https://api.github.com/users/codenameone/received_events","type":"User","site_admin":false},"open_issues":47,"closed_issues":34,"state":"open","created_at":"2017-06-29T05:12:38Z","updated_at":"2018-07-11T15:40:29Z","due_on":"2018-07-17T07:00:00Z","closed_at":null},"comments":4,"created_at":"2018-07-09T04:02:41Z","updated_at":"2018-07-12T04:04:12Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"In my previous bug reportings (https://github.com/codenameone/CodenameOne/issues/2470 and https://github.com/codenameone/CodenameOne/issues/2471) I didn't provide an easy to check test case.\r\n\r\nIn this post I provide one test case that demostrates both bugs (on Android 7).\r\n\r\nFirst of all, let's start with two screenshots.\r\n\r\nScreenshot 1: I entered the following data. You can note that the Validator doesn't work properly because the \"FieldFirstName\" is not marked as invalid (it requires at least two chars to be valid):\r\n![screenshot_20180709-054406](https://user-images.githubusercontent.com/1997316/42429939-68e61146-833c-11e8-8cc5-a4806787a238.png)\r\n\r\nScreenshot 2: this dialog (opened tapping the \"ShowProperties\" in the toolbar after entering the data in the previous screenshot) shows that the \"firstName\" property is \"null\"!!! Instead that property should contain the text \"F\" previously entered.\r\n\r\nCode to do the test to replicate these bugs:\r\n\r\n```\r\npackage [...];\r\n\r\nimport com.codename1.components.SpanLabel;\r\nimport static com.codename1.ui.CN.*;\r\nimport com.codename1.ui.Form;\r\nimport com.codename1.ui.Dialog;\r\nimport com.codename1.ui.plaf.UIManager;\r\nimport com.codename1.ui.util.Resources;\r\nimport com.codename1.io.Log;\r\nimport com.codename1.properties.BooleanProperty;\r\nimport com.codename1.properties.ListProperty;\r\nimport com.codename1.properties.LongProperty;\r\nimport com.codename1.properties.Property;\r\nimport com.codename1.properties.PropertyBusinessObject;\r\nimport com.codename1.properties.PropertyIndex;\r\nimport com.codename1.properties.UiBinding;\r\nimport com.codename1.ui.Button;\r\nimport com.codename1.ui.Toolbar;\r\nimport com.codename1.ui.layouts.BoxLayout;\r\nimport com.codename1.ui.Container;\r\nimport com.codename1.ui.FontImage;\r\nimport com.codename1.ui.InputComponent;\r\nimport com.codename1.ui.PickerComponent;\r\nimport com.codename1.ui.TextArea;\r\nimport com.codename1.ui.TextComponent;\r\nimport com.codename1.ui.TextField;\r\nimport com.codename1.ui.layouts.TextModeLayout;\r\nimport com.codename1.ui.validation.Constraint;\r\nimport com.codename1.ui.validation.Validator;\r\nimport java.util.Calendar;\r\nimport java.util.Date;\r\n\r\n/**\r\n * This file was generated by <a href=\"https://www.codenameone.com/\">Codename\r\n * One</a> for the purpose of building native mobile applications using Java.\r\n */\r\npublic class UIBindingBug {\r\n\r\n    private Form current;\r\n    private Resources theme;\r\n    // Data stored in PropertyBusinessObjects\r\n    public static User me = new User();\r\n\r\n    public void init(Object context) {\r\n\r\n        // Properties registerExternalizable\r\n        me.getPropertyIndex().registerExternalizable();\r\n\r\n        // use two network threads instead of one\r\n        updateNetworkThreadCount(2);\r\n\r\n        theme = UIManager.initFirstTheme(\"/theme\");\r\n\r\n        // Enable Toolbar on all Forms by default\r\n        Toolbar.setGlobalToolbar(true);\r\n\r\n        // Pro only feature\r\n        Log.bindCrashProtection(true);\r\n\r\n        addNetworkErrorListener(err -> {\r\n            // prevent the event from propagating\r\n            err.consume();\r\n            if (err.getError() != null) {\r\n                Log.e(err.getError());\r\n            }\r\n            Log.sendLogAsync();\r\n            Dialog.show(\"Connection Error\", \"There was a networking error in the connection to \" + err.getConnectionRequest().getUrl(), \"OK\", null);\r\n        });\r\n\r\n    }\r\n\r\n    public void start() {\r\n        if (current != null) {\r\n            current.show();\r\n            return;\r\n        }\r\n\r\n        Form inputForm = new Form(\"Input\", BoxLayout.y());\r\n        TextModeLayout tl = new TextModeLayout(7, 1);\r\n        Container userInputCnt = new Container(tl);\r\n        userInputCnt.setUIID(\"MyContainer01\");\r\n\r\n        TextComponent firstName = new TextComponent().label(\"FieldFirstName\");\r\n        firstName.getField().setConstraint(TextArea.INITIAL_CAPS_WORD);\r\n\r\n        TextComponent familyName = new TextComponent().label(\"FieldFamilyName\");\r\n        familyName.getField().setConstraint(TextArea.INITIAL_CAPS_WORD);\r\n\r\n        PickerComponent birthday = PickerComponent.createDate(new Date()).label(\"PickerBirthday\");\r\n        birthday.getPicker().setUseLightweightPopup(true);\r\n\r\n        PickerComponent gender = PickerComponent.createStrings(me.genders).label(\"Gender\");\r\n        gender.getPicker().setUseLightweightPopup(true);\r\n\r\n        TextComponent email = new TextComponent().label(\"FieldEmail\");\r\n        email.getField().setConstraint(TextField.EMAILADDR);\r\n\r\n        TextComponent password = new TextComponent().label(\"FieldPassword\");\r\n        password.getField().setConstraint(TextField.PASSWORD);\r\n\r\n        TextComponent pswConfirm = new TextComponent().label(\"FieldConfirmPassword\");\r\n        pswConfirm.getField().setConstraint(TextField.PASSWORD);\r\n\r\n        InputComponent.group(firstName, familyName, birthday, gender, email, password, pswConfirm);\r\n        userInputCnt.addAll(firstName, familyName, birthday, gender, email, password, pswConfirm);\r\n\r\n        inputForm.add(userInputCnt);\r\n        Button createAccount = new Button(\"createAccount\");\r\n        inputForm.add(createAccount);\r\n\r\n        Validator val = new Validator();\r\n        val.addConstraint(firstName, ValidatorUtils.getStringLenghtConstraint(2));\r\n        val.addConstraint(familyName, ValidatorUtils.getStringLenghtConstraint(2));\r\n        val.addConstraint(birthday, ValidatorUtils.get13yearsConstraint());\r\n        val.addConstraint(email, ValidatorUtils.getEmailConstraint());\r\n        val.addConstraint(password, ValidatorUtils.getStringLenghtConstraint(8));\r\n        val.addConstraint(pswConfirm, ValidatorUtils.confirmPassword(password));\r\n        val.addSubmitButtons(createAccount);\r\n\r\n        Form hi = new Form(\"Hi World\", BoxLayout.y());\r\n        Button myButton = new Button(\"Tap here\");\r\n        myButton.addActionListener(l -> {\r\n            inputForm.show();\r\n        });\r\n        hi.add(myButton);\r\n        hi.show();\r\n\r\n        // Add back command\r\n        inputForm.setBackCommand(\r\n                inputForm.getToolbar().addCommandToLeftBar(\"Back\", FontImage.createMaterial(FontImage.MATERIAL_ARROW_BACK, \"Button\", 3), ev -> {\r\n                    hi.showBack();\r\n                }));\r\n        \r\n        // Add show Properties command\r\n        inputForm.getToolbar().addCommandToRightBar(\"ShowProperties\", null, ev -> {\r\n                    Dialog.show(\"PropertyBusinessObject\", me.getPropertyIndex().toString(), \"OK\", \"Cancel\");\r\n                });\r\n\r\n        // Bind the text fields to the User object\r\n        UiBinding uib = new UiBinding();\r\n        uib.bind(me.firstName, firstName.getField());\r\n        uib.bind(me.familyName, familyName.getField());\r\n        uib.bind(me.birthdayDate, birthday.getPicker());\r\n        uib.bind(me.gender, gender.getPicker());\r\n        uib.bind(me.email, email.getField());\r\n        uib.bind(me.password, password.getField());\r\n\r\n    }\r\n\r\n    public void stop() {\r\n        current = getCurrentForm();\r\n        if (current instanceof Dialog) {\r\n            ((Dialog) current).dispose();\r\n            current = getCurrentForm();\r\n        }\r\n    }\r\n\r\n    public void destroy() {\r\n    }\r\n\r\n    /**\r\n     * Save the current User object \"me\" to the Storage\r\n     */\r\n    public static void saveUser() {\r\n        writeObjectToStorage(\"MyUser\", me);\r\n        Log.p(\"User object written to the Storage\");\r\n    }\r\n\r\n    /**\r\n     * Restore the User object \"me\" from the Storage, if it's possible. Returns\r\n     * true if the operation successes, otherwise false (that means that no User\r\n     * object was previously saved).\r\n     *\r\n     * @return true if the User object \"me\" is restored, false if the operation\r\n     * fails (in that case the value of \"me\" is not changed)\r\n     */\r\n    public static boolean restoreUser() {\r\n        Object objUser = readObjectFromStorage(\"MyUser\");\r\n        if (objUser != null && objUser instanceof User) {\r\n            me = (User) objUser;\r\n            Log.p(\"User object restored from the Storage\");\r\n            return true;\r\n        } else {\r\n            Log.p(\"User object cannot be restored from the Storage\");\r\n            return false;\r\n        }\r\n    }\r\n\r\n}\r\n\r\nclass User implements PropertyBusinessObject {\r\n\r\n    public static final String[] genders = {\"Male\", \"Female\", \"Other\", \"Unknown\"};\r\n\r\n    public final Property<String, User> id = new Property<>(\"id\");\r\n    public final Property<String, User> mainMacroFigure = new Property<>(\"mainMacroFigure\");\r\n    public final Property<String, User> secondMacroFigure = new Property<>(\"secondMacroFigure\");\r\n    public final Property<String, User> personFigure = new Property<>(\"personFigure\");\r\n    public final Property<String, User> organizationFigure = new Property<>(\"organizationFigure\");\r\n    public final Property<String, User> mediaFigure = new Property<>(\"mediaFigure\");\r\n\r\n    public final BooleanProperty<User> isIndividual = new BooleanProperty<>(\"isIndividual\");\r\n    public final BooleanProperty<User> isJournalist = new BooleanProperty<>(\"isJournalist\");\r\n    public final BooleanProperty<User> isClubMember = new BooleanProperty<>(\"isClubMember\");\r\n\r\n    public final Property<String, User> firstName = new Property<>(\"firstName\");\r\n    public final Property<String, User> familyName = new Property<>(\"familyName\");\r\n    public final Property<String, User> email = new Property<>(\"email\");\r\n    public final Property<String, User> phone = new Property<>(\"phone\");\r\n    public final Property<String, User> gender = new Property<>(\"gender\");\r\n    public final LongProperty<User> birthday\r\n            = new LongProperty<>(\"birthday\");\r\n    public final Property<String, User> avatar = new Property<>(\"avatar\");\r\n    public final Property<String, User> cover = new Property<>(\"cover\");\r\n    public final ListProperty<User, User> friends\r\n            = new ListProperty<>(\"friends\", User.class);\r\n    public final ListProperty<User, User> friendRequests\r\n            = new ListProperty<>(\"friendRequests\", User.class);\r\n    public final ListProperty<User, User> peopleYouMayKnow\r\n            = new ListProperty<>(\"peopleYouMayKnow\", User.class);\r\n    public final Property<String, User> authtoken = new Property<>(\"authtoken\");\r\n    public final Property<String, User> password = new Property<>(\"password\");\r\n\r\n    public final Property<Date, User> birthdayDate\r\n            = new Property<Date, User>(\"birthdayDate\", Date.class) {\r\n        @Override\r\n        public Date get() {\r\n            if (birthday.get() == null) {\r\n                return null;\r\n            }\r\n            return new Date(birthday.get());\r\n        }\r\n\r\n        @Override\r\n        public User set(Date value) {\r\n            if (value == null) {\r\n                return birthday.set(null);\r\n            }\r\n            return birthday.set(value.getTime());\r\n        }\r\n    };\r\n\r\n    private final PropertyIndex idx = new PropertyIndex(this, \"User\",\r\n            id, mainMacroFigure, secondMacroFigure, personFigure,\r\n            organizationFigure,\r\n            isIndividual, isJournalist, isClubMember,\r\n            firstName, familyName, email, phone, gender, avatar, cover, birthday,\r\n            birthdayDate, friends, friendRequests, peopleYouMayKnow, authtoken,\r\n            password);\r\n\r\n    public User() {\r\n        firstName.setLabel(\"First Name\");\r\n        familyName.setLabel(\"Family Name\");\r\n        email.setLabel(\"FieldEmail\");\r\n        phone.setLabel(\"Phone\");\r\n        gender.setLabel(\"Gender\");\r\n        birthdayDate.setLabel(\"Birthday\");\r\n        password.setLabel(\"FieldPassword\");\r\n        idx.setExcludeFromJSON(birthdayDate, true);\r\n        idx.setExcludeFromMap(birthdayDate, true);\r\n    }\r\n\r\n    @Override\r\n    public PropertyIndex getPropertyIndex() {\r\n        return idx;\r\n    }\r\n\r\n    public String fullName() {\r\n        return firstName.get() + \" \" + familyName.get();\r\n    }\r\n}\r\n\r\nclass ValidatorUtils {\r\n\r\n    /**\r\n     * String lenght Validator constraint for TextComponent\r\n     *\r\n     * @param stringLenght\r\n     * @return\r\n     */\r\n    public static Constraint getStringLenghtConstraint(int stringLenght) {\r\n        return new Constraint() {\r\n\r\n            @Override\r\n            public boolean isValid(Object value) {\r\n                if (value instanceof String) {\r\n                    int currentLenght = ((String) value).length();\r\n                    if (currentLenght >= stringLenght) {\r\n                        return true;\r\n                    }\r\n                }\r\n                return false;\r\n            }\r\n\r\n            @Override\r\n            public String getDefaultFailMessage() {\r\n                return \"StringTooShort\";\r\n            }\r\n        };\r\n    }\r\n\r\n    /**\r\n     * (Very simple and minimalistic) Email Validator constraint for\r\n     * TextComponent\r\n     *\r\n     * @param stringLenght\r\n     * @return\r\n     */\r\n    public static Constraint getEmailConstraint() {\r\n        return new Constraint() {\r\n\r\n            @Override\r\n            public boolean isValid(Object value) {\r\n                if (value instanceof String) {\r\n                    String email = (String) value;\r\n                    if (email.indexOf('@') > 1\r\n                            && email.indexOf('@') == email.lastIndexOf('@')\r\n                            && email.length() - email.indexOf('@') > 3\r\n                            && email.lastIndexOf('.') > email.indexOf('@')\r\n                            && email.length() - email.lastIndexOf('.') > 2) {\r\n                        return true;\r\n                    }\r\n                }\r\n                return false;\r\n            }\r\n\r\n            @Override\r\n            public String getDefaultFailMessage() {\r\n                return \"EmailInvalid\";\r\n            }\r\n        };\r\n    }\r\n\r\n    /**\r\n     * 13-years old Validator constraint for PickerComponent\r\n     *\r\n     * @return 13-years old Validator constraint\r\n     */\r\n    public static Constraint get13yearsConstraint() {\r\n        return new Constraint() {\r\n            @Override\r\n            public boolean isValid(Object value) {\r\n                boolean res = false;\r\n                if (value != null && value instanceof Date) {\r\n                    Calendar birthday = Calendar.getInstance();\r\n                    birthday.setTime((Date) value);\r\n                    Calendar calendar = Calendar.getInstance();\r\n                    calendar.setTime(new Date());\r\n                    calendar.add(Calendar.YEAR, -13);\r\n                    if (birthday.before(calendar)) {\r\n                        res = true;\r\n                    }\r\n                }\r\n                return res;\r\n            }\r\n\r\n            @Override\r\n            public String getDefaultFailMessage() {\r\n                return \"AreYou13YearsOld\";\r\n            }\r\n        };\r\n    }\r\n\r\n    /**\r\n     * Confirm password (using the value stored in the User\r\n     * PropertyBusinessObject\r\n     *\r\n     * @param password\r\n     * @return the Validator Constraint\r\n     */\r\n    public static Constraint confirmPassword(TextComponent password) {\r\n        return new Constraint() {\r\n            @Override\r\n            public boolean isValid(Object value) {\r\n                if (value != null && value instanceof String) {\r\n                    String passToConfirm = (String) value;\r\n                    String originalPassword = \"\";\r\n                    String enteredValue = password.getText();\r\n                    if (enteredValue != null) {\r\n                        originalPassword = enteredValue;\r\n                    }\r\n                    return passToConfirm.equals(originalPassword);\r\n                }\r\n                return false;\r\n            }\r\n\r\n            @Override\r\n            public String getDefaultFailMessage() {\r\n                return \"PasswordsAreDifferent\";\r\n            }\r\n        };\r\n    }\r\n\r\n}\r\n\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/codenameone/CodenameOne/issues/comments/404382589","html_url":"https://github.com/codenameone/CodenameOne/issues/2472#issuecomment-404382589","issue_url":"https://api.github.com/repos/codenameone/CodenameOne/issues/2472","id":404382589,"node_id":"MDEyOklzc3VlQ29tbWVudDQwNDM4MjU4OQ==","user":{"login":"shannah","id":2677562,"node_id":"MDQ6VXNlcjI2Nzc1NjI=","avatar_url":"https://avatars3.githubusercontent.com/u/2677562?v=4","gravatar_id":"","url":"https://api.github.com/users/shannah","html_url":"https://github.com/shannah","followers_url":"https://api.github.com/users/shannah/followers","following_url":"https://api.github.com/users/shannah/following{/other_user}","gists_url":"https://api.github.com/users/shannah/gists{/gist_id}","starred_url":"https://api.github.com/users/shannah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shannah/subscriptions","organizations_url":"https://api.github.com/users/shannah/orgs","repos_url":"https://api.github.com/users/shannah/repos","events_url":"https://api.github.com/users/shannah/events{/privacy}","received_events_url":"https://api.github.com/users/shannah/received_events","type":"User","site_admin":false},"created_at":"2018-07-12T04:04:12Z","updated_at":"2018-07-12T04:04:12Z","author_association":"COLLABORATOR","body":"The click on the \"ShowProperties\" button, will trigger \"unfocus/end editing\" on the text field, and it will also trigger the action listeners of the ShowProperties button.  It is in the action event of ShowProperties that the modal dialog is shown where this test case demonstrates that the input of the text field hasn't propagated to its binding yet.\r\n\r\nAlthough the unfocus/end editing trigger on the text field will always happen before the show properties button action event (in the case of a button click), that doesn't necessarily mean that text field's action event will be triggered before the button's action listener.  This is because the unfocus has to do async cross-thread communication to the native thread to end the native text editing, which will ultimately trigger an action event on the EDT.  That final action event may occur after the button's action event.\r\n\r\n"}}
{"actor":{"display_login":"shannah"}}