【问题标题】:Firebase with Vaadin 14 Flow - The data provider hasn't ever called getLimit()带有 Vaadin 14 Flow 的 Firebase - 数据提供者从未调用过 getLimit()
【发布时间】:2022-01-12 23:43:21
【问题描述】:

我正在尝试将 Vaadin 14 表中的数据存储在 firebase 实时数据库中。我可以调用 setValuesAsync,但在 db 的 firebase 控制台中看不到任何结果。当我重新加载页面时,我收到错误“数据提供者尚未调用 getLimit()”。我试图将我的代码与“https://github.com/Artur-/vaadin-on-fire”进行比较,但他们也没有在任何地方调用该函数。 这是我的代码: TableView.java

package com.example.application.views.table;

import com.example.application.data.FirebaseDataProvider;
import com.example.application.data.db.Firebase;
import com.example.application.data.db.RunDB;
import com.example.application.data.entity.Run;
import com.example.application.data.service.SampleRunService;
import com.example.application.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

import java.io.IOException;
import java.time.LocalDate;
import java.util.Locale;
import java.util.Optional;

import com.vaadin.ui.DateField;
import org.springframework.beans.factory.annotation.Autowired;
import org.vaadin.artur.helpers.CrudServiceDataProvider;

@PageTitle("Table")
@Route(value = "table/:samplePersonID?/:action?(edit)", layout = MainLayout.class)
@Uses(Icon.class)
public class TableView extends Div implements BeforeEnterObserver {

    private final String RUN_ID = "samplePersonID";
    private final String RUN_EDIT_ROUTE_TEMPLATE = "table/%d/edit";

    private Grid<Run> grid = new Grid<>(Run.class);

    private TextField name;
    private NumberField distance;
    private NumberField time;
    private DatePicker date;

    private Button cancel = new Button("Abbrechen");
    private Button save = new Button("Speichern");
    private Button delete = new Button("Löschen");

    private BeanValidationBinder<Run> binder;

    private Run run;

    private SampleRunService sampleRunService;
    private FirebaseDataProvider<Run> dataProvider;

    public TableView(@Autowired SampleRunService sampleRunService) {
        try {
            Firebase.setup();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Unable to setup Firebase connection. See the log for details");
        }
        dataProvider = new FirebaseDataProvider<>(Run.class, RunDB.getRunsDb());
        grid.setDataProvider(dataProvider);
        this.sampleRunService = sampleRunService;
        addClassNames("table-view", "flex", "flex-col", "h-full");
        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        splitLayout.setSizeFull();

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

        grid.removeColumn(grid.getColumnByKey("key"));
        grid.removeColumn(grid.getColumnByKey("id"));
        grid.setColumnOrder(
                grid.getColumnByKey("name"),
                grid.getColumnByKey("distance"),
                grid.getColumnByKey("time"),
                grid.getColumnByKey("date")
        );
        grid.getColumnByKey("distance").setHeader("Distanz");
        grid.getColumnByKey("time").setHeader("Zeit");
        grid.getColumnByKey("date")
                .setHeader("Datum");

        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        grid.setHeightFull();

        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(RUN_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(TableView.class);
            }
        });

        // Configure Form
        binder = new BeanValidationBinder<>(Run.class);

        binder.forField(date)
                .withConverter(new LocalDateLongConverter())
                .bind(Run::getDate, Run::setDate);
        // Bind fields. This where you'd define e.g. validation rules

        binder.bindInstanceFields(this);

        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
            date.setValue(LocalDate.now());
        });

        delete.addClickListener(e -> {
            clearForm();
            refreshGrid();
            date.setValue(LocalDate.now());
        });

        save.addClickListener(e -> {
            try {
                if (this.run == null) {
                    this.run = new Run();
                }
                binder.writeBean(this.run);

                sampleRunService.update(this.run);
                if (run.getKey() == null) {
                    RunDB.add(run);
                } else {
                    RunDB.update(run.getKey(), run);
                }


                clearForm();
                refreshGrid();
                date.setValue(LocalDate.now());
                Notification.show("SamplePerson details stored.");
                UI.getCurrent().navigate(TableView.class);
            } catch (ValidationException validationException) {
                Notification.show("An exception happened while trying to store the samplePerson details.");
            }
        });

    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<Integer> samplePersonId = event.getRouteParameters().getInteger(RUN_ID);
        if (samplePersonId.isPresent()) {
            Optional<Run> samplePersonFromBackend = sampleRunService.get(samplePersonId.get());
            if (samplePersonFromBackend.isPresent()) {
                populateForm(samplePersonFromBackend.get());
            } else {
                Notification.show(
                        String.format("The requested samplePerson was not found, ID = %d", samplePersonId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(TableView.class);
            }
        }
    }

    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("flex flex-col");
        editorLayoutDiv.setWidth("400px");

        Div editorDiv = new Div();
        editorDiv.setClassName("p-l flex-grow");
        editorLayoutDiv.add(editorDiv);

        FormLayout formLayout = new FormLayout();
        name = new TextField("Name");
        distance = new NumberField("Distanz");
        time = new NumberField("Zeit");
        date = new DatePicker("Datum");
        date.setValue(LocalDate.now());
        date.setLocale(Locale.GERMANY);
        Component[] fields = new Component[]{name, distance, time, date};

        for (Component field : fields) {
            ((HasStyle) field).addClassName("full-width");
        }
        formLayout.add(fields);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);

        splitLayout.addToSecondary(editorLayoutDiv);
    }

    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("w-full flex-wrap bg-contrast-5 py-s px-l");
        buttonLayout.setSpacing(true);
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }

    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setId("grid-wrapper");
        wrapper.setWidthFull();
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }

    private void refreshGrid() {
        grid.select(null);
        grid.getDataProvider().refreshAll();
    }

    private void clearForm() {
        populateForm(null);
    }

    private void populateForm(Run value) {
        this.run = value;
        binder.readBean(this.run);

    }
}

