-1

The issue I am ultimately trying to solve, before I pose my question, is the synchronicity of querying my Firebase database and writing code based on the result. A simple example to illustrate:

Boolean userFound = false;
DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
      String name = dataSnapshot.getValue().toString();
      userFound = true;
      Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
      }

      @Override
      public void onCancelled(DatabaseError databaseError) {
          //Never used this section     
      }
});
If (!userFound) {
   Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
}

In the example above, the listener looks for a name in the database. If the name is found it gives a welcome message and sets "userFound" to true. If a name is not found, "userFound" will remain as false and you can generate a user not found message.

The problem with this is that everything runs at the same instant and so you will always get the "User not found" message instantly, and then a few seconds later the listener might actually find the user and say "Welcome back".

I have been looking into how I can possible resolve this, and I have found Java Promises. Am I looking in the right direction? Here are two promise examples:

CompletableFuture.supplyAsync(this::failingMsg)  
             .exceptionally(ex -> new Result(Status.FAILED))
             .thenAccept(this::notify);

This code looks great, and the article here is very detailed in its usage: http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/

Except for the fact that is will ONLY work in API 24 and above. Which means your app will not work on 90% of devices. So this is essentially worthless.

The other way of doing this is as follows:

 try {
 Promise { client.newCall(request).execute() }
  .then { ... }
  .thenAsync { ... }
  .then { ... }
 } catch (e: Exception) {
    ...
 }

As explained here: https://medium.com/@sampsonjoliver/promises-in-android-and-java-d6b1c418ea6c

Except that when I try to use this code there is no such thing as Promise. It just says it cannot resolve the symbol. So this guy has written an article on something that doesn't even exist.

Am I looking at the right stuff here? The end game is to make my app wait for the result of any database lookup before continuing to process code. If I cannot do this, then the database becomes completely useless.

Thanks guys. Please help!

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Bisclavret
  • 1,327
  • 9
  • 37
  • 65
  • 1
    Promise is now known as CompletableFuture in Java8. Have a look at https://stackoverflow.com/questions/14541975/difference-between-future-and-promise – Balwinder Singh Mar 07 '18 at 00:40
  • Thanks for your comment, Balwinder. If you read my question you will see that I included that, but then I also included the reasons why no one can use this yet. – Bisclavret Mar 07 '18 at 00:48
  • Please explain why "the database becomes completely useless". Asynchronous programming is not such a foreign concept. In fact, all Firebase APIs are asynchronous when it comes to doing blocking work. The recommendation is to design for this. You definitely **do not want to block your app**, as that creates severe usability problems for your users. https://medium.com/google-developers/why-are-the-firebase-apis-asynchronous-e037a6654a93 – Doug Stevenson Mar 07 '18 at 00:56
  • It becomes useless for the reasons given in the question. It prevents me from actually being able to effectively query the database and react to the results of said query. If you can solve the issue I describe (testing for a user and saying either "welcome back" or "not found", depending on the result) either logically or with a code example, then you have yourself a point. If you cannot, then I can't see how my comments do not still stand if I am unable to use the database to do even this simple thing. – Bisclavret Mar 07 '18 at 01:00
  • 2
    Perhaps you should look into using streamsupport if you wish to use this kind of functionality for API 24 and less. Have a look at https://stackoverflow.com/questions/38221673/completablefuture-in-the-android-support-library and https://stackoverflow.com/questions/32740637/java-optimizing-an-application-using-asynchronous-programming/32741591#32741591 – Balwinder Singh Mar 07 '18 at 01:12
  • Thank you, Balwinder. I added the dependencies for this as listed here: https://github.com/stefan-zobel/streamsupport but when I try to use CompletableFuture, the .supplyAsync function that is required to do this still errors and says it requires API 24. So this is not a fix :( Thank you for trying though. I am really at a loss here to see what I am missing, why is everyone saying asynchronous databases are ok? I can't even get past coding the login screen like this. How does anyone else tackle this? How can I code for a result of a query that I will essentially never receive? – Bisclavret Mar 07 '18 at 01:33
  • 1
    @Bisclavret Well the streamsupport specifically mentions `Java 8 / Java 9 CompletableFuture backport`. Maybe you can look for relevant examples/tutorials for this using streamsupport – Balwinder Singh Mar 07 '18 at 01:49
  • I have searched and I cannot find any examples mate, only those in the link I posted in my original question. – Bisclavret Mar 07 '18 at 01:53
  • Not sure about this one. But give it a go https://github.com/crawlinknetworks/android-promise – Balwinder Singh Mar 07 '18 at 01:59
  • Also found something similar to your issue here https://stackoverflow.com/questions/38173569/only-load-layout-when-firebase-calls-are-complete/38188683#38188683 – Balwinder Singh Mar 07 '18 at 02:02
  • Not an expert of Java 8 or 9 here but I wouldn't count on them. Have you looked at RxJava? – ericn Mar 07 '18 at 02:59
  • 1
    I still don't understand the "useless" argument, as there are lots of examples, codelabs, and actual apps out there that make use of Firebase APIs. Have you looked into any of these? – Doug Stevenson Mar 07 '18 at 03:52
  • @Doug I get that there would be other apps. I do not have the source code for other apps. Again, I can only repeat again this very simple question: If you can solve the issue I describe (testing for a user and saying either "welcome back" or "not found", depending on the result) either logically or with a code example, then all of your arguments are now valid and we can delve into these. If you cannot answer that question then they are not. There is nothing else to this. Either I can make use of the data in the database or I cannot. – Bisclavret Mar 07 '18 at 05:57
  • Please try following the codelab for Firebase on Android to get a sense for how things work. https://codelabs.developers.google.com/codelabs/firebase-android/ – Doug Stevenson Mar 07 '18 at 06:08
  • @Bisclavret "but when I try to use CompletableFuture, the .supplyAsync function that is required to do this still errors and says it requires API 24". You should ask a new question and describe exactly what you are doing. That "it requires API 24" stuff is definitely nonsense. – Sartorius Mar 09 '18 at 08:49
  • 1
    Hint: I've often found it helpful to setup a simple test on plain Java 7 when Android Studio unexpectedly claimed that some code of mine "requires API 24". Usually it was my fault, inadvertently mixing a Java 8 feature into my code. – Sartorius Mar 09 '18 at 09:04

1 Answers1

2

The solution with a problem using asynchronous APIs is pretty much always the same: move the code that needs access to the data into the method that is called when the data is available.

So in your case that means moving the check and toast into onDataChange:

DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
        Boolean userFound = false;
        String name = dataSnapshot.getValue().toString();
        userFound = true;
        Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
        if (!userFound) {
           Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
        }
      }

      @Override
      public void onCancelled(DatabaseError databaseError) {
          throw databaseError.toException();
      }
});

For more on this, see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • That would be the common sense answer, if any of this worked in a common sense way! The problem with this is that the onDataChange function is ONLY entered into when the data is found. So "if (!userFound) {" will never, ever trigger. You either put the check where you have and it never triggers, or you move it outside the function and it always triggers. Each case regardless of the actual result. Yay asynchronous databasing! – Bisclavret Mar 07 '18 at 06:07
  • 1
    `onDataChange` fires with the current value. If there is no value, it fires with an empty snapshot. Does that not happen for you? – Frank van Puffelen Mar 07 '18 at 13:51
  • Thanks @Frank van Puffelen, I do exactly that, but just out of curiosity, sometimes I have to get a value that needs me to nest 2 more levels down. Meaning inside my this onDataChangethere is listener no2, and inside listeener2's onDataChange there is litener3. Is there a better way of handling this?? – user3833732 Sep 14 '18 at 05:26