--- UPDATED QUESTION WITH MORE DETAILS ---
I'm updating this question to add more details. I'm sorry to report multiple bugs in this question, but there are multiple issues that I experienced with the following code.
First of all, this is my (new) code to test the TextModeLayout, that is easy to full copy and test (I changed the code from the previous version of this question):
package ...;
import com.codename1.io.Log;
import static com.codename1.ui.CN.addNetworkErrorListener;
import static com.codename1.ui.CN.getCurrentForm;
import static com.codename1.ui.CN.updateNetworkThreadCount;
import com.codename1.ui.Container;
import com.codename1.ui.Dialog;
import com.codename1.ui.Form;
import com.codename1.ui.PickerComponent;
import com.codename1.ui.TextComponent;
import com.codename1.ui.Toolbar;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.TextModeLayout;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.ui.validation.Constraint;
import com.codename1.ui.validation.LengthConstraint;
import com.codename1.ui.validation.Validator;
import java.util.Date;
import java.util.Calendar;
public class MyApplication {
private Form current;
private Resources theme;
public void init(Object context) {
// use two network threads instead of one
updateNetworkThreadCount(2);
theme = UIManager.initFirstTheme("/theme");
// Enable Toolbar on all Forms by default
Toolbar.setGlobalToolbar(true);
// Pro only feature
Log.bindCrashProtection(true);
addNetworkErrorListener(err -> {
// prevent the event from propagating
err.consume();
if (err.getError() != null) {
Log.e(err.getError());
}
Log.sendLogAsync();
Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
});
}
public void start() {
if (current != null) {
current.show();
return;
}
MyForm test = new MyForm();
test.show();
}
public void stop() {
current = getCurrentForm();
if (current instanceof Dialog) {
((Dialog) current).dispose();
current = getCurrentForm();
}
}
public void destroy() {
}
}
class MyForm extends Form {
public MyForm() {
super("Anagrafica", BoxLayout.y());
}
/**
* Creates and shows the Registry Form
*/
@Override
public void show() {
// More info: https://www.codenameone.com/blog/pixel-perfect-text-input-part-2.html
TextModeLayout textModeLayout = new TextModeLayout(4, 1);
Container inputPersonData = new Container(textModeLayout);
TextComponent name = new TextComponent().labelAndHint("Name").errorMessage("Insert your name");
TextComponent surname = new TextComponent().labelAndHint("Surname").errorMessage("Insert your surname");
PickerComponent gender = PickerComponent.createStrings("Male", "Female", "Other", "Not specified").label("Gender").errorMessage("Please choose an option");
PickerComponent date = PickerComponent.createDate(null).label("Birthday").errorMessage("Are you at least 13 years old?");
Validator validator = new Validator();
validator.setShowErrorMessageForFocusedComponent(false);
validator.addConstraint(name, new LengthConstraint(2));
validator.addConstraint(surname, new LengthConstraint(2));
validator.addConstraint(gender, new Constraint() {
@Override
public boolean isValid(Object value) {
boolean res = false;
if (value != null && value instanceof String) {
res = true;
}
return res;
}
@Override
public String getDefaultFailMessage() {
return "Please choose an option";
}
});
validator.addConstraint(date, new Constraint() {
@Override
public boolean isValid(Object value) {
boolean res = false;
if (value != null && value instanceof Date) {
Calendar birthday = Calendar.getInstance();
birthday.setTime((Date) value);
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.YEAR, -13);
if (birthday.before(calendar)) {
res = true;
}
}
return res;
}
@Override
public String getDefaultFailMessage() {
return "You must be at least 13 years old";
}
});
inputPersonData.add(name);
inputPersonData.add(surname);
inputPersonData.add(gender);
inputPersonData.add(date);
add(inputPersonData);
//setEditOnShow(name.getField());
super.show();
Log.p("Registry Form shown correctly");
}
}
I generated the apk and the ipa and I tested them on real devices in my hand (not in a device farm). To better understand the behaviors, I recorded two videos.
Android 7: As you can see in the video Android.mp4, the error message of the name only (but not of the surname) is always shown, regardless I provided a valid input. Moreover, the app crashes when I tap the birthday picker with a java.lang.NullPointerException. The full log is here: android_date_picker_crash.txt
iPhoneX (iOS 11.2.2): As you can see in the video iPhone.mp4, the red crosses on the right of the fields are correctly shown only when the inputs are not valid (as I expected), but the errorMessages texts are always shown also with valid inputs. There is also a problem on selection the gender, because if I tap the gender and then press OK (without scrolling the options), the first option (that is "male") is not selected (in the video I tried two times).
So the same code produces a crash on Android but not in iOS. Moreover, the errorTexts are shown incorrectly on both OSes, but with different behaviors.
On iOS there is also a strange bug: if you block the screen and then unlock it (tried using the right side button of iPhone X, and then unlock using FaceID), the input fields duplicate, like in this screenshot: error.jpg. This bug can be easily replicated.
Thank you for the support.
--- OLD QUESTION ---
Please note the following screenshots (that refers to the same code): on Android the errorMessage of TextComponent name is shown with a valid input (while the errorMessage of TextComponent surname is not shown with a valid input, as expected); on iPhoneX the errorMessages of TextComponent name and TextComponent surname are shown with a valid input. Why? What's wrong?
Of course, the expected behavior is that no errorMessage is shown with a valid input.
Below the screenshots, I copy the full code of the Form.
Screenshot of a real Android 7 (in my hand):

