I have a root node named "Posts" in the Firebase Realtime Database. Inside that, I have two nodes called "ImagePosts" and "TextPosts". And inside "ImagePosts" (and "TextPosts"), I have postIds of various posts. And inside a postID, I have all the details of that particular post including postedAt (post time).
What I want to do is that write a query to fetch data from "ImagePosts" and "TextPosts" TOGETHER AND display all the posts in descending/reverse order (that is, the post which is posted last/recently should show up at the top in my Recycler View according to "postedAt").
Please click here to see database structure
To achieve this, I created a single model named Post and two adapters named "PostAdapter" and "TextPostAdapter". And my Recycler View is "DashboardRV". What have I tried so far:
Code of Home Fragment:
public class HomeFragment extends Fragment {
ShimmerRecyclerView dashboardRV;
ArrayList<Post> postList;
public HomeFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
dashboardRV = view.findViewById(R.id.dashboardRv);
dashboardRV.showShimmerAdapter();
postList = new ArrayList<>();
PostAdapter postAdapter = new PostAdapter(postList, getContext());
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
dashboardRV.setLayoutManager(layoutManager);
dashboardRV.addItemDecoration(new DividerItemDecoration(dashboardRV.getContext(), DividerItemDecoration.VERTICAL));
dashboardRV.setNestedScrollingEnabled(false);
dashboardRV.setAdapter(postAdapter);
postList.clear();
database.getReference()
.child("Posts")
.child("ImagePosts")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
Post post = dataSnapshot.getValue(Post.class);
post.setPostId(dataSnapshot.getKey());
postList.add(post);
}
Collections.reverse(postList);
dashboardRV.hideShimmerAdapter();
postAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
database.getReference()
.child("Posts")
.child("TextPosts")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
postList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
Post post = dataSnapshot.getValue(Post.class);
post.setPostId(dataSnapshot.getKey());
postList.add(post);
}
Collections.reverse(postList);
dashboardRV.hideShimmerAdapter();
textPostAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
But the problem with this approach is that it doesn't display all the "TextPosts" and "ImagePosts" together. It only shows all the image posts on the opening app, then when I change fragment and come back, then it displays all text posts. I am just stuck here.
Use only one adapter for one recycler view at a time:
Here is the code for a single adapter with both (image and text posts):
postList = new ArrayList<>();
PostAdapter postAdapter = new PostAdapter(postList, getContext());
dashboardRV.setAdapter(postAdapter);
// call clear before refreshing the list
postList.clear();
database.getReference()
.child("Posts")
.child("ImagePosts")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot : snapshot.getChildren()){
Post post = dataSnapshot.getValue(Post.class);
post.setPostId(dataSnapshot.getKey());
postList.add(post);
}
Collections.sort(postList);
dashboardRV.hideShimmerAdapter();
postAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
database.getReference()
.child("Posts")
.child("TextPosts")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot : snapshot.getChildren())
Post post = dataSnapshot.getValue(Post.class);
post.setPostId(dataSnapshot.getKey());
postList.add(post);
}
Collections.sort(postList);
dashboardRV.hideShimmerAdapter();
postAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
// make Post class implemented comparable to sort the list after fetching:
// A class 'Post' that implements Comparable
class Post implements Comparable<Post>
{
...
// Used to sort posts by their postedAt
public int compareTo(Post p)
{
return this.postedAt - p.postedAt;
}
...
}
This link will help to explore how to sort according to postedAt: Using Comparable
To merge 2 separate Firebase Realtime Database requests locally, I recommend you to use Tasks.whenAllSuccess() method. You can achieve this, using the following lines of code:
DatabaseReference imagePostsRef = database.getReference()
.child("Posts")
.child("ImagePosts");
DatabaseReference textPostsRef = database.getReference()
.child("Posts")
.child("TextPosts");
Task firstTask = imagePostsRef.get();
Task secondTask = textPostsRef.get();
Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list
}
});
As you can see, when overriding the "onSuccess()" method the result is a list of objects. In the end, simply map each object from the list into an object of type Post, and pass the new list to a single adapter.
Related
I am trying to implement an SwipeRefreshLayout into my app. What I want to happen is that when the user opens the app initially that the data is retrieved from Firebase without having to pull down the layout so the data refreshes. After that, if the user wants to see new data they have to pull down the layout so the RecyclerView is refreshed with new posts that other users have uploaded.
The problem that I am running into is that when I upload a post from my emulator it pops up on screen automatically on my phone without me having to pull down. That's not what I want. First, initial opening of app data should be retrieved automatically without pull down. After that, every time you want to see new data, if I go to a different fragment and then come back the data shouldn't be refreshed unless I pull down.
The code that I have currently is the following. Can someone tell me why it isn't working the way that I would like it to?
I am relatively sure that it's because of my notifyDataSetChanged(); in my readPosts(); method, but I'm not sure how to fix it... Should I just remove it from readPosts(); and add it to the mSwipeRefreshLayout.post...Runnable?
HomeFragment
public class HomeTabLayoutFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private ProgressBar mProgressBar;
private PostAdapter mPostAdapter;
private List<Post> mPostLists;
private FirebaseAuth mFirebaseAuth;
private FirebaseUser mFirebaseUser;
private List<String> mFollowingList;
private SwipeRefreshLayout mSwipeRefreshLayout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_home_tab_layout, container, false);
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseUser = FirebaseAuth.getInstance().getCurrentUser();
mProgressBar = v.findViewById(R.id.progress_circular);
RecyclerView recyclerView = v.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
mPostLists = new ArrayList<>();
mPostAdapter = new PostAdapter(getContext(), mPostLists);
recyclerView.setAdapter(mPostAdapter);
mSwipeRefreshLayout = v.findViewById(R.id.refresh);
mSwipeRefreshLayout.setOnRefreshListener(this);
mSwipeRefreshLayout.post(() -> {
mSwipeRefreshLayout.setRefreshing(true);
readPosts();
});
checkIfUserExists();
checkFollowing();
return v;
}
#Override
public void onRefresh() {
readPosts();
}
private void checkIfUserExists() {
if (mFirebaseAuth == null) {
Intent intent = new Intent(getContext(), RegisterActivity.class);
startActivity(intent);
}
}
private void checkFollowing() {
mFollowingList = new ArrayList<>();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Follow").child(mFirebaseUser.getUid()).child("Following");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mFollowingList.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
mFollowingList.add(snapshot.getKey());
}
readPosts();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private void readPosts() {
mSwipeRefreshLayout.setRefreshing(true);
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mPostLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Post post = snapshot.getValue(Post.class);
if (post != null && shouldAddPost(post)) {
mPostLists.add(post);
}
}
mPostAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
mSwipeRefreshLayout.setRefreshing(false);
}
private boolean shouldAddPost(#NotNull Post post) {
boolean isFollowingPublisher = false;
for (String id : mFollowingList) {
if (post.getPublisher().equals(id)) {
isFollowingPublisher = true;
break;
}
}
boolean isPublisher = post.getPublisher().equals(mFirebaseUser.getUid());
return isFollowingPublisher || isPublisher;
}
}
That is because you are using a addValueEventListener. In this case a ValueEventListener will get called each time any changes made in this hierarchy.
You should be using addListenerForSingleValueEvent if you want data only once. In other words if you do not want realtime updates for a node then you use addListenerForSingleValueEvent.
->Answer about mSwipeRefreshLayout.setRefreshing(false) positioning inside readPost .
Right now how you added mSwipeRefreshLayout.setRefreshing(true) and mSwipeRefreshLayout.setRefreshing(false) is actually useless . It will call immediately.. What you should be doing is changing the state inside listeners.
private void readPosts() {
mSwipeRefreshLayout.setRefreshing(true);
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mPostLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Post post = snapshot.getValue(Post.class);
if (post != null && shouldAddPost(post)) {
mPostLists.add(post);
}
}
mPostAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
mSwipeRefreshLayout.setRefreshing(false);
}
});
I have the following problem. Posts of other users are coming out fine only one post. But if it's your profile, in the HomeFragment the posts are coming out duplicated and I am not sure why that is.
In my readPosts(); method, if I get rid of this line } else if (mFirebaseUser.getUid().equals(post.getPublisher())) {mPostLists.add(post); it's fine, but then my posts don't show up only the posts of the people I follow. I need mine to show up also in the newsfeed, but just once not duplicated. All of my posts are being duplicated. Same post comes up twice on my feed. Can someone tell me what I am doing wrong?
Something I am doing wrong in my readPosts(); method, but I am not sure what after having played around with it a bunch.
HomeFragment
public class HomeTabLayoutFragment extends Fragment {
private ProgressBar mProgressBar;
private PostAdapter mPostAdapter;
private List<Post> mPostLists;
private FirebaseAuth mFirebaseAuth;
private FirebaseUser mFirebaseUser;
private List<String> mFollowingList;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_home_tab_layout, container, false);
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseUser = FirebaseAuth.getInstance().getCurrentUser();
mProgressBar = v.findViewById(R.id.progress_circular);
RecyclerView recyclerView = v.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
mPostLists = new ArrayList<>();
mPostAdapter = new PostAdapter(getContext(), mPostLists);
recyclerView.setAdapter(mPostAdapter);
SwipeRefreshLayout refreshLayout = v.findViewById(R.id.refresh);
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
refreshLayout.setRefreshing(false);
}
});
checkIfUserExists();
checkFollowing();
return v;
}
private void checkIfUserExists() {
if (mFirebaseAuth == null) {
Intent intent = new Intent(getContext(), RegisterActivity.class);
startActivity(intent);
}
}
private void checkFollowing() {
mFollowingList = new ArrayList<>();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Follow").child(mFirebaseAuth.getCurrentUser().getUid()).child("Following");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mFollowingList.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
mFollowingList.add(snapshot.getKey());
}
readPosts();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private void readPosts() {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mPostLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Post post = snapshot.getValue(Post.class);
for (String id : mFollowingList) {
if (post != null) {
if (post.getPublisher().equals(id)) {
mPostLists.add(post);
} else if (mFirebaseUser.getUid().equals(post.getPublisher())) {
mPostLists.add(post);
}
}
}
mPostAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
Check your inner loop in readPosts(). The loop is based on elements of mFollowingList. This condition has nothing to do with that:
if (mFirebaseUser.getUid().equals(post.getPublisher()))
It will be either true or false for every execution of the internal loop. Therefore, it may add more elements than you want. You probably want to put it in the outer loop, not the inner one - to execute it just once per post.
I'd make some more changes though. The check (post != null) can be done outside the loop. No need to let it loop multiple times for no reason. notifyDataSetChanged() also does not need to be inside any loop, it needs to be called once.
Extra regarding code style :-) - You're using too much indentation. It makes sense to extract things to a separate method for readability, it's a good idea to keep methods short, with descriptive names.
private void readPosts() {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mPostLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Post post = snapshot.getValue(Post.class);
if (post != null && shouldAddPost(post)) {
mPostLists.add(post);
}
}
mPostAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private boolean shouldAddPost(#NotNull Post post) {
boolean isFollowingPublisher = false;
for (String id : mFollowingList) {
if (post.getPublisher().equals(id)) {
isFollowingPublisher = true;
}
}
boolean isPublisher = post.getPublisher().equals(mFirebaseUser.getUid());
return isFollowingPublisher || isPublisher;
}
private void readPosts() {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mPostLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Post post = snapshot.getValue(Post.class);
for (String id : mFollowingList) {
if (post != null) {
if (post.getPublisher().equals(id)) {
mPostLists.add(post);
}
}
}
if (post != null)
if (mFirebaseUser.getUid().equals(post.getPublisher())) {
mPostLists.add(post);
}
}
mPostAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
I am trying to have an activity inside of my fragment, the fragment shows posts of people you follow and the activity shows the most recent post from anyone.
My fragment..
public class HomeFragment extends Fragment {
private PostAdapter postAdapter;
private List<Post> postLists;
private List<String> followingList;
private ProgressBar progressBar;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_home, container,false);
RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager=new LinearLayoutManager(getContext());
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
postLists=new ArrayList<>();
postAdapter=new PostAdapter(getContext(), postLists);
recyclerView.setAdapter(postAdapter);
ImageView globalPosts = view.findViewById(R.id.globalPosts);
globalPosts.setOnClickListener(v->{
Intent intent= new Intent(getContext(), GlobalPostsActivity.class);
startActivity(intent);
});
progressBar=view.findViewById(R.id.progress_circular);
checkFollowing();
return view;
}
private void checkFollowing(){
followingList=new ArrayList<>();
DatabaseReference reference=FirebaseDatabase.getInstance().getReference("Support")
.child(Objects.requireNonNull(FirebaseAuth.getInstance().getCurrentUser()).getUid())
.child("supporting");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
followingList.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
followingList.add(snapshot.getKey());
}
readPosts();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
private void readPosts(){
DatabaseReference reference= FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
postLists.clear();
for (DataSnapshot snapshot :dataSnapshot.getChildren()){
Post post=snapshot.getValue(Post.class);
for (String id: followingList){
assert post != null;
if (post.getPublisher().equals(id)){
postLists.add(post);
}
}
}
postAdapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
My activity..
public class GlobalPostsActivity extends AppCompatActivity {
private PostAdapter postAdapter;
private List<Post> postLists;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_global_posts);
RecyclerView recyclerView = findViewById(R.id.recycler_view1);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);
postLists=new ArrayList<>();
postAdapter=new PostAdapter(this, postLists);
recyclerView.setAdapter(postAdapter);
readGlobalPosts();
}
private void readGlobalPosts(){
DatabaseReference reference= FirebaseDatabase.getInstance().getReference("Posts");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
postLists.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Post post=snapshot.getValue(Post.class);
assert post !=null;
postLists.add(post);
}
postAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
I get a fatal exception: main and my app crashes when i click on a users name to go to their profile. I am not sure why this is happening so any help is greatly appreciated.
There is one bit of your code I can see that will crash if asserts are enabled:
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Post post=snapshot.getValue(Post.class);
assert post !=null; // THIS
postLists.add(post);
}
What you have written there is the equivalent of:
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Post post=snapshot.getValue(Post.class);
if(post == null) {
throw new IllegalStateException("Post was null");
}
postLists.add(post);
}
and I am sure you don't want to crash your whole app when there is no Post?
If you want to test if it exists (and get feedback if it doesn't), try something like this:
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Post post=snapshot.getValue(Post.class);
if(post == null) {
Log.w("TUT", "Expected a post and didn't get one.");
} else {
postLists.add(post);
}
}
Ref:
https://en.wikibooks.org/wiki/Java_Programming/Keywords/assert
How to use assert in android?
My Previous question was closed and was linked to some other same questions but non of them cover my case and I still require some help. The post they were linked to were:
Android: Retrieve data of a specific user from a Firebase database
Retrieving data from Firebase Realtime Database in Android
Retrieve Current User's Data (user_id) Firebase?
However, I am still struggling and hope someone can help me.
As you can see that it has an User ID first then has the random key then the two information i want to get. My application keeps crashing when i try to get the information
My code in what i am currently doing:
onCreate
auth = FirebaseAuth.getInstance();
user = auth.getCurrentUser();
databaseReference = FirebaseDatabase.getInstance().getReference();
After that:
protected void onStart() {
super.onStart();
String userID = user.getUid();
databaseReference.child(userID).child(databaseReference.push().getKey()).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot data : dataSnapshot.getChildren()){
Author author = data.getValue(Author.class);
authorList.add(author);
}
SavedQuotes savedQuotes = new SavedQuotes(ShowQuotes.this, authorList);
list.setAdapter(savedQuotes);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
The savedQuotes class has this information:
public SavedQuotes(Activity activity, List<Author> authorQuotes){
super(activity, R.layout.activity_saved_quotes, authorQuotes);
this.activity = activity;
this.authors = authorQuotes;
}
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
LayoutInflater inflater = activity.getLayoutInflater();
View listView = inflater.inflate(R.layout.activity_saved_quotes, null, true);
name = listView.findViewById(R.id.name);
quotes = listView.findViewById(R.id.quote);
Author author = authors.get(position);
name.setText(author.getName());
quotes.setText(author.getQuote());
return listView;
}
I hope you can help me please
No need to use databaseReference.push().getKey() here. Check below:
databaseReference = FirebaseDatabase.getInstance().getReference();
databaseReference.child(userID).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot data : dataSnapshot.getChildren()){
Author author = data.getValue(Author.class);
authorList.add(author);
}
...
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
In my app I use the firebase database. There are questions and the corresponding comments stored in seperat nodes. Now I try to get the questions with the one listener and access the comments with a second listener. Unfortunately I‘m confused by their behavior: the recyclerView always gets an empty questionsList, like the second listener is skipped. But after the recyclerView got the list and the adapter is set up, my LogCat starts printing the questions and the comment information.
But why is the recyclerView populated and used before the for loop that processes the data has finished?
The method to fetch the information:
private void getQuestionsFromDatabase() {
mQuestions.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
questionList = new ArrayList<>();
for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
final String title = dataSnapshot1.child("title").getValue().toString();
final String question = dataSnapshot1.child("question").getValue().toString();
final String commentId = dataSnapshot1.child("commentId").getValue().toString();
mComments.child(commentId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
count = dataSnapshot.getChildrenCount();
QuestionModel questionModel = new QuestionModel(title, question, commentId, String.valueOf(count));
questionList.add(questionModel);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Log.d("questionList length: ", String.valueOf(questionList.size()));
recyclerViewAdapter = new RecyclerViewQuestionAdapter(questionList, getActivity());
recyclerViewlayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(recyclerViewlayoutManager);
recyclerView.setAdapter(recyclerViewAdapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
It is used before, because the onDataChange is asynchronous, which means that the compiler will not wait until data is fetched from the database, instead it will execute the code after the listener. Therefore to solve your problem, you should do the following:
mComments.child(commentId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
count = dataSnapshot.getChildrenCount();
QuestionModel questionModel = new QuestionModel(title, question, commentId, String.valueOf(count));
questionList.add(questionModel);
Log.d("questionList length: ", String.valueOf(questionList.size()));
recyclerViewAdapter = new RecyclerViewQuestionAdapter(questionList, getActivity());
recyclerViewlayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(recyclerViewlayoutManager);
recyclerView.setAdapter(recyclerViewAdapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}