vaadin 10 - Push - метка не обновляется

MainView включает в себя компонент InformationCOmponent:

@Push
@Route
public class MainView extends VerticalLayout {       
    InformationComponent infoComponent;

    public MainView(@Autowired StudentRepository studentRepo, @Autowired Job jobImportCsv, @Autowired JobLauncher jobLauncher, @Value("${file.local-tmp-file}") String inputFile) {     
    [...] // some stuffs

    infoComponent = new InformationComponent(studentRepo);
    add(infoComponent);
    }

    //update when job process is over
    private void uploadFileSuccceed() {
       infoComponent.update(myUploadComponent.getFile());
    }

ИнформацияКомпонент:

public class InformationComponent extends HorizontalLayout {

    StudentRepository studentRepo;

    Label nbLineInFile = new Label();

    VerticalLayout componentLeft = new VerticalLayout();;
    VerticalLayout componentRight = new VerticalLayout();;

    public InformationComponent(StudentRepository studentRepo) {
    [...] // some init and style stuff

    addLine("Nombre de lignes dans le fichier", nbLineInFile);
    }

    private void addLine(String label, Label value) {
    componentLeft.add(new Label(label));
    componentRight.add(value);
    }

    public void update(File file) {
    try {


        long nbLines = Files.lines(file.toPath(), Charset.defaultCharset()).count();

        System.out.println("UPDATED! " +nbLines); // value is display in console ok!
        UI.getCurrent().access(() -> nbLineInFile.setText(nbLines)); // UI is not updated!!
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
  }
}

Когда я вызываю InformationComponent из MainView , метка не обновляется в браузере.

UI.getCurrent().access(() -> nbLineInFile.setText(nbLines))

также попробуйте использовать @Push (PushMode.MANUAL) и ui.push (); но тоже не работает ...

Полный исходный код находится здесь: https://github.com/Tyvain/ProcessUploadedFile-Vaadin_SpringBatch/tree/push-not-working


person Tyvain    schedule 22.08.2018    source источник
comment
Теперь работает. На самом деле я сделал большую ошибку в своем информационном компоненте, не добавив должным образом метку. Полную рабочую версию можно найти здесь: github.com/Tyvain/ProcessUploadedFile-Vaadin_SpringBatch   -  person Tyvain    schedule 24.08.2018


Ответы (2)


Я подозреваю, что проблема здесь в том, что uploadFileSuccceed() запускается из фонового потока, и в этом случае UI.getCurrent() вернет null. Это может вызвать NullPointerException, который либо убивает фоновый поток, либо, в качестве альтернативы, исключение перехватывается и молча игнорируется вызывающей стороной. Другой альтернативой является то, что uploadFileSuccceed() происходит через другое окно браузера и, следовательно, также из другого экземпляра UI, что означает, что изменения будут внесены в контексте неправильного UI.

Именно по этим причинам UI.getCurrent().access(...) обычно является антипаттерном, хотя, к сожалению, довольно широко используется в старых примерах.

Вы можете проверить, является ли это причиной вашей проблемы, зарегистрировав значение UI.getCurrent() в начале метода update и сравнив его со значением UI.getCurrent(), например. в конструкторе InformationComponent.

Чтобы правильно решить проблему, вы должны передать правильный UI экземпляр через всю цепочку событий, происходящих от того, что запускает фоновую обработку. Вы также должны отметить, что может возникнуть соблазн использовать метод getUI(), который доступен в любом подклассе Component, но этот метод не является потокобезопасным, и поэтому его следует избегать в фоновых потоках.

В качестве последнего замечания я бы рекомендовал использовать компонент Span или Text вместо Label в подобных случаях. В Vaadin 10 компонент Label был изменен на использование HTML-элемента <label>, что означает, что он в основном предназначен для использования в качестве метки компонента ввода.

person Leif Åstrand    schedule 23.08.2018
comment
все пользовательские интерфейсы одинаковы на всех этапах: конструктор MainView ui: com.vaadin.flow.component.UI@4a5d32df конструктор InformationComponent ui: com.vaadin.flow.component.UI@4a5d32df update InformationComponent ui: com.vaadin.flow.component .UI @ 4a5d32df - person Tyvain; 24.08.2018
comment
Компонент Label был изменен на использование HTML-элемента ‹label›. Вы имеете в виду, что я должен связать свою java-версию с настоящим html-файлом? потому что целью было создать приложение без HTML. - person Tyvain; 24.08.2018
comment
Если UI.getCurrent() дает правильное значение, то похоже, что update запускается во время обычной обработки запроса, что, в свою очередь, означает, что push не имеет значения. Вместо этого я бы посмотрел на действительно простые вещи, например действительно ли nbLineInFile добавлен к некоторому макету, являющемуся частью приложения. - person Leif Åstrand; 24.08.2018
comment
Что касается Label, дело в том, что в конечном итоге все заканчивается в браузере как HTML (или фактически элементы DOM), потому что это то, что увидит пользователь. Vaadin Flow управляет тем, что происходит в браузере, поэтому вам не нужно создавать какие-либо HTML-файлы или что-то подобное, но вам все же иногда нужно знать, что разные классы компонентов имеют разное семантическое значение с точки зрения браузера. - person Leif Åstrand; 24.08.2018

Основываясь на информации, предоставленной Лейфом, вы должны сделать что-то вроде следующего примера.

Во время выполнения, когда этот _1 _ присоединяется к родительскому объекту UI, его _ 3_. В этот момент мы можем вспомнить пользовательский интерфейс, сохранив ссылку на переменную-член с именем ui. Фактически, _5 _ _6 _, поэтому нам нужно проверить на ноль, хотя он никогда не должен быть нулевым в точке onAttach.

public class InformationComponent extends HorizontalLayout {

