I want to display a list of match objects (match = two users having liked each other) in a recycler view with the help of an adapter.
This is my activity which is meant to display those matches:
public class MatchesActivity extends AppCompatActivity {
// variables:
private RecyclerView mMatchesRecyclerView;
private RecyclerView.Adapter mMatchItemAdapter;
private RecyclerView.LayoutManager mMatchesLayoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_matches);
mMatchesRecyclerView = findViewById(R.id.matches_recyclerView);
mMatchesRecyclerView.setNestedScrollingEnabled(false);
mMatchesRecyclerView.setHasFixedSize(true);
// set layout manager & pass it to the recycler view:
mMatchesLayoutManager = new LinearLayoutManager(MatchesActivity.this);
mMatchesRecyclerView.setLayoutManager(mMatchesLayoutManager);
// set match adapter & pass it to the recycler view:
mMatchItemAdapter = new MatchItemAdapter(getMatchesList(), MatchesActivity.this);
mMatchesRecyclerView.setAdapter(mMatchItemAdapter);
// add test items to the recycler view:
Match testMatch = new Match("abcdefgh");
matchesList.add(testMatch);
mMatchItemAdapter.notifyDataSetChanged();
Log.d("MatchesActivity", "TEST LIST: " + matchesList.toString());
}
private ArrayList<Match> matchesList = new ArrayList<Match>();
private List<Match> getMatchesList() {
Log.d("MatchesActivity", "getMatchesList function: " + matchesList.toString());
return matchesList;
}
}
And this is my adapter which is supposed to inflate the relevant layout & populate it with relevant object data:
public class MatchItemAdapter extends RecyclerView.Adapter<MatchViewholder> {
private List<Match> mMatchesList;
private Context mViewContext;
public MatchItemAdapter(List<Match> matchesList, Context context) {
this.mMatchesList = matchesList;
this.mViewContext = context;
Log.d("MatchItemAdapter", "Constructor: " + mMatchesList.toString());
}
// inflate the layout:
#Override
public MatchViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_matches, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutView.setLayoutParams(lp);
MatchViewholder matchViewholder = new MatchViewholder(layoutView);
Log.d("MatchItemAdapter", "onCreateViewHolder: " + mMatchesList.toString());
return matchViewholder;
}
// populate each row within the layout:
#Override
public void onBindViewHolder(MatchViewholder holder, int position) {
Log.d("MatchItemAdapter", "onBindViewHolder: " + mMatchesList.toString());
holder.mMatchID.setText(mMatchesList.get(position).getMatchID());
}
#Override
public int getItemCount() {
return 0;
}
}
The Match class currently only takes matchID parameter which is string. An object is created with a default image and this matchID string.
At the moment, I have no real match objects from database ready, so I wanted to check that the recycler view along with adapter are working as expected before i move on to that later.
However, when I go to Matches Activity, it is empty, showing nothing at all. As you can see from the MatchesActivity onCreate method, I created a test Match object with matchID = "abcdefgh" and then added that to the matchesList. So I am expecting the "abcdefgh" text to be passed to the adapter and to be shown in the MatchesActivity.
My log statements indicate that the Match object has been created and added to the list successfully, however, getMatchesList() function returns an empty list which is then used in the Adapter constructor too, (I think this is) causing Activity not show anything.
I am relatively new to Android and Java development, especially recycler view and adapters, but from what I gathered it seems to be as if the
mMatchItemAdapter.notifyDataSetChanged();
is not working properly as everything seems to be fine up until that point. Any help would be appreciated!
You're returning 0. What you should do instead is return the length of the mMatchesList list.
#Override
public int getItemCount() {
return mMatchesList.size();
}
Related
I've written a game where the user inputs the number of player and every player gets an own tab with an empty table.
Therefore I used a PagerAdapterClass (extends FragmentStatePagerAdapter) and a viewpager.
So every player has the same fragmentView.
Now the user can put variables into the table, bu everytime I switch between the tabs, the input gets lost.
Well, i 'fixed' that problem by adding this to my pageradapter:
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
}
But it's more stopping the viewpager from destroying than actually saving the data.
My main goal is to really save that stuff in that table.
I already tried https://stackoverflow.com/a/17135346/11956040 but i cannot get mContent because i cannot get the reference of the fragment, because all fragments are not created on their own but all at the same time (or something like that).
I also don't know how to set a Tag.
This way: https://stackoverflow.com/a/18993042/11956040
doesn't work for me.
MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
Toolbar toolbar = findViewById(R.id.toolbar2);
setSupportActionBar(toolbar);
...
//numPlayer = num of tabs
SectionsPagerAdapter adapter = new SectionsPagerAdapter(numPlayer, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(adapter);
TabLayout tabs = findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
if(numPlayer >= 5) {
tabs.setTabMode(TabLayout.MODE_SCROLLABLE);
}
}
PagerAdapter:
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
private int tabNum;
public SectionsPagerAdapter(int tabNum, FragmentManager fm) {
super(fm);
this.tabNum = tabNum;
}
#Override
public PlaceholderFragment getItem(int position) {
return PlaceholderFragment.newInstance(position);
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
int playerNum = position + 1;
return "Spieler " + playerNum;
}
#Override
public int getCount() {
// Show 2 total pages.
return tabNum;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
}
}
Fragment:
public static PlaceholderFragment newInstance(int index) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle bundle = new Bundle();
bundle.putInt("player", index);
fragment.setArguments(bundle);
return fragment;
}
There must be a solution but I cannot find it or cannot implement it.
Pls help.
Solved my problem this way:
define 2 dimensional ArrayList for rows and columns and counter for columns:
private ArrayList<ArrayList<Integer>> columnArray;
private int column;
onCreateView (for fragments) set column = 0 and add one entry with an empty list to columnArray
and set the first rowList on column index of columnArray:
pointArray.add(column, new ArrayList<Integer>());
final ArrayList<Integer> rowList = pointArray.get(column);
fill the empty rowListwith 0 (maybe it also works in an other way, but I made it this way to have on empty EditTexts a 0 and can easily replace them)
define View.OnFocusChangeListener for all EditTexts like this:
/*I dont know if I could set column final in general,
but you need to set a final int because you call this value in an inner class*/
final int pos = column
for (int i = 0; i <= getEditTexts(pos).size() - 1; i++) {
EditText editTexts = getEditTexts(pos).get(i);
final String editTextsTag = editTexts.getTag().toString();
View.OnFocusChangeListener listener = new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, final boolean b) {
if (view.getTag().toString().equals(editTextsTag) && !b) {
//fills rowList
addEntries(pos, rowList);
//adds rowList to columnArray
columnArray.set(pos, rowList);
//save the columnsArray or use it
saveData(columnArray);
}
}
};
editTexts.setOnFocusChangeListener(listener);
define method which collects data from each cell, depending on column position (pos), add it to rowList
for example:
private void addEntries(int pos, ArrayList<Integer> rowList) {
for(int i = 0; i <= 16; i++) {
//this requires EditText_label, i made them dynamically
String edit_label = "edit_" + pos + i;
EditText editText = table.findViewWithTag(edit_label);
String mEditTextString = editText.getText().toString();
try {
int thisValue = Integer.parseInt(mEditString);
rowList.set(j, thisValue);
} catch (NumberFormatException e) {
//maybe you do not need this, but I need it for something else
int thisValue = 0;
rowList.set(j, thisValue);
}
}
}
define a method for saving the columnArray. I used an interface to give it to parent Activity: Here you can find how I made it
Otherwise you can convert the columnArray to a String and save it in a database.
NOTE
I made it with column value set beacuse I increase the value for every column I add during runtime using a method. If you just have one column, you dont need to set it. Just use 0 instead of pos, column
Overview:
The recycler view has a horizontal layout manager, in that linear layout manager i override the canScrollHorizontally method, so that the user can not scroll by himself.
Also i have a RecyclerView.SmoothScroller to change visible item programmatically, (the recycler view has a PagerSnapHelper attached, each item of the recylcler cover the entire screen, with this feature the recycler view works like ViewPager).
But when i call the method myRecyclerView.getLayoutManager().startSmoothScroll(smoothScrollerForward) the scroll can not been performed because the linear layout manager canScrollHorizontally return always false.
If i make canScrollHorizontally method returns true before call startSmoothScroll it works, but how to know when myRecyclerView.getLayoutManager().startSmoothScroll(smoothScrollerForward) ends? so that i can move the variable returned by canScrollHorizontally to false again.
Here is the code:
private RecyclerView rvCards;
private LinearLayoutManager manager;
private RecyclerView.SmoothScroller smoothScrollerForward;
private int currentPosition;
private boolean horizontalScroll = false;
rvCards = findViewById(R.id.rvCards);
manager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,
false)
{
#Override
public boolean canScrollHorizontally() {
return horizontalScroll;
}
};
rvCards.setLayoutManager(manager);
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(rvCards);
smoothScrollerForward = new LinearSmoothScroller(this){
#Override
protected int getHorizontalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_END;
}
};
ivForwardCard.setOnClickListener(v -> {
smoothScrollerForward.setTargetPosition(++currentPosition);
rvCards.getLayoutManager().startSmoothScroll(smoothScrollerForward);
});
public void changeItem(int position) {
horizontalScroll = true;
ivForwardCard.performClick();
}
I am facing a Firebase RecyclerView problem where I cannot remove unwanted CardViews from my RecyclerViews. In my code I check the city's name and the guide's chosen city to match them. It populates guide's details only if the guide's city matches the picked city, but it also shows empty cardview with default layout.
guideDataRef = FirebaseDatabase.getInstance().getReference().child("Guides");
public void recycler() {
super.onStart();
try {
//Guide RecyclerView
Query guideQuery = guideDataRef.orderByKey();
guideQuery.keepSynced(true);
FirebaseRecyclerOptions guideOptions =
new FirebaseRecyclerOptions.Builder<UserModelClass>().setQuery(guideQuery, UserModelClass.class).build();
guideAdapter = new FirebaseRecyclerAdapter<UserModelClass, guideViewHolder>(guideOptions) {
#Override
protected void onBindViewHolder(#NonNull guideViewHolder holder, final int position, #NonNull final UserModelClass model) {
String pickedcity = model.getPickedCity();
String postname = (String) cityName.getText();
if(pickedcity.equals(postname)) {
final String guide_key= getRef(position).getKey();
holder.setGuideName(model.getName());
holder.setGuideSurname(model.getSurName());
holder.setGuideImage(getApplicationContext(), model.getPhotoURL());
// holder.mView.setVisibility(View.VISIBLE);
//Guide Click listener
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent guideHireIntent = new Intent(getApplication(), GuideHireActivity.class);
guideHireIntent.putExtra("guide_id", guide_key);
finish();
startActivity(guideHireIntent);
}
});
}
}
#NonNull
#Override
public guideViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout_guides, parent, false);
return new guideViewHolder(view);
}
#Override
public void onError(DatabaseError e){
Toast.makeText(getApplicationContext(), "Error by stopping ", Toast.LENGTH_SHORT).show();
}
#Override
public int getItemCount() {
return super.getItemCount();
}
#Override
public void onDataChanged() {
super.onDataChanged();
notifyDataSetChanged();
}
};
guideAdapter.notifyDataSetChanged();
guideRecyclerView.setAdapter(guideAdapter);
guideAdapter.startListening();
} catch (DatabaseException e) {
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
enter image description here
enter image description here
I can change the adapter visibility to gone if it does not match with the requirements but the problem is that after making it's visibility gone it is still there holding the place (but invisible - there's still an empty space). How can I avoid populating an item from the recycler view completely, instead of making it invisible if the requirements do not match?
You're not showing what guideDataRef is in your code, so I'm assuming that it's just aDatabaseReference object for everything beneath a \Guides node.
If you're doing that, you're going to get a call for onBindViewHolder for every child at that particular location. This means that you're going to be asked to make a view for every child. You cannot choose whether or not a view will appear for that item.
It looks like you're assuming that your if statement in onBindViewHolder method will skip over those items. But what's actually happening is that you're simply allowing an empty view to occupy that spot in the list.
Instead, you should come up with a query that generates only the items of interest to your list. This means you'll have to tell Firebase to filter for children that meet your criteria.
You can also read the entire contents of the location, manually filter out the items you don't want, and build a list of items you do want. You can then build an custom adapter with that list, and it can then become the input to a ListView or even better to a RecyclerView.
I have seen this question answered a few times, however none of the fixes have worked for me, so i'm reaching out.
I have built an app that features the Diolor Swipeable Cards Library (here) and now am trying to implement Course Card Filters.
Essentially when a user clicks a course filter we want to change the data that is being fed to the adapter.
Currently I am trying to update the data and calling notifyDataSetChanged() on the adapter, expecting the cards to refresh to show the new data set, however am finding that it is not refreshing at all.
Any help with this would be hugely appreciated.
All code below is from my Main Activity.
I declare the data set that i will be feeding to the adapter at the top of the activity:
ArrayList<CourseCardModel> courseCardModelList;
then in my onCreate() method I instantiate the adapter, attach it to the view, and call a generateCourseCards() method which populates the courseCardModelList with objects pulled from a firebase database.
// Set up and assign card adapter
ca = new CustomCardAdapter(CardsActivity.this, android.R.layout.simple_list_item_1, generateCourseCards());
flingContainer.init(CardsActivity.this, ca);
generateCourseCards() method
private ArrayList<CourseCardModel> generateCourseCards() {
Toast.makeText(getApplicationContext(), "Retrieving Courses", Toast.LENGTH_LONG).show();
courseCardModelList = new ArrayList<CourseCardModel>();
dbref = FirebaseDatabase.getInstance().getReference().child("courses");
// Retrieve the course data from Firebase db and cast as Course object
dbref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
Log.e("Count " ,"" + snapshot.getChildrenCount());
for (DataSnapshot postSnapshot: snapshot.getChildren()) {
c = postSnapshot.getValue(Course.class);
System.out.println(c.getCourseName());
CourseCardModel model = new CourseCardModel();
model.setCourse(c);
courseCardModelList.add(model);
}
Collections.shuffle(courseCardModelList);
ca.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e("The read failed: ", databaseError.getMessage());
}
});
return courseCardModelList;
}
Attempt to update the dataset (a simple shuffle for the time being) and refresh the cards
// Shuffle the collection and refresh the cards
Collections.shuffle(courseCardModelList);
ca.notifyDataSetChanged();
EDIT: added adapter code
public class CustomCardAdapter extends ArrayAdapter {
private TextView courseName, uniName, entryStandards, courseDuration, studyMode, qualification,
studentSatisfaction, gradProspects, t1, t2, t3, t4, t5, t6;
ArrayList<CourseCardModel> items;
View v;
LayoutInflater vi;
public CustomCardAdapter(Activity context, int resource, ArrayList<CourseCardModel> courses) {
super(context, resource, courses);
vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
v = convertView;
if (v == null) {
v = vi.inflate(R.layout.course_card_inner_template, parent , false);
}
CourseCardModel c = (CourseCardModel) getItem(position);
if (c != null) {
courseName = (TextView) v.findViewById(R.id.courseCardCourseName);
uniName = (TextView) v.findViewById(R.id.courseCardUniName);
entryStandards = (TextView) v.findViewById(R.id.courseCardEntryStandards);
courseDuration = (TextView) v.findViewById(R.id.courseCardCourseDuration);
studyMode = (TextView) v.findViewById(R.id.courseCardStudyMode);
qualification = (TextView) v.findViewById(R.id.courseCardQualification);
studentSatisfaction = (TextView) v.findViewById(R.id.courseCardStudentSatisfaction);
gradProspects = (TextView) v.findViewById(R.id.courseCardGraduateProspects);
t1 = (TextView) v.findViewById(R.id.cardTV1);
t2 = (TextView) v.findViewById(R.id.cardTV2);
t3 = (TextView) v.findViewById(R.id.cardTV3);
t4 = (TextView) v.findViewById(R.id.cardTV4);
t5 = (TextView) v.findViewById(R.id.cardTV5);
t6 = (TextView) v.findViewById(R.id.cardTV6);
v.setBackgroundResource(R.drawable.newcard);
courseName.setText(c.getCourse().getCourseName());
uniName.setText(c.getCourse().getUniversity());
entryStandards.setText(c.getCourse().getEntryStandards());
courseDuration.setText(c.getCourse().getCourseDuration());
studyMode.setText(c.getCourse().getStudyMode());
qualification.setText(c.getCourse().getQualification());
studentSatisfaction.setText(c.getCourse().getStudentSatisfaction().toString() + " / 5");
gradProspects.setText(c.getCourse().getGradProspects() + " / 100");
}
if(position ==0)
{
//float alpha = (float) 0.8;
//v.setAlpha(alpha);
courseName.setVisibility(View.VISIBLE);
}
else if (position == 1){
// Prepare the View for the animation
v.setVisibility(View.VISIBLE);
float alpha = (float) 0.8;
float alpha2 = (float) 0.3;
courseName.setAlpha(alpha2);
uniName.setAlpha(alpha2);
entryStandards.setAlpha(alpha2);
courseDuration.setAlpha(alpha2);
studyMode.setAlpha(alpha2);
qualification.setAlpha(alpha2);
studentSatisfaction.setAlpha(alpha2);
gradProspects.setAlpha(alpha2);
t1.setAlpha(alpha2);
t2.setAlpha(alpha2);
t3.setAlpha(alpha2);
t4.setAlpha(alpha2);
t5.setAlpha(alpha2);
t6.setAlpha(alpha2);
v.setAlpha(alpha);
}
else {
v.setVisibility(View.INVISIBLE);
}
return v ;
}
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items = courseCardModels;
notifyDataSetChanged();
}
}
Problem is in this method.
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items = courseCardModels;
notifyDataSetChanged();
}
here you are giving another array reference to your adapter.
Just rewrite as below.
public void updateData(ArrayList<CourseCardModel> courseCardModels) {
this.items.clear();
this.items.addAll(courseCardModels);
notifyDataSetChanged();
}
Without adapter class provided my first guess would be that you messed the references up. Maybe you are shuffling the data that is not referenced from the adapter. Once you share your adapter's code, I'll update my answer.
== EDIT ==
Avoid referencing some external collection of data from adapter, and updating that referenced data. Updating adapter/list data should be done using adapter's interface and methods such as add(), addAll() or remove() It might happen that (parent) adapter makes clone/copy of your data and in that case updating external/referenced collection is not doing any good.
You're extending an ArrayAdapter which holds his own array of models (the array passed to the constructor). If you would like to update the items, do something like this:
ca.clear();
for (CourseCardModel object : courseCardModelList) {
ca.insert(object, ca.getCount());
}
ca.notifyDataSetChanged();
Or you can override the getItem method and return an item from your items array.
And another option would be extending BaseAdapter instead of the ArrayAdapter.
I'm using ParseQueryAdapter to display a ListView including the set of elements given by the Parse query:
ParseQueryAdapter.QueryFactory<AlertObject> factory =
new ParseQueryAdapter.QueryFactory<AlertObject>() {
public ParseQuery<AlertObject> create() {
ParseQuery<AlertObject> query = AlertObject.getQuery();
query.orderByDescending(AlertObject.TIMESTAMP_KEY);
query.fromLocalDatastore();
return query;
}
};
alertsListAdapter = new AlertListItemAdapter(activity, factory, thisFragment);
ListView alertsListView = (ListView) rootView.findViewById(R.id.alerts_list_view);
alertsListView.setAdapter(alertsListAdapter);
Now, I'd like to know the number of items in the ListView, but if I call alertsListView.getCount(), it returns 0. What am I doing wrong?
EDIT: someone gave this post a negative vote, but without leaving a comment or a request for clarification. So, I ask for some explanation about the reason of that in order to improve the readability of my question.
UPDATE: below my adapter
public class AlertListItemAdapter extends ParseQueryAdapter<AlertObject> {
private Context context;
private Fragment listAlertsFragment;
public AlertListItemAdapter(Context context,
ParseQueryAdapter.QueryFactory<AlertObject> queryFactory,
Fragment fragment) {
super(context, queryFactory);
this.context = context;
this.listAlertsFragment = fragment;
}
#Override
public View getItemView(final AlertObject alertObject, View view, final ViewGroup parent) {
[...]
}
#Override
public int getCount() {
return super.getCount();
}
}
I suspect (cannot be sure without seeing the Parse code/docs) that the adapter is not immediately populated with items, and when the query is executed, it'll call notifyDataSetChanged() on itself so that the ListView requeries it for item Views.
This would explain why your getCount() returns 0 immediately after setAdapter(ListAdapter) but why you can also see 33 items.
You can verify this logging adapter.getCount() as you do, and in addition, overriding notifyDataSetChanged to then observe the order of statements:
public class AlertListItemAdapter extends ParseQueryAdapter<AlertObject> {
public AlertListItemAdapter(
Context context,
ParseQueryAdapter.QueryFactory<AlertObject> queryFactory,
Fragment fragment) {
super(context, queryFactory);
}
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
Log.d("FOO", "item count: " + getCount());
}
#Override
public View getItemView(AlertObject alertObject, View view, ViewGroup parent) {
Log.d("FOO", "getItemView()");
...
}
...
}
If you need to know when the data changes, you can register a dataset changed listener on the adapter:
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
Log.d("Foo", adapter.getCount());
}
});
Are you sure you populated your ListView with parse AlertObjects?
I think you should add something like this to your query:
query.findInBackground(new FindCallback<AlertObject>() {
#Override
public void done(List<AlertObject> alerts, ParseException e) {
if (e == null) {
// Success
mAlerts = alerts;
String[] alertObjects = new String[mAlerts.size()];
Log.v(TAG, "There are " + mAlerts.size() + “ on the parse");
}
} else {
Log.e(TAG, e.getMessage());
}
}
});
Log can tell you how many objects you have on Parse.
In this Callback you can populate your ListView and then use
.getCount();
on your alertListAdapter.
Make sure you pass, store, and override the getCount() method correctly.
Please provide the code of adapter class if possible
Simply write following line to get number of items in the list view:
int count = alertsListView.alertsListAdapter().getCount();
After:
alertsListView.setAdapter(alertsListAdapter);
So your code will look like:
alertsListAdapter = new AlertListItemAdapter(activity, factory, thisFragment);
ListView alertsListView = (ListView) rootView.findViewById(R.id.alerts_list_view);
alertsListView.setAdapter(alertsListAdapter);
int count = alertsListView.alertsListAdapter().getCount();