1

I have an Android Fragment (in Java) that displays recyclerview. Now I would like to read the data for the items in the recyclerview from a firebase database. They should be stored into an array list orderList. For this purpose, I would like to use LiveData and a ViewModel because I read several times that this is the recommended way of implementing it. Further, I would like the Fragment to update the recyclerview automatically whenever new data is stored in the firebase database.

I tried to follow the steps that are described in the offical Firebase Blog (https://firebase.googleblog.com/2017/12/using-android-architecture-components.html) but unfortunately the result is always an empty list. No elements are being displayed altough there are some relevant entries in the database that should be returned and displayed. The implementation of the recyclerview itself is correct (I checked that by manually adding items into the recylcerview).

Here is my Fragment that holds the recyclerview:

public class FR_Orders extends Fragment  {


    private FragmentOrdersBinding binding;

    //Define variables for the RecyclerView
    private RecyclerView recyclerView_Order;
    private RV_Adapter_Orders adapter_Order;
    private RecyclerView.LayoutManager layoutManager_Order;
    private ArrayList<RV_Item_Order> orderList;

    public FR_Orders() {
        // Required empty public constructor
    }


    public static FR_Orders newInstance(String param1, String param2) {
        FR_Orders fragment = new FR_Orders();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        orderList = new ArrayList<RV_Item_Order>();

        // Obtain a new or prior instance of ViewModel_FR_Orders from the ViewModelProviders utility class.
        ViewModel_FR_Orders viewModel = new ViewModelProvider(this).get(ViewModel_FR_Orders.class);

        LiveData<DataSnapshot> liveData = viewModel.getDataSnapshotLiveData();

        liveData.observe(this, new Observer<DataSnapshot>() {
            @Override
            public void onChanged(@Nullable DataSnapshot dataSnapshot) {
                for(DataSnapshot ds: dataSnapshot.getChildren()) {

                    if (dataSnapshot != null) {

                        String drinkName  = "";
                        String drinkSize = "";
                        String orderDate = "";
                        String orderStatus = "";
                        int orderDateInMilliseconds = 0;
                        int orderID = 0;
                        int quantity = 0;
                        int tableNumber = 0;

                        // update the UI here with values in the snapshot

                        if( dataSnapshot.child("drinkName").getValue(String.class)!=null) {
                            drinkName = dataSnapshot.child("drinkName").getValue(String.class);
                        }
                        if(dataSnapshot.child("drinkSize").getValue(String.class)!=null) {
                            drinkSize = dataSnapshot.child("drinkSize").getValue(String.class);
                        }

                        if(dataSnapshot.child("orderDate").getValue(String.class)!=null) {
                            orderDate = dataSnapshot.child("orderDate").getValue(String.class);
                        }

                        if(dataSnapshot.child("orderStatus").getValue(String.class)!=null) {
                            orderStatus = dataSnapshot.child("orderStatus").getValue(String.class);
                        }
                        if(dataSnapshot.child("orderDateInMilliseconds").getValue(Integer.class)!=null) {
                            orderDateInMilliseconds = dataSnapshot.child("orderDateInMilliseconds").getValue(Integer.class);
                        }
                        if(dataSnapshot.child("quantity").getValue(Integer.class)!=null) {
                            quantity = dataSnapshot.child("quantity").getValue(Integer.class);
                        }
                        if(dataSnapshot.child("orderID").getValue(Integer.class)!=null) {
                            orderID = dataSnapshot.child("orderID").getValue(Integer.class);
                        }
                        if(dataSnapshot.child("tableNumber").getValue(Integer.class)!=null) {
                            tableNumber = dataSnapshot.child("tableNumber").getValue(Integer.class);
                        }

                        orderList.add(new RV_Item_Order(drinkName, drinkSize, orderDateInMilliseconds, orderDate, tableNumber, orderStatus, quantity, orderID));
;
                    }
                }

            }
        });

    }// end onCreate



    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentOrdersBinding.inflate(inflater, container, false);

        buildRecyclerView();

        return binding.getRoot();
    }


    public void buildRecyclerView() {
        recyclerView_Order = binding.rvDrinksToBeDisplayed;
        recyclerView_Order.setHasFixedSize(true);
        layoutManager_Order = new LinearLayoutManager(this.getContext());
        adapter_Order = new RV_Adapter_Orders(orderList);

        recyclerView_Order.setLayoutManager(layoutManager_Order);
        recyclerView_Order.setAdapter(adapter_Order);

        adapter_Order.setOnItemClickListener(new RV_Adapter_Orders.OnItemClickListener() {

            /*
        Define what happens when clicking on an item in the RecyclerView
         */
            @Override
            public void onItemClick(int position) {


            }

        });

    }//end build recyclerView



}//End class

