Espresso - how to access child elements of ViewHolder - android

For the given RecyclerView, I can access the LinearLayout element by:
recyclerView.findViewHolderForAdapterPosition(1)
Where 1 is the 2nd element/position within RecyclerView and the ViewHolder is returned.
How can I then access the child elements of the ViewHolder LinearLayout? Or is there a way to get to the AppCompatTextView element so that I can pull the text from that element?

I have created RecyclerViewMatcher class then following code
onView(withRecyclerView(R.id.rv_fragment_recipes).atPosition(0))
.check(matches(isDisplayed()));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPositionOnView(0, R.id.tv_recipe_name))
.check(matches(isDisplayed()))
.check(matches(withText("Nutella Pie")));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPositionOnView(0, R.id.tv_servings))
.check(matches(isDisplayed()))
.check(matches(withText("Servings : 8")));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPosition(0))
.perform(click());
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
new Object[] { Integer.valueOf
(recyclerViewId) });
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
for refernce you can follow this project
Use IdelingResource if recyclerView data is not static

Related

Espresso - Check RecyclerView items are ordered correctly

How to go about checking whether RecyclerView items are displayed in the correct order using Espresso? I'm trying to test it checking it by the text for the title of each element.
When I try this piece of code it works to click the element but can't go on to instead of performing a click trying to Assert the text for the element
onView(withId(R.id.rv_metrics)).perform(actionOnItemAtPosition(0, click()));
When I try to use a custom matcher instead I keep getting the error
Error performing 'load adapter data' on view 'with id: mypackage_name:id/rv_metrics'
I know now onData doesn't work for RecyclerView but before that I was trying to use a custom matcher for this task.
public static Matcher<Object> hasTitle(final String inputString) {
return new BoundedMatcher<Object, Metric>(Metric.class) {
#Override
protected boolean matchesSafely(Metric metric) {
return inputString.equals(metric.getMetric());
}
#Override
public void describeTo(org.hamcrest.Description description) {
description.appendText("with title: ");
}
};
}
I also tried something like this but it obviously doesn't work due to the type given as parameter to the actionOnItemAtPosition method but would we have something similar to it that could maybe work?
onView(withId(R.id.rv_metrics)).check(actionOnItemAtPosition(0, ViewAssertions.matches(withText("Weight"))));
What am I missing here please?
Thanks a lot.
As it's been mentioned here, RecyclerView objects work differently than AdapterView objects, so onData() cannot be used to interact with them.
In order to find a view at specific position of a RecyclerView you need to implement a custom RecyclerViewMatcher like below:
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
new Object[] { Integer.valueOf
(recyclerViewId) });
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
And then use it in your test case in this way:
#Test
void testCase() {
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(0, R.id.txt_title))
.check(matches(withText("Weight")))
.perform(click());
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(1, R.id.txt_title))
.check(matches(withText("Height")))
.perform(click());
}
If somebody is interested in the Kotlin version, here it is
fun hasItemAtPosition(position: Int, matcher: Matcher<View>) : Matcher<View> {
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("has item at position $position : ")
matcher.describeTo(description)
}
override fun matchesSafely(item: RecyclerView?): Boolean {
val viewHolder = item?.findViewHolderForAdapterPosition(position)
return matcher.matches(viewHolder?.itemView)
}
}
}
I simplified a bit Mosius answer:
public static Matcher<View> hasItemAtPosition(final Matcher<View> matcher, final int position) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
#Override
public void describeTo(Description description) {
description.appendText("has item at position " + position + ": ");
matcher.describeTo(description);
}
#Override
protected boolean matchesSafely(RecyclerView recyclerView) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
return matcher.matches(viewHolder.itemView);
}
};
}
We pass Matcher to the function so we can provide further conditions. Example usage:
onView(hasItemAtPosition(hasDescendant(withText("Item 1")), 0)).check(matches(isDisplayed()));
onView(hasItemAtPosition(hasDescendant(withText("Item 2")), 1)).check(matches(isDisplayed()));
The original problem has been solved but am posting an answer here as found the Barista library solves this problem in one single line of code.
assertDisplayedAtPosition(R.id.rv_metrics, 0, R.id.tv_title, "weight");
It's made on top of Espresso and the documentation for it can be found here
Hope this may be helpful to someone. :)
If you want to match a matcher on a position in RecyclerView, then you can try to create a custom Matcher<View>:
public static Matcher<View> hasItemAtPosition(int position, Matcher<View> matcher) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
#Override public void describeTo(Description description) {
description.appendText("has item: ");
matcher.describeTo(description);
description.appendText(" at position: " + position);
}
#Override protected boolean matchesSafely(RecyclerView view) {
RecyclerView.Adapter adapter = view.getAdapter();
int type = adapter.getItemViewType(position);
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, type);
adapter.onBindViewHolder(holder, position);
return matcher.matches(holder.itemView);
}
};
}
And you can use it for example:
onView(withId(R.id.rv_metrics)).check(matches(0, hasDescendant(withText("Weight")))))

