I am developing an android application where i am using a RecyclerView to display a list of items.I am getting the list from server as json.So my problem is within this list i am getting another list as item.That is if my main arraylist contain title and materials, the material is another arraylist.So can you please suggest a solution to display a list within recyclerview.
The code below is my adapter
public class CurriculumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private ArrayList<Curriculum> mArrayListCurriculum;
public CurriculumAdapter(Context mContext, ArrayList<Curriculum> mArrayListCurriculum) {
this.mContext = mContext;
this.mArrayListCurriculum = mArrayListCurriculum;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_key_features, parent,false);
return new KeyFeatureViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof KeyFeatureViewHolder) {
((KeyFeatureViewHolder) holder).mTextViewFeatureTitle.setText(mArrayListCurriculum.get(position).getTitle());
}
}
#Override
public int getItemCount() {
return mArrayListCurriculum == null ? 0 : mArrayListCurriculum.size();
}
public static class KeyFeatureViewHolder extends RecyclerView.ViewHolder {
public TextView mTextViewFeatureTitle;
public KeyFeatureViewHolder(View itemView) {
super(itemView);
mTextViewFeatureTitle = (TextView) itemView.findViewById(R.id.txtFeature);
}
}
}
The code below is my fragment with dummy arraylist data
public class CourseCurriculumFragment extends Fragment {
private FragmentInterface mFragmentInterface;
private ArrayList<Curriculum> mArrayListCurriculum;
private ArrayList<Material> mArrayListMaterial;
private RecyclerView mRecyclerViewCurriculum;
private LinearLayoutManager mLinearLayoutManager;
private CurriculumAdapter mCurriculumAdapter;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_course_curriculum, container, false);
return view;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
}
private void initView(View view) {
mArrayListMaterial = new ArrayList<>();
mArrayListCurriculum = new ArrayList<>();
populateMaterials();
populateKeyFeatures();
mRecyclerViewCurriculum = (RecyclerView) view.findViewById(R.id.recyclerViewCurriculum);
mCurriculumAdapter = new CurriculumAdapter(getActivity(), mArrayListCurriculum);
mLinearLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerViewCurriculum.setLayoutManager(mLinearLayoutManager);
mRecyclerViewCurriculum.setAdapter(mCurriculumAdapter);
mRecyclerViewCurriculum.setItemAnimator(new DefaultItemAnimator());
}
private void populateMaterials() {
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
}
private void populateKeyFeatures() {
mArrayListCurriculum.add(new Curriculum("UNIT 1",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 2",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 3",mArrayListMaterial));
}
}
A bind method in a holder is a good way to pass data to it.
In your case this bind method should take in a Curriculum and a Material object as parameters.
Inside the onBindViewHolder method of the adapter, instead of reaching into the variables of the holder, you should call this bind method.
In the implementation of the method inside the you KeyFeatureViewHolder class you should use these passed parameters and display them in the appropriate UI elements.
Lastly, to get the Material object data into adapter, add ArrayList<Material> as a constructor parameter just like you did with Curriculum.
Use RecyclerView with header, title as header and materials as items of that header. Look at this example.
You need to design a custom list for yourself. For example take an object like this.
public class ListItem {
public curriculumName = null;
public materialName = null;
}
Now populate this list after you parse the JSON string. Get your first Curriculum and populate the object like this
private ArrayList<ListItem> mListItemArray = new ArrayList<ListItem> ();
for(curriculum : mArrayListCurriculum) {
ListItem mListItemHead = new ListItem();
mListItemHead.curriculumName = curriculum.getName();
// Set the header here
mListItemArray.add(mListItemHead);
for(material : curriculum.getMaterials()){
ListItem mListItem = new ListItem();
mListItem.materialName = material.getName();
// Add materials here
mListItemArray.add(mListItem);
}
}
Now, you've a list with headers and materials. When the materialName in your mListItemArray is null, it identifies that this is a header and vice versa.
Now the trick is to modify your adapter of your RecyclerView so that you can bind proper view to your items in your list.
You can find an indication from this answer on how you can achieve this desired behaviour.
Basically, the idea is to modify your getItemViewType to pass the proper view in your onBindViewHolder. Your getItemViewType might look like this.
#Override
public int getItemViewType(int position) {
if (mListItemArray.get(position).curriculumName != null) {
// This is where we'll add header.
return HEADER_VIEW;
}
return super.getItemViewType(position);
}
Related
I have a fragment Users which has 3 other fragments in it (tabs). For one tab ( called Friends2Fragment ) I made a recycler View and made an adapter for it. In each item of RecyclerView I have a button "Add friend" and I want to call it from Friends2Fragment, not to call it from the adapter because I can't use Firestore Database properly.
RecyclerViewInterface:
public interface RecyclerViewInterface {
void onItemClick(int position, String button_pressed);
}
Friends2Fragment.java :
public void onStart(){
super.onStart();
recyclerView = (RecyclerView) v.findViewById(R.id.recycler);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
friendslist = new ArrayList<>();
myAdapter = new MyAdapter(friendslist,v.getContext());
recyclerView.setAdapter(myAdapter);
------ Firestore operations ------
}
#Override
public void onItemClick(int position, String button_pressed) {
switch ( button_pressed ){
case "ADD_FRIEND":
Log.d(TAG, "item clicked: " + friendslist.get(position).username);
}
}
MyAdapter.java :
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
Context context;
public ArrayList<User> userArrayList;
public MyAdapter(ArrayList<User> userArrayList, Context context) {
this.userArrayList = userArrayList;
this.context = context;
}
public Context getContext() {
return context;
}
public ArrayList<User> getUserArrayList() {
return userArrayList;
}
#NonNull
#Override
public MyAdapter.myViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
MyAdapter.myViewHolder myViewHolder = new MyAdapter.myViewHolder(v);
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((Friends2Fragment)context).onItemClick(myViewHolder.getAdapterPosition(),"ADD_FRIEND");
}
});
return myViewHolder;
}
#Override
public void onBindViewHolder(#NonNull MyAdapter.myViewHolder holder, int position) {
User user = userArrayList.get(position);
holder.usernamerecycle.setText(user.username);
}
#Override
public int getItemCount() {
return userArrayList.size();
}
public void filterList(List<User> filteredList){
userArrayList = (ArrayList<User>) filteredList;
notifyDataSetChanged();
}
public class myViewHolder extends RecyclerView.ViewHolder{
TextView usernamerecycle;
Button addbutton;
View rootview;
public myViewHolder(#NonNull View itemView) {
super(itemView);
rootview = itemView;
usernamerecycle = itemView.findViewById(R.id.usernamerecycler);
addbutton = itemView.findViewById(R.id.addfriendbutton);
}
}
}
The problem is at this line : ((Friends2Fragment)context).onItemClick(myViewHolder.getAdapterPosition(),"ADD_FRIEND"); in onCreateViewHolder method in MyAdapter.
I have this error : Inconvertible types; cannot cast 'android.content.Context' to 'com.example.birthday.Fragments.Friends2Fragment'
Please help me ..
A Fragment isn't a Context (that's not one of its supertypes) so that cast is impossible, that's why you're getting the error.
I think you should organise it like this: your Adapter holds a bunch of User objects, right? It displays those, and you have a click listener on each ViewHolder that knows which index in the User list it's currently displaying, and it wants to inform some listener when it's clicked. That index is an internal detail really, it would make more sense to look up the actual User, and provide that to the listener.
The simplest way is to just provide your fragment as a listener. First store it in your adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
// store a reference to your fragment
private Friends2Fragment listener;
// add a function to provide that fragment
public void setListener(Friends2Fragment: listener) {
this.listener = listener
}
...
public MyAdapter.myViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
...
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
// look up the actual user
User user = userArrayList.get(myViewHolder.getAdapterPosition());
// call a function on your fragment
listener.onItemClick(user, "ADD_FRIEND");
}
}
});
}
Then add the callback function your adapter uses, and also set your fragment on the adapter as a listener:
// Friends2Fragment
// You should REALLY be doing this in onViewCreated or something, so this setup happens once.
// You're losing all your state by creating a new adapter whenever the user returns to the app
public void onStart(){
...
myAdapter = new MyAdapter(friendslist,v.getContext());
// set the fragment as the listener
myAdapter.setListener(this);
recyclerView.setAdapter(myAdapter);
}
// now add the function the adapter calls
private void onItemClick(User user, String someString) {
// handle the clicked user
}
A better way is to create an interface with all the events that need to be handled, and make your Fragment implement those. It breaks the hard association with the Fragment since you could pass any object that implements those functions, and it's also clearer because the interface kinda documents all the data the adapter produces, and that a listener needs to be able to handle. Something like this:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myViewHolder> {
// the listener is now something that implements the Callbacks interface
private Callbacks listener;
...
// nesting it inside MyAdapter makes the path MyAdapter.Callbacks, which makes it clear
// exactly what it is and what it relates to, and kinda gives the Adapter "ownership"
interface Callbacks {
void addFriend(User user)
}
And then you just make the Fragment implement that interface
public class Friends2Fragment() extends Fragment implements MyAdapter.Callbacks {
...
// implement all the callbacks you need to handle
override public void addFriend(User user) {
// do the thing
}
// set it in the same way, since this Fragment implements MyAdapter.Callbacks
myAdapter.setListener(this);
Which is a bit neater and cleaner, I think - but slightly more work. Also if you notice, I renamed the callback function from the generic handleItemClick to the more specific addFriend - so instead of having to pass a String saying what kind of click it is, you just have a function for each event you want to handle, and you can name them appropriately
I have a recycler view for image display with a simple adapter.
public class ImageListAdapter extends RecyclerView.Adapter<ImageListAdapter.SingleItemRowHolder> {
private ArrayList<Bitmap> itemsList;
public ImageListAdapter(ArrayList<Bitmap> itemsList) {
this.itemsList = itemsList;
}
#Override
public SingleItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, null);
return new SingleItemRowHolder(v);
}
#Override
public void onBindViewHolder(SingleItemRowHolder holder, int i) {
Bitmap img = itemsList.get(i);
holder.itemImage.setImageBitmap(img);
}
#Override
public int getItemCount() {
return (null != itemsList ? itemsList.size() : 0);
}
public void addItem(Bitmap image) {
itemsList.add(image);
notifyItemInserted(itemsList.size()-1);
}
public void setItemsList(ArrayList<Bitmap> bmps) {
itemsList = bmps;
}
public ArrayList<Bitmap> getItemsList() {
return itemsList;
}
public void cleanData() {
itemsList = new ArrayList<>();
notifyDataSetChanged();
}
public class SingleItemRowHolder extends RecyclerView.ViewHolder {
protected ImageView itemImage;
public SingleItemRowHolder(View view) {
super(view);
this.itemImage = (ImageView) view.findViewById(R.id.itemImage);
}
}
}
Nothing complex here. Just a simple adapter that getting bitmap list as input and show them in the recycle view.
The point is, the bitmaps are asynchronously added via a retrofit call, as shown in the folloing:
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey).enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
}
});
As you can see here, the bitmap is added to the adapter and the adapter should be able to notify the changes as addItem() contains a notifyItemInserted() call. And this retrofit call is called several times depending on how many times the view model has changes its value (there is a observer observing the change of view model's value. Once the value changes, the above retrofit will get called). So the code that changes view model value look like this:
if (placeDetail.getPhotoRef() != null) {
for (PlaceDetail.PhotoRef ref : placeDetail.getPhotoRef()) {
viewModel.setPhoto(ref.getPhotoRef()); // ref.getPhotoRef returns the photo ref string
}
}
In my expectation, the item list of the adapter should now contains several images. I tried to debug it, and I found when the addItem() method get called, the amount of item list did change as expected. But when it came into the onBindViewHolder() callback, there is only one item left in the item list, which is the first bitmap.
Can anyone tell me why this issue happened? Any suggestion would be appreciated.
---------------------------------- Update --------------------------------
Interesting. I finally found why it only shows one photo. As you can see here, the adapter (id:24856) item list size is 4, which is in my expectation.
However, there is another adapter(id:24962) exists. For that adapter, the add item method only called once thus only one item in that adapter. When the recycler view changes the content, the onBindViewHolder is called on the second adapter. Now my question is, where does the second adapter come from ???
Here is my code to initialize recycler view and adapter
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
RecyclerView recyclerView = binding.imageGallery;
recyclerView.setHasFixedSize(true);
ImageListAdapter adapter = new ImageListAdapter(new ArrayList<>());
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerView.setAdapter(adapter);
return binding.getRoot();
}
Hej AhSoHard,
did you check if it is a timing issue, because you are loading your data asynchronously and it could lead to missing updates.
also could it be that setItemsList(ArrayList<Bitmap> bmps) is called and overwrites the current list?
is there a reason why you do not use the viewGroup as a base for inflating the layout resource in onCreateViewHolder()?
LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, viewGroup, false)
in my opinion the Adapter should not be the holder of the current state, maybe have a list outside and use a ListAdapter to display it. When you have a new list of bitmaps, you just call adapter.submit(bitmaps) and the new list will be displayed. Together with a DiffCallback, the old items will not update and the new ones added at the end of the list.
Let's implement the DiffUtils adapter and try to change your calls in this way. As kotlin works as interoperable with java, therefore I am mentioning the gist for an adapter that is written in kotlin.
Check adapter and view holder implementation
Code
ImageListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = PlaceFragmentBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
binding.imageGallery.setHasFixedSize(true);
adapter = new ImageListAdapter(new ArrayList<>());
binding.imageGallery.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
binding.imageGallery.setAdapter(adapter);
return binding.getRoot();
}
Interface googleMapService
Single<ResponseBody> getImageByPhotoReference(_,_,_,_)
Network Call
googleMapService.getImageByPhotoReference(s, 300, 300, apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ResponseBody>() {
#Override
public void onSuccess(ResponseBody response) {
if (response != null) {
Bitmap bmp = BitmapFactory.decodeStream(response.body().byteStream());
adapter.addItem(bmp);
}
}
#Override
public void onError(Throwable e) {
// Do with your error
}
});
It's interesting that you initialize the adapter in onCreateView method and it has local visibility(only visible inside of the onCreateView method). But in the googleMapService.getImageByPhotoReference() method's callback you are referencing it adapter.addItem(bmp);. So my guess is that you are referencing the wrong adapter object in the googleMapService.getImageByPhotoReference() method's callback. The callback may reference an old instance of ImageListAdapter.
Try to initialize ImageListAdapter adapter as a property of your Fragment class and see if it works.
My database (Firestore) stores Charts, created by Users.
On opening the app, the User is shown their own Charts, sorted by Chart Name (this is displayed in a Fragment called MyChartsFragment). They can click a button to change the sort field; this should retrieve the same list of Charts, but sorted by the selected field.
I'm able to pass the new sort field name into MyChartsFragment, but at that point the list of Charts goes blank. I assume I need to redraw it somehow.
The MyChartsFragment is as follows (I've removed most of the try-catch blocks, error checks etc, for clarity):
public class FragmentMyCharts extends FragmentChartsList {
public FragmentMyCharts() {}
public Query getQuery(FirebaseFirestore databaseReference, String orderBy) {
// Specify the query which is used to retrieve this user's charts
return databaseReference.collection("charts")
.whereEqualTo("uid", getUid())
.orderBy(orderBy);
}
}
This extends FragmentChartsList, which contains:
public abstract class FragmentChartsList extends Fragment {
private FirebaseFirestore mDatabaseRef;
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
private String mOrder = "name";
FragmentManager mFragmentManager;
ToolbarFragmentListCharts mFragmentTLC;
public FragmentChartsList() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout, and find Recycler View to hold the list
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database, and get the appropriate query
mDatabaseRef = FirebaseFirestore.getInstance();
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add listener to charts collection, and deal with any changes by re-showing the list
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null && queryDocumentSnapshots.isEmpty()) {
((MainActivity) Objects.requireNonNull(getActivity())).setPage(1);
}
}
});
}
#Override
public void onStart() {
super.onStart();
mAdapter.startListening();
}
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, this.getActivity());
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
}
public abstract Query getQuery(FirebaseFirestore databaseReference, String orderBy);
}
So, when the main activity is notified by the toolbar that the sort field has changed, it retrieves the MyChartsFragment (as a FragmentChartsList) and calls the setOrderField function:
public void setOrder(String order) {
FragmentChartsList myCharts = (FragmentChartsList) mPagerAdapter.getItem(0);
myCharts.setOrderField(order);
}
This appears to update the value of mOrder correctly, but then it doesn't re-display the RecyclerView, so I just get a blank space.
What am I doing wrong?
EDITED TO ADD DETAIL:
My ChartListAdapter class is:
public class ChartListAdapter
extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
private Activity mActivity;
private FirestoreRecyclerOptions<Chart> recyclerOptions;
public ChartListAdapter(FirestoreRecyclerOptions<Chart> recyclerOptions, Activity activity) {
super(recyclerOptions);
mActivity = activity;
}
#Override
protected void onBindViewHolder(#NonNull ChartViewHolder holder, int position, #NonNull Chart model) {
final String chartKey = this.getSnapshots().getSnapshot(position).getId();
model.setKey(chartKey);
// Set click listener for the chart
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, ActivityViewChart.class);
intent.putExtra("ChartKey", chartKey);
mActivity.startActivity(intent);
}
});
// Implement long-click menu
mActivity.registerForContextMenu(holder.itemView);
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#NonNull
#Override
public ChartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
}
rather then recreating the recyclerView adapter, try updating the options in the adapter.
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
// Use Recycler Adapter in RecyclerView
mAdapter.setRecyclerOptions(recyclerOptions);
// then call the notify data set changed on the adapter to recreate the recyclerView elements
mAdapter.notifyDataSetChanged();
}
and in the recyclerView adapter class, add a setter for the recyclerOptions :
public void setRecyclerOptions(FirestoreRecyclerOptions<Chart> recyclerOptions) {
this.recyclerOptions = recyclerOptions;
}
Found one problem... I had declared mAdapter with:
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
when it should have been:
private ChartListAdapter mAdapter;
Solved... in setOrderField I was declaring a new mAdapter, but not listening to it. Once I added
mAdapter.startListening()
it all worked fine!
UPDATE 2021:
I don't know, for how long this new method works, but I'm so thankful for finding it out by myself, because I couldn't find any good response to that:
You don't have to create any Methods in your Adapter anymore.
Simply create the setOrder Method like this:
public FirestoreRecyclerOptions<event_log_Item> getOrder(String order){
String mOrder = order;
Query query = current_productRef.orderBy(mOrder, Query.Direction.DESCENDING);
FirestoreRecyclerOptions<event_log_Item> options = new FirestoreRecyclerOptions.Builder<event_log_Item>().setQuery(query, event_log_Item.class).build();
return options;
}
This Method will return the order-options for the field you want.
Then simply do:
mAdapter.updateOptions(setOrder("Timestamp"));
mAdapter.notifyDataSetChanged();
The Method "updateOptions" is already implemented. Just simply give it the option you want and add "notifyDataSetChanged();"
I'm retrieving data using a FirestoreRecyclerAdapter, and, on completion, I need to check whether any items have been retrieved or not. I can't figure out how to do this.
I'm calling it from a class called FragmentChartsList, shown below. This should set up the adapter initially, with "name" as the value for mOrder. Later, the Activity which contains this Fragment can call setOrderField() with a different value of mOrder, which the user has selected from a Spinner.
Each time setOrderField() is called, a new adapter instance is created and attached to the recyclerView. At this point I need to check whether the new version of the adapter contains any data, and either show a "no Charts found" message, or show the Charts which were retrieved (obviously if the list is just being sorted, then the number of items remains the same, but I'm going to be expanding this to allow the user to filter the Charts by different criteria, so the number of Charts returned will change).
Currently, setOrderField() calls refreshViewOnNewData(), which should find out how many Charts are being shown; if it's 0, it should show the "no Charts found" message, and if it's >0 it should show the RecyclerView containing the Charts.
At the moment, I'm always getting a value of 0 when I try to count the Charts. I suspect it's because the adapter hasn't finished retrieving them from the database yet, but I can't find anything that allows me to add some kind of "onComplete" listener so that I know it's finished.
Can anyone suggest how I can achieve this?
public abstract class FragmentChartsList extends Fragment {
private FirebaseFirestore mDatabaseRef;
private ChartListAdapter mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
private String mOrder = "name";
private TextView mLoadingList, mEmptyList;
public FragmentChartsList() {}
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
mLoadingList = rootView.findViewById(R.id.loading_list);
mEmptyList = rootView.findViewById(R.id.empty_list);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database
mDatabaseRef = FirebaseFirestore.getInstance();
setOrderField(mOrder); // Initialised to "name"
return rootView;
}
#Override
public void onStart() {
super.onStart();
mAdapter.startListening();
}
#Override
public void onStop() {
super.onStop();
mAdapter.stopListening();
}
#Override
public void onDestroy() {
super.onDestroy();
mAdapter.stopListening();
}
// HELPER FUNCTIONS
public void setOrderField(String order) {
mOrder = order;
mChartsQuery = getQuery(mDatabaseRef, mOrder);
// Update recycler options
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions, getActivity());
mAdapter.startListening();
mRecycler.swapAdapter(mAdapter, true);
refreshViewOnNewData();
}
private void refreshViewOnNewData() {
// Hide "loading" text
mLoadingList.setVisibility(View.GONE);
// Check number of charts being shown
//if (mAdapter != null && (mAdapter.getCount() > 0)) {
// If > 0, show Charts
mEmptyList.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
// If number of Charts = 0
// show "no charts"
mEmptyList.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
The adapter class looks like this:
public class ChartListAdapter extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
private Activity mActivity;
private int mCount;
public ChartListAdapter(FirestoreRecyclerOptions<Chart> recyclerOptions, Activity activity) {
super(recyclerOptions);
mActivity = activity;
}
#Override
protected void onBindViewHolder(#NonNull ChartViewHolder holder, int position, #NonNull Chart model) {
final String chartKey = this.getSnapshots().getSnapshot(position).getId();
model.setKey(chartKey);
// Set click listener for the chart
// On click, the user can view the chart
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, ActivityViewChart.class);
intent.putExtra("ChartKey", chartKey);
mActivity.startActivity(intent);
}
});
// Implement long-click menu
mActivity.registerForContextMenu(holder.itemView);
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#NonNull
#Override
public ChartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
#Override
public void onDataChanged() {
super.onDataChanged();
mCount = getItemCount();
}
public int getCount() {
return mCount;
}
}
Figured this out... I needed to set a listener on the query instead.
So, instead of having the call to refreshViewOnNewData from setOrder above, I now have:
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null) {
mLoadingList.setVisibility(View.GONE);
if(queryDocumentSnapshots.size() > 0) {
mEmptyList.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
}else {
mEmptyList.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
});
}
Also removed mCount from the adapter class, along with getCount and onDataChanged
I am creating an AlertDialog custom class, called ActionDialog, which will contains a RecyclerView containing Buttons. I have a List of Button that I populate in the custom class ActionDialog (for now i just populate with useless Button just to try to use it, except one which I create in another class).
The problem is that when i create the AlertDialog, all buttons are showing empty, they are showed but with no text/no clicklistener (as you can see in the image below).
(I have added a custom ActionListener to a Button in another class and then give it as parameter in ActionDialog class. Will it lose the ActionListener?)
Here is the result.
I will leave here my ActionDialog class code, and the adapter class.
This is ActionDialog class:
public class ActionDialog extends AlertDialog{
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private Button actionButtons;
private List<Button> buttons;
private Activity context;
public ActionDialog(#NonNull Activity context, Button actionButtons) {
super(context);
this.context = context;
this.actionButtons = actionButtons;
buttons = new ArrayList<>();
initButton();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
}
private void initButton(){
initZoneButton();
//TODO init all buttons
Button b1 = new Button(context);
b1.setText("ExampleButton1");
Button b2 = new Button(context);
b2.setText("ExampleButton2");
b1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String a;
}
});
buttons.add(b1);
buttons.add(b2);
}
private void initZoneButton(){
buttons.add(actionButtons); //this button is created in another class and give as parameter in this class
}
public void createDialog(){
Builder mBuilder = new Builder(context);
View view = context.getLayoutInflater().inflate(R.layout.dialog_actionbuttons_layout, null);
mRecyclerView = view.findViewById(R.id.dialog_actionbuttons_rv);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(context);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new ActionButtonsAdapter(buttons);
mRecyclerView.setAdapter(mAdapter);
mBuilder.setView(view);
mBuilder.create().show();
}
}
Here is the RecyclerView adapter class:
public class ActionButtonsAdapter extends RecyclerView.Adapter<ActionButtonsAdapter.ViewHolder>{
private List<Button> dataButtons;
static class ViewHolder extends RecyclerView.ViewHolder {
Button actionButton;
ViewHolder(View v) {
super(v);
actionButton = v.findViewById(R.id.action_button_rv);
}
}
public ActionButtonsAdapter(List<Button> dataButtons){
this.dataButtons = dataButtons;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.actionButton = dataButtons.get(position);
//i think the problem is here, maybe
}
#Override
public ActionButtonsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_actionbutton_layout, parent, false);
return new ViewHolder(v);
}
#Override
public int getItemCount() {
return dataButtons.size();
}
}
I think in the onBindViewHolder method you should do what ever you want to do with your button.
Also there is no need for the list of buttons here. Make a list the data you need to be held in the Buttons RecyclerView.
I have a RecyclerView that will display Genres for restaurants lets say, So I will create a List of strings to hold these genres names (chickens, meats, etc,..)
Setting its text
holder.actionButton.setText(// Make use of position here);
Or Click Listeners.
Update
You can check google samples for recyclerview here
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
// Get element from your dataset at this position and replace the contents of the view
// with that element
viewHolder.getTextView().setText(mDataSet[position]);
}
wheres mDataset is Array of Strings.