0

I have an app with about 20 Fragments in Android and each of them can write Data into the Firebase Realtime Database. For not implementing the write operation in each of those 20 Fragments, I have created a HelpFunctionsclass that has the method writeDataIntoFirebaseDB

public class HelpFunctions {    
    static boolean dataCouldBeWrittenIntoFirebaseDB;

    public static boolean writeDataIntoFirebaseDB (Object dataBaseEntry, String dbTable, String entryIdPrefix) {

        dataCouldBeWrittenIntoFirebaseDB = false;
        long currentTimeMillis = System.currentTimeMillis();
        SimpleDateFormat sdf1 = new SimpleDateFormat("dd-MM-yy");
        sdf1.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
        SimpleDateFormat sdf2 = new SimpleDateFormat("HH-mm-ss");
        sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));

        DatabaseReference rootRef = FirebaseDatabase.getInstance("https://...").getReference();
        DatabaseReference ordersRef = rootRef.child(dbTable);

        String entryIdComplete =entryIdPrefix + "_date_" + sdf1.format(new Date()) + "_time_" + sdf2.format(new Date());
        ordersRef.child(entryIdComplete).setValue(dataBaseEntry).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    Log.e("HelpFuncTag",  "Data successfully written.");

                    dataCouldBeWrittenIntoFirebaseDB = true;
                }
                else {
                    Log.e("HelpFuncTag", task.getException().getMessage());
                    dataCouldBeWrittenIntoFirebaseDB  = false;
                }
            }
        });

        return dataCouldBeWrittenIntoFirebaseDB ;
    }
}

The method is called from the 20 Fragments and it should return true or false to the fragments if the writing operation was successfull to Firebase, such that the Fragments can show a message (Snackbar) to the user. When using this approach, the method always returns false, even though the data could be written into Firebase because of the asynchronous operation of Firebase.

This is why I tried to implement a callback similarly as explained in this video: https://www.youtube.com/watch?v=OvDZVV5CbQg&ab_channel=AlexMamo

So I have the following code:

public class HelpFunctions {
public static boolean writeDataIntoFirebaseDB (Object dataBaseEntry, String dbTable, String entryIdPrefix) {

        final boolean[] dataCouldBeWrittenIntoFirebaseDB = {false};
        long currentTimeMillis = System.currentTimeMillis();
        SimpleDateFormat sdf1 = new SimpleDateFormat("dd-MM-yy");
        sdf1.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
        SimpleDateFormat sdf2 = new SimpleDateFormat("HH-mm-ss");
        sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));

        DatabaseReference rootRef = FirebaseDatabase.getInstance("https://...").getReference();
        DatabaseReference ordersRef = rootRef.child(dbTable);

        String entryIdComplete =entryIdPrefix + "_date_" + sdf1.format(new Date()) + "_time_" + sdf2.format(new Date());

        HelpFunctions.writeIntoFirebase(new HelpFunctions.FirebaseCallback() {
            @Override
            public void onCallBack(boolean writingSuccesfull) {
                if (writingSuccesfull==true) {
                    dataCouldBeWrittenIntoFirebaseDB[0] =  true;
                }
                if (writingSuccesfull==false) {
                    dataCouldBeWrittenIntoFirebaseDB[0] =  false;
                }
            }
        }, entryIdComplete, dataBaseEntry, ordersRef);

        return dataCouldBeWrittenIntoFirebaseDB[0];
    }

    private static void writeIntoFirebase (HelpFunctions.FirebaseCallback firebaseCallback, String entryIdComplete, Object dataBaseEntry, DatabaseReference ordersRef ) {
        boolean dataCouldBeWrittenIntoFirebaseDB = false;
        ordersRef.child(entryIdComplete).setValue(dataBaseEntry).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    Log.e("HelpFuncTag",  "Data successfully written.");
                    firebaseCallback.onCallBack (true);
                }
                else {
                    Log.e("HelpFuncTag", task.getException().getMessage());
                    firebaseCallback.onCallBack (false);    
                }
            }
        });
    }//End write data method

    private interface FirebaseCallback  {
        void onCallBack(boolean dataCouldBeWrittenIntoFirebase);
    }
}