Getting a RecyclerView to reload items

I am currently working on an app, that finds all MP3s on a users phone and then puts them into a list. This works very fine and is very quick, even with many songs. Now I populate a new list with an object for each item of the list to then display it inside my recyclerview. The problem is, that I have 700+ songs on my phone and this blocks the UI thread quite some time.
Now, I want to use the recyclerview to not load all items from the list into the objects all at once but rather only when they are about to be displayed - but I have NO clue over how to do this. Right now, all objects are build and then displayed in a very long scrollview from the recyclerview after the UI thread has been blocked for a good 30 seconds. Can please anyone help me? Here is my code:
namespace Media_Player
{
[Activity(Label = "Media_Player", MainLauncher = true)]
public class MainActivity : Activity
{
static public MediaPlayer mediaPlayer;
List<MP3object> mp3;
MediaMetadataRetriever reader;
public static Button btn_StartOrPause, btn_Stop;
public static TextView txt_CurrentSong;
public static bool stopIsActive = false, firstStart = true;
public static Android.Net.Uri CurrentActiveSongUri;
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
PhotoAlbumAdapter mAdapter;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.test);
reader = new MediaMetadataRetriever();
PopulateMP3List(ReturnPlayableMp3(true));
mediaPlayer = new MediaPlayer();
InitRecView();
}
private void InitRecView()
{
// Instantiate the adapter and pass in its data source:
mAdapter = new PhotoAlbumAdapter(mp3);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter(mAdapter);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.SetLayoutManager(mLayoutManager);
}
private void PopulateMP3List(List<string> content)
{
mp3 = new List<MP3object>();
foreach (string obj in content)
{
WriteMetaDataToFileList(obj);
}
}
void WriteMetaDataToFileList(string obj)
{
reader.SetDataSource(obj);
//Write Mp3 as object to global list
MP3object ob = new MP3object();
{
if(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle) != null)
{
ob.SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
}
else
{
ob.SongName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist) != null)
{
ob.ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
}
else
{
ob.ArtistName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum) != null)
{
ob.AlbumName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyAlbum);
}
else
{
ob.AlbumName = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
if (reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != "" && reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear) != null)
{
ob.Year = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyYear);
}
else
{
ob.Year = Resources.GetString(Resource.String.Unknown);
}
ob.Mp3Uri = obj; // can never be unknown!
ob.DurationInSec = int.Parse(reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyDuration)) / 1000; // can never be unknown, div by 1000 to get sec not millis
}
mp3.Add(ob);
}
public List<string> ReturnPlayableMp3(bool sdCard)
{
List<string> res = new List<string>();
string phyle;
string path1 = null;
if(sdCard) // get mp3 from SD card
{
string baseFolderPath = "";
try
{
bool getSDPath = true;
Context context = Application.Context;
Java.IO.File[] dirs = context.GetExternalFilesDirs(null);
foreach (Java.IO.File folder in dirs)
{
bool IsRemovable = Android.OS.Environment.InvokeIsExternalStorageRemovable(folder);
bool IsEmulated = Android.OS.Environment.InvokeIsExternalStorageEmulated(folder);
if (getSDPath ? IsRemovable && !IsEmulated : !IsRemovable && IsEmulated)
baseFolderPath = folder.Path;
}
}
catch (Exception ex)
{
Console.WriteLine("GetBaseFolderPath caused the following exception: {0}", ex);
}
string xy = baseFolderPath.Remove(18); // This is result after this, but this hard coded solution could be a problem on different phones.: "/storage/05B6-2226/Android/data/Media_Player.Media_Player/files"
path1 = xy;
// path to SD card and MUSIC "/storage/05B6-2226/"
}
else // get Mp3 from internal storage
{
path1 = Android.OS.Environment.ExternalStorageDirectory.ToString();
}
var mp3Files = Directory.EnumerateFiles(path1, "*.mp3", SearchOption.AllDirectories);
foreach (string currentFile in mp3Files)
{
phyle = currentFile;
res.Add(phyle);
}
return res;
}
}
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView Caption { get; private set; }
public PhotoViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
Image = itemView.FindViewById<ImageView>(Resource.Id.imageView);
Caption = itemView.FindViewById<TextView>(Resource.Id.textView);
}
}
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public List<MP3object> mp3;
public PhotoAlbumAdapter(List<MP3object> mp3)
{
this.mp3 = mp3;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.lay, parent, false);
PhotoViewHolder vh = new PhotoViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
vh.Caption.Text = mp3[position].SongName;
}
public override int ItemCount
{
get { return mp3.Count(); }
}
}
}
So getting the list of strings with the locations of the Mp3 works very quickly, but then "WriteMetaDataToFileList(obj)" kicks in, comming from "PopulateMP3List(List content)" and this is what takes so long. What I think I need is for the recyclerview to only build the first 20 objects, and when the user starts scrolling, builds the next 20 objects and attaches them to list for them to also be scrolled. Please help me out here :)
Here is an abstract class:
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
private LinearLayoutManager linearLayoutManager;
protected PaginationScrollListener(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = linearLayoutManager.getChildCount();
int totalItemCount = linearLayoutManager.getItemCount();
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
and In your adapter you must follow this pattern:
public class ConsultancyAdapter extends RecyclerView.Adapter<ConsultancyAdapter.ConsultancyVH> {
private static final int ITEM = 0;
private static final int LOADING = 1;
private boolean isLoadingAdded = false;
public ConsultancyAdapter(List<Consultancy> consultancies, ConsultancyAdapterListener listener) {
}
#NonNull
#Override
public ConsultancyVH onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, layoutInflater);
break;
case LOADING:
View v2 = layoutInflater.inflate(R.layout.item_progress, parent, false);
viewHolder = new ConsultancyVH(v2);
break;
}
return (ConsultancyVH) viewHolder;
}
#NonNull
private RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
RecyclerView.ViewHolder viewHolder;
View v1 = inflater.inflate(R.layout.item_consultancy, parent, false);
viewHolder = new ConsultancyVH(v1);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ConsultancyVH holder, int position) {
Consultancy consultancy = consultancies.get(position);
switch (getItemViewType(position)) {
case ITEM:
ConsultancyVH mySingeCounseller = holder;
holder.title.setText(consultancy.getTitle()); // set cardTitle
holder.fieldArea.setText(consultancy.getField_filedoctorskills());
break;
case LOADING:
break;
}
}
#Override
public int getItemCount() {
return consultancies.size();
}
#Override
public int getItemViewType(int position) {
return (position == consultancies.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
public void add(Consultancy mc) {
consultancies.add(mc);
notifyItemInserted(consultancies.size() - 1);
}
public void addAll(List<Consultancy> mcList) {
for (Consultancy mc : mcList) {
add(mc);
}
}
public void remove(Consultancy city) {
int position = consultancies.indexOf(city);
if (position > -1) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public Consultancy getItem(int position) {
return consultancies.get(position);
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Consultancy());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = consultancies.size() - 1;
Consultancy item = getItem(position);
if (item != null) {
consultancies.remove(position);
notifyItemRemoved(position);
}
}
public interface ConsultancyAdapterListener {
void onCaseClicked(int position, String nid, String fieldArea, String title);
}
protected class ConsultancyVH extends RecyclerView.ViewHolder {
private TextView title, fieldArea;
private CircleImageView iconProfile;
private MaterialRippleLayout caseButtonRipple;
public ConsultancyVH(View itemView) {
super(itemView);
caseButtonRipple = itemView.findViewById(R.id.case_button_ripple);
this.title = itemView.findViewById(R.id.docName);
this.fieldArea = itemView.findViewById(R.id.fieldArea);
this.iconProfile = itemView.findViewById(R.id.icon_profile);
}
}
}
and in your activity:
private void setScrollListener() {
recyclerView.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
#Override
protected void loadMoreItems() {
isLoading = true;
currentPage += 1;
loadNextPage();
}
#Override
public boolean isLastPage() {
return isLastPage;
}
#Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
}
and in my loadFirstPage i talk to a API and you need some your code:
private void loadFirstPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
dataList = response;
adapter.addAll(dataList);
recyclerView.setAdapter(adapter);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}
and loadNextPage:
private void loadNextPage() {
CallData().enqueue(new DefaultRetrofitCallback<List<Consultancy>>() {
#Override
protected void onFailure(Throwable t) {
super.onFailure(t);
}
#Override
protected void onSuccess(List<Consultancy> response) {
swipeRefreshLayout.setRefreshing(false);
adapter.removeLoadingFooter();
isLoading = false;
swipeRefreshLayout.setRefreshing(false);
adapter.addAll(response);
if (!checkLast(response)) adapter.addLoadingFooter();
else isLastPage = true;
}
#Override
protected void onOtherStatus(Response<List<Consultancy>> response) {
super.onOtherStatus(response);
}
#Override
protected void always() {
super.always();
}
});
}

