0

The purpose of this function is to go through the first 200 hundred days of each user's schedule. Go through a each user's schedule one by one to check if they're available on a certain day. If they are available on a certain day a counter will increment and if the counter matches the amount of users the specified event will be added to the user's schedule. This isn't working however as my code is not reading the data from firebase correctly. Here is the code for the function:

private void findTime(String eventName, int  EventTime)
{

    Boolean dayFound = false;
    countNeeded = TheUserList.size();

    DaySearched = LocalDate.now();
    while (!dayFound) {
        for (int i = 0; i <200; i++)
        {
            // the current count variable tracks the amount of users in the schedule have free tim slots for a specific day
            currentCount = 0;
            Toast.makeText(this, ("The first for loop has begun " + String.valueOf(currentCount)), Toast.LENGTH_SHORT).show();

            for (int e =0; e < TheUserList.size(); e++)
            {
                String currentUser = TheUserList.get(e);
                    Log.i("Firebase", Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").toString());
    

                Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                        int TimeForDay = snapshot.getValue(int.class);
                        if (TimeForDay + EventTime < 1441)
                        {
                            currentCount = currentCount + 1;
                        }
                        else
                        {
                            currentCount = 0;
                        }
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError error) {
                        Toast.makeText(PlanEvent.this, "Failed to get Data", Toast.LENGTH_SHORT).show();

                    }
                });
            }
            if (currentCount == countNeeded)
            {
                Log.d("FindTime Function", "Found and available day at" + String.valueOf(DaySearched));

                Tasks newEvent = new Tasks(eventName, DaySearched, LocalTime.now(), EventTime);
                Day.enoughTimeChecker(DaySearched, EventTime, newEvent);
                dayFound = true;

            }
            DaySearched = DaySearched.plusDays(1);
        }
    }
}

I have added Toast messages in the while loop to see if it being called (this works) and also one to see if the function has found an appropriate date (this isn't appearing so I assume it cannot find a day for some reason).

Here is an image to understand the structure of the real-time-database

Structure picture 2

Here is the output of the log.i() line: Here is the output of the log.i() line

Any help would be greatly appreciated.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Suf1an65
  • 1
  • 1
  • "This isn't working" doesn't provide enough information so we can help. What exactly doesn't work? Do you have any errors? – Alex Mamo Mar 20 '23 at 13:41
  • There are no errors, I think the problem might have something to do with searching the real-time database. The if statement (if currentCount == neededCount) should return a log message but it doesn't (meaning the statement hasn't been called, not that it has and just isn't doing so). Is there a mistake in how I retrieve data from the database? – Suf1an65 Mar 20 '23 at 13:46
  • 1
    If you `Log.i("Firebase", Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").toString());`, what does that output? – Frank van Puffelen Mar 20 '23 at 13:50
  • Is there anywhere specific I should write this? – Suf1an65 Mar 20 '23 at 13:51
  • Please edit your question and add the information Frank asked for, and please also respond using @. – Alex Mamo Mar 20 '23 at 13:51
  • @FrankvanPuffelen For some reason it returns the days from 2027 onwards when searching each user's schedule. This returns null as the database shouldn't even go that far – Suf1an65 Mar 20 '23 at 13:57
  • @Suf1an65 I'm not sure what that means. Can you edit your question to show the code that includes the `Log.i` statement and its output? – Frank van Puffelen Mar 20 '23 at 15:07
  • @FrankvanPuffelen sorry for the very late reply, I made the edits to the question to show the problem. I re-run the code however this time it returns the "Time for day" value of mid August rather than returning the value held in the current date. Even then, with the values it got this should have made currentCount == countNeeded and the if statement should have been called. – Suf1an65 Mar 20 '23 at 17:51
  • Sorry this took me a long time to notice, but I now see that you're not handling the asynchronous nature of `onDataChange` correctly. I'll write an answer below, but until then you'll want to read https://stackoverflow.com/questions/50434836/getcontactsfromfirebase-method-return-an-empty-list/50435519#50435519 and https://stackoverflow.com/questions/33203379/setting-singleton-property-value-in-firebase-listener – Frank van Puffelen Mar 20 '23 at 21:48

1 Answers1

0

All data is loaded from Firebase (and all modern cloud APIs) asynchronously, since it may take some time to get it. Instead of waiting (and blocking the user from using your app) your main code continues to run, and a callback to your code (onDataChange here) is then made when the data is available.


The problem is that you're using currentCount before all of your nested data has loaded. It's easiest to see this if you add some logging:

Log.i("Firebase", "Before loop starts");
while (!dayFound) {
    for (int i = 0; i <200; i++)
    {
        Log.i("Firebase", "Before nested loop starts");

        for (int e =0; e < TheUserList.size(); e++)
        {
            String currentUser = TheUserList.get(e);
            Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").addValueEventListener(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot snapshot) {
                    Log.i("Firebase", "Got data, incrementing count");
                }

                @Override
                public void onCancelled(@NonNull DatabaseError error) {
                    Toast.makeText(PlanEvent.this, "Failed to get Data", Toast.LENGTH_SHORT).show();
                }
            });
        }
        Log.i("Firebase", "After loop is done, checking count");
    }
}

The output of this will be something like:

Before loop starts
Before nested loop starts
After loop is done, checking count
Before nested loop starts
After loop is done, checking count
Before nested loop starts
After loop is done, checking count
... Got data, incrementing count
Got data, incrementing count
Got data, incrementing count
Got data, incrementing count

This is probably not the order you expected, but it is working as designed and explains perfectly why your check doesn't work: by the time you check the count, the data hasn't loaded yet!


The solution for this is always the same. Any code that needs data from the database, has to be inside onDataChanged, be called from there, or be otherwise synchronized.

The simplest way is usually to move the code into the onDataChange:

private void findTime(String eventName, int  EventTime)
{

    Boolean dayFound = false;
    countNeeded = TheUserList.size();

    DaySearched = LocalDate.now();
    while (!dayFound) {
        for (int i = 0; i <200; i++)
        {
            // the current count variable tracks the amount of users in the schedule have free tim slots for a specific day
            currentCount = 0;
            Toast.makeText(this, ("The first for loop has begun " + String.valueOf(currentCount)), Toast.LENGTH_SHORT).show();

            for (int e =0; e < TheUserList.size(); e++)
            {
                String currentUser = TheUserList.get(e);
                    Log.i("Firebase", Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").toString());
    

                Login.reference.child(currentUser).child(String.valueOf(DaySearched)).child("Time for day").addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                        int TimeForDay = snapshot.getValue(int.class);
                        if (TimeForDay + EventTime < 1441)
                        {
                            currentCount = currentCount + 1;
                            // 
                            if (currentCount == countNeeded)
                            {
                                Log.d("FindTime Function", "Found and available day at" + String.valueOf(DaySearched));

                                Tasks newEvent = new Tasks(eventName, DaySearched, LocalTime.now(), EventTime);
                                Day.enoughTimeChecker(DaySearched, EventTime, newEvent);
                                dayFound = true;

                            }
                            DaySearched = DaySearched.plusDays(1);
                            // 
                        }
                        else
                        {
                            currentCount = 0;
                        }
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError error) {
                        Toast.makeText(PlanEvent.this, "Failed to get Data", Toast.LENGTH_SHORT).show();

                    }
                });
            }
        }
    }
}

You may need to perform some additional checks inside onDataChange, for example if you want to ensure that DaySearched.plusDays(1); runs at most once.


For more on this, see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807