how to update recyclerview from a dialog which is in another class?
My dialog is as a separate class which is called from mainActivity. When I do changes in database, I would like to update recyclerview, which is on mainActivity.
Dialog:
public class Dialog {
DatabaseExecutor databaseExecutor = new DatabaseExecutor();
private final Activity activity;
private final List<Passenger> passengers;
private final int position;
public Dialog (final Activity activity, final List<Passenger> passengers, final int position){
this.activity = activity;
this.passengers = passengers;
this.position = position;
}
public void showDialog (){
final BottomSheetDialog dialog = new BottomSheetDialog(activity);
dialog.setContentView(R.layout.custom_dialog);
final AppCompatImageView dial, message, info, paid, edit, delete;
final AppCompatTextView name;
name = dialog.findViewById(R.id.dialog_name);
paid = dialog.findViewById(R.id.dialog_paid);
name.setText(passengers.get(position).getName());
if(passengers.get(position).isPaid())
paid.setImageResource(R.drawable.money_paid_72);
else
paid.setImageResource(R.drawable.money_unpaid_72);
paid.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Passenger passenger = passengers.get(position);
if (!passengers.get(position).isPaid()){
passenger.setPaid(true);
passenger.setTumblr(R.drawable.money_paid);
passenger.setUser(R.drawable.user_icon);
paid.setImageResource(R.drawable.money_paid_72);
}
else {
passenger.setPaid(false);
passenger.setTumblr(R.drawable.money_unpaid);
passenger.setUser(R.drawable.user_icon_unpaid);
paid.setImageResource(R.drawable.money_unpaid_72);
}
databaseExecutor.updatePassenger(activity, passenger);
}
});
dialog.show();
}
}
P.s. when this dialog was in mainActivity, I just called populateData method and it worked. But how to refresh it from this Dialog class?
You can use callback with dialog in MainActivity,
public interface DialogCallback {
public void onDialogCallback();
}
Your Dialog constructor should be,
DialogCallback callback;
public Dialog (final Activity activity, final List<Passenger> passengers, final int position, DialogCallback callback){
this.activity = activity;
this.passengers = passengers;
this.position = position;
this.callback = callback;
}
In your Dialog button click use below code,
paid.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Passenger passenger = passengers.get(position);
if (!passengers.get(position).isPaid()){
passenger.setPaid(true);
passenger.setTumblr(R.drawable.money_paid);
passenger.setUser(R.drawable.user_icon);
paid.setImageResource(R.drawable.money_paid_72);
}
else {
passenger.setPaid(false);
passenger.setTumblr(R.drawable.money_unpaid);
passenger.setUser(R.drawable.user_icon_unpaid);
paid.setImageResource(R.drawable.money_unpaid_72);
}
databaseExecutor.updatePassenger(activity, passenger);
callback.onDialogCallback(); // Add this line
}
});
In your MainActivity use below code,
Dialog dialog = new Dialog(this, passengers, position, new DialogCallback() {
#Override
public void onDialogCallback() {
// Update recycler view code here
}
});
dialog.showDialog();
In Dialog :
Have an interface
public interface onDialogFinishCallback
{
void refreshRecyclerView();
}
Now implement the above in your activity.
before dismiss the dialog or after the db change operation call
callback.refreshRecyclerView
A direct solution would be to call method on activity you passed to the dialog. There refresh data of recyclerview and notifyDataSetChanged() or appropriate.
A more general and imo better, architecture-related solution is to use Room or similar db, where you can observe data for changes. Let's say data in the db is changed anywhere. All the places where this data is observed (like with LiveData), data is refreshed. If you also use Paging library, data is refreshed and animated in recyclerview too.
Dialog shouldn't refresh RecyclverView directly. Instead you should pass listener from activity. Activity can refresh recycler if needed with notifyDataSetChanged.
Usually dialog should be 'dumb' ui and you shouldn't give it too much control, especially not over elements that are not shown inside dialog. Such approach will make your dialogs more reusable and easy to maintain.
Write an interface in your dialog
public interface onClickInterface{
public void updateRecyclerView(int position);
}
declare new variable for this interface in your dialog class
private onClickInterface mOnClickInterface;
then call method updateRecyclerView() from dialog class where you want to update recyclerview
mOnClickInterface.updateRecyclerView(position);
then implement your MainActivity for this interface and override this method
#override
public void updateRecyclerView(int position){
//alter your list which you are passing to your adapter
Passenger passenger = passengers.get(position);
passenger.setPaid(true);
rAdapter.notifyDatasetChanged();
}
Related
In my app I have implemented this custom dialog (which has a fairly complex layout) by extending DialogFragment. I expect this dialog to pop up when I click a button in my layout. (Which I have successfully achieved). But the problem is that the dialog shows up in a janky manner.
My custom dialog class:
public class CustomizeDialog extends DialogFragment implements AdapterView.OnItemSelectedListener {
// field declarations go here
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.customize_dialog, null);
builder.setView(view)
.setTitle("Customize")
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setPositiveButton("Let's go!", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction("fromDialog");
intent.putExtra("ratio",getRatio(paperSizeSpinner.getSelectedItem().toString()));
if(isOrientationSpinnerVisible){
intent.putExtra("isCustom",false);
intent.putExtra("orientation",orientationSpinner.getSelectedItem().toString());
} else {
intent.putExtra("isCustom",true);
}
intentProvider.getIntent(intent);
}
});
widthEditText = view.findViewById(R.id.width_et);
heightEditText = view.findViewById(R.id.height_et);
widthEditText.setEnabled(false);
heightEditText.setEnabled(false);
paperSizeSpinner = view.findViewById(R.id.paper_size_spinner);
orientationSpinner = view.findViewById(R.id.orientation_spinner);
// ArrayList for populating paperSize spinner via paperSizeAdapter
ArrayList<String> paperSizes = new ArrayList<>();
paperSizes.add("A0");
paperSizes.add("A1");
paperSizes.add("A2");
paperSizes.add("A3");
paperSizes.add("A4");
paperSizes.add("A5");
paperSizes.add("Custom");
// ArrayList for populating orientation spinner via orientationAdapter
ArrayList<String> orientation = new ArrayList<>();
orientation.add("Portrait");
orientation.add("Landscape");
// arrayAdapters containing arraylists to populate spinners
ArrayAdapter paperSizeAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_spinner_dropdown_item, paperSizes);
ArrayAdapter orientationAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_spinner_dropdown_item, orientation);
paperSizeSpinner.setAdapter(paperSizeAdapter);
orientationSpinner.setAdapter(orientationAdapter);
paperSizeSpinner.setSelection(4);
paperSizeSpinner.setOnItemSelectedListener(this);
orientationSpinner.setOnItemSelectedListener(this);
return builder.create();
}
// These are some important complex ui functionalities
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent.getId() == R.id.paper_size_spinner) {
if (position == 6) {
widthEditText.setEnabled(true);
heightEditText.setEnabled(true);
orientationSpinner.setEnabled(false);
isOrientationSpinnerVisible = false;
} else {
widthEditText.setEnabled(false);
heightEditText.setEnabled(false);
orientationSpinner.setEnabled(true);
isOrientationSpinnerVisible = true;
}
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
// interface used to communicate with the parent activity
public interface IntentProvider {
// this method is used to provide the intent to the parent activity
void getIntent(Intent intent);
}
// instantiating the interface object and throwing error if parent activity does not implement this interface
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
intentProvider = (IntentProvider) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement IntentProvider");
}
}
}
MainActivity class:
public class MainActivity extends AppCompatActivity implements CustomizeDialog.IntentProvider {
// field declarations go here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.image);
// instantiating the dialog
final CustomizeDialog dialog = new CustomizeDialog();
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// showing the dialog on click
dialog.show(getSupportFragmentManager(),"");
}
});
}
// via this method I receive the intent from the dialog
// I know intent might not be the best option for this function but let's let it be here for now
#Override
public void getIntent(Intent intent) {
ratio = intent.getFloatExtra("ratio",3);
isCustom = intent.getBooleanExtra("isCustom",false);
orientation = intent.getStringExtra("orientation");
launchChooser();
}
}
Let me know in the comments if you want the layout code for the dialog.
What I tried:
Implementing threading so that my dialog is ready in a background thread and show it onButtonClick. But this is not allowed in general as any other thread except UI thread aren't supposed to touch UI related events.
Using onCreateView instead of onCreateDialog to inflate the layout directly.
Making the dialog a global variable, initialized it in onCreate and then show the dialog onButtonClick.
Switched to CONSTRAINT LAYOUT
Using an activity as a dialog by setting the dialog theme to the activity in the manifest file.
Launched my app in a device with better hardware than mine.
BUT NOTHING WORKED
What I want:
Why is my dialog janky? and what I need to do to make the dialog pop up faster?
In case anybody wants here's the link to my app repo on github.
AlertDialog and DialogFragment frameworks are slow because they need to some time to do calculations and fragment stuffs. So a solution to this problem is, using the Dialog framework straight away.
Use the Dialog framework's constructor to initialize a Dialog object like this:
Dialog dialog = new Dialog(context, R.style.Theme_AppCompat_Dialog);
// the second parameter is not compulsory and you can use other themes as well
Define the layout and then use dialog.setContentView(R.layout.name_of_layout).
Use dialog.findViewById(R.id.name_of_view) to reference views from the dialog's layout file
And then implement the logic just like anyone would do in an activity class. Find out the best implementation for your use case by reading the official documentation.
I would like to like to ask you how I call a method in activity from adapter recycler view.
In function buildRecyclerView there is set up the adapter:
private void buildRecyclerView() {
offerAdapter = new OfferAdapter();
recyclerView.setAdapter(offerAdapter);
}
In class OfferAdapter.java there is created submenu for each item and with onMenuItemClickListener:
#Override
public void onBindViewHolder(NoteHolder holder, int position) {
PopupMenu popup = new PopupMenu(mCtx, holder.button);
popup.inflate(R.menu.menu);
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
// TODO here I want to call delete item in MyOfferFragment.java
}
The main question: How I can call from *onMenuItemClickListnere* the function in *MyOfferFragment*.
Thank you very much in advance
You can pass a listener object in the constructor which implements by fragment OR activity
/**
* item click interface of adapter
*/
public interface OfferAdapterListener {
void onItemClick(int position)
}
This interface implent by fragment
/**
* On item clicked Implement Method from adapter listener.
*
* #param position
*/
#Override
public void onItemClick(int position) {
// Here you can call that method
}
then you pass this listener in the constructor of the adapter.
private void buildRecyclerView() {
offerAdapter = new OfferAdapter(this);
recyclerView.setAdapter(offerAdapter);
}
In the constructor, you can assign like this
private OfferAdapterListener mOfferAdapterListener;
public OfferAdapter(OfferAdapterListener mOfferAdapterListener) {
this.mOfferAdapterListener = mOfferAdapterListener
}
}
Now you can use this listener by setting click listener on anyViwe like this
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mOfferAdapterListener.onItemClick(position);
}
});
It returns to call the method of onItemClick which implements this method.
OR
You can pass activity or fragment context in the constructor like above and call it through by it reference like this
((MainActivity) mActivity).methodName(arguments);
Here is mActivity reference context which you pass through in constructor.
You can add the MyOfferFragment to your adapter constructor.
private void buildRecyclerView() {
offerAdapter = new OfferAdapter(this); // Adding fragment to constructor
recyclerView.setAdapter(offerAdapter);
}
In OfferAdapter.java:
private MyOfferFragment mFragment; // field variable
OfferAdapter (MyOfferFragment fragment){
mFragment = fragment;
}
You can then access MyOfferFragment methods via mFragment:
mFragment.deleteItem();
However, I think it is a more conventional way to just move methods related to RecyclerView into the adapter if possible. You can refer to this if you want to delete an item from RecyclerView: Android RecyclerView addition & removal of items
Here there is minor issue Like I had Recyclerview in dialog fragment.ie name of bank in recyclerview When we select one bank in recyclerview and after dialogfragment dismiss that name should be appear on Button ie when we selected Union Bank from dialog fragment it should appear on button.Issue is when we click on button then its text changes rather then on time of dismiss listener
here is Dialog dismissal code:
mRecyclerView.addOnItemTouchListener(new RecyclerTouchListener(getContext(), mRecyclerView, new ClickListener() {
#Override
public void onClick(View view, final int position) {
Employee e = bank.get(position);
Toast.makeText(getContext(), e.getBank_id() + "" + e.getBank_name(), Toast.LENGTH_SHORT).show();
getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialogInterface) {
Employee e = bank.get(position);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
SharedPreferences.Editor edit = sp.edit();
edit.putString("bankname", e.getBank_name());
edit.commit();
}
});
c.onItemSelect(e.getBank_name());
onDismiss(getDialog());
}
Here is onclick event where dialog opens and where the value should be printed:
select_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FragmentManager fm=getFragmentManager();
DialogRecyclerview dr = new DialogRecyclerview(AccountManagement_banks.this,callback);
dr.setRetainInstance(true);
dr.show(getSupportFragmentManager(), "Dialog");
SharedPreferences st = PreferenceManager.getDefaultSharedPreferences(AccountManagement_banks.this);
String mode=st.getString("bankname","");
select_button.setText(mode);
Toast.makeText(getApplication(),mode,Toast.LENGTH_SHORT).show();
}
});
Same in:
#Override
public void onItemSelect(String text) {
select_button.setText(text);
}
Here I had created new Interface:
public interface CallBack {
void onItemSelect(String text);}
just create a callback and implement it on your main class (where you want to display the name) and pass the callback instance to adapter. Now dialog fragment, now when you are selecting any item just call callback function which is overridden in main calss and inside this function just change the text of your button.
public interface CallBack {
void onItemSelect(String text);
}
implement this in your main class like
public class MainActivity extends Activity implements CallBack {
.
.
.
public void onItemSelect(String text){
button.setText(text);
}
.
.
}
when you are opening your dialogfragment from your main activity just pass MainActivity.this as an argument in the dialog constructor. And in your Dialog class constructor write your code like this
private Callback callback;
public YourDialog(Context context, Callback callback){
this.callback = callback;
}
and when you selecting list item just call
callback.onItemSelect(e.getBank_name());
Hope it will help you out.
Could you please help with the below:
I am trying to call the method deletePlayer inside the fragment PlayersActivityFragment from the alertdialog NameAlertDialogFragment.
The code is below:
public static class PlayersActivityFragment extends Fragment {
ArrayList<Player> arrayPlayers;
ListView listViewPlayers;
//PlayerAdapter adapter;
public PlayersActivityFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
arrayPlayers = new ArrayList<Player>();
View rootView = inflater.inflate(R.layout.fragment_activity_players, container, false);
Button buttonAddPlayer = (Button) rootView.findViewById(R.id.button_addplayers);
buttonAddPlayer.setOnClickListener(new View.OnClickListener(){
public void onClick(View view) {
arrayPlayers.add(new Player("Player", 0));
Player selectedPlayer = arrayPlayers.get(arrayPlayers.size()-1);
((PlayersActivity)getActivity()).showNameDialogFragment(selectedPlayer);
}
});
listViewPlayers = (ListView) rootView.findViewById(R.id.listView_playername);
return rootView;
}
public void deletePlayer(){
arrayPlayers.remove(arrayPlayers.size()-1);
}
}
void showNameDialogFragment(Player player) {
mDialog = NameAlertDialogFragment.newInstance(player);
mDialog.show(getFragmentManager(),"SCORE DIALOG");
}
// Class that creates the AlertDialog
public static class NameAlertDialogFragment extends DialogFragment {
static Player selectedPlayer;
public static NameAlertDialogFragment newInstance(Player player) {
selectedPlayer = player;
return new NameAlertDialogFragment();
}
// Build AlertDialog using AlertDialog.Builder
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
final View view = inflater.inflate(R.layout.alertdialog_name, null);
final EditText editTextName = (EditText) view.findViewById(R.id.edittext_name);
return new AlertDialog.Builder(getActivity())
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
.setView(view)
.setMessage("Enter Player's Name:")
//Set up Yes Button
.setPositiveButton("Done", new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, int id) {
mName = editTextName.getText().toString().trim();
selectedPlayer.setName(mName);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//PlayersActivityFragment playersActivityFragment = (PlayersActivityFragment) getFragmentManager().findFragmentById(R.id.container);
//playersActivityFragment.deletePlayer();
//((PlayersActivityFragment)getTargetFragment()).deletePlayer();
NameAlertDialogFragment.this.getDialog().cancel();
}
})
.create();
}
}
The two different ways I have tried to call the methods are commented out in the .setNegativeButton onClickListener:
PlayersActivityFragment playersActivityFragment = (PlayersActivityFragment) getFragmentManager().findFragmentById(R.id.container);
playersActivityFragment.deletePlayer();
and
((PlayersActivityFragment)getTargetFragment()).deletePlayer();
Thank you!
First of all, why are all of your classes static? Anyway, here's an answer that should work...
Try using an interface as a callback. For example:
First create an interface.
public interface NameAlertDialogListener {
public void onNegativeClick();
}
Then have PlayersFragment implement NameAlertDialogListener.
public static class PlayersActivityFragment extends Fragment implements NameAlertDialogListener
Next, in the PlayersFragment, create a method called onNegativeClick.
#Override
public void onNegativeClick() {
//delete or whatever you want to do.
}
Create a member variable for the listener:
static Player selectedPlayer;
static NameAlertDialogListener mCallBack;
Next create a method in the dialog fragment called setListener.
public void setListener(NameAlertDialogListener callback) {
try {
mCallBack = callback;
} catch (ClassCastException e){
throw new ClassCastException(callback.toString() + " must implement NameAlertDialogListener" );
}
}
Then, when you create the dialog fragment call the setListener method.
void showNameDialogFragment(Player player) {
mDialog = NameAlertDialogFragment.newInstance(player);
mDialog.setListener(this);
mDialog.show(getFragmentManager(),"SCORE DIALOG");
}
Lastly, in your negative click listener:
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mCallBack.onNegativeClick() ;
NameAlertDialogFragment.this.getDialog().cancel();
}
})
I am not sure if this is the correct way of doing things, but I have come to a working solution.
First I moved ArrayList<Player> arrayPlayers; outside of the PlayersActivityFragment fragment.
Then I moved the method:
public void deletePlayer(){
arrayPlayers.remove(arrayPlayers.size()-1);
}
outside of the PlayersActivityFragment fragment.
I then called the deletePlayer() method inside the alertdialog with the line ((PlayersActivity)getActivity()).deletePlayer();.
Actually, I have a little hack, it's not really good, but it's easy to implement: declare PlayersActivityFragment variable in your DialogFragment. Then change your constructor to:
public static NameAlertDialogFragment newInstance(Player player,PlayersActivityFragment fragment ){
selectedPlayer = player;
NameAlertDialogFragment test = new NameAlertDialogFragment();
test.playerActivityFragment = fragment;
return test;
}
Then you can call playerActivityFragment.deletePlayer() everywhere in your DialogFragment.
P/s: The best way is implement interface, but for lazy coder like me, the method above is better lol!
In my main fragment, I have a listView called notesListView. noteAdapter populates notesListView. When user long clicks on one of the notesListView's elements, a dialog shows up and asks if user really wants to remove an item. If he agrees, then that item is removed from the database. If not - then life goes on.
The issue is that my Dialog is other class (other Fragment). For this class, I pass my database object and noteAdapter object as well, so it could remove item from database and then notify noteAdapter that data has changed. Sounds good enough, but it doesn't work, and I have absolutely no idea why. Give it a look please and help me out.
This is a method in mainFragment, which handles the mentioned listView:
public void handleNotes(final ListView notesListView) {
if (database.getNoteCount() != 0) {
notesListView.setAdapter(noteAdapter);
notesListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
TextView textViewId = (TextView) view.findViewById(R.id.textViewId);
DeleteNoteFragment newFragment = new DeleteNoteFragment(database, noteAdapter, Integer.parseInt(textViewId.getText().toString()));
newFragment.show(getActivity().getSupportFragmentManager(), "deleteConfirmation");
return false;
}
});
}
}
As you can see, DeleteNoteFragment is being created and then shown.
Lets look at DeleteNoteFragment itself:
public class DeleteNoteFragment extends DialogFragment {
private Database database;
private NoteAdapter noteAdapter;
private int i;
public DeleteNoteFragment(Database database, NoteAdapter noteAdapter, int i) {
this.database = database;
this.noteAdapter = noteAdapter;
this.i = i;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_delete_note)
.setPositiveButton(R.string.dialog_delete_confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Toast.makeText(getActivity(), "Note deleted successfully!", Toast.LENGTH_LONG).show();
}
})
.setNegativeButton(R.string.dialog_delete_denny, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
Maybe you can spot where I am making a mistake, or got any tips how to solve this issue?
You are deleting the data in database but not in the adapter:
database.removeNote(i);
noteAdapter.notifyDataSetChanged();
Creates a method in the adapter to get the list that you have in the adapter. Something like:
database.removeNote(i);
noteAdapter.getListOfItems().remove(i);
noteAdapter.notifyDataSetChanged();
notifyDataSetChanged - "Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself." It doesn't reload data from database. You still have to remove the item from the adapter by calling remove method and then call notifyDataSetChanged