Xamarin Expandable RecyclerView, How to create ChildClickListener

https://github.com/bignerdranch/expandable-recycler-view
With the help of the above library, I was able to implement a expandable RecyclerView, the expand and collapse all works fine. Now I need the different click listeners for child and parent. I was able to able implement a click listener, but I'm having trouble finding the child and parent positions.
the position integer variable available inside the Adapter returns only the overall position in the case of a normal RecyclerView. When clicking parent and child items, it returns different values depending on whether the parent is expanded or collapsed.
What I really want is When I click a parent I want the parent position. And when I click a child I want the parent and child position.
This is my Adapter class
public class LeftNavAdapter : ExpandableRecyclerAdapter<LeftNavParentViewHolder, LeftNavChildViewHolder>
{
LayoutInflater _inflater;
public event EventHandler<int> ItemClick;
public LeftNavAdapter(Context context, List<IParentObject> itemList) : base(context, itemList)
{
_inflater = LayoutInflater.From(context);
}
#region implemented abstract members of ExpandableRecyclerAdapter
public override LeftNavParentViewHolder OnCreateParentViewHolder(ViewGroup parentViewGroup)
{
var view = _inflater.Inflate(Resource.Layout.left_nav_item_parent, parentViewGroup, false);
return new LeftNavParentViewHolder(view);
}
public override LeftNavChildViewHolder OnCreateChildViewHolder(ViewGroup childViewGroup)
{
var view = _inflater.Inflate(Resource.Layout.left_nav_item_child, childViewGroup, false);
return new LeftNavChildViewHolder(view, OnChildClick);
}
public override void OnBindParentViewHolder(LeftNavParentViewHolder parentViewHolder, int position, object parentObject)
{
var parent = (LeftNavParent)parentObject;
parentViewHolder.nameTextView.Text = parent.title;
parentViewHolder.imageImageView.SetImageResource(parent.image);
if (parent.ChildObjectList.Count == 0)
parentViewHolder.exapandCollapseButton.Visibility = ViewStates.Gone;
}
public override void OnBindChildViewHolder(LeftNavChildViewHolder childViewHolder, int position, object childObject)
{
var child = (LeftNavChild) childObject;
childViewHolder.cNameTextView.Text = child.childTitle;
//childViewHolder._crimeSolvedCheckBox.CheckedChange += (object sender, CompoundButton.CheckedChangeEventArgs e) =>
//{
// Console.WriteLine("Child CheckedChanged Position: {0}", position);
//};
}
#endregion
private void OnChildClick(int position)
{
Console.WriteLine("checkpoint 2");
if (ItemClick != null)
{
Console.WriteLine("checkpoint 3");
ItemClick(this, position);
Console.WriteLine("checkpoint 4");
}
}
public override void OnParentItemClickListener(int position)
{
Toast.MakeText(_context, position + "touched", ToastLength.Short).Show();
if (_itemList[position] is IParentObject)
{
var parentObject = (IParentObject)_itemList[position];
if (parentObject.ChildObjectList.Count != 0)
{
ExpandParent(parentObject, position);
}
}
}
}
Expandable RecylerView class
public abstract class ExpandableRecyclerAdapter<PVH, CVH> : RecyclerView.Adapter, IParentItemClickListener
where PVH : ParentViewHolder
where CVH : ChildViewHolder
{
const int TypeParent = 0;
const int TypeChild = 1;
const string StableIdMap = "ExpandableRecyclerAdapter.StableIdMap";
const string StableIdList = "ExpandableRecyclerAdapter.StableIdList";
public const int CustomAnimationViewNotSet = -1;
public const long DefaultRotateDurationMs = 200;
public const long CustomAnimationDurationNotSet = -1;
Dictionary<long, bool> _stableIdMap;
ExpandableRecyclerAdapterHelper _adapterHelper;
IExpandCollapseListener _expandCollapseListener;
bool _parentAndIconClickable = false;
int _customParentAnimationViewId = CustomAnimationViewNotSet;
long _animationDuration = CustomAnimationDurationNotSet;
protected Context _context;
protected List<Object> _itemList;
protected List<IParentObject> _parentItemList;
#region Constructors
public ExpandableRecyclerAdapter(Context context, List<IParentObject> parentItemList)
: this(context, parentItemList, CustomAnimationViewNotSet, DefaultRotateDurationMs)
{
}
public ExpandableRecyclerAdapter(Context context, List<IParentObject> parentItemList,
int customParentAnimationViewId)
: this(context, parentItemList, customParentAnimationViewId, DefaultRotateDurationMs)
{
}
public ExpandableRecyclerAdapter(Context context, List<IParentObject> parentItemList,
int customParentAnimationViewId, long animationDuration)
{
_context = context;
_parentItemList = parentItemList;
_itemList = GenerateObjectList(parentItemList);
_adapterHelper = new ExpandableRecyclerAdapterHelper(_itemList);
_stableIdMap = GenerateStableIdMapFromList(_adapterHelper.HelperItemList);
_customParentAnimationViewId = customParentAnimationViewId;
_animationDuration = animationDuration;
}
#endregion
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup viewGroup, int viewType)
{
if (viewType == TypeParent)
{
var pvh = OnCreateParentViewHolder(viewGroup);
pvh.ParentItemClickListener = this;
return pvh;
}
else if (viewType == TypeChild)
{
return OnCreateChildViewHolder(viewGroup);
}
else
{
throw new ArgumentException("Invalid ViewType found");
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (_adapterHelper.GetHelperItemAtPosition(position) is ParentWrapper)
{
var parentViewHolder = (PVH)holder;
if (_parentAndIconClickable)
{
if (_customParentAnimationViewId != CustomAnimationViewNotSet &&
_animationDuration != CustomAnimationDurationNotSet)
{
parentViewHolder.SetCustomClickableViewAndItem(_customParentAnimationViewId);
parentViewHolder.AnimationDuration = _animationDuration;
}
else if (_customParentAnimationViewId != CustomAnimationViewNotSet)
{
parentViewHolder.SetCustomClickableViewAndItem(_customParentAnimationViewId);
parentViewHolder.CancelAnimation();
}
else
{
parentViewHolder.SetMainItemClickToExpand();
}
}
else
{
if (_customParentAnimationViewId != CustomAnimationViewNotSet &&
_animationDuration != CustomAnimationDurationNotSet)
{
parentViewHolder.SetCustomClickableViewOnly(_customParentAnimationViewId);
parentViewHolder.AnimationDuration = _animationDuration;
}
else if (_customParentAnimationViewId != CustomAnimationViewNotSet)
{
parentViewHolder.SetCustomClickableViewOnly(_customParentAnimationViewId);
parentViewHolder.CancelAnimation();
}
else
{
parentViewHolder.SetMainItemClickToExpand();
}
}
parentViewHolder.Expanded = ((ParentWrapper)_adapterHelper.GetHelperItemAtPosition(position)).Expanded;
OnBindParentViewHolder(parentViewHolder, position, _itemList[position]);
}
else if (_itemList[position] == null)
{
throw new NullReferenceException("Incorrect ViewHolder found");
}
else
{
OnBindChildViewHolder((CVH)holder, position, _itemList[position]);
}
}
private Dictionary<long, bool> GenerateStableIdMapFromList(List<Object> itemList)
{
var parentObjectHashMap = new Dictionary<long, bool>();
for (int i = 0; i < itemList.Count; i++)
{
if (itemList[i] != null)
{
var parentWrapper = (ParentWrapper)_adapterHelper.GetHelperItemAtPosition(i);
parentObjectHashMap.Add(parentWrapper.StableId, parentWrapper.Expanded);
}
}
return parentObjectHashMap;
}
private List<Object> GenerateObjectList(List<IParentObject> parentObjectList)
{
var objectList = new List<Object>();
foreach (var parentObject in parentObjectList)
{
objectList.Add(parentObject);
}
return objectList;
}
public override int ItemCount
{
get
{
return _itemList.Count;
}
}
public override int GetItemViewType(int position)
{
if (_itemList[position] is IParentObject)
{
return TypeParent;
}
else if (_itemList[position] == null)
{
throw new NullReferenceException("Null object added");
}
else
{
return TypeChild;
}
}
public void SetParentClickableViewAnimationDefaultDuration()
{
_animationDuration = DefaultRotateDurationMs;
}
public long AnimationDuration
{
get { return _animationDuration; }
set { _animationDuration = value; }
}
public int CustomParentAnimationViewId
{
get { return _customParentAnimationViewId; }
set { _customParentAnimationViewId = value; }
}
public bool ParentAndIconExpandOnClick
{
get { return _parentAndIconClickable; }
set { _parentAndIconClickable = value; }
}
public void RemoveAnimation()
{
_customParentAnimationViewId = CustomAnimationViewNotSet;
_animationDuration = CustomAnimationDurationNotSet;
}
public void AddExpandCollapseListener(IExpandCollapseListener expandCollapseListener)
{
_expandCollapseListener = expandCollapseListener;
}
public void ExpandParent(IParentObject parentObject, int position)
{
var parentWrapper = (ParentWrapper)_adapterHelper.GetHelperItemAtPosition(position);
if (parentWrapper == null)
{
return;
}
if (parentWrapper.Expanded)
{
parentWrapper.Expanded = false;
if (_expandCollapseListener != null)
{
var expandedCountBeforePosition = GetExpandedItemCount(position);
_expandCollapseListener.OnRecyclerViewItemCollapsed(position - expandedCountBeforePosition);
}
// Was Java HashMap put, need to replace the value
_stableIdMap[parentWrapper.StableId] = false;
//_stableIdMap.Add(parentWrapper.StableId, false);
var childObjectList = ((IParentObject)parentWrapper.ParentObject).ChildObjectList;
if (childObjectList != null)
{
for (int i = childObjectList.Count - 1; i >= 0; i--)
{
var pos = position + i + 1;
_itemList.RemoveAt(pos);
_adapterHelper.HelperItemList.RemoveAt(pos);
NotifyItemRemoved(pos);
}
}
}
else
{
parentWrapper.Expanded = true;
if (_expandCollapseListener != null)
{
var expandedCountBeforePosition = GetExpandedItemCount(position);
_expandCollapseListener.OnRecyclerViewItemExpanded(position - expandedCountBeforePosition);
}
// Was Java HashMap put, need to replace the value
_stableIdMap[parentWrapper.StableId] = true;
//_stableIdMap.Add(parentWrapper.StableId, true);
var childObjectList = ((IParentObject)parentWrapper.ParentObject).ChildObjectList;
if (childObjectList != null)
{
for (int i = 0; i < childObjectList.Count; i++)
{
var pos = position + i + 1;
_itemList.Insert(pos, childObjectList[i]);
_adapterHelper.HelperItemList.Insert(pos, childObjectList[i]);
NotifyItemInserted(pos);
}
}
}
}
private int GetExpandedItemCount(int position)
{
if (position == 0)
return 0;
var expandedCount = 0;
for (int i = 0; i < position; i++)
{
var obj = _itemList[i];
if (!(obj is IParentObject))
expandedCount++;
}
return expandedCount;
}
public Bundle OnSaveInstanceState(Bundle savedInstanceStateBundle)
{
savedInstanceStateBundle.PutString(StableIdMap, JsonConvert.SerializeObject(_stableIdMap));
return savedInstanceStateBundle;
}
public void OnRestoreInstanceState(Bundle savedInstanceStateBundle)
{
if (savedInstanceStateBundle == null)
return;
if (!savedInstanceStateBundle.ContainsKey(StableIdMap))
return;
_stableIdMap = JsonConvert.DeserializeObject<Dictionary<long, bool>>(savedInstanceStateBundle.GetString(StableIdMap));
var i = 0;
while (i < _adapterHelper.HelperItemList.Count)
{
if (_adapterHelper.GetHelperItemAtPosition(i) is ParentWrapper)
{
var parentWrapper = (ParentWrapper)_adapterHelper.GetHelperItemAtPosition(i);
if (_stableIdMap.ContainsKey(parentWrapper.StableId))
{
parentWrapper.Expanded = _stableIdMap[parentWrapper.StableId];
if (parentWrapper.Expanded)
{
var childObjectList = ((IParentObject)parentWrapper.ParentObject).ChildObjectList;
if (childObjectList != null)
{
for (int j = 0; j < childObjectList.Count; j++)
{
i++;
_itemList.Insert(i, childObjectList[j]);
_adapterHelper.HelperItemList.Insert(i, childObjectList[j]);
}
}
}
}
else
{
parentWrapper.Expanded = false;
}
}
i++;
}
NotifyDataSetChanged();
}
public abstract PVH OnCreateParentViewHolder(ViewGroup parentViewGroup);
public abstract CVH OnCreateChildViewHolder(ViewGroup childViewGroup);
public abstract void OnBindParentViewHolder(PVH parentViewHolder, int position, Object parentObject);
public abstract void OnBindChildViewHolder(CVH childViewHolder, int position, Object childObject);
#region IParentItemClickListener implementation
public abstract void OnParentItemClickListener(int position);
// {
//// if (_itemList[position] is IParentObject)
//// {
//// var parentObject = (IParentObject)_itemList[position];
////if (parentObject.ChildObjectList.Count != 0)
////{
//// ExpandParent(parentObject, position);
////}
//// }
// }
#endregion
}
This is how I accomplished it.
Created ChildViewHolder like this, passed an item click listener via constructor.
public class LeftNavChildViewHolder : ChildViewHolder
{
public TextView cNameTextView;
public LeftNavChildViewHolder(View itemView, Action<int> listener) : base(itemView)
{
cNameTextView = itemView.FindViewById<TextView>(Resource.Id.leftNavTitleChild);
itemView.Click += (sender, e) => listener(Position);
}
}
then in adapter:
public class LeftNavAdapter : ExpandableRecyclerAdapter<LeftNavParentViewHolder, LeftNavChildViewHolder>
{
public override LeftNavChildViewHolder OnCreateChildViewHolder(ViewGroup childViewGroup)
{
var view = _inflater.Inflate(Resource.Layout.left_nav_item_child, childViewGroup, false);
return new LeftNavChildViewHolder(view, OnChildClick);
}
void OnChildClick(int position)
{
// Do whatever you want
}
}

