There is a new version of Recommender in the play sore. The main change is that I’ve added a Bottom Sheet using the Design Support Library. The Bottom Sheet is used to display the list of categories that you have currently used. Category is a free text field but is used to group recommendations so its best to try and avoid misspelt duplicate. The list of current categories is displayed to to try and discourage duplication. It looks like this

 Screenshot_20160728-204032

Its an extension of BottomSheetDialogFragment. Triggered by tapping the down arrow on the Category field. Its modal as I wanted you to either be selecting a current category or entering a new one. I found doing both at the same time made the screen to cluttered and I didn't want to break the flow by forcing you to go to a separate screen to enter a category.

The dialog that goes in the BottomSheet fragment is pretty straightforward the layout looks like this

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <View
  android:layout_width="fill_parent"
  android:layout_height="1dp"
  android:background="@color/color_divider"
 />

 <TextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/category_fragment_title"
  android:textColor="@color/color_accent"
  android:paddingLeft="2dp"
  android:paddingRight="2dp"
  android:textSize="16sp"/>

 <ListView
  android:id="@+id/category_list"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
 </ListView>
</LinearLayout>

Then I just put an ImageButton down and called showBottomSheet() on an instance of a CategoryBottomSheetFragment

public class CategoryBottomSheetFragment extends BottomSheetDialogFragment {

 private IEditRecommendationComponent component;

 @Inject
 protected IRecommendationRepository repository;
 @Inject
 protected ILoggerFactory logger;
 @Inject
 protected ITextUtils textUtils;

 @BindView(R.id.category_list)
 protected ListView categoryList;

 private CategoryAdapter categoryAdapter;
 private Subscription categorySubscription = null;
 private BottomSheetBehavior bottomSheetBehavior;
 private RecommendationEditFragment editFragment;
 private BottomSheetBehavior.BottomSheetCallback bottomSheetBehaviorCallback =
            new BottomSheetBehavior.BottomSheetCallback() {
              @Override
              public void onStateChanged(@NonNull View bottomSheet, int newState) {
                if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                 dismiss();
                }
              }

              @Override
              public void onSlide(@NonNull View bottomSheet, float slideOffset) {
              }
            };

 private IEditRecommendationComponent getComponent() {
  if (component == null) {
   component = DaggerIEditRecommendationComponent.builder()
       .iApplicationComponent(getApplicationComponent())
       .baseActivityModule(new BaseActivityModule(getActivity()))
       .editRecommendationModule(new EditRecommendationModule())
       .build();
  }
  return component;
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  getComponent().inject(this);
 }

 @Override
 public void setupDialog(Dialog dialog, int style) {
  super.setupDialog(dialog, style);
  View contentView = View.inflate(getContext(), R.layout.fragment_category_bottomsheet, null);
  ButterKnife.bind(this, contentView);
  dialog.setContentView(contentView);

  // lets set the size of the dialog to be half the height of the screen
  Display display = getActivity().getWindowManager().getDefaultDisplay();
  Point size = new Point();
  display.getSize(size);
  contentView.getLayoutParams().height = size.y / 2;

  CoordinatorLayout.LayoutParams params = 
   (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
  CoordinatorLayout.Behavior behavior = params.getBehavior();

  if ( behavior != null && behavior instanceof BottomSheetBehavior ) {
   bottomSheetBehavior = (BottomSheetBehavior) behavior;
   bottomSheetBehavior.setBottomSheetCallback(bottomSheetBehaviorCallback);
  } else {
   throw new UnsupportedOperationException("cannot find bottomSheetBehaviour");
  }

  categoryAdapter = new CategoryAdapter(this.getActivity(), textUtils);
  categoryList.setAdapter(categoryAdapter);
  categoryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   @Override
   public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
    if (editFragment != null) {
     editFragment.setCategory(categoryAdapter.getItem(position));
    }
    dismiss();
   }
  });
 }

 @Override public void onResume() {
  super.onResume();
  categorySubscription = repository.getCategories(new Action1>() {
   @Override
   public void call(List categories) {
    categoryAdapter.call(categories);
   }
  });
 }

 @Override public void onPause() {
  super.onPause();
  // stop any subscriptions
  if (categorySubscription != null) {
   categorySubscription.unsubscribe();
   categorySubscription = null;
  }
 }

 @Override
 public void onStart() {
  super.onStart();
  bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
 }

 public void hideBottomSheet() {
  bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
 }

 public void showBottomSheet(
    FragmentManager supportFragmentManager, 
    RecommendationEditFragment editFragment)
 {
  this.editFragment =  editFragment;
  show(supportFragmentManager, getTag());
 }
}

For completeness the adapter looks like this

final public class CategoryAdapter extends BaseAdapter implements Action1> {
 private final LayoutInflater inflater;

 private ITextUtils textUtils;
 private List items = Collections.emptyList();

 public CategoryAdapter(Context context, ITextUtils textUtils) {
  this.textUtils = textUtils;
  inflater = LayoutInflater.from(context);
 }

 @Override
 public void call(List strings) {
  if (strings == null || strings.size() < 1) {
   return;
  }
  this.items = new ArrayList(strings.size());
  for (String thisString : strings) {
   if (!textUtils.isEmpty(thisString)) {
    items.add(thisString);
   }
  }
  notifyDataSetChanged();
 }

 @Override
 public int getCount() { return items == null ? 0 : items.size(); }

 @Override
 public String getItem(int position) {
  return items.get(position);
 }

 @Override
 public long getItemId(int position) {
  return getItem(position).hashCode();
 }

 @Override
 public boolean hasStableIds() {
  return true;
 }

 @Override
 public View getView(int position, View rowView, ViewGroup parent) {
  if (rowView == null) {
   rowView = inflater.inflate(R.layout.list_item_category, parent, false);
  }
  ViewHolder holder = new ViewHolder(rowView);
  String thisCategory = items.get(position);
  holder.label.setText(thisCategory);
  return rowView;
 }

 static class ViewHolder {
  @BindView(R.id.category_row_label)
  TextView label;

  public ViewHolder(View view) {
   ButterKnife.bind(this, view);
  }
 }
}

The layout for each line in the adapter can be as complex as you like but this one is pretty simple

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical"
  android:background="?attr/selectableItemBackground"
>
 <LinearLayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical"
  android:minHeight="@dimen/listview_min_height"
  android:gravity="center_vertical"
  android:paddingLeft="@dimen/listview_padding"
  android:paddingRight="@dimen/listview_padding"
  android:paddingTop="@dimen/listview_item_padding"
  android:paddingBottom="@dimen/listview_item_padding"
  >

  <TextView
   android:id="@+id/category_row_label"
   android:layout_width="fill_parent"
   android:layout_height="26dip"
   android:layout_centerHorizontal="true"
   android:ellipsize="marquee"
   android:singleLine="true"
   android:text="@string/placeholder"
   android:textSize="16sp"/>

 </LinearLayout>
</LinearLayout>

You can also look at the full source code to get a better idea of how it all hangs together