Single choice recyclerView

Single choice recyclerView

When we want to Multiple choice in RecyclerView, we will need these things:

– String []
– Adapter with itemClickListener
– Activity or fragment
– activity_main (recyclerView)
– list_item (TextView, CheckBox)

We use notifydatasetchanged(), it’s very important than We understand this method.
You can check the full code in GitHub.

Our views activity_main.xml (recyclerView):

<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="com.thedeveloperworldisyours.fullrecycleview.single.SingleFragment">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/single_fragment_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</RelativeLayout>

and list_item.xml (TextView, CheckBox)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/single_list_item_text"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:text="@string/app_name"
        android:typeface="monospace"
        android:layout_toLeftOf="@+id/single_list_item_check_button"
        android:gravity="center"
        android:textSize="@dimen/multiple_list_item_size_rock_stars"/>

    <RadioButton
        android:id="@+id/single_list_item_check_button"
        android:layout_width="wrap_content"
        android:layout_height="90dp"
        android:layout_alignParentRight="true"
        android:checked="false"
        android:clickable="false"
        android:focusable="false" />

</RelativeLayout>

Adapter with ClickListener


public class SingleRecyclerViewAdapter extends RecyclerView.Adapter<SingleRecyclerViewAdapter.DataObjectHolder> {

    private String[] mData;
    private static SingleClickListener sClickListener;
    private static int sSelected = -1;

    public SingleRecyclerViewAdapter(String[] mData) {
        this.mData = mData;
    }

    static class DataObjectHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        TextView mTextView;
        RadioButton mRadioButton;

        public DataObjectHolder(View itemView) {
            super(itemView);
            this.mTextView = (TextView) itemView.findViewById(R.id.single_list_item_text);
            this.mRadioButton = (RadioButton) itemView.findViewById(R.id.single_list_item_check_button);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            sSelected = getAdapterPosition();
            sClickListener.onItemClickListener(getAdapterPosition(), view);
        }
    }

    public void selectedItem() {
        notifyDataSetChanged();
    }

    void setOnItemClickListener(SingleClickListener clickListener) {
        sClickListener = clickListener;
    }

    @Override
    public DataObjectHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.single_list_item, parent, false);

        DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
        return dataObjectHolder;
    }

    @Override
    public void onBindViewHolder(DataObjectHolder holder, int position) {
        holder.mTextView.setText(mData[position]);

        if (sSelected == position) {
            holder.mRadioButton.setChecked(true);
        } else {
            holder.mRadioButton.setChecked(false);
        }

    }

    @Override
    public int getItemCount() {
        return mData.length;
    }

    interface SingleClickListener {
        void onItemClickListener(int position, View view);
    }

}

Activity or fragment

public class SingleFragment extends Fragment implements SingleRecyclerViewAdapter.SingleClickListener {

    SingleRecyclerViewAdapter mAdapter;

    public SingleFragment() {
        // Required empty public constructor
    }

    public static SingleFragment newInstance() {
        return new SingleFragment();
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.single_fragment, container, false);
        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.single_fragment_recycler_view);

        String[] list = new String[]{"Jimi Hendrix", "David Bowie", "Jim Morrison", "Elvis Presley",
                "Mick Jagger", "Kurt Cobain", "Bob Dylan", "John Lennon", "Freddie Mercury", "Elton John", "Eric Clapton"};

        mAdapter = new SingleRecyclerViewAdapter(list);
        recyclerView.setAdapter(mAdapter);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
        RecyclerView.ItemDecoration itemDecoration =
                new DividerVerticalItemDecoration(getActivity());
        recyclerView.addItemDecoration(itemDecoration);
        recyclerView.setLayoutManager(layoutManager);
        mAdapter.setOnItemClickListener(this);
        return view;
    }

    @Override
    public void onItemClickListener(int position, View view) {
        mAdapter.selectedItem();
    }
}

You can check the full code in GitHub.

Multiple choice recyclerView

Multiple choice recyclerView

The completed code GitHub

When we want to Multiple choice in RecyclerView, we will need these things:

– Data (String name, boolean selected)
– Adapter with itemClickListener
– Activity or fragment
– activity_main (recyclerView)
– list_item (TextView, CheckBox)

We use notifydatasetchanged(), it’s very important than We understand this method.

Out Data will have String and boolean, this boolean will save if user select or not the item.

public class MultipleData {

    private String mTitle;
    private boolean mBoolean;

    public MultipleData(String title, boolean mBoolean) {
        this.mTitle = title;
        this.mBoolean = mBoolean;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String mTitle) {
        this.mTitle = mTitle;
    }

    public boolean isBoolean() {
        return mBoolean;
    }

    public void setBoolean(boolean mBoolean) {
        this.mBoolean = mBoolean;
    }
}

Our views activity_main.xml (recyclerView)

<FrameLayout 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="com.thedeveloperworldisyours.fullrecycleview.multiple.MultipleFragment">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/multiple_fragment_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

and list_item.xml (TextView, CheckBox)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/multiple_list_item_text"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:text="@string/app_name"
        android:typeface="monospace"
        android:layout_toLeftOf="@+id/multiple_list_item_check_button"
        android:gravity="center"
        android:textSize="@dimen/multiple_list_item_size_rock_stars"/>

    <RadioButton
        android:id="@+id/multiple_list_item_check_button"
        android:layout_width="wrap_content"
        android:layout_height="90dp"
        android:layout_alignParentRight="true"
        android:checked="false"
        android:clickable="false"
        android:focusable="false" />

</RelativeLayout>

Adapter with ClickListener

public class MultipleRecyclerViewAdapter extends RecyclerView
        .Adapter<MultipleRecyclerViewAdapter
        .DataObjectHolder> {

    private List<MultipleData> mList;
    private static MultipleClickListener sClickListener;

    MultipleRecyclerViewAdapter(List<MultipleData> mList) {
        this.mList = mList;
    }

    static class DataObjectHolder extends RecyclerView.ViewHolder
            implements View
            .OnClickListener {

        TextView mTextView;
        RadioButton mRadioButton;

         DataObjectHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.multiple_list_item_text);
            mRadioButton = (RadioButton) itemView.findViewById(R.id.multiple_list_item_check_button);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            sClickListener.onItemClick(getAdapterPosition(), v);
        }
    }

    void changedData(int position) {
            if (mList.get(position).isBoolean()) {
                mList.get(position).setBoolean(false);
            } else {
                mList.get(position).setBoolean(true);
            }
        notifyDataSetChanged();
    }

    void setOnItemClickListener(MultipleClickListener myClickListener) {
        this.sClickListener = myClickListener;
    }

    @Override
    public DataObjectHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.multiple_list_item, parent, false);

        DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
        return dataObjectHolder;
    }

    @Override
    public void onBindViewHolder(DataObjectHolder holder, int position) {
        holder.mTextView.setText(mList.get(position).getTitle());
        holder.mRadioButton.setChecked(mList.get(position).isBoolean());
    }

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

    interface MultipleClickListener {
        void onItemClick(int position, View v);
    }


}

Activity or fragment

package com.thedeveloperworldisyours.fullrecycleview.multiple;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.thedeveloperworldisyours.fullrecycleview.R;
import com.thedeveloperworldisyours.fullrecycleview.swipe.DividerItemDecoration;

import java.util.ArrayList;
import java.util.List;

public class MultipleFragment extends Fragment implements MultipleRecyclerViewAdapter.MultipleClickListener{

    MultipleRecyclerViewAdapter mAdapter;

    public MultipleFragment() {
        // Required empty public constructor
    }

    public static MultipleFragment newInstance() {
        return new MultipleFragment();
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.multiple_fragment, container, false);

        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.multiple_fragment_recycler_view);

        MultipleData hendrix = new MultipleData("Jimi Hendrix", false);
        MultipleData bowie = new MultipleData("David Bowie", false);
        MultipleData morrison = new MultipleData("Jim Morrison", false);

        MultipleData presley = new MultipleData("Elvis Presley", false);
        MultipleData jagger = new MultipleData("Mick Jagger", false);
        MultipleData cobain = new MultipleData("Kurt Cobain", false);

        MultipleData dylan = new MultipleData("Bob Dylan", false);
        MultipleData lennon = new MultipleData("John Lennon", false);
        MultipleData mercury = new MultipleData("Freddie Mercury", false);

        MultipleData elton = new MultipleData("Elton John", false);
        MultipleData clapton = new MultipleData("Eric Clapton", false);

        List<MultipleData> list = new ArrayList<>();
        list.add(0, hendrix);
        list.add(1, bowie);
        list.add(2, morrison);

        list.add(3, presley);
        list.add(4, jagger);
        list.add(5, cobain);

        list.add(6, dylan);
        list.add(7, lennon);
        list.add(8, mercury);

        list.add(9, elton);
        list.add(10, clapton);

        mAdapter = new MultipleRecyclerViewAdapter(list);
        recyclerView.setAdapter(mAdapter);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
        RecyclerView.ItemDecoration itemDecoration =
                new DividerItemDecoration(getActivity(), LinearLayoutManager.VERTICAL);
        recyclerView.addItemDecoration(itemDecoration);
        recyclerView.setLayoutManager(layoutManager);
        mAdapter.setOnItemClickListener(this);

        return view;
    }

    @Override
    public void onItemClick(int position, View v) {
        mAdapter.changedData(position);
    }
}

The completed code GitHub

To restore SearchView state

When you want to restore searchView state, we should use onSaveInstanceState(Bundle outState). Called to retrieve per-instance state from an activity before being killed so that the state can be restored in onCreate(Bundle) or onRestoreInstanceState(Bundle) (the Bundle populated by this method will be passed to both).

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via onCreate(Bundle) or onRestoreInstanceState(Bundle).

Complete code in github

There is a example how to work this important method in our Activity.

import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
 
public class MainActivity extends AppCompatActivity {
 
