Vaadin 8.5.1- Refresh grid after row update
Asked Answered
A

2

1

I am using Vaadin 8.5.1 Grid to display 1000's of rows. Once a row is updated with change in it property, I use grid.getDataProvider().refreshItem(selectedRow) or grid.getDataProvider().refreshAll() which fails to update the row.

I need to do explicit grid.setItems() to see the updated property of the row.

I am using below snippet to create a Grid

    msgGrid = new ABSMsgGrid();

    List<ConsoleEntry> messageEntryList = new ArrayList<>();
    if (inputConsole != null) {
        messageEntryList.addAll(inputConsole.getMessageEntryList());
    }

    msgGridDataProvider = new ListDataProvider<ConsoleEntry>(messageEntryList) {

        @Override
        public Object getId(ConsoleEntry item) {
            return item.getId();
        }
    };

    msgGrid.setDataProvider(msgGridDataProvider);



//on changing property of the grid row, i use the below snippet
private void handleHideRowMenuItem(GridContextMenu<ConsoleEntry> contextMenu, ConsoleEntry selectedConsoleItem) {
        if (!selectedConsoleItem.isHidden()) {
            hideRowMenuItem = contextMenu.addItem("Hide Row", VaadinIcons.EYE_SLASH, selectedMenuItem -> {
                    selectedConsoleItem.hide();
                    **msgGridDataProvider.refreshItem(selectedConsoleItem);**
                }
            });
        }
}

public class ConsoleEntry {

        @Override
        public boolean equals(Object obj) {
            // TODO Auto-generated method stub
            if (obj instanceof ConsoleEntry) {
                ConsoleEntry temp = (ConsoleEntry) obj;
                String msgRef2 = temp.getMsgRef();
                return this.getMsgRef().equalsIgnoreCase(msgRef2);
            }
            return false;
        }

        @Override
        public int hashCode() {
            // TODO Auto-generated method stub
            return super.hashCode();
        }

        public String getId(){
            return this.getMsgRef();
        }
}       

I have seen similar question but none of the solutions worked.

How to refresh the vaadin Grid after you change something?

Vaadin - Refresh grid after row modification

Appreciate if any one could share pointers on how to solve this problem

TIA

Atbara answered 16/8, 2018 at 7:4 Comment(5)
How do you populate your Grid initially? What data structure is it?Candi
I use list of objects to do setItemsAtbara
Please show your code which does this, so we can only guess what you do. Are you using the ListDataProvider?Candi
By default Grid uses ListDataProvider and here is my code below msgGrid = new Grid<ConsoleEntry>(); List<ConsoleEntry> messageEntryListSize = new ArrayList<>(); messageEntryListSize.addAll(console.getMessageEntryList()); msgGrid.setItems(messageEntryListSize);Atbara
@Atbara Provide further info as edits to your Question rather than as comments.Hoehne
P
4

For a item to be seen as the same item, (and the refresh working) you need a corretly implemented equals() and hashCode() methods on the object.

From the documentation

public void refreshItem(T​ item)

Description copied from interface: DataProvider

Refreshes the given item. This method should be used to inform all DataProviderListeners that an item has been updated or replaced with a new instance.

For this to work properly, the item must either implement

equals(​Object) and #hashCode() to consider both the old and the new item instances to be equal, or alternatively

DataProvider.​getId(​Object) should be implemented to return an appropriate identifier.

In addition to this, it's you should create a ListDataProvider, assign it to the grid and then do the updated via the same instance of the previously assigned ListDataProvider

Phew answered 17/8, 2018 at 6:19 Comment(3)
I am still unable to get the Grid working with DataProvider and change in the ItemObj to include equals and hashcode. If I edit any property of the ItemObj, the grid.getDataProvider.refreshItem(itemObj) doesnot refresh the updated row. I need to trigger explicit setDataProvider with new list of input obj. Appreciate if you could help. TIAAtbara
Finally, got it working with creating method getId in Item Class and not overriding equals or hashcode methods of Item Class.Atbara
@Atbara I do not see what creating a getId method on your business object did for you. The DataProvider can optionally implement a getId, but neither the Grid nor DataProvider call getId on your business object. Can you explain more, or perhaps post on Answer with details?Hoehne
H
2

As the correct Answer by Schild said, you must override your equals and hashCode methods in such away that they consider the same field(s), and they must only consider field(s) whose values will not be changing during the time frame when you depend on them.

For data-driven business objects, this usually means simply looking at the identifier on the object that is your primary key in a database (or would be if you used a database).

As seen in the example below, our Status class has a UUID object as its identifier, a universally unique identifier (UUID) (a 128-bit value, canonically displayed as a 36-character hex string with hyphens). Our equals and hashCode considers only that one member of the object.

