I am trying to create a timer for user-generated posts that is displayed on the posts. I am using Firestore to store all of the information about the posts.
The code I have does display the timer but when multiple posts are made by multiple users the timer flashes between the correct time and another count down which is unrelated as far as I can see. Here is what I have:
This is how I create each post.
post.setTimer(data.getStringExtra("timerDuration"));
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, setEndDate());
Date endtime = cal.getTime();
post.setTimerEndDate(endtime);
addPhotoInfoToDatabase();
finish();
My Adapter for the Fragment:
adapter = new FirestoreRecyclerAdapter<Post, PostViewHolder>(options) {
@Override
protected void onBindViewHolder(@NonNull PostViewHolder postViewHolder, int position, @NonNull Post post) {
postViewHolder.setPost(post);
}
@NonNull
@Override
public PostViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_view_layout, parent, false);
return new PostViewHolder(view);
}
};
If there is more information you need please let me know. Thank you for any help.
Edit:
My app also contains a ViewPager
and I have noticed that when I swipe away from the problem timer and swipe back the issue is fixed.
Update:
void setPost(final Post post) {
ImageView imageView1 = view.findViewById(R.id.firstImageCardView);
ImageView imageView2 = view.findViewById(R.id.secondImageCardView);
ImageView profilePic = view.findViewById(R.id.user_image);
TextView username = view.findViewById(R.id.user_name);
final TextView timer = view.findViewById(R.id.timer);
final Button chooseRight = view.findViewById(R.id.choose_right);
final Button chooseLeft = view.findViewById(R.id.choose_left);
final Button follow = view.findViewById(R.id.follow);
String displayName;
final String userId = currentUser.getUid();
final String postId = post.getUserId();
setProfilePicture(post);
final DocumentReference docRefUsers = db
.collection("users")
.document(currentUser.getUid());
final DocumentReference docRefFollowing = db
.collection("following")
.document(currentUser.getUid())
.collection("UserIsFollowing")
.document(post.getUserId());
final DocumentReference docRefPosts = db
.collection("posts")
.document(post.getUserId())
.collection("userPosts")
.document(post.getDate().toString());
if (userId.equals(postId)) {
displayName = "Me";
chooseLeft.setEnabled(false);
chooseRight.setEnabled(false);
follow.setText("");
follow.setEnabled(false);
} else if (post.getUsername() == null) {
displayName = "Anonymous";
} else {
displayName = post.getUsername();
}
if (post.getProfilePicture() != null) {
Picasso.get().load(post.getProfilePicture())
.transform(new CircleTransformActivity())
.fit()
.centerCrop()
.into(profilePic);
username.setText(displayName);
} else {
Picasso.get().load(R.drawable.blank_profile_pic)
.transform(new CircleTransformActivity())
.fit()
.into(profilePic);
}
/***********************************************************/
if(post.getTimerEndDate() != null) {
Date date = java.util.Calendar.getInstance().getTime();
long currentTime = date.getTime();
long endTime = post.getTimerEndDate().getTime();
long timeLeft = endTime - currentTime;
new CountDownTimer(timeLeft, 1000) {
@SuppressLint("DefaultLocale")
public void onTick(long millisUntilFinished) {
timer.setText(String.format("%02d:%02d:%02d",
(int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
(int) ((millisUntilFinished / (1000 * 60)) % 60),
(int) (millisUntilFinished / 1000) % 60));
//here you can have your logic to set text to edittext
}
public void onFinish() {
timer.setText("done!");
}
}.start();
}
Picasso.get()
.load(post.getImageUrl_1())
.fit()
.transform(new RoundedCornersTransformation(30, 30))
.into(imageView1);
Picasso.get()
.load(post.getImageUrl_2())
.fit()
.transform(new RoundedCornersTransformation(30, 30))
.into(imageView2);
if (!postId.equals(userId)) {
docRefFollowing.addSnapshotListener(new EventListener<DocumentSnapshot>() {
@Override
public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
follow.setText("Following");
follow.setEnabled(false);
}
}
});
}
follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final Map<String, Object> following = new HashMap<>();
following.put("Exists", true);
docRefFollowing.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
docRefFollowing.set(following);
docRefUsers.update("following", FieldValue.increment(1));
}
}
});
db.collection("users")
.document(postId)
.update("followers", FieldValue.increment(1));
follow.setText("Following");
}
});
if (!postId.equals(userId)) {
docRefPosts.collection("voted")
.document(currentUser.getUid()).addSnapshotListener(new EventListener<DocumentSnapshot>() {
@Override
public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
whichVoted = documentSnapshot.get("votedFor").toString();
}
if (whichVoted != null) {
switch (whichVoted) {
case ("left"):
chooseLeft.setEnabled(false);
chooseRight.setEnabled(true);
break;
case ("right"):
chooseRight.setEnabled(false);
chooseLeft.setEnabled(true);
break;
}
} else {
chooseRight.setEnabled(true);
chooseLeft.setEnabled(true);
}
}
});
}
chooseLeft.setText(post.getLeftText());
chooseRight.setText(post.getRightText());
chooseLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
chooseLeft.setEnabled(false);
chooseRight.setEnabled(true);
upLeft(docRefPosts, chooseLeft);
}
});
chooseRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
chooseRight.setEnabled(false);
chooseLeft.setEnabled(true);
upRight(docRefPosts, chooseRight);
}
});
}
Firstly, you should be aware that RecyclerView re-uses its viewholders each time you scroll past the last visible item on the screen.
Based on your implementation for every time a user scroll past the last visible item or post on the screen, because it is inside a RecyclerView the original first-item on the list and even other items being scrolled past are recycled and the next item becomes the new first-item but being populated with a different post-values as expected.
But remember your code instantiates a new CountdownTimer for every new post added to the list (where 50 posts equals 50 Countdowntimers and 50 posts does not necessarily mean 50 views because some of the views would probably be recycled by the RecyclerView; you get the logic), hence it allows the possibility for multiple CountdownTimer accessing same view which causes the flash and will surely lead to a memory leak causing an OutOfMemoryError when the user keeps scrolling continuously on a long list.
Recommended Implementation
Create your CountDownTimer inside your ViewHolder Class
public static class PostViewHolder extends RecyclerView.ViewHolder {
...
CountDownTimer countdowntimer;
public FeedViewHolder(View itemView) {
...
}
}
Access the timer inside setPost method
void setPost(final Post post) {
...
long currentTime = date.getTime();
long endTime = post.getTimerEndDate().getTime();
long timeLeft = endTime - currentTime;
if (countdowntimer != null) {
//there is an existing timer so we cancel it to prevent duplicates
countdowntimer.cancel();
}
countdowntimer = new CountDownTimer(timeLeft, 1000) {
@SuppressLint("DefaultLocale")
public void onTick(long millisUntilFinished) {
timer.setText(String.format("%02d:%02d:%02d",
(int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
(int) ((millisUntilFinished / (1000 * 60)) % 60),
(int) (millisUntilFinished / 1000) % 60));
//here you can have your logic to set text to edittext
}
public void onFinish() {
timer.setText("done!");
}
}.start();
}