Here is the LiveData class

public class LiveData_FirebaseOrder extends LiveData<DataSnapshot> {
    private static final String LOG_TAG = "LiveData_FirebaseOrder";

    private final Query query;
    private final MyValueEventListener listener = new MyValueEventListener();

    public LiveData_FirebaseOrder(Query query) {
        this.query = query;
    }

    public LiveData_FirebaseOrder(DatabaseReference ref) {
        this.query = ref;
    }

    @Override
    protected void onActive() {
        Log.d(LOG_TAG, "onActive");
        query.addValueEventListener(listener);
    }

    @Override
    protected void onInactive() {
        Log.d(LOG_TAG, "onInactive");
        query.removeEventListener(listener);
    }

    private class MyValueEventListener implements ValueEventListener {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            setValue(dataSnapshot);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
        }
    }
}

Here is the ViewModel class (the full name of the database is not given because of privacy issues).

public class ViewModel_FR_Orders extends ViewModel {
    private static final DatabaseReference ORDERS_REF =
            FirebaseDatabase.getInstance("https://....firebasedatabase.app").getReference("/Orders");


    private final LiveData_FirebaseOrder liveData = new LiveData_FirebaseOrder(ORDERS_REF);

    @NonNull
    public LiveData<DataSnapshot> getDataSnapshotLiveData() {
        return liveData;
    }
}

And here is a screenshot of the Firebase Database that shows that there are some entries in the node Orders that should be returned

Does anyone have an idea what I am making wrong? I tried to stick exactly to the instructions of the offical Firebase Blog.

Firebase Database

Update: I found out that the query itself returns the correct datasnapshots but the recyclerview is not built and updated.

