Skip to content

Data Table

A data table displays labeled columns and rows used to present numbers, text, or images.

Anatomy

Data table rows consist of a collection of columns, each containing an image or up to two lines of text or numbers. In the case of a header row, only one line of text is allowed.

A data table view consists of a horizontally and vertically scrollable list of data table rows. The first row is a sticky header containing the labels for the columns. Optionally, the first column can be a sticky column. A sticky column of chevrons can optionally be displayed at the end of the screen.

Data Table View Anatomy

Usage

A data table row is used to display info about an item and should lead to more information when the user clicks the row. A data table view is used to display these rows.

Construction

You can create a data table view using the constructor in code:

DataTableView dataTable = new DataTableView(getContext());

You can also create a data table view by declaring a DataTableView element in XML like this:

<com.sap.cloud.mobile.fiori.object.DataTableView
        android:id="@+id/dataTable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
</com.sap.cloud.mobile.fiori.object.DataTableView>

Populating the Table

To populate the data table view with data, create an adapter extending DataTableProvider and add it to the DataTableView. Within this class, override onCreateViewHolder(ViewGroup, int) and onBindViewHolder(DataTableRowViewHolder, int)
with the implementation for each DataTableRow, and override getItemCount() with the number of rows in the table. For example:

DataTableAdapter adapter = new DataTableAdapter();
dataTable.setAdapter(adapter);

public class DataTableAdapter extends DataTableView.DataTableProvider {
    public DataTableAdapter() {}

    @NonNull
    @Override
    public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
        DataTableRow row = holder.mRow;
        row.setTextAlignment(DataTableRow.ALIGN_START,
                DataTableRow.ALIGN_START,
                DataTableRow.ALIGN_START,
                DataTableRow.ALIGN_END,
                DataTableRow.ALIGN_START);
        row.setEditType(DataTableCellView.EDIT_TYPE_NONE,
                DataTableCellView.EDIT_TYPE_NONE,
                DataTableCellView.EDIT_TYPE_TEXT,
                DataTableCellView.EDIT_TYPE_LIST_PICKER,
                DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
        DataTableRow row = (DataTableRow)holder.itemView;
        row.setColumnWidths(110, 150, 400, 100, 400);
        if (pos == 0) {
            row.setText(0, "");
            row.setText(1, "Work Number");
            row.setText(2, "Headline");
            row.setText(3, "Price (USD)");
            row.setText(4, "Description");
        } else {
            row.setLines(2);
            row.setImage(0, getResources().getDrawable(R.drawable.image));
            row.setText(1, "Work number info");
            row.setText(2, "Headline info");
            row.setText(3, "Price info");
            row.setText(4, "Description info");
        }
    }

    @Override
    public int getItemCount() {
        return 25;
    }
}

Edit Mode

The data table can enter edit mode, which alters the behavior of each column according to their specified edit type. You can utilize these unique behaviors to edit the data within the table. The adapter keeps track of edit mode in mEditMode, so you can use it to determine whether to display tentative data or saved data.

The edit type for each column can be customized using setEditType(int...). Each argument corresponds to the column of that index. By default, a column's edit type is EDIT_TYPE_NONE.

Alternatively, you could use setCellEditType(int, int) to assign the edit type for a specific column.

dataTable.setEditMode(true);

public class DataTableAdapter extends DataTableView.DataTableProvider {
    public DataTableAdapter() {}

    @NonNull
    @Override
    public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
        DataTableRow row = holder.mRow;
        row.setEditType(DataTableCellView.EDIT_TYPE_NONE,
                DataTableCellView.EDIT_TYPE_TEXT,
                DataTableCellView.EDIT_TYPE_LIST_PICKER,
                DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER,
                DataTableCellView.EDIT_TYPE_DURATION_PICKER);

        // alternative method
        row.setCellEditType(1, DataTableCellView.EDIT_TYPE_TEXT);
        row.setCellEditType(2, DataTableCellView.EDIT_TYPE_LIST_PICKER);
        row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
        row.setCellEditType(4, DataTableCellView.EDIT_TYPE_DURATION_PICKER);

        return holder;
    }
}

Data Table Edit Mode

EDIT_TYPE_NONE

EDIT_TYPE_NONE is the default edit type. Columns with this edit type are not clickable.

EDIT_TYPE_TEXT

Clicking a column with EDIT_TYPE_TEXT will open a text box above the column where the user can directly edit the text in the column. To have your data reflect the changes in the text box, attach a DataTableEditTextListener to the column using setTextWatcher(int, TextWatcher) and override onTextChanged(CharSequence, int, int, int) with the intended interactions with your data. Use mPosition and setPosition(int) to keep the DataTableEditTextListener synchronized with the movement around the data table.