Screenshot of a real iPhoneX iOS11 (in a device farm):

Full Code:
import com.codename1.io.Log;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.PickerComponent;
import com.codename1.ui.TextComponent;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.TextModeLayout;
import com.codename1.ui.validation.Constraint;
import com.codename1.ui.validation.LengthConstraint;
import com.codename1.ui.validation.Validator;
import static com.myproject.registration.Registrazione.getArrowBack;
import java.util.Date;
public class Registry extends Form {
public Registry() {
super("Anagrafica", BoxLayout.y());
}
/**
* Creates and shows the Registry Form
*
* @param backForm the previous Form to show tapping the back arrow
*/
public void show(Form backForm) {
// Back command (linked to the left arrow in the toolbar and to back hardware button)
setBackCommand(getToolbar().addCommandToLeftBar("", getArrowBack(), (e) -> {
Log.p("Invoked setBackCommand to go to the previous form");
Display.getInstance().callSerially(() -> {
backForm.showBack();
Log.p("Previous form shown correctly");
});
}
));
// More info: https://www.codenameone.com/blog/pixel-perfect-text-input-part-2.html
TextModeLayout textModeLayout = new TextModeLayout(4, 1);
Container inputPersonData = new Container(textModeLayout);
TextComponent name = new TextComponent().label("Nome").errorMessage("Inserisci il tuo nome");
TextComponent surname = new TextComponent().label("Cognome").errorMessage("Inserisci il tuo cognome");
PickerComponent gender = PickerComponent.createStrings("Maschio", "Femmina", "altro").label("Genere");
pickerComponentSetUnselectedText(gender, "Seleziona genere");
PickerComponent date = PickerComponent.createDate(new Date()).label("Data di nascita").errorMessage("Hai almeno 13 anni?");
pickerComponentSetUnselectedText(date, "Seleziona data");
Validator validator = new Validator();
// to make better
// Codename One - Validate only one input at a time
// https://stackguides.com/questions/50249453/codename-one-validate-only-one-input-at-a-time
validator.setShowErrorMessageForFocusedComponent(false);
validator.addConstraint(name, new LengthConstraint(2));
validator.addConstraint(surname, new LengthConstraint(2));
validator.addConstraint(date, new Constraint() {
@Override
public boolean isValid(Object value) {
boolean res = false;
// to do
// Codename One - addConstraint to PickerComponent date
// https://stackguides.com/questions/50249148/codename-one-addconstraint-to-pickercomponent-date
return res;
}
@Override
public String getDefaultFailMessage() {
return "You must be at least 13 years old";
}
});
inputPersonData.add(name);
inputPersonData.add(surname);
inputPersonData.add(gender);
inputPersonData.add(date);
add(inputPersonData);
setEditOnShow(name.getField());
super.show();
Log.p("Registry Form shown correctly");
}
/**
* Set a custom text for an unselected PickerComponent placed in a
* TextModeLayout
*
* @param picker
* @param text
*/
private void pickerComponentSetUnselectedText(PickerComponent picker, String text) {
if (picker.isOnTopMode()) {
picker.getPicker().setText(text);
} else {
picker.getPicker().setText("");
}
picker.getPicker().setUIID("TextHint");
picker.getPicker().addActionListener(l -> {
l.getComponent().setUIID("TextField");
});
}
}