@Override
public boolean equals ( Object obj ) {  // Compare the UUID member named `uuid`.
    if ( obj == this ) { return true; }
    if ( obj instanceof Status ) { return this.getUuid().equals( ( ( Status ) obj ).getUuid() ); }
    return false;
}

@Override
public int hashCode () {
    return Objects.hashCode( this.uuid );
}  // Compare the UUID member named `uuid`.

Example app

Here is a complete working example. Written for Vaadin 8.5.

enter image description here

In this example, we track three pieces of equipment. A Status object represents each.

We modify the current status number (-1, 0, or 1) by choosing a new value number from the pop-up menu (a NativeSelect in Vaadin), and clicking a button for which piece of equipment. Selecting a row is irrelevant here.

For your edification, I wrote two different pieces of code for updating the item in the Grid. Both approaches are common in Vaadin business apps:

  • Modifying the existing Status object backing the Grid.
  • Replace the target Status object with a fresh instance, but using the same identifier value in its id field.

For example, in some cases, a data repository service code may be written to always return a fresh instance. In other cases, we may intentionally be updating an existing object with changes.

    private void updateByModifying ( Status s , Integer integer ) {
        s.setCurrentStatus( integer );
        grid.getDataProvider().refreshItem( s );
    }

    private void updateByInstantiating ( Status s , Integer integer ) {
        boolean removed = statuses.remove( s );
        Status replacement = new Status( s.getUuid() , s.getName() , integer );
        boolean added = statuses.add( replacement );
        grid.getDataProvider().refreshItem( replacement );
    }

In this demo, switch between these approaches with the Update row by pop-up menu (also known as a drop-down list). Both of these approaches work by calling DataProvider::refreshItem. Note that both of these approaches depend on your equals and hashCode being properly implemented.

As a further example, I threw in a Refresh all button to show the use of DataProvider::refreshAll method.

Source code

Three Java files in this example app:

  • MyUI.java (entry into Vaadin)
  • Status.java (our business object, POJO)
  • StatusLayout (the UI content and business logic)

MyUI.java

package com.basilbourque.example;

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.*;

import java.util.List;
import java.util.UUID;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of an HTML page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme ( "mytheme" )
public class MyUI extends UI {

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        Layout layout = new StatusLayout();
        this.setContent( layout );
    }

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

Status.java

package com.basilbourque.example;

import java.util.Objects;
import java.util.UUID;

public class Status {
    // Members.
    private UUID uuid;
    private String name;
    private Integer currentStatus;  // 1 = Good, 0 = okay, -1 = Bad.

    // Constructor.
    public Status ( UUID uuid , String name , Integer currentStatus ) {
        this.uuid = uuid;
        this.name = name;
        this.currentStatus = currentStatus;
    }

    // -----------|  Object  |-------------------------

    @Override
    public boolean equals ( Object obj ) {  // Compare the UUID member named `uuid`.
        if ( obj == this ) { return true; }
        if ( obj instanceof Status ) { return this.getUuid().equals( ( ( Status ) obj ).getUuid() ); }
        return false;
    }

    @Override
    public int hashCode () {
        return Objects.hashCode( this.uuid );
    }  // Compare the UUID member named `uuid`.

    @Override
    public String toString () {
        return "Status{ " +
        "uuid=" + uuid +
        " | name='" + name + '\'' +
        " | currentStatus=" + currentStatus +
        " }";
    }

    // -----------|  Accessors  |-----------------------------

    public UUID getUuid () {
        return uuid;
    }

    public void setUuid ( UUID uuid ) {
        this.uuid = uuid;
    }

    public String getName () {
        return name;
    }

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

    public Integer getCurrentStatus () {
        return currentStatus;
    }

    public void setCurrentStatus ( Integer currentStatus ) {
        this.currentStatus = currentStatus;
    }
}

StatusLayout.java

package com.basilbourque.example;

import com.vaadin.data.provider.ListDataProvider;
import com.vaadin.ui.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

public class StatusLayout extends VerticalLayout {
    // Members
    List< Status > statuses;
    Grid< Status > grid;

    final List< Integer > numbers = List.of( 1 , 0 , - 1 );
    NativeSelect< Integer > numberPopup;
    NativeSelect< Boolean > updatyByModifyingOrInstantiating;
    Button setPump, setCamera, setSensor, refreshAllButton;