Unfortunately, also the Callback solution always returns false to the Fragments altough the data could be written into the Firebase database. Do you know another approach how to get feedback from Firebase Realtime Database about writing operation in Android?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
VanessaF
  • 515
  • 11
  • 36
  • If you want, you can learn Kotlin and use Coroutines. Here is useful [resource](https://medium.com/firebase-tips-tricks/how-to-read-data-from-firebase-realtime-database-using-get-269ef3e179c5). – Alex Mamo Oct 26 '22 at 14:56
  • @AlexMamo: Thanks for your comment. I don't want to learn Kotlin and proceed using Java. So I can't use the Coroutines. I need to use interface callbacks in Java but so far it just does not work and I don't understand why because I used the suggested approach from this video: https://www.youtube.com/watch?v=OvDZVV5CbQg&ab_channel=AlexMamo – VanessaF Oct 27 '22 at 16:01
  • I have just seen Frank's answer and he explains perfectly why you have that behavior and how to solve this. What is wrong then? – Alex Mamo Oct 27 '22 at 16:39
  • @AlexMamo: Thanks for your comment. Actually I already do what Frank suggests in his comment. The method private static void writeIntoFirebase (HelpFunctions.FirebaseCallback firebaseCallback, String entryIdComplete, Object dataBaseEntry, DatabaseReference ordersRef ) expects a Callback and calls the callback during the execution of the onComplete method of Firebase firebaseCallback.onCallBack (true/false); But still it does not work. I always get `false` as a result altough data could be written into Firebase – VanessaF Oct 27 '22 at 16:47
  • Even if you create a custom callback, there is no way you can use that value outside it. Why did I say that it would be good to learn Kotlin? It's because you only need to call await(), and everything will work as expected. – Alex Mamo Oct 28 '22 at 07:57
  • @AlexMamo: Thanks for your comment. Unfortunately I don't have time to learn Kotlin and I want to proceed using Java because overall it is way more used than Kotlin (I do Android programming in my spare time and I use Java also in my job). Is there not a convenient solution for Java with just one method call to handle the asynchronous behaviour of Firebase? As far as I know, Kotlin is also compiled into Java Byte code so everything possible with Kotlin should also be possible with Java. You'd need a wrapper function around the callbacks. Is there a native implementation from Firebase for that? – VanessaF Oct 28 '22 at 15:54
  • As far as I know, there is nothing related to Kotlin Coroutines in Java. But maybe you can use RxJava. – Alex Mamo Oct 28 '22 at 19:15

2 Answers2

1

Data is written to Firebase (and most modern cloud APIs) asynchronously. It's easiest to see what this means by adding some logging to your code:

Log.i("HelpFuncTag", "Before starting to write");
ordersRef.child(entryIdComplete).setValue(dataBaseEntry).addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        Log.i("HelpFuncTag", "Done writing");    
    }
});
Log.i("HelpFuncTag", "After starting to write");    

The output of this is:

Before starting to write

After starting to write

Done writing

This is probably not the order your expected the output in, but it is working as designed - and it completely explains why the code always returns true. The dataCouldBeWrittenIntoFirebaseDB[0] = false hasn't run yet by the time your function returns a value.


The solution for this problem is always the same: any code that needs to run after the data has been written, has to be inside the onComplete function, be called from there, or be otherwise synchronized.

In fact, that's precisely why you have to call addOnCompleteListener to begin with. If anything else was possible, setValue would have simply returned the result instead of requiring an OnCompleteListener.

