Vaadin 14 DatePicker Usage

Hi,

I jumpstarted a vaadin14 project from basic-starter-spring-gradle (exact version: 14.2.0 and I use the same for vaadin-bom). I followe the code samples from https://vaadin.com/components/vaadin-date-picker/java-examples am trying to define a page with FormLayout and am trying to add DatePicker for a date field. No matter whichever constructor I use, I always see a message ‘NullPointerException: Locale must not be null’ and vaadin attempts to handle with a default error page InternalServerError. Here is the stacktrace…

at java.base/java.util.Objects.requireNonNull(Objects.java:246)
        at com.vaadin.flow.component.datepicker.DatePicker.setLocale(DatePicker.java:270)
        at com.vaadin.flow.component.datepicker.DatePicker.<init>(DatePicker.java:87)
        at com.vaadin.flow.component.datepicker.DatePicker.<init>(DatePicker.java:74)
        at com.vaadin.flow.component.datepicker.DatePicker.<init>(DatePicker.java:105)
        at com.obstreperus.dbw.pwa.view.app.SignupView.profileStep(SignupView.java:210)

I find this line of code to be the offender… setLocale(UI.getCurrent().getLocale());

What is the correct way to initialize and use this component in vaadin14 project?

Thanks.

Can you please show the code of SignupView?

Sure… Here is the snippet.

        TextField dobValue = new TextField();
        dobValue.setVisible(false);
        DatePicker dateOfBirth = new DatePicker(getTranslation("signup.dateOfBirth"), LocalDate.now());
        dateOfBirth.setLocale(Locale.ENGLISH);
        dateOfBirth.setLabel(getTranslation("signup.dateOfBirth"));
        dateOfBirth.setMin(LocalDate.now().withYear(new Date().getYear() - client.maxAge()));
        dateOfBirth.setMax(LocalDate.now().withYear(new Date().getYear() - client.minAge()));
        dateOfBirth.addValueChangeListener(evt -> {
            LocalDate dob = evt.getValue();
            dobValue.setValue(dob.format(DateTimeFormatter.ofPattern("dd-MMM-yyyy", Locale.US)));
        });
        signupBinder.bind(dobValue, SignupRequest::dateOfBirth, SignupRequest::dateOfBirth);
        signupForm.add(dobValue);
        signupForm.add(dateOfBirth);

Thanks for helping out @Simon. Much appreciated.
Thanks.

Seems to be another symptom to the same issue you raised in [this thread]
(https://vaadin.com/forum/thread/18303724/vaadin14-languageselect) → I believe both issues disappear once you fix your I18NProvider. Could you show your implementation of I18NProvider::getProvidedLocales and I18NProvider::getTranslation so I can point out the exact code that needs to change?

Sure @Kesper… Pasting my I18nProvider implementation below. I did notice the pattern too and I had also tried to check the stacktrace only to understand how to fix if there is any mistakes from my part that offends the framework/addons. Of course i cannot fathom the framework with 3-4 days of exposure. :slight_smile:

On the other hand, The LanguageSelect issue was resolved when I use its constructor to populate the locale items. Earlier I had used setItems() which was directly handled by parent class Select<?>.

Anyhow, here is the I18NProvider code…

@Component
public class MyI18NProvider implements I18NProvider {
    private static final long serialVersionUID = 2758660919973343707L;
    
    public static final String RB__BASE_NAME = "i18n/myapp";
    
    private static final Locale LOCALE_MALAY      = new Locale("ms");
    private static final Locale LOCALE_INDONESIAN = new Locale("id");
    private static final Locale LOCALE_TAMIZH     = new Locale("ta");
    
    private static final ResourceBundle RB__ENGLISH    = getBundle(RB__BASE_NAME, Locale.ENGLISH);
    private static final ResourceBundle RB__MALAY      = getBundle(RB__BASE_NAME, LOCALE_MALAY);
    private static final ResourceBundle RB__INDONESIAN = getBundle(RB__BASE_NAME, LOCALE_INDONESIAN);
    private static final ResourceBundle RB__TAMILZH    = getBundle(RB__BASE_NAME, LOCALE_TAMIZH);
    private static final ResourceBundle RB__CHINESE    = getBundle(RB__BASE_NAME, Locale.CHINESE);
    
    private static final Map<Locale, ResourceBundle> translations = new LinkedHashMap<Locale, ResourceBundle>();
    public static final List<Locale> locales = new ArrayList<Locale>();
    
    @Autowired
    @Qualifier("logging")
    private LoggingService logging;
    
    @PostConstruct
    public void init() {
        logging.info("My I18n Provider initializing...");
        translations.put(Locale.ENGLISH, RB__ENGLISH);
        translations.put(LOCALE_MALAY, RB__MALAY);
        translations.put(LOCALE_INDONESIAN, RB__INDONESIAN);
        translations.put(LOCALE_TAMIZH, RB__TAMILZH);
        translations.put(Locale.CHINESE, RB__CHINESE);
        logging.info("My I18n Translations loaded.");
        locales.addAll(translations.keySet());
    }
    
    @Override
    public List<Locale> getProvidedLocales() {
        logging.info("Providing locales: " + locales);
        return locales;
    }
    
    public Locale[] getLocalesAsArray() {
        return locales.toArray(new Locale[locales.size()]
);
    }
    
    @Override
    public String getTranslation(String key, Locale locale, Object... params) {
        ResourceBundle bundle = translations.get(locale);
        if (bundle == null) {
            logging.warn(format("Requested locale: '%s' not found. Falling back to English...", locale.getDisplayName()));
            bundle = RB__ENGLISH;
        }
        
        String translation = null;
        try {
            translation = bundle.getString(key);
        } catch (MissingResourceException e) {
            logging.error(format("Missing i18n.token: '%s' in locale: '%s'!!", key, locale.getDisplayName()));
        }
        
        if (translation == null) {
            logging.warn(format("Translation for Key: '%s' in Locale: '%s' (or fallback) is missing!!", key, locale.getDisplayName()));
            return key;
        } else {
            logging.info(format("Translation for key: '%s' in Locale: '%s' : '%s'.",
                    key, locale.getDisplayName(), (translation.length() > 64) ? translation.substring(0, 64) : translation));
            return translation;
        }
    }   
}

Some quick pointers… I am using linkedHashMap and ArrayList to ensure the insertion order is preserved. This is to ensure that Locale.en is always presented first (since the vaadin documentation describes the locale selection logic tree as (1) match <langCode>_<countryCode> first, (2) failng which match <langCode> only, (3) failing which use the first locale from getProvidedLocales(), and finally (4) use Locale.getDefault(). So I put in my implementation this way).

Thanks a ton for helping out :slight_smile:

Oops deleted instead of edited my comment… Sorry for assuming a faulty implementation on your side - it looks good. I take it your logs tell you everything is fine?

Have you tried to add a breakpoint in VaadinService::createAndRegisterSession and debug there? The only way that the ui’s locale is null is if it doesn’t find your I18NProvider or if you return an empty locale array in the I18NProvider - even though it sounds both options do not apply to your situation.

Hi @Kaspar,

I am not really sure what happened. it does seem the errors were related. Once the LanguageSelect problem was fixed, the DatePicker started working fine too. I just finished the page and tested it fine.

I really have no clue whats under the hood with the framework, but I am sure glad things are working fine now :slight_smile:

Thanks a ton for the help.