Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When editing an updating Grid, I get exception.. #6

Open
skoude opened this issue Jul 14, 2017 · 9 comments
Open

When editing an updating Grid, I get exception.. #6

skoude opened this issue Jul 14, 2017 · 9 comments

Comments

@skoude
Copy link

skoude commented Jul 14, 2017

I've been trying to figure out, what is wrong in my code. When I go to edit some of the data in grid, I get this exception..

10:03:03.833 [qtp279680875-909] ERROR c.vaadin.server.DefaultErrorHandler - 
java.lang.IllegalStateException: Duplicate key Person(firstName=matti222, lastName=meikalainen22, sotu=111111)
        at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
        at java.util.HashMap.merge(HashMap.java:1245)
        at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
        at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
        at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1540)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
        at com.vaadin.data.provider.DataCommunicator$ActiveDataHandler.getActiveData(DataCommunicator.java:165)
        at com.vaadin.data.provider.DataCommunicator.refresh(DataCommunicator.java:521)
        at com.vaadin.ui.AbstractListing$AbstractListingExtension.refresh(AbstractListing.java:122)
        at com.vaadin.ui.components.grid.EditorImpl.save(EditorImpl.java:250)
        at com.vaadin.ui.components.grid.EditorImpl$1.save(EditorImpl.java:133)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        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.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:90)
        at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
        at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1577)
        at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:381)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:812)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
        at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:224)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
        at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
        at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
        at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
        at org.eclipse.jetty.server.Server.handle(Server.java:499)
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
        at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
        at java.lang.Thread.run(Thread.java:745)

And here is the code I'm using:

data class Person(var firstName: String, var lastName: String, var sotu: String)


/**
 * Person View
 */


@AutoView("")
class TestView: VerticalLayout(), View {


    private lateinit  var personGrid: Grid<Person>
    private lateinit  var firstNameFilter: TextField
    var persons = listOf<Person>(Person("matti", "meikalainen", "111111"), Person("teppo", "testaajaa", "2222222"))

    companion object {
        fun navigateTo() = navigateToView<WelcomeView>()
    }

    init {

        var personsListProvider = ListDataProvider<Person>(persons).withConfigurableFilter()


        isMargin = false
        label("Vaadin On Kotlin testview") {
            w = fillParent
            addStyleNames(ValoTheme.LABEL_H1, ValoTheme.LABEL_COLORED)
        }


        personGrid = grid(Person::class, "List of Persons", dataProvider = personsListProvider) {
            expandRatio = 1f;
            setSizeFull()

            val binder: Binder<Person> = getEditor().getBinder()

            column(Person::firstName) {
                caption="firstname"
                setEditorComponent(TextField("firstname"), Person::firstName.setter)
               isEditable = true
                isHidden = false
            }




            column(Person::lastName) {
                setEditorComponent(TextField("lastname"), Person::lastName.setter)
                isEditable = true
            }

            column(Person::sotu) {
                setEditorComponent(TextField("sotu"), Person::sotu.setter)
                isEditable = true

            }

            editor.addCancelListener( { event -> Notification.show("Editing Cancelled...")
            } )
            editor.addSaveListener({ event ->
                Notification.show("Saving ${event.bean.firstName} - ${event.bean.lastName} - ${event.bean.sotu} -...")
                binder.writeBean(event.bean)

                personsListProvider.refreshAll()

                // just for debugging to see what's in the provider now..
                personsListProvider.getAll().forEach(
                        {
                            println("${it.firstName} - ${it.lastName} - ${it.sotu}")
                        }
                )

                refresh()

            })

            editor.setSaveCaption("Tallenna")
            editor.setCancelCaption("Peruuta")

            editor.setEnabled(true)

            addColumn({ "Show" }, ButtonRenderer<Person>({ event -> personGrid.refresh() }))
            addColumn({ "Edit" }, ButtonRenderer<Person>({ event -> null }))
            addColumn({ "Delete" }, ButtonRenderer<Person>({ event -> null }))

            // automatically create filters, based on the types of values present in particular columns.
            appendHeaderRow().generateFilterComponents(this, Person::class)


        }

        personGrid.addItemClickListener({ event -> Notification.show("Value: " + event.getItem()) })

    }


    override fun enter(event: ViewChangeListener.ViewChangeEvent?) {
        personGrid.dataProvider.refreshAll()
    }

}

Even if I leave the whole ListDataProvider out of it, I still get the same duplicate key error.
So I'm starting to wonder, is this a bug in Vaadin, or am I just doing something wrong? Is the problem with ListDataProvider, or with the Binder? I'm just not getting it how to use this properly through the docs..

@mvysny
Copy link
Owner

mvysny commented Jul 14, 2017

