0

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.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193

2 Answers2

0

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

DeePanShu
  • 1,236
  • 10
  • 23
  • Hey @DeePanShu I tried what you said. The problem is still not solved. First thing, only image posts are displayed in Home Fragment as app opens. Only if I switch between fragments, and come back to Home Fragment, then image posts + text posts are shown. Secondly, since you have advised me to use only one Adapter for one RV, how can I inflate two layouts in one adapter because layouts for text post and image post are different. – Krishna Jindal Nov 02 '21 at 06:49
  • Share the code for HomeFragment, and for two layouts in one adapter you have to use 2 view holders within one adapter: https://stackoverflow.com/a/58744127/10954249 – DeePanShu Nov 02 '21 at 06:52
  • I have updated & included the code for Home Fragment. Please check @DeePanShu. – Krishna Jindal Nov 02 '21 at 07:12
  • please shift the database node fetching codes to onViewCreated() method and apply my code, I didn't see my code is applied. – DeePanShu Nov 02 '21 at 07:16
  • I have applied your code already. Changed my post class to include comparable, used only one adapter and shifted postList.clear() and and dashboardRV.setAdapter(postAdapter) on top (just copy-pasted your code). And I don't really understand what you mean by shifting code to onViewCreated() method. Sorry I am kinda beginner. – Krishna Jindal Nov 02 '21 at 07:44
  • Because your text posts in not getting set, so try to fetch the nodes when view is created. – DeePanShu Nov 02 '21 at 07:47
  • Okay I shifted the code for database.getReference().child("Posts").child("ImagePosts") and database.getReference().child("Posts").child("TextPosts") in onViewCreated() and still problem remains same. Only image posts are loaded first and on switching fragments, both type of posts are shown. – Krishna Jindal Nov 02 '21 at 07:50
  • now you need to debug line by line or print logs which data comes first and then tell me the status – DeePanShu Nov 02 '21 at 07:56
  • What I think is that I need to write a SINGLE QUERY to fetch all posts together. Writing these two queries : database.getReference().child("Posts").child("ImagePosts") and database.getReference().child("Posts").child("TextPosts") separately is causing this issue. Obviously I have written query for Image Posts first, so they load first and on switching between fragments, text posts are also added. So I think a SINGLE QUERY to fetch all posts TOGETHER will solve the problem. – Krishna Jindal Nov 02 '21 at 08:05
0

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.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193