> Markdown version of [Handle Background Work](https://vaadin.com/docs/latest/building-apps/views/mvvm/background-work). Section index: [llms.txt](https://vaadin.com/docs/latest/building-apps/llms.txt)

# Handle Background Work

Some state changes without a click: a search that takes seconds to answer, a refresh on a timer, or a change that another user saves. This guide shows how a ViewModel handles values that arrive later, and how the view keeps the ViewModel up to date.

> **Tip: Part of a Series**
>
> This is part 7 of the **Structure Views with MVVM** series. It builds on the ViewModel from [Write a ViewModel](https://vaadin.com/docs/latest/building-apps/views/mvvm/write-viewmodel.md) and the view from [Bind the View](https://vaadin.com/docs/latest/building-apps/views/mvvm/bind-view.md).

The example is a product catalog that a slow remote system searches. The service returns a `CompletableFuture` that completes when the answer arrives:

CatalogService.java

`CatalogService.java`

```java
import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;

@Service
public class CatalogService {

    private static final List<String> PRODUCTS = List.of("Desk", "Desk lamp",
            "Monitor", "Monitor arm", "Office chair");

    private final TaskExecutor taskExecutor;

    public CatalogService(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    // In a real application, this would call a slow remote system
    public CompletableFuture<List<String>> search(String text) {
        return CompletableFuture
                .supplyAsync(
                        () -> PRODUCTS.stream()
                                .filter(p -> p.toLowerCase()
                                        .contains(text.toLowerCase()))
                                .toList(),
                        taskExecutor);
    }
}
```

For more about running work in the background, see [Background Jobs](https://vaadin.com/docs/latest/building-apps/business-logic/background-jobs.md).

## <a id="keep-a-pending-value-in-one-signal"></a>Keep a Pending Value in One Signal

A value that arrives later is in one of three states: loading, loaded, or failed. Keep all three in one signal, as a sealed interface:

AsyncState.java

`AsyncState.java`

```java
// Loading, loaded, or failed: the three states of a value that arrives later
sealed interface AsyncState<T> {

    record Loading<T>() implements AsyncState<T> {
    }

    record Loaded<T>(T value) implements AsyncState<T> {
    }

    record Failed<T>(Throwable error) implements AsyncState<T> {
    }
}
```

A single `set()` then changes the state, the result, and the error together. Separate signals would need a transaction to change together, and local signals don’t support transactions.

## <a id="let-the-latest-request-win"></a>Let the Latest Request Win

The ViewModel starts the work, and sets the signal when the answer arrives:

CatalogViewModel.java

`CatalogViewModel.java`

```java
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

import com.vaadin.flow.signals.Signal;
import com.vaadin.flow.signals.local.ValueSignal;

class CatalogViewModel {

    private final CatalogService catalogService;

    private final ValueSignal<String> searchText = new ValueSignal<>("");
    private final ValueSignal<AsyncState<List<String>>> results = new ValueSignal<>(
            new AsyncState.Loading<>());

    // Not state that the view shows: bookkeeping for the background work
    private final AtomicLong latestRequest = new AtomicLong();
    private volatile CompletableFuture<List<String>> running;

    CatalogViewModel(CatalogService catalogService) {
        this.catalogService = catalogService;
    }

    Signal<String> searchText() {
        return searchText.asReadonly();
    }

    Signal<AsyncState<List<String>>> results() {
        return results.asReadonly();
    }

    Signal<Boolean> loading() {
        return results.map(r -> r instanceof AsyncState.Loading);
    }

    void refresh() {
        load();
    }

    void search(String text) {
        searchText.set(text == null ? "" : text);
        load();
    }

    // Ignores the answer of the running request, and cancels its future
    void cancel() {
        latestRequest.incrementAndGet();
        CompletableFuture<?> future = running;
        if (future != null) {
            future.cancel(false);
        }
    }

    private void load() {
        long request = latestRequest.incrementAndGet();
        results.set(new AsyncState.Loading<>());
        CompletableFuture<List<String>> future = catalogService
                .search(searchText.peek());
        running = future;
        // Runs on the thread that completes the future
        future.whenComplete((value, error) -> {
            if (request != latestRequest.get()) {
                return; // A newer request or a cancel made this answer stale
            }
            results.set(error == null ? new AsyncState.Loaded<>(value)
                    : new AsyncState.Failed<>(error));
        });
    }
}
```

The callback of the future runs on the thread that completes the future, not in a request of the user. Local signals are thread-safe, so the ViewModel sets the signal there without `UI.access()`, and Vaadin updates the UI components that are bound to it. See [Thread Safety](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#thread-safety).

The ViewModel numbers its requests, and ignores an answer to any request that isn’t the latest. Otherwise, a slow answer to an old search could arrive after the answer to a newer search, and replace it.

The `cancel()` command makes sure that the answer of the running request is ignored, and cancels its future. Because `CatalogService` runs the search with `CompletableFuture.supplyAsync()`, a search that hasn’t started yet then doesn’t run at all. A search that has already started finishes on its own thread, because canceling a `CompletableFuture` doesn’t interrupt the work. Stopping work that has already started is up to the service: it can check whether its future has been canceled, as [Returning Futures](https://vaadin.com/docs/latest/building-apps/business-logic/background-jobs/interaction/futures.md#cancelling) shows.

The ViewModel has no detach event of its own, so the view calls `cancel()` when it’s detached.

## <a id="bind-the-state"></a>Bind the State

The view binds to the state, and calls `cancel()` when it’s detached:

CatalogView\.java

`CatalogView.java`

```java
import java.util.List;

import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.progressbar.ProgressBar;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.AfterNavigationEvent;
import com.vaadin.flow.router.AfterNavigationObserver;
import com.vaadin.flow.router.Route;

@Route("building-apps/mvvm/catalog")
class CatalogView extends VerticalLayout implements AfterNavigationObserver {

    private final CatalogViewModel viewModel;

    public CatalogView(CatalogService catalogService) {
        viewModel = new CatalogViewModel(catalogService);

        TextField search = new TextField("Search");
        search.setValueChangeMode(ValueChangeMode.LAZY);
        search.bindValue(viewModel.searchText(), viewModel::search);

        ProgressBar progress = new ProgressBar();
        progress.setIndeterminate(true);
        progress.bindVisible(viewModel.loading());

        Span results = new Span(
                viewModel.results().map(state -> switch (state) {
                case AsyncState.Loading<List<String>> loading -> "Searching…";
                case AsyncState.Loaded<List<String>> loaded ->
                    String.join(", ", loaded.value());
                case AsyncState.Failed<List<String>> failed -> "Search failed";
                }));

        add(search, progress, results);
        addDetachListener(event -> viewModel.cancel());
    }

    @Override
    public void afterNavigation(AfterNavigationEvent event) {
        viewModel.refresh();
    }
}
```

For a change made in a background thread to reach the browser without an action of the user, the application needs [server push](https://vaadin.com/docs/latest/building-apps/server-push.md#enabling-push).

## <a id="test-background-work"></a>Test Background Work

In a ViewModel test, decide when each answer arrives. The following test gives the service an executor that only collects the searches, and runs them in the order that the test chooses:

CatalogViewModelTest.java

```java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

class CatalogViewModelTest {

    List<Runnable> searches = new ArrayList<>();
    CatalogViewModel viewModel = new CatalogViewModel(
            new CatalogService(searches::add));

    @Test
    void showsLoadingUntilTheAnswerArrives() {
        viewModel.search("desk");
        assertTrue(viewModel.loading().peek());

        searches.get(0).run();

        assertFalse(viewModel.loading().peek());
        assertEquals(new AsyncState.Loaded<>(List.of("Desk", "Desk lamp")),
                viewModel.results().peek());
    }

    @Test
    void aLateAnswerToAnOlderSearchIsIgnored() {
        viewModel.search("desk");
        viewModel.search("monitor");

        searches.get(1).run();
        searches.get(0).run();

        assertEquals(
                new AsyncState.Loaded<>(List.of("Monitor", "Monitor arm")),
                viewModel.results().peek());
    }
}
```

In a browserless test of the view, a signal that another thread sets reaches the UI components only when the test runs the pending signal tasks of the window. Complete the work, then call `window.runPendingSignalsTasks()` before you check the UI. See [Test Signal-Based Views](https://vaadin.com/docs/latest/building-apps/testing/browserless/test-signals.md).

## <a id="refresh-on-a-timer"></a>Refresh on a Timer

A refresh on a timer is the view’s job, because the timer has to stop when the view goes away. Start the timer when the view is attached, and stop it when the view is detached, and let the timer call a ViewModel command. Use either `UI.setPollInterval()` with a poll listener, or a scheduled task together with server push.

## <a id="show-slow-commands-as-state"></a>Show Slow Commands as State

A command that takes a while, such as a save that takes seconds, works like a search. Keep a `saving` state in a signal, and derive `canSave` from it, so that the **Save** button disables itself while the save is running. Set the outcome in the signal when the work completes.

## <a id="react-to-changes-in-the-model"></a>React to Changes in the Model

The Model may announce its changes through listeners, for example with a service method such as `addChangeListener()`. Subscribing to them is the view’s job, like a timer. The ViewModel has no attach or detach event, and a subscription that outlives the view keeps the view in memory.

The view subscribes when it’s attached, unsubscribes when it’s detached, and calls a ViewModel command that’s named after what happened:

```java
@Override
protected void onAttach(AttachEvent attachEvent) {
    var registration = ticketService.addChangeListener(attachEvent.getUI()
            .accessLater(event -> viewModel.ticketsChanged(), null));
    addDetachListener(detachEvent -> {
        detachEvent.unregisterListener();
        registration.remove();
    });
}
```

Keep the following in mind:

- One Subscription

  Only the route view subscribes. Child UI components don’t subscribe on their own. The route view passes each event to the ViewModel, and the ViewModel decides which parts of the view reload.

- The Event Comes from Another Thread

  The listener often runs in the request of another user, while that user’s session is the current one. A local signal throws an exception when it’s changed while another session is the current one, so the listener runs the command through `UI.accessLater()`. The command then runs with the session of this view locked. See [Callbacks](https://vaadin.com/docs/latest/building-apps/server-push/callbacks.md) for more.

- Testing

  The ViewModel test calls the command directly, in place of the event: `viewModel.ticketsChanged()`.

A shared signal in the Model is the alternative, as in [Refresh on Changes by Other Users](https://vaadin.com/docs/latest/building-apps/views/mvvm/lists.md#refresh-on-changes-by-other-users). The view then needs no listener, but the Model depends on the signal classes of Vaadin.