Save Checkbox state in RecyclerView

I have a list of options with CheckBoxes in RecyclerView. When I tap on checkbox the state is changed, but after scrolling up and down the state of the checkboxes are losts.
How to save state of checkboxes ? I'm trying to change the state in adapter:
private void setAdapter() {
if (mAdapter == null) {
mAdapter = new FacetChildAdapter(mValues, getActivity(), query.getSpecValueAsString(mParentValue.getSlug())) {
#Override
protected void onCheckBoxRowClicked(CheckboxRow box, Value value, int adapterPosition) {
if (type == FacetChildType.Brands) {
if (box.isChecked()) {
query.removeBrand(value);
} else {
query.addBrand(value);
}
}
else if (type == FacetChildType.Categories) {
if (box.isChecked()) {
query.removeCategory(value);
} else {
query.addCategory(value);
}
}
else if (type == FacetChildType.Deals) {
if (box.isChecked()) {
query.removeDealType(value);
} else {
query.addDealType(value);
}
}
else if (type == FacetChildType.Specifications) {
if (box.isChecked()) {
query.removeSpecification(mParentValue.getSlug(), value);
} else {
query.addSpecification(mParentValue.getSlug(), value);
}
}
box.setChecked(!box.isChecked());
mHeading.setBackText(getResources().getString(R.string.apply));
}
};
mRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setSource(query.getSpecValueAsString(mParentValue.getSlug()));
mAdapter.refresh(mValues);
}
}
FacetChildAdapter:
public class FacetChildAdapter extends GenericRecycleAdapter<Value, Holders.TextImageHolder> {
private String source;
public FacetChildAdapter(List<Value> list, Context context, String source) {
super(list, context);
this.source = source;
}
public void setSource(String source) {
this.source = source;
}
#Override
protected void onItem(Value s) {
}
public Holders.TextImageHolder getCustomHolder(View v) {
return new Holders.TextImageHolder(v) {
#Override
public void onCheckBoxRowClicked(CheckboxRow v) {
FacetChildAdapter.this.onCheckBoxRowClicked(v, mList.get(getAdapterPosition()), getAdapterPosition());
}
};
}
protected void onCheckBoxRowClicked(CheckboxRow box, Value value, int adapterPosition) {
}
#Override
public int getLayout() {
return R.layout.facet_child_row;
}
#Override
public void onSet(final Value item,final Holders.TextImageHolder holder) {
holder.checkboxRow.setTitle(Html.fromHtml(item.getFullName()));
holder.checkboxRow.setSubText("(" + String.valueOf(item.getCount()) + ")");
holder.checkboxRow.setChecked(ShowProductsWithId.containsValueInValueStringLine(source, item.getSlug()));
holder.checkboxRow.setDisabled(!item.isEnabled());
}
}
It can Maintain in two ways:
1) Use the Hashmap to store the position of check box view.
You can store the position and also store the Unique id of Particular view and then after in Adapter use can easily get the check box position in it.
2) When You can use the Getter setter class then you do create the one integer variable in it and then You can easily set the position in this variable like
When You can check the checkbox then variable value is 1
When You can uncheck the checkbox then variable value is 0
Maintain Your check box Position checked or unchecked in below ways.
class TeamNamesAdapter extends RecyclerView.Adapter<TeamNamesAdapter.ViewHolder>
implements ItemTouchHelperAdapter, View.OnClickListener {
private ArrayList<Person> people;
public TeamNamesAdapter(TinyDB db) {
people = new ArrayList<>();
try {
ArrayList<Object> peopleAsObjects = db.getListObject(PEOPLE, Person.class);
for (Object obj : peopleAsObjects) {
people.add((Person) obj);
}
if (db.getListObject(PEOPLE_ORDER, Person.class) == null) {
db.putListObject(PEOPLE_ORDER, peopleAsObjects);
}
}
catch (NullPointerException e) {
return;
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.team_names_list_item, null);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Person curr = people.get(position);
holder.name.setText(curr.name);
holder.name.setChecked(curr.available);
}
#Override
public int getItemCount() {
return people.size();
}
public void addName(String name) {
if(people.contains(name) || name.isEmpty())
return;
people.add(new Person(name, true));
notifyDataSetChanged();
update(true);
}
#Override
public void onItemDismiss(int position) {
people.remove(position);
notifyItemRemoved(position);
update(true);
}
#Override
public void onItemMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(people, i, i + 1);
}
}
else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(people, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
update(true);
undoRandomizeButton.setEnabled(false);
}
#Override
public void onClick(View v) {
if (!(v instanceof CheckedTextView))
return;
CheckedTextView ctv = (CheckedTextView) v;
ctv.toggle();
String name = ctv.getText().toString();
for (Person p : people) {
if (p.name.equals(name)) {
p.available = ctv.isChecked();
update(true);
return;
}
}
}
class ViewHolder extends RecyclerView.ViewHolder {
CheckedTextView name;
public ViewHolder(View itemView) {
super(itemView);
name = (CheckedTextView)itemView.findViewById(R.id.name_in_list);
name.setOnClickListener(TeamNamesAdapter.this);
}
}
}
This is a recyclerview adapter for a list that holds a CheckedTextView. I save a class called Person, which contains a string and a bool. You situation is similar, as you can hold only booleans in the ArrayList (instead of Person), and on onBindViewHolder, set checkbox.isChecked to the boolean in that location. And you can set an View.OnClickerListener on the RecyclerView adapter, and set the ViewHolder onClickListener to YourAdapterName.this. And implement the method like I did, just disregard the Person stuff, just update your boolean
You can see that on the adapters onBindViewHolder method,