    UI ui;

    StudentRepository studentRepo;

    Label nbLineInFile = new Label();

    VerticalLayout componentLeft = new VerticalLayout();;
    VerticalLayout componentRight = new VerticalLayout();;

    public InformationComponent(StudentRepository studentRepo) {
        [...] // some init and style stuff

        addLine("Nombre de lignes dans le fichier", nbLineInFile);
    }

    private void addLine(String label, Label value) {
        componentLeft.add(new Label(label));
        componentRight.add(value);
    }

    public void update(File file) {
    try {
        long nbLines = Files.lines(file.toPath(), Charset.defaultCharset()).count();

        System.out.println("UPDATED! " +nbLines); // value is display in console ok!
        this.ui.access(() -> nbLineInFile.setText(nbLines)); // UI is not updated!!
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (UIDetachedException e) {
        // Do here what is needed to do if UI is no longer attached, user has closed the browser
    }

    @Override  // Called when this component (this `HorizontalLayout`) is attached to a `UI` object.
    public void onAttach() {
        ui = this.getUI().orElseThrow( () -> new IllegalStateException("No UI found, which should be impossible at point of `onAttach` being called.") );

}
person Tatu Lund    schedule 23.08.2018
comment
onAttach () должен быть переопределен? потому что есть только onAttach (AttachEvent attachEvent). Или мне следует передать пользовательский интерфейс из моего mainView (например, в конструкторе)? - person Tyvain; 23.08.2018
comment
@Tyvain Да, метод onAttach здесь является заменой _ 2_ (Component является интерфейсом, реализованным HorizontalLayout). Этот метод вызывается, когда этот HorizontalLayout объект в конечном итоге присоединяется к UI для представления пользователю. Я добавил аннотацию @Override, чтобы прояснить это. - person Basil Bourque; 24.08.2018
comment
Я немного изменил этот код, чтобы учесть _ 1_ возвращает Optional<UI>, а не UI объект. - person Basil Bourque; 24.08.2018
comment
В этом конкретном примере кода, какова польза от запоминания ссылки UI в переменной-члене ui? Почему бы не использовать this.getUI().orElseThrow(…).access(…) вместо this.ui.access(…)? Я не понимаю, как это связано с ответом Лейфа Остранда. - person Basil Bourque; 24.08.2018
comment
Спасибо @Basil gor за исправления. Вы можете использовать getUI () вместо сохраненной ссылки на пользовательский интерфейс, однако getUI () также не на 100% потокобезопасен. Это намного лучше, чем подход UI.getCurrent (), но были и крайние случаи. - person Tatu Lund; 24.08.2018