1

I have a fragment whith a Searchview at the top, and below this I show a call log with the las 10 calls. To show the call log I use recyclerview with cards. This behaviour works fine, but now I want to implement something else.

If I search a name in the Searchview, I would like to do something like when a result list gets updated showing the coincidences with the contact list. This is, I would need to reuse the recyclerview, but at this time, instead of the call log, I will show coincidences with the contact list.

I've used code found here, but is not working. I'm making some test to see what is wrong and I've found that I'm not able to reuse the recyclerview.

At first, I do this to show the call log:

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this.getActivity()));
mLogAdapter = new LogAdapter(DisplayCallLog());
mRecyclerView.setAdapter(mLogAdapter);

And when the searchview get's updated, I'm trying just showing the contactlist to verify that this is working:

public boolean onQueryTextChange(String query) {
    mContactAdapter = new ContactAdapter(ContactsList());
    mRecyclerView.swapAdapter(mContactAdapter, true);
    //final List<ContactInfo> filteredModelList = filter(ContactsList(), query);
    //mContactAdapter.animateTo(filteredModelList);
    //mRecyclerView.scrollToPosition(0);
    return true;
}

But I don't get to show the contactlist on the same recyclerview.


EDIT 1 -> Comment

I've tried loading the contactlist at startup instead of the loglist, and it does not load it neither. Maybe the problem is that the contactlist is to long?


EDIT 2 -> Extensive code added

1) Set up the SearchView

Instead of adding the searchview in the actionbar, I use a cardview to contain it. This is the phone_layout.xml.

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="60dp">
    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardCornerRadius="4dp"
        card_view:cardElevation="4dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="80dp"
        android:layout_marginRight="80dp">
        <android.support.v7.widget.SearchView
            android:id="@+id/dialpad_searchview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:textSize="18sp"
            app:queryHint="@string/enter_phone_number"
            app:iconifiedByDefault="false"
            android:imeOptions="flagNoFullscreen"/>
    </android.support.v7.widget.CardView>
</LinearLayout>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:layout_marginLeft="80dp"
    android:layout_marginRight="80dp"/>

2) Set up the adapter

First I define the model class ContactInfo. This is same for booth uses calllog and contact coincidences.

public class ContactInfo {

public int id;
public String name;
public String number;
public String type;
public int logType;

public static final String ID_PREFIX = "ID_";
public static final String NUMBER_PREFIX = "Name_";
public static final String NAME_PREFIX = "Number_";
public static final String TYPE_PREFIX = "Type_";

public String getContactName() {
    return name;
}
public String getContactNumber() {
    return number;
}

The ContactViewHolder is also the same for booth.

public class ContactViewHolder extends RecyclerView.ViewHolder {
protected TextView vName;
protected TextView vType;
protected ImageView vPic;

public ContactViewHolder(View v) {
    super(v);
    vName =  (TextView) v.findViewById(R.id.contactname);
    vType = (TextView)  v.findViewById(R.id.contacttype);
    vPic = (ImageView)  v.findViewById(R.id.contactpic);
}

Now, what is diferent for each use is the layout and the adapter. Starting from the calllog, here is the phone_calllog_card.xml

<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
android:layout_marginBottom="5dp">
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:selectableItemBackground">
    <ImageView
        android:id="@+id/contactpic"
        android:tag="image_tag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="20dp"/>
    <TextView
        android:id="@+id/contactname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/contactpic"
        android:layout_marginLeft="40dp"
        android:text="Name"
        android:textAppearance="?android:attr/textAppearanceLarge"/>
    <TextView
        android:id="@+id/contacttype"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/contactname"
        android:layout_toRightOf="@+id/contactpic"
        android:layout_marginLeft="40dp"
        android:text="Type"
        android:textAppearance="?android:attr/textAppearanceMedium"/>
</RelativeLayout>

And the LogAdapter class.

public class LogAdapter extends RecyclerView.Adapter<ContactViewHolder> {

private List<ContactInfo> logList;

public LogAdapter(List<ContactInfo> logList) {
    this.logList = logList;
}

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

@Override
public void onBindViewHolder(ContactViewHolder contactViewHolder, int i) {
    final String number;
    String name;
    ContactInfo ci = logList.get(i);
    name = ci.name;
    if (name.equals("-2")) {
        name = "Private";
    }
    contactViewHolder.vName.setText(name);
    contactViewHolder.vType.setText(ci.type);
    number = ci.number;
    }

    contactViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:" + number.trim()));
            if (ActivityCompat.checkSelfPermission(v.getContext(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
                v.getContext().startActivity(intent);
            }
        }
    });
}

@Override
public ContactViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View itemView = LayoutInflater.
            from(viewGroup.getContext()).
            inflate(R.layout.phone_calllog_card, viewGroup, false);

    return new ContactViewHolder(itemView);
}

}

The phone_contact_card.xml and the ContactAdapter are almost indetical to these previous ones, but with few variations.

<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
android:layout_marginBottom="5dp">
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:selectableItemBackground">
    <ImageView
        android:id="@+id/contactpic"
        android:tag="image_tag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="20dp"/>
    <TextView
        android:id="@+id/contactname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/contactpic"
        android:layout_marginLeft="40dp"
        android:layout_centerVertical="true"
        android:text="Name"
        android:textAppearance="?android:attr/textAppearanceLarge"/>
</RelativeLayout>

The diference between the Adapter for the call log and the contact coincidences is that the call log is shown when entered to the fragment and it shows 10 static results from the call log. But with the contact coincidences, it must have some kind of animation to refresh the list each time you enter a letter in the serachview to find coincidences, so here are some aditional methods.

public class ContactAdapter extends RecyclerView.Adapter<ContactViewHolder> {

private List<ContactInfo> contactList;

public ContactAdapter(List<ContactInfo> contactList) {
    this.contactList = contactList;
}

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

@Override
public void onBindViewHolder(ContactViewHolder contactViewHolder, int i) {
    final String number;
    ContactInfo ci = contactList.get(i);
    contactViewHolder.vName.setText(ci.name);
    contactViewHolder.vPic.setImageResource(R.drawable.contact_icon_blue);
    number = ci.number;

    contactViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:" + number.trim()));
            if (ActivityCompat.checkSelfPermission(v.getContext(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
                v.getContext().startActivity(intent);
            }
        }
    });
}

@Override
public ContactViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View itemView = LayoutInflater.
            from(viewGroup.getContext()).
            inflate(R.layout.phone_contact_card, viewGroup, false);

    return new ContactViewHolder(itemView);
}

public void animateTo(List<ContactInfo> contacts) {
    applyAndAnimateRemovals(contacts);
    applyAndAnimateAdditions(contacts);
    applyAndAnimateMovedItems(contacts);
}

private void applyAndAnimateRemovals(List<ContactInfo> newContacts) {
    for (int i = contactList.size() - 1; i >= 0; i--) {
        final ContactInfo model = contactList.get(i);
        if (!newContacts.contains(model)) {
            removeItem(i);
        }
    }
}

private void applyAndAnimateAdditions(List<ContactInfo> newContacts) {
    for (int i = 0, count = contactList.size(); i < count; i++) {
        final ContactInfo model = newContacts.get(i);
        if (!contactList.contains(model)) {
            addItem(i, model);
        }
    }
}

private void applyAndAnimateMovedItems(List<ContactInfo> newContacts) {
    for (int toPosition = newContacts.size() - 1; toPosition >= 0; toPosition--) {
        final ContactInfo model = newContacts.get(toPosition);
        final int fromPosition = contactList.indexOf(model);
        if (fromPosition >= 0 && fromPosition != toPosition) {
            moveItem(fromPosition, toPosition);
        }
    }
}

public ContactInfo removeItem(int position) {
    final ContactInfo model = contactList.remove(position);
    notifyItemRemoved(position);
    return model;
}

public void addItem(int position, ContactInfo model) {
    contactList.add(position, model);
    notifyItemInserted(position);
}

public void moveItem(int fromPosition, int toPosition) {
    final ContactInfo model = contactList.remove(fromPosition);
    contactList.add(toPosition, model);
    notifyItemMoved(fromPosition, toPosition);
}

3) Implementing logic

Finally, in the PhoneFragment, this is the implementation to make all this work.

public class PhoneFragment extends Fragment implements SearchView.OnQueryTextListener {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.phone_layout, container, false);

    //query Searchview
    svContact.setOnQueryTextListener(this);

    //Recyclerview
    mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this.getActivity()));
    //Loads the calllog
    mLogAdapter = new LogAdapter(DisplayCallLog());
    mRecyclerView.setAdapter(mLogAdapter);

    //RecyclerView animation
    RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator();
    itemAnimator.setAddDuration(1000);
    itemAnimator.setRemoveDuration(1000);
    mRecyclerView.setItemAnimator(itemAnimator);

    return view;
}

Note: As the call log is showing correctly, I'll avoid copying here the DisplayCallLog() method as is too long.

When text is entered in the searchview, we define the new adapter to achieve the functionallity of showing coincidences in the recyclerview.

    @Override
public boolean onQueryTextChange(String query) {
    final List<ContactInfo> filteredModelList = filter(ContactsList(), query);
    mContactAdapter.animateTo(filteredModelList);
    mRecyclerView.scrollToPosition(0);
    return true;
}

private List<ContactInfo> filter(List<ContactInfo> contacts, String query) {
    query = query.toLowerCase();

    final List<ContactInfo> filteredModelList = new ArrayList<>();
    for (ContactInfo contact : contacts) {
        final String name = contact.getContactName().toLowerCase();
        final String number = contact.getContactNumber().toLowerCase();
        Log.i("FILTERED_QUERY", "name " + name + "/ number " + number);
        if (name.contains(query) || number.contains(query)) {
            filteredModelList.add(contact);
        }
    }
    return filteredModelList;
}

    private ArrayList<ContactInfo> ContactsList() {

    ArrayList<ContactInfo> contactsList = new ArrayList<ContactInfo>();
    int contactID = 0;
    String contactNumber = null;
    String contactName = null;
    ContactInfo cI;
    int resultLimit = 0;

    Cursor cursorContacts = getActivity().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null, null, null, null);
    while (cursorContacts.moveToNext() && resultLimit<10) {
        contactID = cursorContacts.getInt(cursorContacts.getColumnIndexOrThrow(
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
        contactNumber = cursorContacts.getString(cursorContacts.getColumnIndexOrThrow(
                ContactsContract.CommonDataKinds.Phone.NUMBER));
        contactName = cursorContacts.getString(cursorContacts.getColumnIndexOrThrow(
                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));

        cI = new ContactInfo();
        cI.id = contactID;
        cI.name = contactName;
        cI.number = contactNumber;
        Log.i("CONTACT_INFO", cI.toString());
        resultLimit++;
    }
    cursorContacts.close();
    return contactsList;
}

EDIT 3 -> New info

I've tryed showing just the contacts at startup. So, instead of visualizing the log, I should see a contacts list. So in fragment's onCreateView I just do this:

mContactAdapter = new ContactAdapter(ContactsList());
mRecyclerView.setAdapter(mContactAdapter);

Instead of what I was doing before:

mLogAdapter = new LogAdapter(DisplayCallLog());
mRecyclerView.setAdapter(mLogAdapter);

This way, I'm just using one adapter, and if this is the problem, it should work. But is not working. So the problem must be something related to the way I achieve the contacts (I'm talking about getting the raw list of contacts from the contact list, without filtering them), or something that is wrong in the ContactAdapter. But this adapter and the LogAdapter are almost identical, so i don't know...

masmic
  • 3,328
  • 10
  • 43
  • 103
  • is your list empty the first time you call `swapAdapter`? – mikepenz Jul 01 '16 at 11:49
  • @mikepenz the list is not empty, I use a log to see the list. I'm triying with booth swatAdapter() and setAdapter(). – masmic Jul 01 '16 at 11:54
  • I mean the RecyclerView itself. does it already show items or is it not yet displaying items? Do your RV elements have an identifier? do you have hasStableIds activated? – mikepenz Jul 01 '16 at 11:56
  • @mikepenz if I load the calllog, it shows items. But if I load the contactlist, doesn't show nothing. I have an adapter for the calllog and another one for the contactlist, but booth are identical. Also the method to retrive the calllog and the contacts are very similar, and via Log I can see that I'm retrieving the contacts correctly – masmic Jul 01 '16 at 11:58
  • @mikepenz I'm just doing what I've posted above. I'm not using identifier neither I have activated hasStableIds. At first, I load the logAdapter on the RecyclerView. When the SearchView text has changed, then I load the contactAdapter on the same RecyclerView, and for this I use the code found on the link provided on the post. – masmic Jul 01 '16 at 12:08
  • This is the answer found here that I've used as a base to achieve the functionality I want. What I've done is modify some things to see the list on cards: https://stackoverflow.com/questions/30398247/how-to-filter-a-recyclerview-with-a-searchview/30429439#30429439 – masmic Jul 01 '16 at 12:11
  • @masmic You do something wrong in your code. Because all that samples works nice.. – GensaGames Jul 11 '16 at 14:13
  • @GensaGames that's de thing... I don't know what is wrong... – masmic Jul 11 '16 at 14:19
  • I can share my example, based on that samples. It's works, you need just compare searing line by line... – GensaGames Jul 11 '16 at 14:25
  • @GensaGames. Ok, but take into account that I'm able to make it work just showing the calllog. What is not working is showing the contact coincidences when I enter any letter in the SearchView. Is a double use for the same recyclerview. If your example fits this, then I'll take a look and if it works, the bounty will be for you – masmic Jul 11 '16 at 14:34
  • https://drive.google.com/file/d/0BxOyJZ6pn6pqMFQ4eGpIb0Itamc/view?usp=sharing – GensaGames Jul 11 '16 at 14:38
  • Hi. I am the guy who wrote the answer you were referencing. If you post more of your code I am confident I can help you fix the problem. – Xaver Kapeller Jul 11 '16 at 15:25
  • @XaverKapeller I've added almost all the code to the post so you can have a look at it. – masmic Jul 12 '16 at 08:35
  • @GensaGames I thought that your sample was a different one, but is the same that I link in the post. I've used your code from github to have a clue on how to do this, and as I've said, I achieve to show correctly the call log, but I don't achieve to show the contact coincidences. I have updated the post adding almost all my code. You could have a look, maybe is easy this way for you to see what I could be doing wrong. – masmic Jul 12 '16 at 08:47
  • From a first glance I see two problems in your code: The `List` of models you pass into your adapters is not copied. Whenever you filter a `List` like in the second `Adapter` you have to copy it, otherwise it won't work properly. So in the constructor of your `Adapter` use `this.contactList = new ArrayList<>(contactList);` instead of `this.contactList = contactList;`. This is actually something I specifically mention and explain the answer you referenced. – Xaver Kapeller Jul 12 '16 at 10:44
  • The other problem is basically the strange way you seem to manage your lists. do your really load them from the database again each time you filter? That's terrible. You should do it the same like in the example project on github. No need to constantly load the database from the list. – Xaver Kapeller Jul 12 '16 at 10:48
  • @XaverKapeller I'll appreciate an answer with a how-to doing this. I'm not sure what do you mean with loading them from database. How could I do this then? – masmic Jul 12 '16 at 11:15
  • @masmic I wrote example that works pretty good, i will update answer now... – GensaGames Jul 12 '16 at 20:08
  • @XaverKapeller have a look at new EDIT in the original post please – masmic Jul 13 '16 at 10:41

2 Answers2

2

I think I get the problem. And it's because you using several Adapters, for showing examples of Log and Call data. So first, what you need to do, it's use only single adapter and different view for item types. RecyclerView has implementation for different ViewHolder.

First, try with example above, by using only single adapter and different item types. If problem is still active, I'll provide example later, using your resource above.

UPDATE!

So, I want you to understand correctly, that using several adapters may produce problems with updating views next time (event this is not a key of your question, cause RecyclerView caches ViewHolders). And there is no need to use several adaptes in your examples. Another things, that I don't understand all works of code above. For more information need to debug all your project...

But I also updated work of Searchable Samples (linked above) to use several examples of Item ViewHolder Types and different in searching, below is result. Anyway it's better solution, than using several Adapters. Also I share code in Github Later.

UPDATE GITHUB LINKS!

enter image description here enter image description here

Kamal
  • 5
  • 6
GensaGames
  • 4,757
  • 1
  • 17
  • 43
  • In the example provided, if I've understood correctly, it is showin the 3 views at same time. maybe, even if i use 2 different adapters, should I need also 2 different ViewHolders? Currently I'm just using one. – masmic Jul 12 '16 at 10:14
  • @masmic, no, using single ViewHolder it's ok. But using several adapters may produce a problem. Try to use several ViewHolder types, instead of creating two adapters... Anyway I think i provide example later... – GensaGames Jul 12 '16 at 10:25
  • Using multiple `Adapters` is no problem at all. Switching between `Adapters` should work completely seamless. – Xaver Kapeller Jul 12 '16 at 10:42
  • @XaverKapeller maybe no, if we invalidating data from adapters, after every swap. But anyway, it's very specific solutio, and more than, not suite for this task. – GensaGames Jul 12 '16 at 21:16
  • @GensaGames I've added a new edit to the original post. Please have a look. I don't think that at this moment, the problem is the switch between different adapters. I've tryed using just one adapter separately at startup. Using the LogAdapter, works fine and shows log list. But using ContactAdapter, doesn't show nothing. – masmic Jul 13 '16 at 10:43
  • @GensaGames I've make it work, but is throwing now a NPE on`mContactAdapter.animateTo(filteredModelList);` any idea? I'll accept your answer and give to you the bounty for the effort, but I would appreciate if you could help me with this last thing – masmic Jul 14 '16 at 09:41
  • @masmic Anyway it was interesting for me, because i wasn't sure about using ItemType in searching adapters.... About your another issue, I also had a problems with animate to. Usually i disabled animation, but in your case you can just ignore result for null reference object in method animateTo(...) – GensaGames Jul 14 '16 at 09:49
  • @masmic Just try to catch null reference in method applyAndAnimate....(List contact info) - check the model, from List is not null – GensaGames Jul 14 '16 at 09:54
0

Try this:

public boolean onQueryTextChange(String query) {
    mContactAdapter = new ContactAdapter(ContactsList());
    mRecyclerView.setAdapter(mContactAdapter);
    //final List<ContactInfo> filteredModelList = filter(ContactsList(), query);
    //mContactAdapter.animateTo(filteredModelList);
    //mRecyclerView.scrollToPosition(0);
    return true;
}
  • I have already tryed that aproach. Also, as I've said in the EDIT 1 in the post, I've tryed loading the contactlist at startup, and I didnt'get it neither – masmic Jul 13 '16 at 09:29