The ListView updates the value of the wrong line

In the code below I have a ListView with a Items list containing Name, Id and Quantity. When I click on a row this value should be updated according to informed parameter in UpdateItemSelectedInListView () method. But only line 9 is having the changed value, independent of the line that I select.
Activity:
public class MainActivity : Activity
{
ListView _ListView;
AdapterItem _Adapter;
List<Item> _ListaItem = new List<Item>();
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
CriaListView();
}
private void CriaListView()
{
_ListView = FindViewById<ListView>(Resource.Id.listaItems);
_ListView.ItemClick += _ListView_ItemClick;
_ListView.Adapter = CriaAdapter();
}
private AdapterItem CriaAdapter()
{
for (int i = 0; i < 15; i++)
{
_ListaItem.Add(new Item("Test Name",i));
}
_Adapter = new AdapterItem(this, _ListaItem);
return _Adapter;
}
void _ListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
_Adapter.UpdateItemSelectedInListView(e.Position, 5);
}
}
Adapter:
public class AdapterItem : BaseAdapter
{
List<Item> _ListaItem = new List<Item>();
Activity _Activity;
LayoutInflater _Inflate;
ViewHolderItem _HolderItem;
Boolean _HasUpdate;
int _IdToUpDate;
int _NewQntd;
int _Position;
public AdapterItem(Activity activity, List<Item> listaItem)
{
_Activity = activity;
_ListaItem = listaItem;
try
{
_Inflate = (LayoutInflater)_Activity.GetSystemService(Context.LayoutInflaterService);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
View _View;
public override View GetView(int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
_View = convertView;
try
{
if (_View != null)
_HolderItem = _View.Tag as ViewHolderItem;
else
{
_View = _Activity.LayoutInflater.Inflate(Resource.Layout.LayoutItem, null);
_HolderItem = CriaViewHolder();
}
PopulaViewHolder(position);
_View.Tag = _HolderItem;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return _View;
}
private ViewHolderItem CriaViewHolder()
{
ViewHolderItem holderItem = new ViewHolderItem();
holderItem.txtNameItem = _View.FindViewById<TextView>(Resource.Id.nameItem);
holderItem.txtIdItem = _View.FindViewById<TextView>(Resource.Id.idItem);
holderItem.txtqntItem = _View.FindViewById<TextView>(Resource.Id.totalInStock);
return holderItem;
}
private void PopulaViewHolder(int position)
{
_HolderItem.txtNameItem.Text = _ListaItem[position].nome;
_HolderItem.txtIdItem.Text = _ListaItem[position].id.ToString();
if (_HasUpdate && (position == _Position))
UpdateAdapter();
}
public void UpdateAdapter()
{
_HolderItem.txtqntItem.Text = _NewQntd.ToString();
_HasUpdate = false;
}
public void UpdateItemSelectedInListView(int position, int newValue)
{
_NewQntd = newValue;
_HasUpdate = true;
_Position = position;
this.NotifyDataSetChanged();
}
public override Java.Lang.Object GetItem(int position)
{
return 0;
}
public override int Count
{
get {return _ListaItem.Count; }
}
public override long GetItemId(int position)
{
return _ListaItem[position].id;
}
public override int GetItemViewType(int position)
{
return base.GetItemViewType(position);
}
public override int ViewTypeCount
{
get
{
return base.ViewTypeCount;
}
}
private class ViewHolderItem : Java.Lang.Object
{
public TextView txtNameItem { get; set; }
public TextView txtIdItem { get; set; }
public TextView txtqntItem { get; set; }
}
}
You need to move _View.Tag = _HolderItem; inside of the else on the GetView method:
public override View GetView(int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
_View = convertView;
try
{
if (_View != null)
_HolderItem = _View.Tag as ViewHolderItem;
else
{
_View = _Activity.LayoutInflater.Inflate(Resource.Layout.LayoutItem, null);
_HolderItem = CriaViewHolder();
//Set the tag only when you create a new one
//otherwise you'll have the problem you are having
_View.Tag = _HolderItem;
}
PopulaViewHolder(position);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return _View;
}
It seems that your _Adapter.UpdateItemSelectedInListView not fetching correct position as it looses the state. Instead you can make use of binding "event action" to each view row from viewholder class.
On trigger of this action event [ in GetView()] another event action to activity class to process Click event.
//Viewholder class
internal Action ActionImgViewSelectedToGetView{ get; set;}
internal void Initialize(View view)
{
imgItem=view.FindViewById<ImageView> (Resource.Id.imgItem);
imgItem.Click += delegate(object sender , EventArgs e )
{
ActionImgViewSelectedToGetView(); //action event to getview()
};
}
//in getview()
viewHolder.ActionImgViewSelectedToGetView = () =>
{
if(ActionImgSelectedToActivity!=null)
ActionImgSelectedToActivity(_lstItem[position].ItemName); //action event,targeting to activity class method
};
//activity
if ( objItemAdapter != null )
{
objItemAdapter.ActionImgSelectedToActivity -= SelectedItem;
objItemAdapter = null;
}
objItemAdapter = new ItemAdapterClass (this, lstItem);
objItemAdapter.ActionImgSelectedToActivity += SelectedItem;
listViewItem.Adapter = objItemAdapter;
void SelectedItem( string strItemName)
{
//seleced item
}
for more detail : http://appliedcodelog.blogspot.in/2015/07/working-on-issues-with-listview-in.html#WrongPosition

Categories

Resources