Menu
Menu

Creating add and remove type list using RecyclerView

Thursday, December 17, 2015

In this post we will create a list using RecyclerView in which each row item contains a add("+") and remove("-") button to add a new row at that position or remove the row at a particular position respectively.

This is how it will look.

image

First let us make the layouts. This is the layout for the list item which will be used by the adapter. It contains the add and the remove button so that we have these buttons on every row of our RecyclerView.

list_item.xml

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

    <LinearLayout
        android:id="@+id/main_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="10">

        <EditText
            android:id="@+id/step"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginStart="5dp"
            android:layout_weight="8"
            android:background="@null"
            android:hint="Next Step"
            android:padding="9dp"
            android:textSize="16sp"/>

        <ImageButton
            android:id="@+id/plus"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@mipmap/add"/>

        <ImageButton
            android:id="@+id/minus"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@mipmap/minus" />

    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_below="@id/main_layout"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:background="#d3d3d3" />

</RelativeLayout>

We will create 2 Activities. One will be MainActivity and other will be ListActivity. The MainActivity just has a single button to start the ListActivity by the startActivityForResult() method. This MainActivity also gets a callback with all the items that were added in the ListActivity in the onActivityResult() method.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Make List"
        android:id="@+id/button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

activity_list.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="yet.best.addremovelist.ListActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="10dp"
        android:descendantFocusability="afterDescendants" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:id="@+id/submit_button"
        android:layout_alignParentBottom="true"
        android:background="@color/colorPrimary"
        android:text="Submit"
        android:textColor="#fff"/>

</RelativeLayout>

We now look at the MainActivity's code. We've set the inClickListener on the button that starts the ListActivity. If we are starting the ListActivity for the first time or there's no data to send(List is empty till now) then we create an empty list and send it to ListActivity. This list is used by the ListActivity to populate the RecyclerView.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    final int LIST_REQUEST = 1;
    final int LIST_RESULT = 100;
    Button button;
    ArrayList<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(MainActivity.this, ListActivity.class);
                if(list == null) {
                    list = new ArrayList<>();
                }
                i.putStringArrayListExtra("list", list);
                startActivityForResult(i, LIST_REQUEST);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == LIST_REQUEST && resultCode == LIST_RESULT)
            list = data.getStringArrayListExtra("list");
    }
}

In the result method of MainActivity, the updated list with all entries is received so that the data is maintained and it can be sent back the to ListActivity again if it is restarted.

In the ListActivity we are just getting a reference to the RecyclerView and setting the adapter on it using the List(empty list if starting this activity for the first time) we got from MainActivity. If the list is empty then a single item is added(blank data) so that we have at least one item visible initially in our RecyclerView.

ListActivity.java

public class ListActivity extends AppCompatActivity {

    final int LIST_RESULT = 100;

    ArrayList<String> list;
    RecyclerView recyclerView;
    ListAdapter listAdapter;
    LinearLayoutManager llm;
    Button submitButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);

        list = getIntent().getStringArrayListExtra("list");
        //To show at least one row
        if(list == null || list.size() == 0) {
            list = new ArrayList<>();
            list.add("");
        }

        submitButton = (Button) findViewById(R.id.submit_button);
        recyclerView = (RecyclerView) findViewById(R.id.rv);
        listAdapter = new ListAdapter(list, this);
        llm = new LinearLayoutManager(this);

        //Setting the adapter
        recyclerView.setAdapter(listAdapter);
        recyclerView.setLayoutManager(llm);

        submitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                list = listAdapter.getStepList();
                i.putStringArrayListExtra("list", list);
                setResult(LIST_RESULT, i);
                finish();
            }
        });
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

The submit button simply sets the data in the list as result and closes this activity. This data is then received by the MainActivity.

Finally, let's look at the adapter used for RecyclerView.

ListAdapter.java

public class AddRecipeStepsAdapter extends RecyclerView.Adapter<AddRecipeStepsAdapter.ViewHolder> {

    Context context;
    ArrayList<String> steps;
    String hint;

    public class ViewHolder extends RecyclerView.ViewHolder{
        ImageButton plus, minus;
        EditText step;

        public ViewHolder(View itemView) {
            super(itemView);
            plus = (ImageButton) itemView.findViewById(R.id.plus);
            minus = (ImageButton) itemView.findViewById(R.id.minus);
            step = (EditText) itemView.findViewById(R.id.step);

            minus.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    try {
                        steps.remove(position);
                        notifyItemRemoved(position);
                    }catch (ArrayIndexOutOfBoundsException e){e.printStackTrace();}
                }
            });

            plus.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    try {
                        steps.add(position + 1, "");
                        notifyItemInserted(position + 1);
                    }catch (ArrayIndexOutOfBoundsException e){e.printStackTrace();}
                }
            });

            step.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    steps.set(getAdapterPosition(), s.toString());
                }

                @Override
                public void afterTextChanged(Editable s) {
                }
            });
        }
    }

    public AddRecipeStepsAdapter(ArrayList<String> steps, Context context, String hint){
        this.steps = steps;
        this.hint = "Next " + hint;
        this.context = context;
    }

    @Override
    public int getItemCount() {
        return steps.size();
    }

    @Override
    public AddRecipeStepsAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.add_recipe_steps_item, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(AddRecipeStepsAdapter.ViewHolder holder, final int i) {
        int x = holder.getLayoutPosition();

        if(steps.get(x).length() > 0) {
            holder.step.setText(steps.get(x));
        }
        else{
            holder.step.setText(null);
            holder.step.setHint(hint);
            holder.step.requestFocus();
        }
    }

    public ArrayList<String> getStepList(){
        return steps;
    }

}

The click listeners for plus and minus button simply get the location of the clicked row and add or delete that position(row) from the ArrayList. For the EditText we have to set a TextChange listener which saves the data of EditText to the ArrayList as soon as the text is modified.

Lastly in our onBindViewHolder method we check if the String(EditText) to be displayed as a step is empty or not. If its empty then we just display the hint else the text is set to it every time the view is visible.

Source Code available at Github