2

MainView include 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());
    }

InformationComponent:

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);
    }
  }
}

When I call InformationComponent from MainView the Label is not update in the browser.

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

also try wwith @Push(PushMode.MANUAL) and ui.push(); but doesn't work either...

Complete source code is here: https://github.com/Tyvain/ProcessUploadedFile-Vaadin_SpringBatch/tree/push-not-working

Tyvain
  • 2,640
  • 6
  • 36
  • 70
  • It's working now. I actually did a big mistake in my InformationComponent, not adding properly the Label. The complete working version can be found here: https://github.com/Tyvain/ProcessUploadedFile-Vaadin_SpringBatch – Tyvain Aug 23 '18 at 21:19

2 Answers2

4

I suspect the problem here is that uploadFileSuccceed() is run from a background thread, in which case UI.getCurrent() will return null. This would cause a NullPointerException that either kills the background thread or alternatively the exception is caught and silently ignored by the caller. Another alternative is that uploadFileSuccceed() happens through a different browser window and thus also a different UI instance, which means that the changes would be pushed in the context of the wrong UI.

For exactly these reasons, UI.getCurrent().access(...) is generally an anti pattern, even though it's unfortunately quite widely used in old examples.

You can check whether this is the cause of your problem by logging the value of UI.getCurrent() in the beginning of the update method, and comparing that to the value of UI.getCurrent() e.g. in the constructor of InformationComponent.

To properly fix the problem, you should pass the correct UI instance through the entire chain of events originating from whatever triggers the background processing to start. You should also note that it might be tempting to use the getUI() method that is available in any Component subclass, but that method is not thread safe and should thus be avoided in background threads.

As a final notice, I would recommend using the Span or Text component instead of Label in cases like this. In Vaadin 10, the Label component has been changed to use the <label> HTML element, which means that it's mainly intended to be used as the label of an input component.

Leif Åstrand
  • 7,820
  • 13
  • 19
  • 1
    all UI are the same at all steps: constructor MainView ui: com.vaadin.flow.component.UI@4a5d32df constructor InformationComponent ui: com.vaadin.flow.component.UI@4a5d32df update InformationComponent ui: com.vaadin.flow.component.UI@4a5d32df – Tyvain Aug 23 '18 at 21:03
  • "Label component has been changed to use the – Tyvain Aug 23 '18 at 21:03
  • If `UI.getCurrent()` gives the right value, then it seems like `update` is run during regular request handling which in turn means that push is irrelevant. I would instead look at really basic things, e.g. whether `nbLineInFile` is actually added to to some layout that is part of the application. – Leif Åstrand Aug 24 '18 at 07:56
  • Regarding `Label`, the thing is that everything eventually ends up as HTML (or actually DOM elements) in the browser because that's what the user will see. Vaadin Flow manages what happens in the browser so that you don't have to do any HTML files or such, but you still sometimes need to be aware that different component classes have different semantical meaning from the browser's perspective. – Leif Åstrand Aug 24 '18 at 07:58
0

Based on information provided by Leif you should do something like the following example.

At runtime, when this HorizontalLayout subclass object is attached to a parent UI object, its onAttach method is called. At that point we can remember the UI by storing its reference is a member variable named ui. Actually, an Optional<UI> is returned rather than a UI object, so we need to test for null, though it should never be null at point of 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.") );

}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Tatu Lund
  • 9,949
  • 1
  • 12
  • 26
  • onAttach() is supposed to be Override ? because there is only onAttach(AttachEvent attachEvent). Or should I pass UI from my mainView (in the constructor for example)? – Tyvain Aug 23 '18 at 20:03
  • @Tyvain Yes, the `onAttach` method here is an override of the [`Component::onAttach`](https://vaadin.com/api/platform/10.0.4/com/vaadin/flow/component/Component.html#onAttach-com.vaadin.flow.component.AttachEvent-) method (`Component` being an interface implemented by `HorizontalLayout`). That method is called when this `HorizontalLayout` object is eventually attached to a `UI` for presentation to the user. I added the `@Override` annotation to make this more clear. – Basil Bourque Aug 23 '18 at 23:28
  • I changed this code a bit to account for [`getUI`](https://vaadin.com/api/platform/10.0.4/com/vaadin/flow/component/Component.html#getUI--) returning an `Optional` rather than a `UI` object. – Basil Bourque Aug 23 '18 at 23:29
  • In this particular code example, what is the benefit of remembering the `UI` reference in a `ui` member variable? Why not use `this.getUI().orElseThrow(…).access(…)` rather than `this.ui.access(…)`? I am not seeing how this relates to [Answer by Leif Åstrand](https://stackoverflow.com/a/51978235/642706). – Basil Bourque Aug 23 '18 at 23:39
  • Thanks @Basil gor the corrections. You could use getUI() instead of stored ui reference, however getUI() is not 100% threadsafe either. Its much better than UI.getCurrent() approach, but there were are corner cases. – Tatu Lund Aug 24 '18 at 04:33