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
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.
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();
}
I know there are similar questions regarding this issue, but none of them worked for me.
I'm making an alarm clock app that has a ListView showing all alarms that are stored in the database. When the user long clicks one of the ListView items, it shows a dialogue confirming they really want to delete the selected alarm, after clicking the 'Yes' button, the alarm is deleted.
The problem is my ListView only refreshes after I start another activity and then go back to the one where the ListView is, I'd like to know what I need to do to refresh it as soon as the alarm is deleted.
PS.: I've already tried adapter.notifyDataSetChanged();
Here is my code:
UITools.adaptAlarmsListView(this, listView, R.layout.alarm_listview_item);
listView.setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener()
{
#Override
public boolean onItemLongClick(AdapterView<?> adapterView,
View view,
final int position, long l)
{
final boolean[] deletedFlag = {false}; // Tells if the alarm has been deleted
UITools.showDialogue(HomeActivity.this,
getString(R.string.delete),
getString(R.string.delete_question),
R.drawable.bin, getString(R.string.no),
new DialogInterface.OnClickListener()
{
#Override
public void onClick(
DialogInterface dialogInterface, int i)
{
// Do nothing
}
}, getString(R.string.yes),
new DialogInterface.OnClickListener()
{
#Override
public void onClick(
DialogInterface dialogInterface, int i)
{
AlarmDAO.delete(getBaseContext(),
position + 1);
UITools.showToast(getBaseContext(),
getString(R.string.deleted),
Toast.LENGTH_SHORT);
deletedFlag[0] = true;
}
});
if (deletedFlag[0])
{
listView.setAdapter(null);
UITools.adaptAlarmsListView(HomeActivity.this,
listView, R.layout.alarm_listview_item);
}
return true;
}
}
);
UITools.adaptAlarmsListView:
/**
* Adapts the alarms ListView
* #param context - Context
* #param listView - ListView
* #param listViewItemId - int
*/
public static void adaptAlarmsListView(Context context, ListView listView,
int listViewItemId)
{
Alarm[] alarms = AlarmDAO.getAlarms(context);
AlarmAdapter adapter = new AlarmAdapter(context, listViewItemId, alarms);
listView.setAdapter(adapter);
}
If your are using a fragment, just create an interface on that fragment
and over the interface in the activity that hosts the fragment and
replace the present fragment with itself. or
create a method the re-initializes the listview, it adapter and the list.
if this is not clear enough, paste your code so i can check where the problem is.
The way my dialog is set up currently, it is supposed to take the values from the EditTexts and save in into a database (a process simplified through Sugar ORM), then place the newly created SubjectInfo object into the RecyclerView. The way the notifyDataSetChanged(); is included gives me errors concerning the Thread (basically no thread is waiting upon the change in the data set). SO, I have two paths as I see it, but I'm still confused as to how each approach would work.
Option 1: Somehow revoke the onCreate() method in the SubjectManagerActivity so that the adapter responds to the new database. (How to revoke an onCreate method?)
Option 2: Create a custom Dialog Fragment Activity. Does this navigate back up to recreate the parent activity?
Please help explain how to make the notifyDataSetChanged(); respond, because once that line is removed, there are no errors, but I can't see the new Subject card until I restart the app.
Here is my code:
public class SubjectManagerActivity extends ActionBarActivity {
public static ArrayList<SubjectInfo> subjectList = new ArrayList<SubjectInfo>();
public static FloatingActionButton fabCreateSubject;
private AlertDialog.Builder build;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_subject_manager);
RecyclerView recList = (RecyclerView) findViewById(R.id.subject_card_list);
recList.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);
subjectList = getSubjectInfoArrayList();
SubjectAdapter sa = new SubjectAdapter(subjectList);
recList.setAdapter(sa);
fabCreateSubject = (FloatingActionButton) findViewById(R.id.fab_create_subject);
fabCreateSubject.setOnClickListener (new View.OnClickListener() {
#Override
public void onClick(View v) {
build = new AlertDialog.Builder(SubjectManagerActivity.this);
LayoutInflater inflater = getLayoutInflater();
View alertview = inflater.inflate(R.layout.create_subject_dialog, null);
// Pass null as the parent view because its going in the dialog layout
build.setView(alertview);
final EditText inputSubjectName = (EditText) alertview.findViewById(R.id.dialog_edit_subject_card_name);
final EditText inputSubjectGrade = (EditText) alertview.findViewById(R.id.dialog_edit_subject_card_grade);
build.setTitle("Add Subject")
.setPositiveButton("Save", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
String enteredSubjectName = inputSubjectName.getText().toString();
boolean enteredSubjectIsArchived = false;
if((!(inputSubjectName.getText().toString().equals("")))) {
SubjectInfo si = new SubjectInfo(enteredSubjectName, enteredSubjectIsArchived);
si.save();
}
dialog.cancel();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = build.create();
alert.show();
}
});
}
public ArrayList<SubjectInfo> getSubjectInfoArrayList(){
ArrayList<SubjectInfo> sial= new ArrayList<SubjectInfo>();
List<SubjectInfo> sil = SubjectInfo.listAll(SubjectInfo.class);
sial.addAll(sil);
notifyDataSetChanged;
return sial;
}
It turns out often the most effective way to deal with an issue is to trust in your code and research to make it work! For anyone else with a similar issue, I included this code:
si.save();
subjectList.add(si);
sa.notifyDataSetChanged();
The method notifyDataSetChanged(); had to be directed to an adapter, which is the container of the Thread. Though the adapter isn't being populated from the getSubjectInfoArrayList() method in real-time (when the dialog is prompted), the ArrayList is still acquiring the same value and when the app is reopened, it can populate the adapter from the database (and in turn, the same get( ) method).
I'm currently facing a stupid issue. I've a listview with a custom adapter (extending CursorAdapter).
It displays a custom view with different buttons (share, like, delete). For the delete button, it has an alertdialog to confirm the removal of the item. When the user confirms, the item is deleted from the db. Everything works fine until here.
My question is, how can I update my listview efficiently with my new dataset?
Thanks a lot for your help.
Code:
public class CommentCursorAdapter extends CursorAdapter{
(...)
#Override
public void bindView(View view, Context arg1, Cursor cursor) {
(...)
holder.list_item_comment_discard_btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final String _id = v.getTag().toString();
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle("Delete");
builder.setMessage("Do you want to delete "+_id);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
DBAdapter dba = new DBAdapter(mActivity);
dba.open();
dba.remove(_id);
Log.i("TAAG", "removed: "+_id);
dba.close();
// How to update the listview ??
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
AlertDialog d = builder.create();
d.show();
}
});
holder.list_item_comment_discard_btn.setTag(_id);
(...)
}
(...)
}
If you use LoaderManager to manage the lifecycle of your cursors (that is the right way to do this) you need only to call restartLoader and then (re)set the cursor of your adapter in onLoadFinished. Your listview will be reloaded with updated data.
The management of a cursor in your activity or fragment should be done in a very simple and straightforward way:
In order to initialize your cursor:
public void onCreate(Bundle savedInstanceState) { // or onStart
// ...
this.getLoaderManager().initLoader(MY_CURSOR_ID, null, this);
// ...
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// TODO: create a cursor loader that will load your cursor here
}
public void onLoadFinished(Loader<Cursor> arg0, final Cursor arg1) {
// TODO: set your cursor as the cursor of your adapter here;
}
public void onLoaderReset(Loader<Cursor> arg0) {
// TODO: set null as the cursor of your adapter and 'free' all cursor
// references;
}
And then, when you need to reload your data:
this.getLoaderManager().restartLoader(MY_CURSOR_ID, null, this);
Call
cursor.requery();
notifyDataSetChanged();
.
If you want real performance use the AsyncTaskLoader.
Just requery the Cursor like this:
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
DBAdapter dba = new DBAdapter(mActivity);
dba.open();
dba.remove(_id);
Log.i("TAAG", "removed: "+_id);
dba.close();
cursor.requery();
notifyDataSetChanged();
}
and then call notifyDataSetChanged() to tell the Adapter that the data has changed, and it has to build the adapter again.
This will be an intense operation if the requery takes long. Consider doing it in another thread.
After you delete the item in the DB, call notifyDataSetChanged on your CommentCursorAdapter, granted with this anonymous inner class you are using you will need a final reference to this since this in inside your CursorAdapter. This should trigger a refresh of your ListView.
http://developer.android.com/reference/android/widget/BaseAdapter.html#notifyDataSetChanged()