I believe that Grid requires all rows to be unique; or rather they have an unique ID that identifies every row without any doubt. The exception would support this belief, but Vaadin's DataProvider.getId() makes no such requirements. I suggest we ask the Vaadin gurus at forums, whether it is allowed for aListDataProvider (or DataProvider in general) to contain two items with the same ID or not. Or perhaps we should create a Vaadin bug since it's not immediately clear from the documentation whether those IDs must be unique or not.

Anyways, I'll continue by assuming that the Grid requires all rows to be unique. By default the Person itself is the row ID. You can see this by looking into the DataProvider.getId() default implementation, which ListDataProvider doesn't override. That means that Grid uses Person's equals() and hashCode(); with data classes this means that the instances of Person are considered equal if their fields are all equal.

I thus believe that there might be more than one matti in your database. The problem should go away by e.g. introducing a primary key column into the Person class.

@mvysny mvysny self-assigned this Jul 14, 2017
@mvysny mvysny added the bug label Jul 14, 2017
@skoude
Copy link
Author

skoude commented Jul 14, 2017

Yes, i was also thinking that it requires them to be unique. But if the Grid uses the hashCode, it should be different, when the variables inside an objects are different. Or did I misunderstood that?

And if the keys are the same, wouldn't the same problem arise when getting in to the view, that lists the objects in Grid?

So this problem basically happens only after editing something. And it does not matter if you edit it so that it's unique. And in that test there is only two objects. And It happens even if I edit them so that they really are unique.

I also tested this by not using the DataProvider. I just assigner this person -list to the grid, and still the same thing..

Did you make a bug report to vaadin already, or will I make it?

@mvysny
Copy link
Owner

mvysny commented Jul 14, 2017

Ah, I haven't realized that this only happens in the edit mode - I'm sorry. Well, then you are way more skilled in this area than me ;) Can you please open the bug report? I think you will be more capable of providing all necessary details than I can.

Assigning a person list to the Grid will actually create a ListDataProvider in the background I believe, so it's the same as creating the ListDataProvider manually yourself.

@mvysny
Copy link
Owner

mvysny commented Jul 14, 2017

Also, if I understand correctly, you're migrating your project to Vaadin 8 and Kotlin? I would be thrilled to learn of the project and how the migration went; also I believe that Vaadin marketing guys would love to hear that, you should drop a mail to all of us - in Finnish, of course :-)

@skoude
Copy link
Author

skoude commented Jul 14, 2017

Yeap, I'm basically rewriting an old JAva and Vaadin 7.7 based project to use Kotlin and Vaadin.. But The whole backend is now rewritten with kotlin, but I'm only struggling with this UI now :) And it seems like the documentation in Vaadin is not the clearest one regarding the Grid component.

Btw. is there any major issues, if I try to use Vaadin 8.0.6 in vaadin-on-kotlin? Is there any features that will not work with older vaadin version?

@mvysny
Copy link
Owner

mvysny commented Jul 14, 2017

Just open a bug report at https://vaadin.com/bug while Vaadin 8.1 is still in RC1. I think that guys will want to learn of the docu shortcomings and will be able to fix it until the 8.1 final is out.

I believe you should be able to use Vaadin 8.0.6 just fine with Vaadin-on-Kotlin. I'm personally using 8.1 RC1 because of ComponentRenderer support added in 8.1, and it's working quite nicely: aedict-online.eu , also https://martin.app.fi/karibudsl/

@mvysny
Copy link
Owner

mvysny commented Jul 14, 2017

Please, when you do open a bug, just link it from here so that I can be notified when it's fixed upstream - thanks!

@skoude
Copy link
Author

skoude commented Jul 14, 2017

Here's the link to the bug: vaadin/framework#9678

@DALDEI
Copy link

DALDEI commented Jul 26, 2019

I have hit similar but not identical symptoms -- may or may not be the same.
My case, the grid started behaving very strangely but didn't always crash -- I debuged/resolved it by following the hints in the docs about unique ID's. The DataProvider getId() uses Object.equals() in some places - basically the same rules for 'identity' for Java collections.
Depending on the specifics of the object -- in kotlin - variants on interface/class/data class, @JvmField, @JvmStatic , extension properties etc can lead to 2 objects being 'the same' even if they are not (or visa-versa).
I solved it with a derived DataProvider with a custom getId()

NOTE: the object in question came from vok-db , where "id" is a special property that's required for data binding and DB access. For a while I was confused that this was what was being used by DataProvider.
Note: I've found the 'safest' classes to use for Grid are 'pure' 'data class' kotlin classes with nothihng fancy. Vaadin does a 'very good' job at discovering properties - often 'too good' for kotlin - finding functions or extension properties not intended for UI puproses. I havent yet found a clean way to ignore these in general to the point that I often dynamically generate classes at runtime just to put in a grid in a reproducible/reliably way with consistent naming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants