masmic
masmic

Reputation: 3574

Update recyclerview with new adapter

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...

Upvotes: 1

Views: 2689

Answers (2)

GensaGames
GensaGames

Reputation: 5788

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

Upvotes: 2

Ros&#225;rio P. Fernandes
Ros&#225;rio P. Fernandes

Reputation: 11344

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;
}

Upvotes: 0

Related Questions