    private SearchView mSearchView;
    private String mSearchString;
    private static final String SEARCH_KEY = "search";
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // if you saved something on outState you can recover them here
        if (savedInstanceState != null) {
            mSearchString = savedInstanceState.getString(SEARCH_KEY);
        }
    }
 
    // This is called before the activity is destroyed
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mSearchString = mSearchView.getQuery().toString();
        outState.putString(SEARCH_KEY, mSearchString);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
 
        MenuItem searchMenuItem = menu.findItem(R.id.menu_main_action_search);
 
        mSearchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);
 
        //focus the SearchView
        if (mSearchString != null && !mSearchString.isEmpty()) {
            searchMenuItem.expandActionView();
            mSearchView.setQuery(mSearchString, true);
            mSearchView.clearFocus();
        }
 
        return super.onCreateOptionsMenu(menu);
    }
}

Now we create our SearchView. First we need to create new folder which name is menu.

After that we create new xml file, which name is “main”.

After that we create new xml file, which name is “main”.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_main_action_search"
        android:icon="@android:drawable/ic_menu_search"
        android:title="@string/menu_main_search_title"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always|collapseActionView" />
</menu>

Finally our string file.

<resources>
    <string name="app_name">"CustomSearchView "</string>
    <string name="activity_main_text">TheDeveloperWorldIsYours.com</string>
    <string name="menu_main_search_title">Search</string>
</resources>

Complete code in github

Model View Presenter on Android

What is MVP

  • View is a layer that displays data and reacts to user actions. On Android, this could be an Activity, a Fragment, an android.view.View or a Dialog.
  • Model is a data access layer such as database API or remote server API.
  • Presenter is a layer that provides View with data from Model. Presenter also handles background tasks.

On Android MVP is a way to separate background tasks from activities/views/fragments to make them independent of most lifecycle-related events. This way an application becomes simpler, overall application reliability increases up to 10 times, application code becomes shorter, code maintainability becomes better and developer’s life becomes happier.

Why MVP on Android

Reason 1: Keep It Stupid Simple

If you haven’t read this article yet, do it: The Kiss Principle

  • Most of the modern Android applications just use View-Model architecture.
  • Programmers are involved into fight with View complexities instead of solving business tasks.

Using only Model-View in your application you usually end up with “everything is connected with everything”.

If this diagram does not look complex, then think about each View can disappear and appear at random time. Do not forget about saving/restoring of Views. Attach a couple of background tasks to that temporary Views, and the cake is ready!

An alternative to the “everything is connected with everything” is a god object.

A god object is overcomplicated; its parts cannot be reused, tested or easily debugged and refactored.

With MVP

  • Complex tasks are split into simpler tasks and are easier to solve.
  • Smaller objects, less bugs, easier to debug.
  • Testable.

View layer with MVP becomes so simple, so it does not even need to have callbacks when requesting for data. View logic becomes very linear.

Reason 2: Background tasks

Whenever you write an Activity, a Fragment or a custom View, you can put all methods that are connected with background tasks to a different external or static class. This way your background tasks will not be connected with an Activity, will not leak memory and will not depend on Activity’s recreation. We call such object “Presenter”.

There are few different approaches to handle background tasks but non of them are as reliable as MVP is.

Why this works

Here is a little diagram that shows what happens with different application parts during a configuration change or during an out-of-memory event. Every Android developer should know this data, however this data is surprisingly hard to find.

                                          |    Case 1     |   Case 2     |    Case 3
                                          |A configuration| An activity  |  A process
                                          |   change      |   restart    |   restart
 ---------------------------------------- | ------------- | ------------ | ------------
 Dialog                                   |     reset     |    reset     |    reset
 Activity, View, Fragment                 | save/restore  | save/restore | save/restore
 Fragment with setRetainInstance(true)    |   no change   | save/restore | save/restore
 Static variables and threads             |   no change   |   no change  |    reset

Case 1: A configuration change normally happens when a user flips the screen, changes language settings, attaches an external monitor, etc. More on this event you can read here:configChanges.

Case 2: An Activity restart happens when a user has set “Don’t keep activities” checkbox in Developer’s settings and another activity becomes topmost.

Case 3: A process restart happens if there is not enough memory and the application is in the background.

Conclusion

Now you can see, a Fragment with setRetainInstance(true) does not help here – we need to save/restore such fragment’s state anyway. So we can simply throw away retained fragments to limit the number of problems.

                                          |A configuration|
                                          |   change,     |
                                          | An activity   |  A process
                                          |   restart     |   restart
 ---------------------------------------- | ------------- | -------------
 Activity, View, Fragment, DialogFragment | save/restore  | save/restore
 Static variables and threads             |   no change   |    reset

Now it looks much better. We only need to write two pieces of code to completely restore an application in any possible case:

  • save/restore for Activity, View, Fragment, DialogFragment;
  • restart background requests in case of a process restart.

The first part can done by usual means of Android API. The second part is a job for Presenter. Presenter just remembers which requests it should execute, and if a process restarts during execution, Presenter will execute them again.

You can see complete code here.

Also you can see how to make Model View Presenter on Android here.