    // Constructor
    public StatusLayout () {

        statuses = new ArrayList<>( 3 );
        statuses.add( new Status( UUID.fromString( "1c0d183e-c2ba-11e8-a355-529269fb1459" ) , "Pump" , numbers.get( 0 ) ) );
        statuses.add( new Status( UUID.fromString( "2490c74e-1aac-4d71-9a2c-880628dcfc28" ) , "Camera" , numbers.get( 1 ) ) );
        statuses.add( new Status( UUID.fromString( "6ae07414-f557-4a1e-a552-cb5ec5f48476" ) , "Sensor" , numbers.get( 2 ) ) );

        // Create a grid bound to the list
        grid = new Grid<>( Status.class );
        grid.setCaption( "Equipment Status" );
        grid.setItems( statuses );
//        grid.addColumn( Status :: getName ).setCaption( "Name" );
//        grid.addColumn( Status :: getCurrentStatus ).setCaption( "Status" );

        updatyByModifyingOrInstantiating = new NativeSelect<>( "Update row by: " , List.of( Boolean.TRUE , Boolean.FALSE ) );
        updatyByModifyingOrInstantiating.setValue( Boolean.TRUE );
        updatyByModifyingOrInstantiating.setItemCaptionGenerator( ( ItemCaptionGenerator< Boolean > ) item -> item ? "modifying" : "instantiating" );

        Label valueSetterLabel = new Label( "Set status:" );
        numberPopup = new NativeSelect<>();
        numberPopup.setItems( numbers );
        numberPopup.setValue( numbers.get( 1 ) );
//        numberPopup.setItemCaptionGenerator( item -> List.of( "Good" , "Okay" , "Bad" ).get( numbers.indexOf( item ) ) );  // Display words rather than the underlying number.

        // The `buttonClick` method below has logic depending on match between button name and `name` property on `Status` objects in grid.
        setPump = new Button( statuses.get( 0 ).getName() );  // Pump
        setPump.addClickListener( this :: buttonClick );
        setCamera = new Button( statuses.get( 1 ).getName() ); // Camera
        setCamera.addClickListener( this :: buttonClick );
        setSensor = new Button( statuses.get( 2 ).getName() );   // Sensor
        setSensor.addClickListener( this :: buttonClick );

        refreshAllButton = new Button( "Refresh all" );
        refreshAllButton.addClickListener( clickEvent -> grid.getDataProvider().refreshAll() );

        // Arrange
        grid.setWidth( 100 , Unit.PERCENTAGE );

        HorizontalLayout valueSetterBar = new HorizontalLayout();
        valueSetterBar.addComponents( valueSetterLabel , numberPopup , setPump , setCamera , setSensor );
        valueSetterBar.setComponentAlignment( valueSetterLabel , Alignment.MIDDLE_CENTER );
        valueSetterBar.setComponentAlignment( numberPopup , Alignment.MIDDLE_CENTER );
        valueSetterBar.setComponentAlignment( setPump , Alignment.MIDDLE_CENTER );
        valueSetterBar.setComponentAlignment( setCamera , Alignment.MIDDLE_CENTER );
        valueSetterBar.setComponentAlignment( setSensor , Alignment.MIDDLE_CENTER );

        addComponents( grid , updatyByModifyingOrInstantiating , valueSetterBar , refreshAllButton );
    }

    private void buttonClick ( Button.ClickEvent clickEvent ) {
        System.out.println( "TRACE - Setting " + clickEvent.getButton().getCaption() + " to " + this.numberPopup.getValue().toString() );
        // Find the `Status` object in the `List` whose name matches the name of the button clicked by user.
        Optional< Status > optionalStatus = statuses.stream().filter( status -> status.getName().equals( clickEvent.getButton().getCaption() ) ).findFirst();
        // We expect the matching `Status` to always be found. If not, throw exception.
        Status s = optionalStatus.orElseThrow( () -> new IllegalStateException( "Failed to find expected item in list of statuses: " + clickEvent.getButton().getCaption() ) );
        Integer valueToSet = this.numberPopup.getValue();
        // Set the `currentStatus` property on the `Status` object to the value of the selected popup menu item.
        // Try either updating by modifying existing row or by instantiating a new one.
        // Comment-out either of the next two lines.
        if(updatyByModifyingOrInstantiating.getValue().equals( Boolean.TRUE )) {
            this.updateByModifying( s , valueToSet );
        } else {
            this.updateByInstantiating( s , valueToSet );
        }
    }

    private void updateByModifying ( Status s , Integer integer ) {
        s.setCurrentStatus( integer );
        grid.getDataProvider().refreshItem( s );
    }

    private void updateByInstantiating ( Status s , Integer integer ) {
        boolean removed = statuses.remove( s );
        Status replacement = new Status( s.getUuid() , s.getName() , integer );
        boolean added = statuses.add( replacement );
        grid.getDataProvider().refreshItem( replacement );
    }
}
Hoehne answered 7/10, 2018 at 3:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.