Skip to content

The Generic List Picker Form Cell

GenericListPickerFormCell is an enhanced version of the older ListPickerFormCell class.

Applications often present a long list of items and allow users to express their choice in either single or multiple selections.

GenericListPickerFormCell is designed to handle such complex lists with ease and allow developers to:

  • Inflate custom views to present the target items.
  • Bind items by their position or custom id.
  • Track user interactions and selections, and notify the application of the changes through callbacks.

The cell can easily be configured to allow:

  • Single selection only – Presents the list of items with radio buttons and allows only one selection.
  • Multi selection – Presents the list of items with check boxes and allows users to make multiple selections.

This Cell also allows you to perform paging for better performance. For example, if you have 100,000 elements to present, then GenericListPickerFormCell allows you to only fetch the initial 100 elements and then load new elements as the user scrolls the list.

Using the GenericListPickerFormCell

This form cell can be used within your activity like any traditional Android view:

<com.sap.cloud.mobile.fiori.formcell.GenericListPickerFormCell
            android:id="@+id/genericTextPicker"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?android:attr/selectableItemBackground"
            app:activityTitle="Choose Text"
            app:bindViewById="true"
            app:key="Pick Text Options" />

GenericListPickerFormCell Activity

The GenericListPickerFormCell depends on GenericListPickerFormCellActivity to present the list items. GenericListPickerFormCellActivity is an abstract class responsible for creating the list items, presenting them to a user, tracking the user actions, and informing the form cell about the changes made. Internally, it uses the GenericListPickerFormCell Fragment to display the list picker.

As shown in the following example, GenericListPickerFormCellActivity has two generic parameters:

  • V extends View – Represents the type of views to be presented to user; for example, TextView or ObjectCell

  • T extends Serializable – Represents the type of key used to identify two different elements in the list being presented; for example, position of the item in actual list or item id from your database

class GenericListPickerFormCellActivity<V extends View, T extends Serializable> extends ...

In order to create and bind the data, GenericListPickerFormCellActivity follows the RecyclerView.Adapter pattern. For the ease of usage, APIs of the Activity are named along the lines of RecyclerView.Adapter APIs.

The following table lists the methods, with descriptions, that you have to override to set up the GenericListPickerFormCellActivity.

Abstract Methods Description
int getItemCount() Informs the GenericListPickerFormCellActivity about the number of items in the List.
int getItemViewType(int position) Inflates different types of list item views. This method informs the GenericListPickerFormCellActivity about type of the view at a given position.
V onCreateView(int viewType, @NonNull Context context) Given a view type, creates the View.
void onBindView(@NonNull V view, int position) This is where you bind your view with data. Given position and view bind it to data.
T getId(int pos) Given a position in adapter, return the id used for the item.
  • You can use app:value XML attribute or setValue(List<Integer>) to set the already selected value on the cell.

  • You can set GenericListPickerFormCellActivity using setPickerActivity(@Nullable GenericListPickerFormCellActivity<V, ?> pickerActivity) method on the form cell.

The following is a basic example of how to use GenericListPickerFormCellActivity:

GenericListPickerFormCell<TextView, String> genericTextPickerString = view.findViewById(R.id.genericTextPickerString);
genericTextPickerString.setSelectedItemLabel("Selected Items");
genericTextPickerString.setAllItemLabel("All Items");
genericTextPickerString.setPickerActivity(new GenericTextStringPickerActivity());

And then in GenericTextStringPickerActivity

public class GenericTextStringPickerActivity extends GenericListPickerFormCellActivity<TextView, String> {

    @NonNull
    private List<String> mItemList;
    private List<String> mSelection;
    private List<String> backup;

    public GenericTextStringPickerActivity() {
        mItemList = new ArrayList<>();
        backup = new ArrayList<>();
        setItemList(setupData());
    }

    public void setItemList(@NonNull List<String> itemList) {
        mItemList.addAll(itemList.subList(0, itemList.size() / 4));
        backup.addAll(itemList);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSelection = new ArrayList<>();
    }

