UI and getting Push from server

Hello folks,

I’ve been reviewing the documentation around server push and I’m not sure why but it isn’t connecting in my brain correctly for some reason. Right now I have a fairly straightforward application that looks something like this

  • MainView
    . LeftPane
    . MiddlePane
    . RightPane

(Each pane has it’s own child classes that do their own thing, each of those panes sit on the MainView).

There is no UI (com.vaadin.flow.component.UI) implemented at the moment, as right now it’s just VerticalLayout / HorizontalLayout used to create a few forms.

I’m not really sure I understand the intent of using a UI and I’m not finding much in the “Creating UI” parts of the documentation about this. Do I just wrap my whole application in a UI and then implement ui.access() on the fields that need updating via push?

You add @Push to your ApplicationConfiguration class and whenever you want to update any component from a background thread you wrap that code in ui.access. The ui instance is what someComponent.getUI().get() returns

I guess I’m just a bit confused about how to set up UI in this case (and I didn’t see anything in the docs outlining the expected usage of UI)

For instance I have

@Push
@SpringBootApplication
public class SetDistributeApplication extends SpringBootServletInitializer implements AppShellConfigurator {

On my main file, so Push should be fine, but the actual implementation of UI is a bit weird.

For instance I have

    public Component getLeftPane(args){
        UI ui = getUI().get();

And that call to UI errors out with “No value present”.

What is the desired way to instantiate a UI, as the constructor for the object seems to offer new () (which would have it be null and error out) or get / getUI / getCurrent, all of which seem to fail me in this instance.

There is always one UI created per browser tab. If getUI() does not return anything then the component is not attached yet. When it had been added to a parent, then getUI returns the UI instance

I’m sorry, but I don’t quite follow still, and I’m not trying to be obtuse here. But like

var main = new HorizontalLayout();
        main.setWidth("3800px");
        main.setHeight("4444px");
        main.getElement().setAttribute("theme", Lumo.DARK);
        main.setSizeFull();
            //Some spacers between our columns
            var space1 = new VerticalLayout();
            space1.setWidth("100px");
            main.add(space1);
            add(main);
            UI ui = main.getUI().get();
            ui.getPushConfiguration().setPushMode(PushMode.AUTOMATIC);

In the above instance ui is no value present after being added and I’m just not sure how that can be where it’s added to MainView(){} which is on the default route.

Is there an article around the correct use of UI? I feel like I’m just not understanding the intended use of it.

the simplest answer about UI usage is: if you don’t know how it works, don’t use it. Most Feature are available without UI usage. It’s a concept from literally over 10 years ago and shouldn’t be the center of attention nowadays. If you call UI.getCurrent() returns null, you are in a different thread.

@quirky-zebra I’m trying to implement push, since I’m trying to update UI elements during a blocking thread. It seems like this is the only path forwards unless the documentation doesn’t cover some easier way to do this?

I’m sorta dying without an example since the javadocs themselves seem to be pretty nondescript

for (User u : users) {
                    ui.access(() ->{
                        progressBar.setValue(progressBar.getValue() + 1);
                        programLogs.setValue(programLogs.getValue() + "\r\nDistributing items to user " + progressBar.getValue() + "/" + users.size() + "\t:  " + u.getEmail());
                        progressBarLabel.setText("Distributing items to user " + progressBar.getValue() + "/ " + users.size());
                        ui.push();
                    });
}

For example this might block the UI for ~40 seconds, where each user takes about ~0.2 seconds, showing progress in the UI would be nice.

When is this called? On e.g. Button click? In the Button click listener you can access the UI from UI.getCurrent() or from the event

Here is an example https://cookbook.vaadin.com/long-running-task

Yeah, it’s from a button click on a form

So, looking at this, it’s a bit confusing but I think I get the gist but have a couple questions. Right now I have things implemented in a particular way, and I’ll obviously need to refactor, it seems like the way to do this is reliant on ListenableFutureCallback<>() { – Could this be any kind of future callback, or is it specifically ListenableFutureCallback

I think, I’m not really familiar with all types of future callbacks

Also – the order of operations are sorta important (e.g. result of one item being created will be passed to the next item being created.) When I run a background thread, I don’t know of an async.await alternative in Java, how can I ensure that I’m still getting the info I need for subsequent calls?

Sounds like it should all run in the same background thread then and just push partial updates with ui.access() in each for Loop Iteration

So just to be clear (some pseudocode).

DistributeButton.onClick → distributeItems as background thread → ui.access() pushes up partials?

@quirky-zebra Do you mind if I DM you directly about this? I’m still struggling a bit with this.

It would be better to post the follow up here, so that other could help as well.

Fair enough.

I finally had some time to dig back into this today, and I tried a few implementations and none of them seemed to work.

So just for reference…

DistributeItemsButton.java

var b = new Button("Distribute Items");
        b.addClickListener(e ->{
            String setName = name.getValue();
            String setPrice = price.getValue();
            DistributeItems d = new DistributeItems();
            d.distributeItems(progressBar,progressBarLabel,productImage,radioGroup,
                    setPrice,setName,setImages, users, programLogs, UI.getCurrent());
        });

Then I have a for loop which uploads images to a server

Excerpt of DistributeItems.java

//Attempt to upload media
        progressBarLabel.setText("Attempting to upload images: ");
        for (int i = 0; i < setImages.size(); i++) {
            final var counter = i;
            BackEndTasks bet = new BackEndTasks();
            bet.uploadMediaProcess(ui, setImages.get(i), programLogs);
                bet.uploadMediaProcess(ui, setImages.get(counter), programLogs).addCallback(new ListenableFutureCallback<Integer>() {
                    @Override
                    public void onFailure(Throwable ex) {

                    }

                    @Override
                    public void onSuccess(Integer result) {
                        ui.access(() ->{
                            progressBarLabel.setText("Attempting to upload images: " + counter + 1 + "/" + setImages.size());
                            programLogs.setValue(programLogs.getValue() + "\r\nAttempting to upload images: " + (counter + 1) + "/" + setImages.size());
                            ui.push();
                        });
                    }
                });

Which calls on my “BackEndTasks”
BackEndTasks.java

@Service
public class BackEndTasks {
    @Async
    public ListenableFuture<Integer> uploadMediaProcess(UI ui, File f, TextArea programLog){
            String uri = "<redacted>";
            String extension = "";
            String mime = "";
            String encoded_cred = Base64.getEncoder().encodeToString(("<redacted>").getBytes());
            Integer mediaID = -1;
            String fileName = f.getName();
            System.out.println("FILENAME: " + fileName);
            int i = fileName.lastIndexOf('.');
            if (i > 0) {
                extension = fileName.substring(i + 1);
            }
            if (extension.equalsIgnoreCase("png")) {
                mime = "image/png";
            }
            if (extension.equalsIgnoreCase("jpeg") || extension.equalsIgnoreCase("jpg")) {
                mime = "image/jpeg";
            }
            Path fp = f.toPath();
            try {
                HttpClient client = HttpClient.newHttpClient();
                HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
                        .POST(HttpRequest.BodyPublishers.ofFile(fp) )
                        .headers("AUTHORIZATION", "Basic " + encoded_cred,
                                "Content-Disposition", "attachment; filename=" + f.getName(),
                                "Content-Type", mime
                        )
                        .build();
                HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
                mediaID = CreateItem.getId(response.body());

            }catch (Exception e){e.printStackTrace(); programLog.setValue(programLog.getValue() + "\r\nUpload failed for file: " + f.getName());}
            return AsyncResult.forValue(mediaID);
    }
}