I got 2 recyclerviews on a fragment. Both recyclerviews contain items and checkboxes. Only a single checkbox should be selectable within the two recyclerviews.
So if a checkbox is selected, all other checkboxes should be switched off IN BOTH RECYCLERVIEWS etc..
Here is my current code.
this code means that only one checkbox can currently be selected in each recyclerview.
My two recylcerviewadapters look like this (NOTE: both a quiet identical so I'm only posting one of them):
#Override
public void onBindViewHolder(#NonNull NameViewHolder holder, #SuppressLint("RecyclerView") int position) {
//getting broadcast from 1st recyclerviewadapter
LocalBroadcastManager.getInstance(context).registerReceiver(mNameReceiver, new IntentFilter("checkbox_first_adapter"));
String name = namesArrayList.get(position);
holder.nameView.setText(name);
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
rowIndex = position;
notifyDataSetChanged();
//If checkbox is checked, send broadcast to 2nd recyclerviewadapter
sendCheckBoxBroadCast();
isClicked = true;
}
});
if (isCheckBoxChecked) {
holder.checkBox.setChecked(false);
}
if (!isClicked) {
if (selectedName != null) {
if (name.equals(selectedName.getName())) {
holder.checkBox.setChecked(true);
sendCheckBoxBroadCast();
}
}
} else {
if (rowIndex == position) {
holder.checkBox.setChecked(true);
} else {
holder.checkBox.setChecked(false);
}
}
}
public BroadcastReceiver mNameReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
isCheckBoxChecked = intent.getBooleanExtra("isCheckBoxChecked", false);
}
};
private void sendCheckBoxBroadCast() {
Intent intent = new Intent("checkbox_second_adapter");
intent.putExtra("isCheckBoxChecked", true);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
How can I signal the 2nd recyclerview adapter that a checkbox has been selected so that it knows to unselect all checkboxes?
You can go three ways with this:
The first, dirty way is to just pass one adapter to the other and viceversa, and when one has a checkbox event, it callbacks the other one.
The second way would be to implement some sort of interface. You said that both adapters are similar, so it shouldn't be a problem. This interface would have a single method, the callback to update the checkboxes. From the caller activity class, you should be able to manage them both (you could also be able to put them in an array, if you ever need to add more) and get callbacks from either one of the adapters.
The third and perhaps the cleanest way would be to merge the recylcerviews: since a recyclerview can display different kind of views (you just have to fiddle a bit with the onBindViewHolder method) it could be possible (depends on the situation, you haven't provided enough information for this to be possible or entirely ruled out) to use only one adapter class.
Related
I'm new to SO and fairly new to coding, so please accept my apologies in advance if I break rules or expectations here.
I have an unusual setup involving two recyclerViews, which I'll explain here and also paste a simplified version of the code below (as there is so much not relevant to this question).
In what I'll call verticalRecyclerViewActivity, a verticalRecyclerViewAdapter is called, with data it fetches from Firebase and loads into arrayLists.
If the user clicks on an item in the vertical recyclerview, a new dialog fragment which I'll call horizontalRecyclerViewDialogFragment is inflated, and that loads what I'll call horizontalRecyclerView (which has similar items to the vertical one, in more detail, with options to click on them to review them).
If the user clicks on an item in the horizontalRecyclerView, a new activity which I'll call reviewItem is started (through an Intent). When the user submits their review, it finishes and returns (through the backstack) to the horizontal RecyclerView. That can also happen if they press the back button without actually submitting a review. That all works fine, but I need the horizontalRecyclerView to show that they have (or haven't) reviewed the item and state the score they gave it in a review.
Calling notifyDataSetChanged won't work for this because of how information comes through two recyclerViews and Firebase calls (or, at least, it would be very inefficient).
I've tried using startActivityForResult (I know it's deprecated, but if I could get that to work I could try using the newer equivalent which I don't yet understand) but the problem is that the result is returned to the original (VerticalRecylcerView) activity, which is two recyclerView adapters and one fragment beneath what needs to be updated, and I don't know how to pass that data to the horizontal Recyclerview.
I've also tried using interfaces but was unable to pass it through the Intent (tried using Parcelable and Serializable, but it seems neither can work in this situation?).
Since the review is updated on Firebase, I could have the horizontal Recyclerview listen for a change, but that seems very inefficient?
So I've found a solution using localBroadcast (which I know is also deprecated). The Intent (with the review score) is transmitted when it is reviewed and received in the horizontal recyclerView adapter. But when and how should I unregister the adapter? Ideally the receiver would be turned on when the user goes to the Review activity and turned off once the user returns from that activity and the (horizontal) recyclerView holder is updated, whether the review is successfully submitted or whether the user just presses the back button and never submits a review.
My question is similar to this one: How to unregister and register BroadcastReceiver from another class?
That is noted as a duplicate of this one: How to unregister and register BroadcastReceiver from another class?
There's a lot in those questions I don't understand, but the important difference I think between their and my cases is that I would just like the receiver to know when a review is submitted, and ideally be unregistered then, or possibly when the viewHolder is recycled, which I tried but also didn't work since it's not connected to the viewHolder (should it be?).
Any help would be much appreciated, thank you!
public class verticalRecyclerViewActivity extends AppCompatActivity {
// Loads an XML file and assembles an array from Firebase.
mVerticalRecyclerView = findViewById(R.id.verticalRecyclerView);
verticalRecyclerViewAdaptor mVerticalRecyclerViewAdaptor = new verticalRecyclerViewAdaptor (this); // also pass other information it needs
mVerticalRecyclerView .setAdapter(mVerticalRecyclerViewAdaptor);
}
public class verticalRecyclerViewAdaptor extends RecyclerView.Adapter<verticalRecyclerViewAdaptor.singleHolder> {
// Usual recyclerView content
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
horizontalRecyclerViewFragment mHorizontalRecyclerViewFragment = new horizontalRecyclerViewFragment();
// lots of arguments passed it needs.
FragmentManager fragmentManager = ((FragmentActivity) view.getContext()).getSupportFragmentManager();
mHorizontalRecyclerViewFragment.show(fragmentManager, null);
}
public class mHorizontalRecyclerViewFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity().getApplicationContext(); // Not sure why I need this, but it works.
View horizontalRecyclerViewView = getActivity().getLayoutInflater().inflate(R.layout.horizontal_recyclerview_holder, new CardView(getActivity()), false);
Dialog horizontalRecyclerViewDialog = new Dialog(getActivity());
horizontalRecyclerViewDialog.setContentView(horizontalRecyclerViewView);
mHorizontalRecyclerView = horizontalRecyclerViewView.getRootView().findViewById(R.id.horizontalRecyclerView);
mHorizontalRecyclerViewAdapter = new horizontalRecyclerViewAdapter (mContext)
// Other arguments passed
mHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(),
LinearLayoutManager.HORIZONTAL, false));
mHorizontalRecyclerView.setAdapter(mHorizontalRecyclerViewAdapter);
}
public class horizontalRecyclerViewAdapter extends RecyclerView.Adapter<horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder> {
public horizontalRecyclerViewAdapter(){}
// Blank constructor and also one with lots of arguments for it to work.
public horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.horizontal_recyclerview_adaptor_holder, parent, false);
return new horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder(view);
}
public void onBindViewHolder(#NonNull horizontalRecyclerViewHolder holder, int position) {
// Connect up various views.
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LocalBroadcastManager.getInstance(mContext).registerReceiver(reviewSubmittedListener, new IntentFilter("reviewSubmitted"));
Intent reviewNow = new Intent(view.getContext(), ReviewActivity.class);
// Put extra details with the intent
view.getContext().startActivity(reviewNow);
}
BroadcastReceiver reviewSubmittedListener = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent reviewFinishedIntent) {
int reviewScore = reviewFinishedIntent.getExtras().getInt("reviewScore");
// Update the horizontal RecyclerView with the information received from the review Activity.
}
};
}
public class ReviewActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_review_item);
// Set up the review, using Firebase and data passed through the intent.
}
public void submitReview() {
// Check that the review is complete/valid and submit it through Firebase
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(ReviewItemActivity.this);
Intent reviewFinishedIntent = new Intent("reviewSubmitted");
reviewFinishedIntent.putExtra("reviewScore", overallScore);
LocalBroadcastManager.getInstance(this).sendBroadcast(reviewFinishedIntent);
finish();
}
If you are using RxJava you can use the RxBus else you can use one of many EventBus implementation for this.
If that is not the path you want to take then you can have a shared view model object that can be used only for communication between fragments see this article.
I am trying to disable button after click in RecyclerView from Retrofit response.
Application is using RecyclerView to populate list and I am using Retrofit to communicate to back-end REST API. There are two buttons inside one item and Retrofit client is activated on click. And if response from API is successful button should be disabled. I came across on two problems:
first few items works just fine, but after few scrolls buttons I have never clicked on are disabled also;
second was that little few random buttons further on in list are still clickable.
public void onBindViewHolder(final NewsViewHolder holder, int position) {
holder.btnPositive.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// init Retrofit Client
JSONPlaceHolderAPI mAPIService;
mAPIService = ApiUtils.getAPIServiceFetch();
mAPIService.getNews(url).enqueue(new Callback<Result>() {
#Override
public void onResponse(Call<Result> call, Response<Result>
response) {
holder.btnPositive.setVisibility(View.GONE);
}
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
// do code
}
});
}
});
holder.btnNegative.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
JSONPlaceHolderAPI mAPIService;
mAPIService = ApiUtils.getAPIServiceFetch();
mAPIService.getNews(url).enqueue(new Callback<Result>() {
#Override
public void onResponse(Call<Result> call, Response<Result>
response) {
holder.btnNegative.setEnabled(false);
}
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
// do code
}
});
}
});
}
I suppose problem is somewhere in Retrofit where it uses background threads or AsyncTask.
This happens as the recycler view recycles(as the name suggests) the view that is visible to you and gives the same set of ids to every set of items which causes this problem .
This problem has 2 solution :
1) Stop the recycle behaviour of recyclerview as follows:-
#Override
public void onBindViewHolder(#NonNull MyViewHolder myViewHolder, int i) {
myViewHolder.setIsRecyclable(false);
}
But i strongly deny to use this cause recycling is reason why we use recyclerview.
2) Using a POJO Class:-
The best solution for it is to use a POJO class which will have two variables the first one is value and the second one is a boolean variable which show is the item disabled or not . Set the value of POJO boolean variable to true for the items u want to disable and in onBindViewHolder method only disable the buttons for the items which has false set in boolean variable .
If you still are confused contact me i will send you an example of it .
You need to hold states of those buttons. You can hold states in items(such as isNegativeEnabled, willPositiveDisplay etc.) of the list that populates recyclerview. When you click positive button, change willPositiveDisplay to false.
list[position].willPositiveDisplay = false;
And check list[position].willPositiveDisplay is true set visibility as visible, if not set as gone. And do this for negative button.
I have an activity with two buttons Next and Previous and a textview, I would like to update the textview content each time I click Next or Back
For example, If I click Next the textview should show me content from the next position or vice versa.
I think that I should be using a loop but it gives me an error when I try to do that and when I add 1 to the position (i+1) it works but it only gives me the second position, I want to get all the positions not only the second one. I don't really know if my question is clear, Hope it is :)
onBindViewHolder
#Override
public void onBindViewHolder(#NonNull final MyViewHolder myViewHolder, #SuppressLint("RecyclerView") final int i) {
myViewHolder.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String nextContent = listItems.get(i).getContent();
String previousContent = listItems.get(i).getContent();
Intent intent = new Intent(v.getContext(), Main2Activity.class);
intent.putExtra("next", nextContent);
intent.putExtra("prev", previousContent);
v.getContext().startActivity(intent);
}
});
}
Main2Activity
btnNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
itemTextView.setText(nextContent);
}
});
btnPrev.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
itemTextView.setText(prevContent);
}
});
It's bad practice to set click listeners in the onBindViewHolder method.
Why? Because onBindViewHolder is called each time the views are recycled for the new content to be displayed on the screen. Say you have a list of 1000 elements with 10 of them visible on the screen.
You scroll it to the end => Then onBindViewHolder would be called 990 times => 990 click listeners set.
You also want to dodge costly operations in onBindViewHolder() because your scrolling would be potentially slowed down.
More tips here:
Recyclerview(Getting item on Recyclerview)
A loop isn't the answer here, a loop is for automating something. This is an event (the user interacted) so it's not suitable.
The algorithm you probably want is basically the following:
User clicked an item
Find out the item index
Increment the index
Find the item with that index (by asking the list)
If it exists, do something with it
However, the issue in the code you posted is that your 'nextContent' is always the current item. So you need the following change (but be careful about bounds):
String nextContent = listItems.get(i+1).getContent();
String previousContent = listItems.get(i-1).getContent();
You current code isn't using a loop, so the onBind method should be called once for each value of i so it should not always be the second item
After your comments it appears you have buttons unrelated to the list, so now what you need to do is make it so every time you click an item in the RV or a Next/Prev, you store the correct index (as you have no access to i)
In the activity:
private int currentTextItem = 0;
public void setCurrentTextItem(int i) {
currentTextItem = i;
//the dots here will be how you get the text from the item
// probably recyclerView.findViewHolderForAdapterPosition(pos)
myTextView.setText( ... );
}
In your view holder code:
myViewHolder.cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//(You should have some kind of call back from viewholder to activity)
activity.setCurrentTextItem(i);
}
});
Then your next/prev:
btnNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setCurrentTextItem(currentTextItem + 1);
}
});
btnPrev.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setCurrentTextItem(currentTextItem - 1);
}
});
I've seen multiple questions about displaying multiple listviews in one activity, but my question relates to the onClick behaviour of the two. I set up two listviews besides one another, and dynamically populated them - this works fine. The issue is when it comes to setting onClick behaviour for the two lists. With my current code the second list opens the desired activity on top of the activity that would have been opened if the same item in the first list were clicked. Thus when the user goes back it goes to the wrong activity as the back stack is wrong.
My code for defining the onItemClick behaviour is here:
//make listview items respond to clicks and open relevant activity
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// selected item
String standard = ((TextView) view).getText().toString();
// Launching new Activity on selecting single List Item
Intent i = new Intent(SubjectActivity.this, StandardInfoActivity.class);
// sending data to new activity
i.putExtra("standard", standard);
i.putExtra("subject", subjectId);
i.putExtra("subjectName", subject);
i.putExtra("level", level);
startActivity(i);
finish();
}
});
gradeView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent2, View view,
int position, long id2) {
// Launching new Activity on selecting single List Item
Intent j = new Intent(SubjectActivity.this, AddGradeActivity.class);
// sending data to new activity
j.putExtra("subjectId", subjectId);
j.putExtra("position", position);
j.putExtra("level", level);
j.putExtra("subject", subject);
startActivity(j);
finish();
}
});
My guess is that because I'm defining the same function for both, it's being called twice with the click on the second list, once for the first list and then again for the desired list. What would be the correct way to define the behaviour of the two lists in this case?
EDIT: Code used to set up adapters:
ArrayList<String> mArrayList = myDb.getStandards(subjectId);
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
this, R.layout.subject_list_item, mArrayList);
lv.setAdapter(arrayAdapter);
ArrayList<String> gradeList = myDb.getGrades(subjectId);
ArrayAdapter<String> arrayAdapter2 = new ArrayAdapter<String>(
this, R.layout.subject_list_item, gradeList);
gradeView.setAdapter(arrayAdapter2);
Calling finish() like that will lead to onDestroy() being called for the current activity ( https://developer.android.com/guide/components/activities/activity-lifecycle#ondestroy ), which will take it off of the back stack. So I think it's possible you're going back and just getting a surprising result.
One way you can see if you're really triggering both onItemClickListeners is you can add temporary debug messages to both and then see what kind of output you get. Like, on one you can try Log.d("ClickListener", "lv was clicked"); and on the other try Log.d("ClickListener", "gradeView was clicked"); and then check Logcat and see what comes up.
Create a interface in your Adapter. Then You can override methods in Your activity.
Interface:
public interface ItemClickAdapterListener {
void itemClick(View v, int position);
}
Item Click:
viewHolder.llayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onClickListener.itemClick(view,position);
}
});
Activity code. Create Adapter object like this
YourAdpter adapter= new YourAdpter(new YourAdapter.ItemClickAdapterListener() {
#Override
public void itemClick(View v, int position) {
}
}):
I have an activity that has 3 fragments on it with Tabs, one of them is called "TaskFragment".
In my main Activity i only load the fragments.
In TaskFragment i have a RecyclerView that is working fine and is showing the items as intended.
The problem comes, when i insert data using a DialogFragment, because it does insert data (i am using DbFlow ORM), but it does not (of course) refresh the adapter since it is in the TaskFragment fragment inside the DetailMainActivity activity as i said.
I have tried to use onResume() and onPause() in order to refresh the adapter, but they are never called since the activity does not get paused or in onresume for a DialogFragment.
I have tried aswell to use an interface, but it does not work and i have searched all over stackoverflow and google with no luck.
I leave here some of my code for you to understand better:
DetailMainActivity.java
Here in the onClick interface i show the DialogFragment to the user to input the information.
FragmentManager fm = getSupportFragmentManager();
AddSimpleTask sptask = new AddSimpleTask();
sptask.show(fm, "tag");
TaskFragment.java
In this fragment i have my RecyclerView
private void setupRecyclerView() {
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (DetailMainActivity.FAB_Status) {
DetailMainActivity.hideFAB();
DetailMainActivity.FAB_Status = false;
}
return false;
}
});
}
private void setupAdapter() {
adapter = new DetailMainTaskAdapter(simpleTaskList, this);
}
AddSimpleTask
And this is my DialogFragment. I have set a setOnShowListener() in order to avoid the DialogFragment to get dismiss early.
#Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog =(AlertDialog) getDialog();
if (dialog != null){
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mEditTextName.getText().toString().trim().isEmpty() ||
mEditTextContent.getText().toString().trim().isEmpty() ) {
if (mEditTextName.getText().toString().trim().isEmpty()) {
mEditTextName.setError("Can not be empty");
}
if (mEditTextContent.getText().toString().trim().isEmpty()) {
mEditTextContent.setError("Can not be empty");
}
}else {
presenter.beingInsertion(mEditTextName.getText().toString().trim(), mEditTextContent.getText().toString().trim()
, foreignId);
}
}
});
negativeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
}
If the insert is successfully achieved the onInsertSuccess method is called (i am using MVP)
#Override
public void onInsertSuccess() {
Snackbar.make(getActivity().findViewById(R.id.containerMainDetail), "Actividad agregada", Snackbar.LENGTH_SHORT).show();
dismiss();
}
I have called adapter.notifyDataSetChanged() in many places, and i also tried with a custom interface, but i can not make this work.
Sorry for the long post, but thanks in advance for your help.
There are some errors in your statement but I'll get to that later. notifyDataSetChanged() only notifies the adapter that the underlying list (or array) has changed. The implication is that you first need to requery your database and obtain the new list before calling notifyDataSetChanged() on the adapter else there is no point as the underlying list will still be the same and it will not update the adapter.
The correct way of calling this will be through your custom listener interface and not in the onPause()/onResume() callbacks as there is the possibility that the user does not enter a value and hence you will unnecessarily be querying the database. In your custom listener interface implementation, first update the list with the new data from the DB and then notify the adapter.
Which leads to the error in assumption that onPause()/onResume() callbacks do not happen when your Activity is covered by a DialogFragment - this is incorrect. The moment the activity view is even partially covered, the onPause() callback is triggered.