Actualizando filas de grid con push y broadcaster singleton

Este es un pequeño ejemplo de como actualizar las filas de un grid haciendo push por medio de un broadcaster, este tiene el fin de actualizar todas las UI, en este caso me base en la versión Vaadin 8 de un proyecto y la migre a la versión 14, nada fuera de lo común, pero si es una idea de como actualizar unicamente un componente Vaadin sin recargar la pagina completa, puede ser el que queramos 🥰

La magia esta en esta clase, le hize refactorin para entenderla de una manera mas facil añadiendo el Registration que nos ofrece Vaadin para estos casos, ella propaga los datos del grid en todas las sesiones de los usuarios, es decir visualizaran siempre el mismo grid aun sabiendo que si actualizamos la pestaña los datos se mantienen como estaban.

  • //1 La lista necesaria y concurrente para actualizar las UI cada unas de ellas es agregada aqui al abrir una tab/pestaña del navegador web.
  • //2 Encargado de ejecutar las actualizaciones en un unico Thread distinto al principal.
  • //3 Este método se ejecuta en la linea 108 con el botón Init transactions! en la GridPushOnRows.java parte de la magia, el recorre toda la lista y actualiza todas las filas.
  • //4 En este método se añaden la lista de strings(datos de cada fila en realidad) a nuestra lista concurrente y remueve a los elementos si estan presentes.
@Log4j2
public class Broadcaster {
    //1
    private static final List<SerializableConsumer<List<String>>> listeners = new CopyOnWriteArrayList<>();
    //2
    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
    //3
    public static void broadcast(List<String> updatedTransactionIds) {
        listeners.forEach(listener -> {
            executorService.execute(() -> listener.accept(updatedTransactionIds));
        });
        log.debug("Notified {} broadcast listeners", listeners.size());
    }
    //4
    public static Registration register(final SerializableConsumer<List<String>> listener) {
        listeners.add(listener);
        return () -> listeners.remove(listener);
    }

}

Esta parte también hace de esa magia, no podemos olvidarla

  • //1 la linea 114 invocamos el método register, por medio de una expresion lambda con el SerializableConsumer<List<String>> tenemos la funcionalidad como parametro, esta clase es propia de Vaadin que extiende a la interface Consumer y Serializable.
  • //2 El método access(() -> )) dentro de el por estar en presencia de un background thread recorremos la lista updatedTransactionId.
  • //3 Añadimos items sino estan al transactionList
  • //4 Refrescamos el grid completo con refreshAll() para que se vean los cambios pusheados por el server.
  • //5 Si las transacciones/items existen se actualizan con el refreshItem()
@Override
protected void onAttach(AttachEvent attachEvent) {
    super.onAttach(attachEvent);
    if (attachEvent.isInitialAttach()) {
        buttonStart.addClickListener(e -> {
            Notification.show("Init transactions!");
            refreshDataTask.initUpdateGrid();
        });
        buttonStop.addClickListener(e -> {
            Notification.show("Stop transactions!");
            refreshDataTask.stopUpdateGrid();
        });
        //1
        broadcasterRegistration = Broadcaster.register(updatedTransactionIds -> {
            getUI().ifPresent(ui -> {
                ui.access(() -> { //2 
                    updatedTransactionIds.forEach(updateId -> {
                        final Transaction transaction = TransactionRepository.getInstance().find(updateId);
                            if (!transactionList.contains(transaction)) {
                                transactionList.add(transaction);//3
                                grid.getDataProvider().refreshAll();//4
                                log.info("Update caption " + transactionList.size());
                                preciousStones.setText("Precious stones: " + transactionList.size());
                            } else {
                                transactionListDataProvider.refreshItem(transaction); //5
                            }
                    });
               });
            });
        });
    }
}

Una breve animación donde se aprecian dos pestañas, cada una de ellas puede representar a usuarios diferentes, compartiendo cada 2 segundos las mismas actualizaciones del grid sin recargar la pagina completa (solo el grid), y en caso de hacerlo no nos afectaria porque se mantienen los datos, un caso parecido hize aqui Arduino led web with Vaadin 8.


Versión con MongoDB + project reactor

He creado un nueva rama llamada mongo-push en la cual hacemos lo mismo pero usando mongodb versión reactiva, lo de reactiva no tiene mucho que ver, porque la ciencia es casi la misma, pero si que hay ajustar un par de cosas, desde el broadcaster, el onAttach() de la vista nueva etc.

El broadcaster tiene nuevas cosas como el siguiente método que pasa al register una lista de libros, que usaremos en la vista nueva.

public static void broadcastForGridReactiveBooks(List<Book> books) {
    listenerBooks.forEach(book -> {
        executorService.execute(() -> book.accept(books));
    });
    log.debug("Notified {} broadcast listeners", listenerBooks.size());
}

Ahora nuestro onAttach necesita el método //1 setItems del grid de la siguiente manera esta con menos lineas que el anterior.

@Override
protected void onAttach(AttachEvent attachEvent) {
    //required for show time and memmory consumption
    super.onAttach(attachEvent);
    if (attachEvent.isInitialAttach()) {
        this.buttonStart.addClickListener(e -> {
            refreshReactiveDataTask.initUpdateGrid("Init refresh items from database");
        });
        this.buttonStop.addClickListener(e -> {
            refreshReactiveDataTask.stopUpdateGrid("Stop refresh items from database");
        });

        this.registration = Broadcaster.registerReactiveBooks(booksList -> {
            attachEvent.getUI().access(() -> {
                labelGridCaption.setText("Documents: " + booksList.size());
                reactiveBookGrid.setItems(booksList);//1
            });
        }, message -> {
            attachEvent.getUI().access(() -> {
                Notification.show(message);
            });
        });
    }
}

Ejecución de Test para crear la colección

Tenemos los siguientes datos simples

Database Collection
- test - books
@Test
@DisplayName("Save some books of programming")
void save() {

    StepVerifier.create(reactiveBookService
            .saveAll(Flux.fromIterable(Arrays.asList(
                    new Book("", "Clean Code", "Rober C. Martin"),
                    new Book("", "BDD IN ACTION", "John Smart"),
                    new Book("", "El feliz abrazo de una madre mocha", "El cuñao"),
                    new Book("", "ITEXT IN ACTION", "Bruno Lowagie"),
                    new Book("", "SPRING BOOT IN ACTION", "Craig Walls"),
                    new Book("", "Kubernetes in Action", "Marko Luksa"),
                    new Book("", "La fuga de los caballos paraliticos", "El cuñao"),
                    new Book("", "Clean Architecture", "Rober C. Martin"))
                    )
            )
            .delayElements(Duration.ofSeconds(1)))
            .expectNextCount(8)
            .verifyComplete();

}

Actualizando documento

dicha imagen muestra que si editamos en compass un documento sera reflejado en nuestro grid.

Nuevo documento

Aqui es casi lo mismo, pero duplicamos el documento, lo renombramos, y se verá nuestro grid con el nuevo item/row.


Comments