Reputation: 6249
--- 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://stackoverflow.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://stackoverflow.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");
});
}
}
Upvotes: 1
Views: 327
Reputation: 6249
Thank you Shai. About the other issues, I found these solutions:
PickerComponent.createDate(null).label("Birthday");
, because the "null" argument caused the reported Android crash (see the log reported in the question). If I remeber correctly, you used createDate(null)
in a lesson of the Facebook clone: it's ok only if you don't use the Validator (this problem affects only Android, instead the code works fine on iOS).There are still minor issues that don't affect functionality, such as the fact that the Validator doesn't disable the submit button after the user invalidate an already validated input.
Finally, this is a (enough) working code:
import com.codename1.io.Log;
import com.codename1.ui.Button;
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 MainClass {
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;
}
// More info:
// https://www.codenameone.com/blog/pixel-perfect-text-input-part-2.html
// https://stackoverflow.com/questions/50250572/codename-one-errormessages-by-the-validator-shown-with-valid-inputs-update
Form hi = new Form("Anagrafica", BoxLayout.y());
TextModeLayout textModeLayout = new TextModeLayout(4, 1);
Container inputPersonData = new Container(textModeLayout);
TextComponent name = new TextComponent().labelAndHint("Name");
TextComponent surname = new TextComponent().labelAndHint("Surname");
PickerComponent gender = PickerComponent.createStrings("Male", "Female", "Other", "Not specified").label("Gender");
gender.getPicker().setSelectedString("Not specified");
PickerComponent date = PickerComponent.createDate(new Date()).label("Birthday");
Validator validator = new Validator();
validator.setShowErrorMessageForFocusedComponent(true);
validator.addConstraint(name, new LengthConstraint(2, "Insert your name"));
validator.addConstraint(surname, new LengthConstraint(2, "Insert your surname"));
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 "Are you at least 13 years old?";
}
});
inputPersonData.add(name);
inputPersonData.add(surname);
inputPersonData.add(gender);
inputPersonData.add(date);
hi.add(inputPersonData);
Button button = new Button("Send");
hi.add(button);
validator.addSubmitButtons(button);
hi.setEditOnShow(name.getField());
hi.show();
}
public void stop() {
current = getCurrentForm();
if (current instanceof Dialog) {
((Dialog) current).dispose();
current = getCurrentForm();
}
}
public void destroy() {
}
}
Upvotes: 1
Reputation: 52770
You shouldn't use this with a validator:
TextComponent name = new TextComponent().labelAndHint("Name").errorMessage("Insert your name");
As you are effectively hardcoding the error message into the component and it will be set to null. You should place the message in the validator instead:
validator.addConstraint(name, new LengthConstraint(2, "Insert your name"));
Upvotes: 1