URL parameter use confusion

I have a view named DeviceView annotated with @Route(value = "devices", layout = MainView.class) that contains a Grid<Device> with the following ValueChangeListener

	grid.asSingleSelect().addValueChangeListener(e -> {
		UI.getCurrent().navigate("devices/details/" + e.getValue().getId());
	});

I then have a DeviceDetails view annotated with @Route(value = "devices/details", layout = MainView.class)

public class DeviceDetails extends VerticalLayout implements HasUrlParameter<Long> {
    private final DeviceRepository repository;
    private Device device;
    private Binder<Device> binder = new Binder<>(Device.class);
	...code continues...
}

With the setParameter method as (I’ve removed a try…catch block for simplicity)

@Override
public void setParameter(BeforeEvent event, @OptionalParameter Long deviceId) {
		Device device = repository.findById(deviceId).get();
		log.info(device.toString());
		binder.readBean(device);
}

My main method then contains

@Autowired
private DeviceDetails(DeviceRepository repository) {
	this.repository = repository;
	
	...form creation code...
	
	binder.bindInstanceFields(this);

	if (device.getDeviceStatus().equals("Available")) {
		...do things...
	}
}

Where the device.getDeviceStatus() check is just one example of how I’d like to use the device object within the main method. I’m running into a confusing issue, though, as it looks like the device object is defined after those calls to it. Whenever I try to use device within the main method it results in a NullPointerException from Spring while trying to construct the view. My confusion is mostly that the form works perfectly fine and displays the correct information about the device, but of course Vaadin is doing the form binding for me.

I’m clearly missing something basic regarding how to use the setParameter method. The [documentatation]
(https://vaadin.com/docs/v13/flow/routing/tutorial-router-url-parameters.html) says “This method will always be invoked before a navigation target is activated.” so I was expecting to be able to use device after it was defined within setParameter, but it looks like it is being defined after. What’s the correct way for me to go about getting that device object into my main method?

I would not recommend to put business logic related code in setParameter(..). Instead I would just save the parameter in the field and use it later when view is being navigated to, like using AfterNavigationObserver.

I’m struggling to find example code that works that way. My goal is, for example, for the user to navigate to a route such as devices/details/100 and then have the DeviceDetails view allow for display and manipulation of the device with id 100. I thought that setParameter would be the appropriate method for getting that id from the url. Right now, I’m mostly confused by why the binder is reading the device bean easily when it is called in setParameter but I can’t get access to that within the DeviceDetails method. I think that’s more an issue of me not understanding how Spring is working, though.

Patrick,

Quick guess is the order of method calls. The constructor will be invoked first, so device will be NULL. After the constructor has been invoked setParameter() will be invoked and then - as Tatu mentioned - the AfterNavigationObserver() method.

Tatu Lund:
I would not recommend to put business logic related code in setParameter(..). Instead I would just save the parameter in the field and use it later when view is being navigated to, like using AfterNavigationObserver.

Hi Patrick,

I have set up my details view exactly like you did. What Martin says is 100% correct. device will be null in your constructor, so you will have to move all logic related to specific device objects into the setParameter method (or as Tatu mentioned the afterNavigation method).

The constructor can still build the whole layout, and bind the input fields to the Binder.
Then later, after you loaded the device from the DB, you can let the binder read/set the bean and update the already built layout according to specific values of the loaded device

public class DeviceDetails extends VerticalLayout implements HasUrlParameter<Long>, AfterNavigationObserver {
    private final DeviceRepository repository;
	private Long deviceId;
    private Device device;
    private Binder<Device> binder = new Binder<>(Device.class);
	
	// examples of updatable components
	private HorizontalLayout onlyEnabledIfDeviceIsAvailable;
	private Grid<DeviceOption> optionsGrid;
	
	@Autowired
	public DeviceDetails(DeviceRepository repository) {
		this.repository = repository;

		add(buildLayout()); // build your complete view layout
		binder.bindInstanceFields(this); // bind all fields
	}
	
	// trying to implement Tatus suggestion in this example (only save id value, let afterNavigation do the rest)
	@Override
	public void setParameter(BeforeEvent event, @OptionalParameter Long deviceId) {
		this.deviceId = deviceId;
	}
	
    @Override
    public void afterNavigation(AfterNavigationEvent event) {
        Device device = repository.findById(this.deviceId).get();
		log.info(device.toString());
		binder.readBean(device);
			
		// update certain layout parts
		onlyEnabledIfDeviceIsAvailable.setEnabled(device.getDeviceStatus().equals("Available");
		optionsGrid.setItems(device.getDeviceOptions());
    }
}

@Tatu: why is it better to use the afterNavigation() method for this instead of setParameter() ?

Thank you all. I’m newish to Java and very new to Vaadin, so I appreciate the patience for basic questions. Kaspar, your code was especially helpful. With the changes I made my final code for the DeviceDetails view looks like:

@Route(value = "devices/details", layout = MainView.class)
public class DeviceDetails extends VerticalLayout implements HasUrlParameter<Long>, AfterNavigationObserver {

    private final DeviceRepository repository;
    private Device device;
    private Long deviceId;
    private Binder<Device> binder = new Binder<>(Device.class);

    ...fields and other layout variables here...

    @Autowired
    public DeviceDetails(DeviceRepository repository) {
        this.repository = repository;

        ...layout construction here...

        binder.bindInstanceFields(this);
        binder.setReadOnly(true);

    }

    ...methods for saving and editing here...

    @Override
	public void setParameter(BeforeEvent event, @OptionalParameter Long deviceId) {
        this.deviceId = deviceId;

    }


    @Override
    public void afterNavigation(AfterNavigationEvent event) {
        try {
            device = repository.findById(deviceId).get();
            binder.readBean(device);

        } catch (Exception e) {
            device = new Device(...constructor for no device found...);
            binder.readBean(device);

        } finally {
            if (device.getDeviceStatus().equals("Available")) {
                ...setVisible() for various components here...

            } else if (device.getDeviceStatus().equals("Checked Out")) {
				...setVisible() for various components here...

            } else {
				...setVisible() for various components here...

            }
        }


    }

}

I know it would probably be cleaner to move some of these into methods used to modify the layout. The really important lesson for anyone else who was confused like me is that any check that uses the device object will need to be done in the afterNavigation method (or another method that it calls). As noted, the order of the method calls is what I wasn’t taking into account.