@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
    DataTableRow row = holder.mRow;
    row.setCellEditType(1, DataTableCellView.EDIT_TYPE_TEXT);

    // attach a DataTableEditTextListener to the column with index 1
    row.setTextWatcher(1, new DataTableView.DataTableProvider.DataTableEditTextListener() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // update data at mPosition with new text
            mObjectsCopy.get(mPosition).setHeadline(s.toString());

            // define error state depending on whether the text is in an appropriate format
            boolean isError = ...

            // update data at mPosition with the error state
            mObjectsCopy.get(mPosition).setError(isError);

            // tell the column whether it is in error state
            ((DataTableCellView)row.getColumn(1)).setIsError(isError);

            // update adapter with the error state
            boolean prev = hasError();
            setIsError(mPosition, row.hasError());
            boolean curr = hasError();

            // perform logic depending on error state, such as displaying a banner
            if (prev != curr) {
                if (prev) {
                    mBanner.dismiss();
                } else {
                    mBanner.show();
                }
            }
        }
    });

    return holder;
}

@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
    // the saved data source
    obj = mObjects.get(pos);
    // the tentative data source
    objCopy = mObjectsCopy.get(pos);

    DataTableRow row = (DataTableRow)holder.itemView;
    row.setColumnWidths(110, 150, 400, 100, 400);

    // display tentative data and update listener position
    if (mEditMode) {
        row.setText(1, objCopy.getHeadline());
        ((DataTableCellView)row.getColumn(1)).setIsError(objCopy.isError());
        TextWatcher watcher = row.getTextWatcher(1);
        if (watcher instanceof DataTableEditTextListener) {
            ((DataTableEditTextListener) watcher).setPosition(pos);
        }
    } else { // display saved data
        row.setText(1, obj.getHeadline());
    }
}

EDIT_TYPE_LIST_PICKER

The EDIT_TYPE_LIST_PICKER edit type makes the column clickable and displays a chevron at its end to indicate that clicking the column will open a list picker. The content of what will be opened is left to the developer to customize. In this example, clicking will move to another activity containing a list picker:

@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
    DataTableRow row = holder.mRow;
    row.setCellEditType(2, DataTableCellView.EDIT_TYPE_LIST_PICKER);

    return holder;
}

@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
    // the saved data source
    obj = mObjects.get(pos);
    // the tentative data source
    objCopy = mObjectsCopy.get(pos);

    DataTableRow row = (DataTableRow)holder.itemView;
    row.setColumnWidths(110, 150, 400, 100, 400);

    // attach a listener to the column with index 2
    DataTableCellView cell = (DataTableCellView)row.getColumn(2);
    if (!cell.hasOnClickListeners()) {
        if (!row.getHeader()) {
            cell.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mEditMode && cell.getEditType() == DataTableCellView.EDIT_TYPE_LIST_PICKER) {
                        Context context = row.getContext();
                        Intent intent = new Intent(context, DataTableListPickerActivity.class);
                        intent.putExtra("pos", holder.getBindingAdapterPosition());
                        startActivityForResult(intent, 0);
                    }
                }
            });
        }
    }

    // display tentative data
    if (mEditMode) {
        row.setText(2, objCopy.getPrice());
    } else { // display saved data
        row.setText(2, obj.getPrice());
    }
}

Since we are moving to another activity in this example, we need to retrieve the results when we return to the data table:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Bundle bundle = data.getExtras();
    mAdapter.mObjectsCopy.get(bundle.getInt("pos")).setPrice(bundle.getInt("value"));
}

EDIT_TYPE_DATE_TIME_PICKER, EDIT_TYPE_DATE_PICKER, EDIT_TYPE_TIME_PICKER

Clicking a column with any of these edit types will open a date time picker. To have your data reflect the results of the picker, attach a FormCell.CellValueChangeListener<Date> to the column using setPickerListener(int, FormCell.CellValueChangeListener<Date>) and override cellChangeHandler(Date) with the intended interactions with your data.

@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
    DataTableRow row = holder.mRow;
    row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_TIME_PICKER);
    //row.setCellEditType(3, DataTableCellView.EDIT_TYPE_DATE_PICKER);
    //row.setCellEditType(3, DataTableCellView.EDIT_TYPE_TIME_PICKER);

    // use the appropriate format to update the data
    DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy hh:mm a");
    //DateFormat formatter = new SimpleDateFormat("MMM dd, yyyy");
    //DateFormat formatter = new SimpleDateFormat("hh:mm a");

    // attach a CellValueChangeListener to the column with index 3
    row.setPickerListener(3, new FormCell.CellValueChangeListener<Date>() {
        @Override
        protected void cellChangeHandler(@Nullable Date value) {
            mObjectsCopy.get(holder.getBindingAdapterPosition()).setDateTime(formatter.format(value));
        }
    });

    return holder;
}

@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
    // the saved data source
    obj = mObjects.get(pos);
    // the tentative data source
    objCopy = mObjectsCopy.get(pos);

    DataTableRow row = (DataTableRow)holder.itemView;
    row.setColumnWidths(110, 150, 400, 100, 400);

    // set a FragmentManager so the picker has proper styling
    ((DataTableCellView)row.getColumn(3)).setManager(mManager);

    // display tentative data
    if (mEditMode) {
        row.setText(3, objCopy.getDateTime());
    } else { // display saved data
        row.setText(3, obj.getDateTime());
    }
}

