简体   繁体   中英

Vaadin double clicking button on grid with active editor throws exception (“java.lang.NullPointerException: Editor can't edit null”)

I have a vaadin grid with active, buffered editor. By default, the editor is opened when double clicking a row. Everything works fine, except when I double click the button I get an exception: (The exception doesn't point anywhere at my code)

java.lang.NullPointerException: Editor can't edit null
at java.base/java.util.Objects.requireNonNull(Objects.java:246)
at com.vaadin.ui.components.grid.EditorImpl.doEdit(EditorImpl.java:216)
at com.vaadin.ui.components.grid.EditorImpl$1.bind(EditorImpl.java:151)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:155)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:116)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:445)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:410)
at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:274)
at com.vaadin.server.communication.PushHandler.lambda$new$1(PushHandler.java:145)
at com.vaadin.server.communication.PushHandler.callWithUi(PushHandler.java:235)
at com.vaadin.server.communication.PushHandler.onMessage(PushHandler.java:520)
at com.vaadin.server.communication.PushAtmosphereHandler.onMessage(PushAtmosphereHandler.java:87)
at com.vaadin.server.communication.PushAtmosphereHandler.onRequest(PushAtmosphereHandler.java:77)
at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:223)
at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:115)
at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:67)
at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:2284)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:593)
at org.atmosphere.websocket.DefaultWebSocketProcessor$3.run(DefaultWebSocketProcessor.java:345)
at org.atmosphere.util.VoidExecutorService.execute(VoidExecutorService.java:101)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:340)
at org.atmosphere.websocket.DefaultWebSocketProcessor.invokeWebSocketProtocol(DefaultWebSocketProcessor.java:447)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:272)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:269)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:844)

This is how I add the Button to the grid:

// Adding the column
    grid.addComponentColumn(this::buildAddButton);

This is the method that returns the buttons for each row:

// Building the button
private Button buildAddButton(ProductTemplate p) {
    Button button = new Button("add");
    // Configurate the button
    ...
    return button;
}

Unfortunatly I have found very little about this problem... Since the vaadin button doesn't stop click event propagation, I tried this:

  1. Adding the button to a layout to "block" the click event by removing the click listeners from the layout.

     // Adding the column grid.addComponentColumn(this::buildAddLayout);

    Build a layout instead of just a button:

     // Building the layout with the button private VerticalLayout buildAddLayout(ProductTemplate p) { Button button = new Button("add"); // Configurate the button ... VerticalLayout layout = new VerticalLayout(); layout.addComponent(button); layout.getListeners(Event.class).clear(); return layout; }
  2. Disabling the button when clicked until it's task is finished (Prohibiting double clicking the button).

     // Building the button private Button buildAddButton(ProductTemplate p) { Button button = new Button("add"); // Configurate the button ... button.addClickListener(e -> { button.setEnabled(false); buttonClicked(p); button.setEnabled(true); }); return button; }

Both didn't make me able to get rid of the exception. Any suggestions, how I can prevent this type of exception, when double clicking a button on a grid with enabled editor?

Edit:

The editor is trying to edit the value of the button, what obviously is not possible. I want to stop the editor from doing this. ("can't edit null "means that vaadin wasn't able to create a valid bean out of the button)

Edit 2: My previous assumption, made in the first edit, seemed to be wrong. My button click is refreshing the grid and I am not able to stop the editor from trying to edit a row, even though all items are getting removed and reloaded.

Here are a class and a pom, you can use to reproduce the exception (The exception occurs when clicking the button very fast):


MyUI.java

package com.example.sample;

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

import javax.servlet.annotation.WebServlet;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("mytheme")
public class MyUI extends UI {
    List<Product> items = new ArrayList<Product>();
    Grid<Product> grid;

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        items.add(new Product("test", "test test"));

        final VerticalLayout layout = new VerticalLayout();

        grid = new Grid<Product>();
        layout.addComponent(grid);

        grid.getEditor().setBuffered(true);
        grid.getEditor().setEnabled(true);

        grid.removeAllColumns();

        grid.addComponentColumn(this::buildAddButton);

        TextField nameField = new TextField();
        TextField descriptionField = new TextField();

        grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
                .setExpandRatio(1);
        grid.addColumn(Product::getDescription).setCaption("Description")
                .setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);

        grid.getEditor().addSaveListener(event -> {

            Notification.show((event.getBean() + "saved"));
        });

        grid.setItems(items);
        setContent(layout);
    }

    private Button buildAddButton(Product p) {

        Button button = new Button();

        button.addClickListener(event -> addButtonClicked(p));

        return button;
    }

    private void addButtonClicked(Product p) {
        refreshGrid();
    }

    private void refreshGrid() {
        grid.setItems(items);
    }

    @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
    @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
    public static class MyUIServlet extends VaadinServlet {
    }

    public class Product {
        String name;
        String description;

        public Product(String name, String description) {
            super();
            this.name = name;
            this.description = description;
        }

        public Product() {
            super();

        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        @Override
        public String toString() {
            return name + " " + description;
        }

    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>sample</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>sample</name>

    <prerequisites>
        <maven>3</maven>
    </prerequisites>

    <properties>
        <vaadin.version>8.3.1</vaadin.version>
        <vaadin.plugin.version>8.3.1</vaadin.plugin.version>
        <jetty.plugin.version>9.3.9.v20160517</jetty.plugin.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <!-- If there are no local customizations, this can also be "fetch" or "cdn" -->
        <vaadin.widgetset.mode>local</vaadin.widgetset.mode>
    </properties>

    <repositories>
        <repository>
            <id>vaadin-addons</id>
            <url>http://maven.vaadin.com/vaadin-addons</url>
        </repository>
    </repositories>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-bom</artifactId>
                <version>${vaadin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-push</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-client-compiled</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-themes</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <!-- Exclude an unnecessary file generated by the GWT compiler. -->
                    <packagingExcludes>WEB-INF/classes/VAADIN/widgetsets/WEB-INF/**</packagingExcludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-maven-plugin</artifactId>
                <version>${vaadin.plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>update-theme</goal>
                            <goal>update-widgetset</goal>
                            <goal>compile</goal>
                            <!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
                            <goal>compile-theme</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <!-- Clean up also any pre-compiled themes -->
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>src/main/webapp/VAADIN/themes</directory>
                            <includes>
                                <include>**/styles.css</include>
                                <include>**/styles.scss.cache</include>
                            </includes>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

            <!-- The Jetty plugin allows us to easily test the development build by
                running jetty:run on the command line. -->
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>${jetty.plugin.version}</version>
                <configuration>
                    <scanIntervalSeconds>2</scanIntervalSeconds>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <!-- Vaadin pre-release repositories -->
            <id>vaadin-prerelease</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>

            <repositories>
                <repository>
                    <id>vaadin-prereleases</id>
                    <url>http://maven.vaadin.com/vaadin-prereleases</url>
                </repository>
                <repository>
                    <id>vaadin-snapshots</id>
                    <url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
                    <releases>
                        <enabled>false</enabled>
                    </releases>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </repository>
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>vaadin-prereleases</id>
                    <url>http://maven.vaadin.com/vaadin-prereleases</url>
                </pluginRepository>
                <pluginRepository>
                    <id>vaadin-snapshots</id>
                    <url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
                    <releases>
                        <enabled>false</enabled>
                    </releases>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>

</project>

Disclaimer : this solution is not applicable for the versions prior 8.3 due to a missing setHandleWidgetEvents method. Otherwise, while complicated, this solution might work for someone:

  1. Remove a click event handling from a button ( addClickListener ). There is no way to get an information about double click there, BUT

  2. There is a way using addItemClickListener and its clickedItem.getMouseEventDetails().isDoubleClick() , BUT again

  3. Once you enable grid using grid.getEditor().setEnabled(true); a double-click check always returns false ,so

  4. You would need to disable editor by default grid.getEditor().setEnabled(false); (A good related answer here Couldn't capture double click event using vaadin 7 )

  5. And , instead, re-enable it once a double click event has occurred(and open a row) grid.getEditor().setEnabled(true); grid.getEditor().editRow(item.getRowIndex()); grid.getEditor().setEnabled(true); grid.getEditor().editRow(item.getRowIndex());

  6. If, otherwise, a button column is clicked once, execute your addButtonClicked action then

  7. In order to receive an event from a Grid column's component, when a button is clicked, column should be able to handle events. This is achieved using grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");

A complete modified code from the init method. All others remain the same (also, remember to remove a addClickListener from button):

        items.add(new Product("test", "test test"));

        grid = new Grid<Product>();

        grid.getEditor().setBuffered(true);
        grid.getEditor().setEnabled(false);

        grid.removeAllColumns();

        // Important! Propagate events from components to Grid
        grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");

        TextField nameField = new TextField();
        TextField descriptionField = new TextField();

        grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
                .setExpandRatio(1);
        grid.addColumn(Product::getDescription).setCaption("Description")
                .setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);

        //Once close editor--> Disable it
        grid.getEditor().addSaveListener(event -> {
            grid.getEditor().setEnabled(false);

        });
        grid.getEditor().addCancelListener(e->{
            grid.getEditor().setEnabled(false);
        });
        //THIS IS WHERE ALL THE LOGIC IS HAPPENING
        grid.addItemClickListener(item->{
            //If the button column is clicked
           if("buttonClick".equals(item.getColumn().getId())){
               //Regual click--> update content; also fired twice before editor is opened
               if(!item.getMouseEventDetails().isDoubleClick()){
                  addButtonClicked(item.getItem());
               }
               //If Double click is detected, just opened editor. The data is already updated
               else{
                   grid.getEditor().setEnabled(true);
                   grid.getEditor().editRow(item.getRowIndex());
               }
            }
           //In all the other cases, when double click is detected--> open editor
           else if(item.getMouseEventDetails().isDoubleClick()){
               grid.getEditor().setEnabled(true);
               grid.getEditor().editRow(item.getRowIndex());
           }
        });

        grid.setItems(items);
        addComponent(grid);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM