0

I swear I searched a lot but in the end I'm surrendering, I'm going to ask. I'm making an app where there is only one Activity and everything is managed through Fragments, everything works perfectly, layout is well setup, but I have a problem with the signup.

I have 3 types of signup, mail and password, Facebook and Google. Emailsignup method:

 private void emailSignUp() {

    String email = etEmail.getText().toString();
    String password = etPassword.getText().toString();
    if(email.isEmpty()||password.isEmpty()) {
        Toast.makeText(getActivity(), "Empty fields are not allowed.",
                Toast.LENGTH_SHORT).show();
        return;
    }

    mAuth.createUserWithEmailAndPassword(email, password)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithCredential:success");
                        user = mAuth.getCurrentUser();
                        //Read DB Score
                        database = FirebaseDatabase.getInstance();
                        database.getReference(user.getUid())
                                .addListenerForSingleValueEvent(new ValueEventListener() {
                                    @Override
                                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                                        if(!dataSnapshot.exists()) database.getReference(user.getUid()).setValue(0);
                                        updateUI(user);
                                    }

                                    @Override
                                    public void onCancelled(@NonNull DatabaseError databaseError) {

                                    }
                                });
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "createUserWithEmail:failure", task.getException());
                        Toast.makeText(getActivity(), "Authentication failed.",
                                Toast.LENGTH_SHORT).show();
                    }
                }
            });
}

The other 2 are the same; the methods are called from onActivityCreated trough the use of onClickListener, the method works correctly.

As you can see, everyone of those methods creates a DB value with the UID of the user just created, and it sets it up to 0. Everything works perfectly up to here. There is an updateUI method which is responsible to move Fragment if user is not null, the next Fragment is a Dashboard and here I have a problem with the data fetch from the server: every time I register a new user I get a null object reference when I call the db because the async creation of the db key in the precedent fragment has still not finished, if I restart the app, the dashboard fragment works perfectly because the value is there.

In the dashboard Fragment I'm fetching DB data from onStart.

I'm almost completely sure that what's wrong in my code is the placing of the button calls or the fetchData in the Dashboard Fragment, I tried understanding Fragment lifecycle but can't understand it if async tasks are called.

Any help?

P.S.: Every object or value is set up as private in the Fragment code.

As requested the error:

2019-11-25 20:16:04.403 3342-3342/com.d136.uselessrank E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.d136.uselessrank, PID: 3342
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.isEmpty()' on a null object reference
    at com.d136.uselessrank.fragments.fragmentDashboard.getName(fragmentDashboard.java:142)
    at com.d136.uselessrank.fragments.fragmentDashboard.onStart(fragmentDashboard.java:67)
    at androidx.fragment.app.Fragment.performStart(Fragment.java:2632)
    at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:915)
    at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
    at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
    at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
    at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
    at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
    at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
    at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
    at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6119)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

and Fragment Dashboard:

    private void getName() {
    if(currentUser.getDisplayName().isEmpty()) email.setText(currentUser.getEmail());
    else email.setText(currentUser.getDisplayName());
}

that is called from the same FragmentDashboard:

    @Override
public void onStart() {
    super.onStart();
    getName();
    getProfileImage();
    readScore();
}

currentUser it's null for sure, the problem is that when currentUser is called (OnActivityCreated) the fragment still has not finished populating that db value. How do I make it wait for it?

Solved thanks to @Frank van Puffelen, in Fragment Dashboard I implemented:

    @Override
public void onStart() {
    super.onStart();
    FirebaseAuth.getInstance().addAuthStateListener(new FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
            mAuth = FirebaseAuth.getInstance();
            currentUser = mAuth.getCurrentUser();
            updateUI(currentUser);
        }
    });

}

and after a little debugging everything went smoothly.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
davide136
  • 3
  • 2
  • " everytime i register a new user i get a null object reference when i call the db" Can you update your question to include the exact error message and the stack trace of the error. Please also highlight what lines in the code that you shared the stack trace points to. – Frank van Puffelen Nov 25 '19 at 15:41
  • updated the code – davide136 Nov 25 '19 at 19:18
  • My first guess is that `currentUser` is `null` there. To learn how to be sure about that, and what you can do to fix it, see: https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it – Frank van Puffelen Nov 25 '19 at 20:50
  • it's null for sure, the problem is that when currentUser is called (OnActivityCreated) the frgment still has not finished populating that db value. How do i make it wait for it? – davide136 Nov 25 '19 at 20:57
  • After `createUserWithEmailAndPassword` completes, the `FirebaseAuthentication.getInstance().getCurrentUser()` should not be `null`. But if you need the current state, add an auth state listener so you can wait for it to complete. Start with `FirebaseAuth.getInstance().addAuthStateListener(listener)`. – Frank van Puffelen Nov 25 '19 at 21:09
  • Ok perfect, solved the problem thanks to you! – davide136 Nov 25 '19 at 22:12
  • Good to hear. I wrote it up in an answer with an example, and included another way to solve the same problem, which also prevents the (slight) delay that the listener incurs. – Frank van Puffelen Nov 26 '19 at 00:36

1 Answers1

0

When you create a new user, the user object in the AuthResult will be populated correctly. However, the FirebaseAuth.getInstance().getCurrentUser() is populated asynchronously.

Usually this is not a problem, but if you transition to a new activity/fragment, you can't rely on FirebaseAuth.getInstance().getCurrentUser() being populated yet.

You have two options here:

  1. Put the value of authResult.getUser() in shared preferences, and use that in the next activity/fragment as the current user.
  2. Attaching an auth state listener with FirebaseAuth.getInstance().addAuthStateListener(listener) in the next activity/fragment, and wait for the user to be initialized there.

    FirebaseAuth.getInstance().addAuthStateListener(new  FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
            FirebaseUser user = firebaseAuth.getCurrentUser();
            if (user != null) {
                // User is signed in
                Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
            } else {
                // User is signed out
                Log.d(TAG, "onAuthStateChanged:signed_out");
            }
            // ...
        }
    });
    
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807