notifyDataSetChange don't work properly on custom adapter - java

I'm trying to add some item to db, add on success to reload my listview in a Fragment.
Actually I do have something like that.
db.collection("quizz").document().set(Quizz).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(context, getString(R.string.quizz_done), Toast.LENGTH_SHORT).show();
Quizz nQuizz = new Quizz(""+items.size()+1,""+mScore,uid,now);
items.add(nQuizz);
adapterquizz.notifyDataSetChanged();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(context,getString(R.string.quizz_not_done), Toast.LENGTH_SHORT).show();
}
});

U only update the list when u init new instance of your adapter. U need to create an update function inside your adapter:
public void updateData(List<Quizz> yourNewList) {
data.clear(); // remove this if u just want to insert new item instead of clear all
data.addAll(yourNewList)
notifyDatasetChange()
}
Call this from activity when u need to update data.
You don't need to pass the list to your adapter constructor anymore.

Related

Recycler view mixing up the contents of its items

I'm using a recycler view to load a list of posts made by and organization. I've used firestore as the backend.
For loading the image I'm first getting the download Url from storage reference and then using Glide to load image in to the image view.
The issue i'm facing is that since a call to the storage reference is an asynchronous one, by the time it gets the download uri from the server the position of the adapter in OnBindviewHolder is already changed Therefore image gets mixed up.
I've provided the OnBindViewHolder code and the method where i get the download url
#Override
public void onBindViewHolder(#NonNull final studentFavouriteUniversityPosts.MyViewHolder holder, int position) {
if(postsList.get(holder.getAdapterPosition()).getImageUrl()!=null &&
!postsList.get(holder.getAdapterPosition()).getImageUrl().isEmpty()){
holder.setPostImage(holder.getAdapterPosition());
}
}
public void setPostImage(int position) {
postImage.setVisibility(View.VISIBLE);
placeholder.placeholder(R.color.white);
//Getting the download uri from the Fire store storage and displaying it using glide.
storageReference.child(postsList.get(position).getImageUrl())
.getDownloadUrl()
.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
// Log.d("URI",uri.toString());
Glide.with(context).applyDefaultRequestOptions(placeholder).load(uri).into(postImage);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("ERROR","err loading image file");
}
});
}
Can anyone of you help me out on this?
EDIT
I changed the structure a bit. So instead of running the storage reference task in adapter, i'm storing the dowload url in the object. Thus the setPostImage method only has the Glide part.
So the method looks like this
public void setPostImage(String downloadURL) {
postImage.setVisibility(View.VISIBLE);
Glide.with(context).applyDefaultRequestOptions(placeholder).load(downloadURL).into(postImage);
}
Still i'm getting this issue. I dont know how to resolve it
First get the list of urls from firestore from the activity or fragment where you are
setting the adapter
1. public void getImages() { storageReference.child("the child name")
.getDownloadUrl()
.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
// Log.d("URI",uri.toString());
adapter.imagesRetrieved();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("ERROR","err loading image file");
}
});
}
2. Then inside on success listener pass the group of values to the adapter method from the success listener and define the method inside the adapter
public void imagesRetreived() {
notifyDataChanged(images);
}
private void notifyDataChanged(Images images) {
this.images = images;
notifydatasetchanged(); //this one is recycler view inbuilt method
}

Set a particular data on Firestore at a specific time using CountDownTimer

Hello i want to set a particular data on firestore at a specific time using countDownTimer. When my countDown finish, the data is well set on firestore but each time the activity is update, the data is reset again and again.
Please there is a possible way to destroy the finish method when my data is set once ?. if you have other suggestion to give your welcome
firebaseAuth=FirebaseAuth.getInstance();
firebaseFirestore=FirebaseFirestore.getInstance();
long mil=200000;
downTimer =new CountDownTimer(mil,1000) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
Map<String,Object> map=new HashMap<>();
map.put("Password","userPassword");
firebaseFirestore.collection("UsersName").document().set(map).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(ForgetPasswordActivity.this, "Done sir", Toast.LENGTH_SHORT).show();
downTimer.cancel();
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
}
});
}
}.start();
}
}
Every time your activity is restarted, a new instance of the timer is created and lives as long as your activity is alive.
If you need a single timer which will survive to activity lifecycle, you should put it in a foreground (or background) service or something similar.
Because you're using firebase, the other solution would be a cloud function.

Refresh adapter in a Fragment after DialogFragment dbflow insert

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.

Fetching data from firebase taking more than one button click to display