    @Override
    protected void onResume() {
        super.onResume();

        FragmentManager fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentByTag(GenericListPickerFormCellFragment.TAG);
        if (fragment != null) {
            GenericListPickerFormCellFragment listPickerDialogFragment = (GenericListPickerFormCellFragment) fragment;
            View view = listPickerDialogFragment.getView();
            MaterialToolbar toolbar = view.findViewById(com.sap.cloud.mobile.fiori.R.id.list_picker_toolbar);
            if (toolbar != null) {
                Menu menu = toolbar.getMenu();
                FioriSearchView mFioriSearchView = (FioriSearchView) menu.findItem(R.id.appbar_search_menu).getActionView();
                if (mFioriSearchView != null) {
                    mFioriSearchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
                        if (hasFocus) {
                            mSelection.clear();
                            mSelection.addAll(getSelections());
                            notifyDataSetChanged();
                        } else {
                            mItemList.clear();
                            mItemList.addAll(backup.subList(0, backup.size() / 4));
                            setSelections(mSelection);
                            notifyDataSetChanged();
                        }
                    });
                    mFioriSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                        @Override
                        public boolean onQueryTextSubmit(String s) {
                            return false;
                        }

                        @Override
                        public boolean onQueryTextChange(String s) {
                            if (!s.isEmpty()) {
                                updateSearch(s);
                            }
                            return true;
                        }
                    });
                }
            }
        }
    }

    @NonNull
    @Override
    public TextView onCreateView(int viewType, @NonNull Context context) {
        TextView view = new TextView(this);
        view.setTextAppearance(com.sap.cloud.mobile.fiori.R.style.TextAppearance_Fiori_Body1);
        view.setPadding(view.getPaddingStart(), 0, view.getPaddingEnd(), 0);
        return view;
    }

    @Override
    protected void onBindView(@NonNull TextView view, String id) {
        view.setText(id);
    }

    @Override
    protected int getItemViewType(int position) {
        return 0;
    }

    @Override
    protected int getItemCount() {
        return mItemList.size();
    }

    @Override
    protected void restoreData(@Nullable Bundle bundle) {
        mItemList = bundle == null ? new ArrayList<>() : bundle.getStringArrayList(String.valueOf("ItemList"));
    }

    @Override
    protected Bundle saveData() {
        Bundle bundle = new Bundle();
        bundle.putStringArrayList(String.valueOf("ItemList"), (ArrayList<String>) mItemList);
        return bundle;
    }

    void updateSearch(String s) {
        List<String> temp = new ArrayList<>(backup);
        mItemList.clear();
        for (String str : temp) {
            if (str.contains(s)) {
                mItemList.add(str);
            }
        }
        notifyDataSetChanged();
        setNewSelection(s);
    }

    void setNewSelection(String query) {
        List<String> newSelections = new ArrayList<>();
        for (String selection : getSelections()) {
            if (selection.contains(query)) {
                newSelections.add(selection);
            }
        }
        setSelections(newSelections);
    }

    @Override
    protected void onBindPosition(int pos) {
        // Load more data if you wish
    }

    @Override
    public String getId(int position) {
        return mItemList.get(position);
    }

    @Override
    protected void onSelectionChanged(String id, boolean isSelected) {
        if (isSelected && !mSelection.contains(id)) {
            mSelection.add(id);
        } else {
            mSelection.remove(id);
        }
    }

    private ArrayList<Integer> setupData() {
        ArrayList<Integer> itemList = new ArrayList<>();
        for (int it = 0; it < 1000; ++it) {
            itemList.add(it);
        }
        return itemList;
    }
}

Generic Picker Activity

GenericListPickerFormCell Fragment

You can replace the GenericListPickerFormCellActivity with GenericListPickerFormCellFragment to present the items in a list. This opens the list picker as a Bottom Sheet on a phone and as an Alert Dialog on a tablet. The Bottom Sheet list picker, by default, opens in half-expanded view and, when dragged, opens in fully-expanded view. It can be changed to open directly in fully-expanded view using the API:

java GenericListPickerFormCellFragment<TextView, String> pickerFragment = new GenericListPickerFormCellFragment<>(); pickerFragment.setShowExpanded(true);

Unlike GenericListPickerFormCellActivity, GenericListPickerFormCellFragment itself is not responsible for creating the list items but depends on GenericListPickerFormCellAdapter class for the same. Like GenericListPickerFormCellActivity, GenericListPickerFormCellAdapter also has similar abstract methods and two generic parameters:

  • V extends View – Represents the type of views to be presented to user; for example, TextView or ObjectCell.
  • T extends Serializable – Represents the type of key used to identify two different elements in the list being presented; for example, position of the item in actual list or item id from your database.
Abstract Methods Description
int getItemCount() Informs the GenericListPickerFormCellActivity about the number of items in the List.
int getItemViewType(int position) Inflates different types of list item views. This method informs the GenericListPickerFormCellActivity about type of the view at a given position.
V onCreateView(int viewType, @NonNull Context context) Given a view type, creates the View.
void onBindView(@NonNull V view, int position) This is where you bind your view with data. Given position and view bind it to data.
T getId(int pos) Given a position in adapter, return the id used for the item.