EDIT_TYPE_DURATION_PICKER

Clicking a column with the EDIT_TYPE_DURATION_PICKER edit type opens a duration picker. To have your data reflect the results of the picker, attach a FormCell.CellValueChangeListener<Duration> to the column using setDurationPickerListener(int, FormCell.CellValueChangeListener<Duration>) and override cellChangeHandler(Duration) with the intended interactions with your data.

@NonNull
@Override
public DataTableRowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    DataTableRowViewHolder holder = super.onCreateViewHolder(parent, viewType);
    DataTableRow row = holder.mRow;
    row.setCellEditType(4, DataTableCellView.EDIT_TYPE_DURATION_PICKER);

    // attach a CellValueChangeListener to the column with index 4
    row.setDurationPickerListener(4, new FormCell.CellValueChangeListener<Duration>() {
        @Override
        protected void cellChangeHandler(@Nullable Duration value) {
            mObjectsCopy.get(holder.getBindingAdapterPosition()).setDuration(value.toString());
        }
    });

    return holder;
}

@Override
public void onBindViewHolder(@NonNull DataTableRowViewHolder holder, int pos) {
    // the saved data source
    obj = mObjects.get(pos);
    // the tentative data source
    objCopy = mObjectsCopy.get(pos);

    DataTableRow row = (DataTableRow)holder.itemView;
    row.setColumnWidths(110, 150, 400, 100, 400);

    // display tentative data
    if (mEditMode) {
        row.setText(4, objCopy.getDuration());
    } else { // display saved data
        row.setText(4, obj.getDuration());
    }
}

Customization

Text/Image

The content for each column can be specified using setText(int, CharSequence) and setImage(int, Drawable). If both text and a non-null image are set, the image will be displayed rather than the text.

row.setImage(0, getResources().getDrawable(R.drawable.image)); // first column is an image
row.setText(1, "Work Number"); // second column is text
row.setText(2, "10007611"); // third column is text

Sticky Column

The sticky column keeps the first column on the screen displayed as the screen is scrolled. By default, the sticky column is not enabled.

dataTable.setStickyMode(true);

Data Table Sticky Column

Chevron Column

The chevron column is a sticky column displayed at the end of the screen to help indicate the row is clickable. By default, the chevron column is enabled.

In edit mode, the chevron column will not be displayed.

dataTable.setShowChevron(true);

Text Alignment

The text alignment for each column can be customized by assigning ALIGN_START or ALIGN_END in setTextAlignment(int...). Each argument corresponds to the column of that index. By default, a column's alignment is ALIGN_START.

Alternatively, you could use setCellTextAlignment(int, int) to assign the text alignment for a specific column.

row.setTextAlignment(DataTableRow.ALIGN_START, // first column aligned with start
    DataTableRow.ALIGN_END, // second column aligned with end
    DataTableRow.ALIGN_START); // third and any other columns aligned with start

// alternative method
row.setCellTextAlignment(1, DataTableRow.ALIGN_END);

Column Width

The width for each column can be customized by assigning a dp value in setColumnWidths(float...). Each argument corresponds to the column of that index.

row.setColumnWidths(110, 150, 400, 100, 400);

By default, columns have a maximum width of half of the screen's width. The sticky column has a maximum width of a third of the screen's width. These maximum widths will adjust accordingly when the screen is rotated 90 degrees. The maximum widths can be customized:

row.setMaxColumnWidth(450);
row.setMaxStickyColumnWidth(300);

Dynamic Column

On a tablet, you can specify one column to be a dynamic column using setDynamicColumn(int). The presence of a dynamic column will cause the row width to be equal to screen width, and the dynamic column's width will be equal to the remaining width after calculating the other columns' widths. Passing a negative value into setDynamicColumn(int) will disable the dynamic column. By default, no column has dynamic width.

row.setDynamicColumn(2); // third column has dynamic width
row.setDynamicColumn(-1); // no column has dynamic width

Click Listener

The Data Table View accepts a FioriItemClickListener that defines click behavior for each item.

In edit mode, this listener will be ignored.

FioriItemClickListener listener = new FioriItemClickListener() {
    @Override
    public void onClick(@NonNull View view, int position) {
        if (position == 0) {
            Toast.makeText(view.getContext(), "Header has been clicked", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(view.getContext(), "Row " + position + " has been clicked", Toast.LENGTH_SHORT).show();
            Context context = view.getContext();
            Intent intent = new Intent(context, ObjectHeaderActivity.class);
            context.startActivity(intent);
        }
    }

    @Override
    public void onLongClick(@NonNull View view, int position) {

    }
};
dataTable.setClickListener(listener);

Last update: February 20, 2023