The common solution would thus be to pass a (custom) callback to your writeDataIntoFirebaseDB that takes a boolean, and then calling that from OnComplete. For an example of this and more information, see: getContactsFromFirebase() method return an empty list

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks a lot for your answer. I know that Firebase (unfortunately) works asychronously. Because of that I have implemented a callback as explained in this video https://www.youtube.com/watch?v=OvDZVV5CbQg&ab_channel=AlexMamo. You can have a look at the code and see that I in fact already use a Callback interface. Still, even with the callback interface the method `writeDataIntoFirebaseDB` always returns `false` to the calling fragments and I am wondering how to tackle this problem. – VanessaF Oct 24 '22 at 17:32
  • Your other suggested solution "The solution for this problem is always the same: any code that needs to run after the data has been written, has to be inside the onComplete function", also does not work. I had tried this before, but the problem is that I can't display the snackbar in Android from the Firebase handling methods. The snackbar needs to be called from the Fragments (see https://stackoverflow.com/questions/73176679/android-snackbar-no-suitable-parent-found-from-the-given-view-please-provide-a). – VanessaF Oct 24 '22 at 17:34
  • Your `writeDataIntoFirebaseDB` doesn't handle the asynchronous nature of the callback. You can't return a value **now** that hasn't been loaded yet, and there's no way delay the `return something` until the loading is done. So this will not work: `return dataCouldBeWrittenIntoFirebaseDB;` Instead, you will on that level too have to pass in a callback to `writeDataIntoFirebaseDB` and then call the callback *when* the async loading has completed. – Frank van Puffelen Oct 24 '22 at 22:38
  • Thanks Frank for your answer. You wrote "Instead, you will on that level too have to pass in a callback to writeDataIntoFirebaseDB and then call the callback when the async loading has completed."--> I am actually exactly doing this as far as I see it. The method `private static void writeIntoFirebase (HelpFunctions.FirebaseCallback firebaseCallback, String entryIdComplete, Object dataBaseEntry, DatabaseReference ordersRef )` expects a Callback and calls the callback during the execution of the `onComplete` method of Firebase `firebaseCallback.onCallBack (true/false);` – VanessaF Oct 27 '22 at 15:59
  • But then you're setting a variable `dataCouldBeWrittenIntoFirebaseDB[0] = true;` (which happens asynchronously) and do `return dataCouldBeWrittenIntoFirebaseDB[0]` (which happens synchronously). It's really simple: there's no way to return the value of an async operation like you're trying to do with this method signature `public static boolean writeDataIntoFirebaseDB`. The questions I linked show why not, how to troubleshoot this (logging values will show the execution order is simplest), and that you **always** have to pass in a callback, which you don't do for that last signature I show – Frank van Puffelen Oct 27 '22 at 19:09
  • Thanks a lot Frank for your comment. Unfortunately the linked questions don't help me. In my case the method `public static boolean writeDataIntoFirebaseDB (Object dataBaseEntry, ...)` is called from 20 other fragments. Now you said that this method needs a callback as an argument. So I need to create this callback-interface in each of the 20 Fragments so each Fragment will have a different callback interface: Fragment1.FirebaseCallback, Fragment2.FirebaseCallback etc.. So what should be the expected callback type in the method `writeDataIntoFirebaseDB ` in the class `HelpFunctions`? – VanessaF Oct 28 '22 at 16:21
  • The callback needs to be of whatever data you "return". So in the linked question it's `UserListCallback` and passes a `UserList`. If you want to "return" a boolean, you'd create a callback where you pass a boolean. – Frank van Puffelen Oct 28 '22 at 17:06
  • Thanks for your answer Frank. I really appreciate it. – VanessaF Oct 30 '22 at 08:57
1

refactor your HelpFunctions class as below. Remove static keyword from the function, make the interface public and create a property for that interface.

public class HelpFunctions {

    FirebaseCallback firebaseCallback;

    public HelpFunctions(FirebaseCallback callback) {
        this.firebaseCallback = callback;
    }

    public void writeDataIntoFirebaseDB (Object dataBaseEntry, String dbTable, String entryIdPrefix) {

        final boolean[] dataCouldBeWrittenIntoFirebaseDB = {false};
        long currentTimeMillis = System.currentTimeMillis();
        SimpleDateFormat sdf1 = new SimpleDateFormat("dd-MM-yy");
        sdf1.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
        SimpleDateFormat sdf2 = new SimpleDateFormat("HH-mm-ss");
        sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));

        DatabaseReference rootRef = FirebaseDatabase.getInstance("https://...").getReference();
        DatabaseReference ordersRef = rootRef.child(dbTable);

        String entryIdComplete =entryIdPrefix + "_date_" + sdf1.format(new Date()) + "_time_" + sdf2.format(new Date());

        ordersRef.child(entryIdComplete).setValue(dataBaseEntry).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    Log.e("HelpFuncTag",  "Data successfully written.");
                    firebaseCallback.onCallBack(true);
                }
                else {
                    Log.e("HelpFuncTag", task.getException().getMessage());
                    firebaseCallback.onCallBack(false);
                }
            }
        });
    }


    public interface FirebaseCallback  {
        void onCallBack(boolean dataCouldBeWrittenIntoFirebase);
    }
}

And in your fragments implements FirebaseCallback and create a HelpFunctions objects like below.

public class SampleFragment extends Fragment implements HelpFunctions.FirebaseCallback {

    HelpFunctions helpFunctions;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        helpFunctions = new  HelpFunctions(this);
    }

    @Override
    public void onCallBack(boolean dataCouldBeWrittenIntoFirebase) {
        // Handle your fragment logic here
    }
}
Byte Code
  • 96
  • 1
  • 3
  • Thanks a lot for your answer. I can't get rid of the static class and static method because I use them multiple times in my app and I can't make it non-static. So I need a solution where the class and the method remain static. – VanessaF Oct 24 '22 at 17:27
  • Then, I suggest to create a BaseFragment and include HelpFunctions class to that BaseFragment. And extend all your fragments from that BaseFragment. [Here](https://dev.to/enyason/how-to-set-up-a-base-fragment-class-with-viewbinding-and-viewmodel-on-android-57g1) is an example on how to do it. – Byte Code Oct 25 '22 at 17:30
  • Thanks for your comments, Muhammed. Unfortunately I don't use Kotlin this is why I can't understand the tutorial you posted. Still I appreciate your effort – VanessaF Oct 27 '22 at 16:05