I want to display data which is fetched from firebase whenever user clicks a button. This is the code which I am using currently
todayPoem = (TextView) findViewById(R.id.todpoem);
/*Firebase related code*/
myFirebaseRef = new Firebase("https://firebase url");
myFirebaseRef.child("poem").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
System.out.println(snapshot.getValue());
s1 = (String) snapshot.getValue();
}
#Override
public void onCancelled(FirebaseError error) {
}
});
}
public void onClickPoem(View v) {
todayPoem.setTextSize(20);
todayPoem.setText(s1);
}
And the xml file contains a textview and a button to fetch data from the firebase. But the problem is whenever I click the button the data is not displayed instantly, usually it takes almost 5-6 button clicks for the data to be displayed. What am I doing wrong here?
I had this problem before... I would advice you doing it like this example:
mrf.setAndroidContext(this);
mrf = new Firebase('Firebase url');
final Firebase myData = mrf.child("info");
myData.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
x1 = dataSnapshot.getValue(String.class);
x2 = x1; // Global variable
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
use variable 'x2' to do what you want. I don't why but there is some problem with using the same variable that was used to receive Data from Firebase.
Hope it work with you...
Also if you would like the user to know that data are being retrieve you can use progressBar with Handler. After a button is clicked and 'x1' equals null do something like this: progressBar.setVisibility(View.VISIBLE);
then
handler.postDelayed(new Runnable() {
public void run() {
progressBar.setVisibility(View.GONE);
t.setText(x2);
}
}, 800);
Good luck..

How to use results from a Firebase query to ultimately populate a ListView in Android Studio

I found that my ListView is not updated when the app is run because the adapter is set before the Firebase query occurs.
First Question: Is there anyway to set a listener for query completion? This way I can set the adapter there instead. I know some Firebase methods auto-generate onComplete()/onSuccess() listeners.
For debugging purposes I can trigger an update to the ListView by re-entering onResume(). This is where I receive an error message:
The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(16908298, class android.widget.ListView) with Adapter(class android.widget.ArrayAdapter)]
To my understanding this error means I cannot add items to my ArrayList "usernames" inside the onChildAdded() method if I use usernames in my ArrayAdapter.
Second Question: How else can I store the values returned from my Firebase query and use those to ultimately populate a ListView without directly changing its contents from a "background thread"?
My attempt:
public class EditFriendsActivity extends AppCompatActivity {
protected ArrayList<String> usernames = new ArrayList<>();
#Override
protected void onResume() {
super.onResume();
Firebase usernameRef = ref.child("users");
Query queryRef = usernameRef.orderByKey();
queryRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String username = (String) dataSnapshot.child("username").getValue();
usernames.add(username);
Log.v(TAG, usernames + "");
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
Log.v(TAG, "Inside ChildChanged");
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.v(TAG, "Inside ChildRemoved");
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
Log.v(TAG, "Inside ChildMoved");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(TAG, firebaseError.getMessage());
AlertDialog.Builder builder = new AlertDialog.Builder(EditFriendsActivity.this);
builder.setTitle(R.string.error_title)
.setMessage(firebaseError.getMessage())
.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.create();
dialog.show();
}
});
// Array adapter
ArrayAdapter<String> adapter = new ArrayAdapter<>(
EditFriendsActivity.this,
android.R.layout.simple_list_item_multiple_choice,
usernames);
mFriendsList.setAdapter(adapter);
}
}
Updated Code: Fixed typo in listener from marked answer and cleared list each time.
Firebase userRef = ref.child("users");
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
usernames.clear(); // Clear list before updating
for (DataSnapshot child : dataSnapshot.getChildren()) {
String username = (String) child.child("username").getValue();
usernames.add(username);
Log.v(TAG, usernames + "");
}
Follow Up Question Here
Is there anyway to set a listener for query completion? This way I can set the adapter there instead. I know some Firebase methods auto-generate onComplete()/onSuccess() listeners.
Firebases synchronizes data from a backend to your application. So unlike a traditional database that has a request/response flow, Firebase queries don't really complete. That's why in the FirebaseUI library we keep an open listener and notify the ListView of any changes.
That said: if you just want to load the data from a location once, you can use addSingleValueEventListener:
queryRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child: snapshot.getChildren()) {
String username = (String) child.child("username").getValue();
usernames.add(username);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(
EditFriendsActivity.this,
android.R.layout.simple_list_item_multiple_choice,
usernames);
mFriendsList.setAdapter(adapter);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(TAG, firebaseError.getMessage());
}
});
There are probably typos in the above, but this is the general gist of it.
You can simply use a Query
Query ref = FirebaseDatabase.getInstance().getReference()
.child("users").orderByChild("name");

Categories

Resources