VanessaF
  • 515
  • 11
  • 36
  • I think this article, [How to read data from Firebase Realtime Database using get()?](https://medium.com/firebase-tips-tricks/how-to-read-data-from-firebase-realtime-database-using-get-269ef3e179c5) might help. – Alex Mamo Jan 08 '22 at 16:02
  • @Alex Mamo: Thanks for the comment Alex. Unfortunately the article explains everything in KOTLIN and I don't understand KOTLIN. So it is not helpful for me. – VanessaF Jan 08 '22 at 16:50
  • Futher, I would like to use a Recyclerview in Android with the Data of the Firebase Database such that the entries are automatically updated when new data is stored in the Firebase Database. This is why I posted my example where I would like to apply the LiveData approach explained in the official blog of Firebase Database to a recyclerview. Unfortunately the query always returns no elements altough there are elements in the Firebase Database and I would like to know what goes wrong. Do you have any idea? – VanessaF Jan 08 '22 at 16:53
  • Then you should listen for real-time updates. – Alex Mamo Jan 08 '22 at 17:18
  • @AlexMamo: Thanks for your comment. What do you mean by "real-time updates"? As far as I understand the purpose of LiveData in Android is that it gets automatically updated if something changes. In the article of the official Firebase blog it is also mentioned, that LiveData listens to changes in the Database. So actually the approach from the article should be suitable for my purposes as it should listen the changes in the Firebase Database and update the LiveData when changes occur (which then should trigger the Recyclerview to be built again) – VanessaF Jan 08 '22 at 17:25
  • I don't see you [notify the `RecyclerView` that your list has changed](https://stackoverflow.com/a/5092426/16653700) anywhere in your code: You need to use the `Adapter` to update your list or notify that the list's contents have changed using `notifyDataSetChanged()`. – Alias Cartellano Jan 08 '22 at 21:59
  • @AliasCartellano: Thanks Alias for your hint. I now notify the recyclerview and the items are displayed in them. However I have 2 comments. 1) In contrast to what is said in the article of the official Firebase Database blog, the elements are not updated automatically when something in the database changes. This is weird because I use Livedata that should observe the changes in the database. Can you think about a reason as to why this is happening? – VanessaF Jan 09 '22 at 16:07
  • 2) At the moment the code is just returns the whole content of the sub-database. I actually don't see where I could specify a query to just retrun specific elements based on a firebase query. Do you know how and where to do this? – VanessaF Jan 09 '22 at 16:07
  • @AlexMamo: Do you have any idea why the view is not updated automatically when new data is inserted into the Firebase Database? I followed the approach from the article in the offical blog of Firebase and I use LiveData. So actually the liveData should listen to changes in the Firebase Database. Further, do you know where I can specify a query to not get all data from Firebase Database but just some specific data? – VanessaF Jan 10 '22 at 18:09
  • To get only specific data you have to create a Query. Regarding the first issue, you should isolate the problem and see if it works for only a few documents. – Alex Mamo Jan 10 '22 at 20:23
  • Thanks Alex for your comment. Where exactly do I have to specify the query? In the Fragment class, in the Livedata class or in the Viewmodel class? I see that a query is used in the Livedata class but it seems that it is not really defined with the code `this.query = query;`. Normally you should have something like ` Query query = rootRef.child("databasetable").orderByChild("attribute1").equalTo(value);` but where shall I put this? Regarding the 1st issue what do you mean by "isolating" the problem? What I want is that the fragment gets updated whenever something changes in firebase database – VanessaF Jan 10 '22 at 20:57
  • Can you use the usual [`ValueEventListeners`](https://firebase.google.com/docs/database/admin/retrieve-data)? With the aforementioned you can [add listeners by queries](https://firebase.google.com/docs/database/admin/retrieve-data#section-queries). – Alias Cartellano Jan 10 '22 at 22:34
  • According to the link you posted for `LiveData` you would pass your query to `LiveData_FirebaseOrder` in your `LiveData` class. – Alias Cartellano Jan 10 '22 at 22:38
  • @AliasCartellano: I am already using the ValueEventListener `private class MyValueEventListener implements ValueEventListener ` as an inner class in the class `LiveData_FirebaseOrder `. But still it does not update anything. What do you mean by "you would pass your query to LiveData_FirebaseOrder in your LiveData class.". Where exactly can I specify the query and how can I do that (I know how a query can be designed for the Firebase database but I don't see how and where I can do this in my example – VanessaF Jan 13 '22 at 16:26
  • I now managed to solve the updating issue (it is now updating autmatically :-). However, I have still problems with the query. Actually I don't know how and where to implement the query. Especially I would like to adjust the query based on some parameters (toggle Buttons) in the UI (Fragment class). So I need to pass information from the fragment class to the LiveData and ViewModel class and according to this information a query needs to be specified. Any idea how I can do that? I'll appreciate any further comments. – VanessaF Jan 13 '22 at 18:29
  • As I tried to say before, your `LiveData` class has a method, `LiveData_FirebaseOrder` where you can pass in queries such as `Query query = rootRef.child("databasetable").orderByChild("attribute1").equalTo(value);`. eg. `LiveData_FirebaseOrder(query)`. Replace this line `private static final DatabaseReference ORDERS_REF = FirebaseDatabase.getInstance("https://....firebasedatabase.app").getReference("/Orders");` with your query in your `ViewModel` and it should work. – Alias Cartellano Jan 13 '22 at 18:44
  • *I mean't the constructor for `LiveData`. – Alias Cartellano Jan 13 '22 at 18:52
  • Thanks a lot Alias for your comment and effort. I really appreciate it. I will try what you suggested. But as written before, I would like to adjust the query based on some toggle button status in the UI. So I have to make a dynamic link (because the status of the toggle buttons can change after an user iteraction) between the UI (fragment class) and LiveData or ViewModel class. How can I do that? – VanessaF Jan 13 '22 at 18:59
  • @AliasCartellano: Thanks for your comment and effort. Basically the query works now. I did as you suggested. However, I still have no clue as to how to adjust the query based on some toggle button status in the UI. So I have to make a dynamic link (because the status of the toggle buttons can change after an user iteraction) between the UI (fragment class) and LiveData or ViewModel class. How can I do that? – VanessaF Jan 14 '22 at 18:19
  • Since your `LiveData` fields are final you would need to make a new `LiveData` object and stop the old one everytime you want to change the query. The `ViewModel` also keeps the reference/query field final. One workaround is to add a new method `changeQuery`(in `LiveData`) that accepts a new `Query` and stops(and writes over) the old one( `private final Query query;`(you'd have to remove `final`)) and call observe again in some method. – Alias Cartellano Jan 14 '22 at 19:46
  • There's too many comments on this post, so I suggest you open a new post with your updated code and new question as your old one has been answered and there may be others who can help there. – Alias Cartellano Jan 14 '22 at 19:52
  • @AliasCartellano: Thanks for your answer and effort. I really appreciate it. Actually the update works (whenever new data to a query is available, the Firebase Database returns updated data). I have a problem and question. How can I trigger an update on the db from the Fragment class? At the moment, the database just returns something, when there is an update in the database. However, based on some selected buttons, I would like to trigger an update of the realtime database. Actually nothing changes in the db but the way I handle the information in the LiveData part of the fragment changes. – VanessaF Jan 15 '22 at 09:57
  • Let's [continue this in a chatroom](https://chat.stackoverflow.com/rooms/241092/firebase-database-query-with-livedata-and-viewmodel-returns-nothing-altough-data). – Alias Cartellano Jan 15 '22 at 18:37

0 Answers0