Docs

Documentation versions (currently viewingVaadin 24)

Add a Service

In this guide, you’ll learn how to inject an application service into a Flow view and how to call it in various scenarios.

Injecting a Service

Since application services are Spring beans, you can inject them directly into your Flow views through constructor injection.

In the following example, CustomerOnboardingService is injected into CustomerOnboardingView:

@Route
public class CustomerOnboardingView extends Main {

    private final CustomerOnboardingService service; 1

    public CustomerOnboardingView(CustomerOnboardingService service) { 2
        this.service = service;
        // ...
    }
    ...
}
  1. Store the service in a final variable for future reference.

  2. Inject the service as a constructor parameter.

Constructor injection is recommended because it ensures that dependencies are provided at object creation, making the class easier to test and avoiding potential issues with uninitialized fields. Additionally, since the service is stored in a final variable, it cannot be reassigned accidentally, ensuring safer code.

Calling a Service

Since Flow views are regular Java objects, calling a service is as simple as invoking a method.

In the following example, the view calls CustomerOnboardingService when the user clicks a button:

@Route
public class CustomerOnboardingView extends Main {

    private final CustomerOnboardingService service;
    private final Binder<CustomerOnboardingForm> binder;

    public CustomerOnboardingView(CustomerOnboardingService service) {
        this.service = service;
        this.binder = new Binder<>(CustomerOnboardingForm.class);

        // Fields omitted

        var createCustomerBtn = new Button("Create");
        createCustomerBtn.addClickListener(event -> createCustomer());
        add(createCustomerBtn);
    }

    private void createCustomer() {
        try {
            var formData = binder.writeRecord(); 1
            var customer = service.onboardCustomer(formData); 2
            CustomerView.navigateTo(customer.customerId()); 3
        } catch (ValidationException ex) {
            // Handle the exception
        }
    }
}
  1. Retrieves a CustomerOnboardingForm record from the binder.

  2. Calls the service to onboard the customer.

  3. Navigates to the newly created customer’s view.

Calling a Service on View Creation

Sometimes, you may need to call a service immediately upon view creation—for example, to populate a combo box or grid with data. While it may be tempting to do this in the constructor, this is not recommended.

Vaadin may instantiate a view without actually displaying it. Because of this, you should keep constructors free of side effects.

Note
What is a side effect?
A side effect is any operation that modifies state outside the object’s scope or interacts with external systems like databases, files, or network services during object construction.

After Navigation

To call a service only after the user has navigated to a view, implement the AfterNavigationObserver interface and call the service in the afterNavigation() method:

@Route
public class MyView extends Main implements AfterNavigationObserver {

    private final CountryService countryService;
    private final ComboBox<Country> countries;

    public MyView(CountryService countryService) {
        this.countryService = countryService;
        countries = new ComboBox<>();
        add(countries);
    }

    @Override
    public void afterNavigation(AfterNavigationEvent afterNavigationEvent) {
        countries.setItems(countryService.getCountries());
    }
}

This ensures that service calls happen only when the view is actually rendered.

Cleaning Up

If a service call requires cleanup afterward — such as unsubscribing from a stream — use Vaadin’s attach and detach events.

Every Flow component is notified when it is attached to or detached from the UI. You can handle these events in two ways:

  1. Override the protected onAttach() and onDetach() methods.

  2. Register attach and detach listeners dynamically.

A common approach is to override onAttach() and register a detach listener.

In the following example, the view subscribes to a reactive stream when attached and unsubscribes when detached:

public class MyView extends Main {

    private final SubscriptionService subscriptionService;

    public MyView(SubscriptionService subscriptionService) {
        this.subscriptionService = subscriptionService;
        // ...
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        var subscription = subscriptionService.myStream().subscribe(message -> { 1
            // Do something with the message
        });
        addDetachListener(detachEvent -> {
            detachEvent.unregisterListener(); 2
            subscription.dispose(); 3
        });
    }
}
  1. Calls the service to subscribe to the stream when attached.

  2. Removes the detach listener to prevent duplicate listeners.

  3. Cancels the subscription to avoid memory leaks.

Important
Components Can Be Attached and Detached Multiple Times
When adding a detach listener inside onAttach(), always remove it when the component is detached. Otherwise, if the component is reattached later, multiple detach listeners will accumulate, leading to potential memory leaks.