RunDB.java

package com.example.application.data.db;

import com.example.application.data.entity.Run;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseReference;

import java.time.LocalDate;
import java.util.HashMap;
import java.util.logging.Logger;

public class RunDB {

    private static final String DATABASE_NAMESPACE = "runs";

    public static DatabaseReference getRunsDb() {
        return Firebase.getDb().child(DATABASE_NAMESPACE);
    }

    public static void maybeCreateInitialData(DataSnapshot snapshot) {
        if (snapshot.hasChild(DATABASE_NAMESPACE)) {
            return;
        }

        add(new Run("Paul", 34.4, 120, LocalDate.now().toEpochDay()));
        add(new Run("Basti", 20.4, 10, LocalDate.now().toEpochDay()));
    }

    public static void add(Run run) {
        getRunsDb().push().setValueAsync(run);
    }

    protected static Logger getLogger() {
        return Logger.getLogger("RunsDB");
    }

    public static void update(String key, Run run) {
        getLogger().info("Set run " + key + " to " + run);
        HashMap<String, Object> toUpdate = new HashMap<>();
        toUpdate.put(key, run);
        getRunsDb().updateChildrenAsync(toUpdate);
    }

    public static void delete(Run run) {
        getLogger().info("Delete user " + run);
        HashMap<String, Object> toUpdate = new HashMap<>();
        toUpdate.put(run.getKey(), null);
        getRunsDb().updateChildrenAsync(toUpdate);

    }
}

FirebaseDataProvider.java

package com.example.application.data;


import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.vaadin.flow.data.provider.AbstractDataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.shared.Registration;

import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

/**
 * A data provider connected to a given child in a Firebase database.
 *
 * @param <T>
 */
public class FirebaseDataProvider<T extends HasKey>
        extends AbstractDataProvider<T, SerializablePredicate<T>>
        implements ChildEventListener {

    private LinkedHashMap<String, T> data = new LinkedHashMap<>();
    private DatabaseReference databaseReference;
    private Class<T> type;
    AtomicInteger registeredListeners = new AtomicInteger(0);

    /**
     * Constructs a new Firebase data provider connected to the given database
     * reference.
     *
     * @param type              the entity type to use for items
     * @param databaseReference the reference containing the child nodes to include
     */
    public FirebaseDataProvider(Class<T> type,
                                DatabaseReference databaseReference) {
        this.databaseReference = databaseReference;
        this.type = type;
    }

    @Override
    public String getId(T item) {
        return item.getKey();
    }

    @Override
    public Stream<T> fetch(Query<T, SerializablePredicate<T>> query) {
        return data.values().stream();
    }

    @Override
    public int size(Query<T, SerializablePredicate<T>> query) {
        return data.size();
    }

    @Override
    public boolean isInMemory() {
        return false;
    }

    @Override
    public Registration addDataProviderListener(
            DataProviderListener<T> listener) {
        if (registeredListeners.incrementAndGet() == 1) {
            registerFirebaseListener();
        }
        Registration realRegistration = super.addDataProviderListener(listener);
        return () -> {
            realRegistration.remove();
            if (registeredListeners.decrementAndGet() == 0) {
                unregisterFirebaseListener();
            }
        };
    }

    private void registerFirebaseListener() {
        databaseReference.addChildEventListener(this);
    }

    private void unregisterFirebaseListener() {
        databaseReference.removeEventListener(this);
    }

    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
        T added = snapshot.getValue(type);
        String key = snapshot.getKey();
        added.setKey(key);

        data.put(key, added);
        refreshAll();
    }

    @Override
    public void onChildChanged(DataSnapshot snapshot,
                               String previousChildName) {
        T updated = snapshot.getValue(type);
        String key = snapshot.getKey();
        updated.setKey(key);

        data.put(key, updated);
        refreshItem(updated);
    }

    @Override
    public void onChildRemoved(DataSnapshot snapshot) {
        data.remove(snapshot.getKey());
        refreshAll();
    }

    @Override
    public void onChildMoved(DataSnapshot snapshot, String previousChildName) {
    }

    @Override
    public void onCancelled(DatabaseError error) {
    }
}

感谢您的帮助。非常感谢你。 保罗

【问题讨论】:

    标签: java firebase firebase-realtime-database vaadin crud


    【解决方案1】:

    数据提供程序中的fetch 方法应该根据query 参数的偏移量和限制值一次仅加载一页数据。这是一个常见的遗漏,它已被实施以主动检查 getLimit()getOffset() 是否曾经运行过,以帮助开发人员意识到缺少什么。

    在您添加任何数据之前不会发生这种情况,因为此后 size 方法返回 0,这反过来意味着甚至没有任何理由调用 fetch

    【讨论】:

    • 感谢您的回答。这是否意味着数据未正确存储。您知道为什么我在 firebase 控制台中看不到任何运行吗?
    • 我对 Firebase 不够熟悉,无法对此发表任何评论。我对 Vaadin 非常熟悉,可以说您的 fetch 必须 使用 query.getLimit()query.getOffset() 以确保它只返回实际请求的项目范围,而不是返回所有数据。
    猜你喜欢
    • 2016-10-20
    • 2021-10-31
    • 2017-06-11
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多