Here is a small snippet demonstrating the implementation:

GenericListPickerFormCell<ObjectCell, String> genericObjectCellPicker = view.findViewById(R.id.genericObjectCellPicker);
genericObjectCellPicker.setOnClickListener(v -> {
        GenericListPickerFormCellFragment<ObjectCell, String> pickerFragment = new GenericListPickerFormCellFragment<>();
        pickerFragment.setTitle("Pick Object Cell");
        pickerFragment.setGenericListPickerAdapter(v.getContext(), new ObjectCellPickerAdapter());
        pickerFragment.setOnSelectionsUpdatedListener(selections -> genericObjectCellPicker.setValue(selections));
        pickerFragment.showNow(getChildFragmentManager(), GenericListPickerFormCellFragment.TAG);
    });

Important

While using GenericListPickerFormCellFragment, the SDK has no way of determining when or how to add the fragment. Hence, an app should add the fragment whenever required, preferably in the onClick event on GenericListPickerFormCell.

Generic Picker Fragment on Phone

Generic Picker Fragment Phone

Generic Picker Fragment on Tablet

Generic Picker Fragment Tablet

Selection Section

The list picker form cell comes with a selection section which, when it is enabled, shows all the selected items on top of the list. It also allows all the unselected items to be selected with a single click by clicking the Select All button and deselect all the selected items with a single click by clicking the Deselect All button. A Floating Action Button(FAB) is also displayed when the user scrolls away from the selected section. Clicking on this FAB takes the user back up to the selected section. You can enable or disable the selection section with the setShowSelected(boolean) method.

Show selected Section Do not show selected section
With Selection No Selection

Single select mode of the GenericListPickerFormCell does not support the selected section.

Left To Right

GenericListPickerFormCell allows you to position the selector (checkbox or radio) button on either left of right end of the item view. Using setLeftToRight API on GenericListPickerFormCell, you can position the selector on either end.

By default, selectors are positioned on the start or left end of the item.

Selector on Left Selector on Right
Selector on Left Selector on Right

Paging Using GenericListPickerFormCell

If it is desired to present a long list of items to the user, then it is good practice to load the items when required. Loading only partial lists saves memory and computation resources.

You can override the void onBindPosition(int pos) method in GenericListPickerFormCellActivity class to be notified when the adapter position of the item for which onBindView(@NonNull V view, T id) is called. This gives you an indication as to whether your current list is going to be exhausted and you can trigger network calls to download more items.

@Override
protected void onBindPosition(int pos) {
        if(pos < getItemCount() - 10) {
            // trigger the network call to load more items as we have only 10 more spare items to present
        }
    }

Editable and Non-Editable Modes

This form cell can be set to editable or non-editable modes. For the list picker form cell, editable and enabled attributes are the same: a non-editable form cell is not enabled.

By default, the list picker form cell is editable and enabled. You can control the editability of the cell by using XML attribute app:editable="false. You can also fetch the view in your activity and set it to be editable using Java APIs.

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setIsEditable(false);

For more information about the editable attribute, see FormCell.

Exit Dialog

When the user makes some selections and clicks the Close button, an optional warning dialog can be displayed asking the user whether they would like to save their selections or discard them. This dialog is displayed by default but can be hidden with the following API:

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setShowExitDialog(false);

Generic Picker Exit Dialog

The GenericListPickerFormCell also supports search functionality that allows users to search the list for specific items and select the ones that satisfy the search criteria. Developers can enable search and are responsible for implementing the filtering logic based on the type of the items.

The following sample illustrates how to enable search using GenericListPickerFormCellActivity:

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setSearchEnabled(true);
mPickerFormCell.setPickerActivity(new GenericTextStringPickerActivity());

And then in the GenericTextStringPickerActivity class:

@Override
protected void onResume() {
    super.onResume();

    // Get the GenericListPickerFormCellFragment object associated with this GenericListPickerFormCellActivity
    FragmentManager fragmentManager = getSupportFragmentManager();
    Fragment fragment = fragmentManager.findFragmentByTag(GenericListPickerFormCellFragment.TAG);
    if (fragment != null) {
        GenericListPickerFormCellFragment listPickerDialogFragment = (GenericListPickerFormCellFragment) fragment;
        // Get the root view for the fragment
        View view = listPickerDialogFragment.getView();
        // Get the fragment toolbar
        MaterialToolbar toolbar = view.findViewById(com.sap.cloud.mobile.fiori.R.id.list_picker_toolbar);
        if (toolbar != null) {
            // Get the fragment toolbar's menu
            Menu menu = toolbar.getMenu();
            // Get the FioriSearchView menu item
            FioriSearchView mFioriSearchView = (FioriSearchView) menu.findItem(R.id.appbar_search_menu).getActionView();
            if (mFioriSearchView != null) {
                mFioriSearchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
                    if (hasFocus) {
                        // Hide selected section so that users only see all items while searching
                        mSelection.clear();
                        mSelection.addAll(getSelections());
                        notifyDataSetChanged();
                    } else {
                        // Show both selected and all items section
                        mItemList.clear();
                        mItemList.addAll(mBackupItemList));
                        setSelections(mSelection);
                        notifyDataSetChanged();
                    }
                });
                mFioriSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                    @Override
                    public boolean onQueryTextSubmit(String s) {
                        return false;
                    }

                    @Override
                    public boolean onQueryTextChange(String s) {
                        if (!s.isEmpty()) {
                            // The search filtering logic
                            updateSearch(s);
                        }
                        return true;
                    }
                });
            }
        }
    }
}

Important

The search logic should be implemented inside the onResume() method of the GenericListPickerFormCellActivity. Implementing it inside the onCreate() method will not lead to the desired behavior, since the views will not yet have been instantiated.

The following sample illustrates how to implement a search using GenericListPickerFormCellFragment:

GenericListPickerFormCell<ObjectCell, String> genericObjectCellPicker = view.findViewById(R.id.genericObjectCellPicker);
genericObjectCellPicker.setOnClickListener(v -> {
        GenericListPickerFormCellFragment<ObjectCell, String> pickerFragment = new GenericListPickerFormCellFragment<>();
        pickerFragment.setTitle("Pick Object Cell");
        pickerFragment.setGenericListPickerAdapter(v.getContext(), new ObjectCellPickerAdapter());
        pickerFragment.setSearchEnabled(true);

        pickerFragment.setSearchOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                if (!TextUtils.isEmpty(newText)) {
                    adapter.updateSearch(newText);
                    pickerFragment.notifyDataSetChanged();
                }
                return true;
            }
        });

        pickerFragment.setSearchOnQueryTextFocusChangeListener((v1, hasFocus) -> {
            if (!hasFocus) {
                adapter.restoreDataFromBackup();
            }
            pickerFragment.notifyDataSetChanged();
        });


        pickerFragment.showNow(getChildFragmentManager(), GenericListPickerFormCellFragment.TAG);
    });

Generic Picker Search

Save and Dismiss on Single Select

In the case of single selection, the GenericListPickerFormCell can be configured to save the selection and close the picker without the user having to click the Apply button. When this is enabled, the footer can also optionally be hidden so that the Apply button is not displayed. This can be achieved as follows:

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setSingleSelectOnly(true);
mPickerFormCell.setDismissOnSingleSelection(true);
// Optionally hide the footer
mPickerFormCell.setHideFooterWhenDismissOnSingleSelection(true);

Error and Helper

GenericListPickerFormCell supports setting error and helper messages and icons that can be used for validating user input.

To set the error message:

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setErrorEnabled(true);
mPickerFormCell.setError("Error Message");
// Use default error icon
mPickerFormCell.setErrorIcon(null);

To set the helper message:

GenericListPickerFormCell mPickerFormCell = findViewById(R.id.pickerCell);
mPickerFormCell.setHelperEnabled(true);
mPickerFormCell.setHelperText("Success Message");
Drawable helperIcon = ResourcesCompat.getDrawable(getResources(), com.sap.cloud.mobile.fiori.R.drawable.ic_sap_icon_accept, context.getTheme());
int helperIconColor = MaterialColors.getColor(context, R.attr.sap_fiori_color_semantic_positive,
context.getResources().getColor(R.color.sap_ui_positive_semantic_color, context.getTheme()));
mPickerFormCell.setHelperTextIcon(helperIcon, helperIconColor);

Listening for Cell Value Changes

Like all FormCells, GenericListPickerFormCell notifies the application about changes through CellValueChangeListener. To listen for updates to a cell value, attach a CellValueChangeListener using setCellValueChangeListener:

mPickerFormCell.setCellValueChangeListener(new FormCell.CellValueChangeListener<List<T>>() {
    @Override
    protected void cellChangeHandler(@NonNull List<T> value) {
        // Application logic here
    }
});

See also FormCell.


Last update: June 15, 2023