1
\$\begingroup\$

I am new to both asynchronous programming and Android development. I am allowing the user to sign in with either their email/password or their Google account. Upon successfully signing in, the user's Stripe payment information is collected if it exists and then forwarded to the CheckIn activity.

Some problems I currently have:

  • I want to separate out the call to my server that requests the user's Stripe information. Because this is an asynchronous task, sometimes the activity will forward to the CheckIn activity without having gathered the info.
  • Clean up the code. I have no idea what to do to organize what has turned into spaghetti
  • Separate out the Firebase calls into a library
  • Implement best practices for the UI

Because both my database and Firebase calls are asynchronous, I'm assuming a way to retrieve their information and continue processing are through callbacks, but I have no idea how to do that.

My LoginActivity.class

public class LoginActivity extends AppCompatActivity implements  View.OnClickListener, GoogleApiClient.OnConnectionFailedListener{

    private EditText emailEditText;
    private EditText passwordEditText;

    private FirebaseAuth mFirebaseAuth;
    private FirebaseAuth.AuthStateListener mAuthStateListener;
    private RelativeLayout progressBar;

    private GoogleApiClient mGoogleApiClient;
    private volatile boolean googleSignOn;
    private long lastClickTime = 0;

    private static final int RC_SIGN_IN = 9001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        if(FirebaseAuth.getInstance().getCurrentUser() != null){
            progressBar = (RelativeLayout) findViewById(R.id.loginProgressBar);
            progressBar.setVisibility(View.VISIBLE);
            progressBar.bringToFront();
            setupUsersManager();
        }
        Calligrapher calligrapher = new Calligrapher(this);
        calligrapher.setFont(this, "fonts/GothamRoundedMedium_21022.ttf", true);

        emailEditText = (EditText) findViewById(R.id.email);
        passwordEditText = (EditText) findViewById(R.id.password);

        mFirebaseAuth = FirebaseAuth.getInstance();
        googleSignOn = new Boolean(false);

        // Configure sign-in to request the user's ID, email address, and basic
        // profile. ID and basic profile are included in DEFAULT_SIGN_IN.
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client))
                .requestEmail()
                .build();


        // Build a GoogleApiClient with access to the Google Sign-In API and the
        // options specified by gso.
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* Activity */, this /* OnConnectionFailedListener */)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

        //set clicklistener for signup button
        findViewById(R.id.signUpButton).setOnClickListener(this);
        findViewById(R.id.signInButton).setOnClickListener(this);
        findViewById(R.id.googleSignIn).setOnClickListener(this);

        mAuthStateListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = mFirebaseAuth.getCurrentUser();

                if(user != null) {
                    Log.d("Sign in", user.getDisplayName() + " is signed in");
                }
                else {
                    Log.d("Sign in", "user is signed out");
                }
            }
        };
    }

    private void signInWithEmail() {
        String email = emailEditText.getText().toString().trim();
        String password = passwordEditText.getText().toString().trim();

        if(TextUtils.isEmpty(email)){
            Toast.makeText(LoginActivity.this, "Please be sure to enter email", Toast.LENGTH_SHORT).show();
            return;
        }
        if(TextUtils.isEmpty(password)){
            Toast.makeText(LoginActivity.this, "Please be sure to enter password", Toast.LENGTH_SHORT).show();
            return;
        }

        mFirebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if(task.isSuccessful()) {
                    progressBar = (RelativeLayout) findViewById(R.id.loginProgressBar);
                    progressBar.setVisibility(View.VISIBLE);
                    progressBar.bringToFront();
                    setupUsersManager();
                }
                else {
                    Toast.makeText(LoginActivity.this, "Something happened!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        //get id of calling element who just clicked
        int i = v.getId();
        //if clicked signup button
        if (i == R.id.signUpButton) {
            startActivity(new Intent(LoginActivity.this, SignupActivity.class));
        }
        else if (i == R.id.signInButton) {
            signInWithEmail();
        }
        else if(i == R.id.googleSignIn) {
            findViewById(R.id.googleSignIn).setOnClickListener(null);
            signInWithGoogle();
        }
    }

    private void signInWithGoogle() {
        Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    @Override
    public void onStart() {
        super.onStart();
        mFirebaseAuth.addAuthStateListener(mAuthStateListener);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mAuthStateListener != null) {
            mFirebaseAuth.removeAuthStateListener(mAuthStateListener);
        }
    }


    private void handleSignInResult(GoogleSignInResult result) {
        Log.d("Google Login", "handleSignInResult:" + result.isSuccess());
        if (result.isSuccess()) {
            progressBar = (RelativeLayout) findViewById(R.id.loginProgressBar);
            progressBar.setVisibility(View.VISIBLE);
            progressBar.bringToFront();

            // Signed in successfully, show authenticated UI.
            GoogleSignInAccount acct = result.getSignInAccount();
            Toast.makeText(LoginActivity.this, "Was successful", Toast.LENGTH_SHORT).show();
            firebaseAuthWithGoogle(acct);

        } else {
            // Signed out, show unauthenticated UI.
            findViewById(R.id.googleSignIn).setOnClickListener(this);
            Toast.makeText(LoginActivity.this, "Was not successful", Toast.LENGTH_SHORT).show();
        }
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            handleSignInResult(result);
        } else {
            findViewById(R.id.googleSignIn).setOnClickListener(this);
        }
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }
    @Override
    public void onBackPressed() {}

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        Log.d("Auth G w/ F", "firebaseAuthWithGoogle:" + acct.getId());

        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mFirebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d("Auth G w/ F", "signInWithCredential:onComplete:" + task.isSuccessful());

                        // If sign in fails, display a message to the user. If sign in succeeds
                        // the auth state listener will be notified and logic to handle the
                        // signed in user can be handled in the listener.
                        if (!task.isSuccessful()) {
                            Log.w("Auth G w/ F", "signInWithCredential", task.getException());
                            Toast.makeText(LoginActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        } else {
                            setupUsersManager();
                        }
                    }
                });
    }
    private void setupUsersManager(){
        final DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();

        final String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();

        if (uid != null){
            DatabaseReference ref = mDatabase.child("users");
            ref.child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    AuthenticatedUser user;
                    user = dataSnapshot.getValue(AuthenticatedUser.class);

                    if (user == null){
                        addUserToDatabase();
                    } else {
                        AuthenticatedUserManager.getInstance().setUser(user);
                        getStripeInformation();
                        startActivity(new Intent(LoginActivity.this, CheckInActivity.class));
                        finish();
                    }
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    Log.d("DatabaseCancelled", "Database returned canceled" + databaseError.toString());
                }
            });
        }
    }

    private void getStripeInformation(){
        final AuthenticatedUserManager userManager = AuthenticatedUserManager.getInstance();
        AuthenticatedUser user = userManager.getUser();

        if(!TextUtils.isEmpty(user.getStripeCustId()) && user.getStripeCustId()!= null){
            // Instantiate the RequestQueue.
            RequestQueue queue = Volley.newRequestQueue(LoginActivity.this);
            String url = "https://sleepy-forest-31423.herokuapp.com/charges/getCustomerPaymentInfo?"
                    + "stripeCustId=" + user.getStripeCustId();

            Log.d("URL", url);

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String responseJSON) {
                        // your response
                        Log.d("HTTP-RESPONSE", responseJSON);

                        try {
                            JSONObject obj = new JSONObject(responseJSON);
                            JSONArray stripeCards = obj.getJSONObject("sources").getJSONArray("data");
                            String card = stripeCards.getJSONObject(0).getString("brand")
                                    + " " + stripeCards.getJSONObject(0).getString("last4");

                            AuthenticatedUser user = AuthenticatedUserManager.getInstance().getUser();
                            user.setPayment(card);
                            user.setStripeCustId(obj.get("id").toString());
                            user.setStripeInfo(obj);
                            AuthenticatedUserManager.getInstance().setUser(user);

                        } catch (Throwable t) {
                            Log.e("FROM OBJ", "Could not parse malformed JSON: \"" + responseJSON + "\"");
                        }

                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // error
            }
        }
        );
        // Add the request to the RequestQueue.
        queue.add(stringRequest);
    }
    }
    private void addUserToDatabase(){
        DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
        AuthenticatedUser user = new AuthenticatedUser(
                mFirebaseAuth.getCurrentUser().getDisplayName(),
                "none",
                mFirebaseAuth.getCurrentUser().getEmail(),
                "none",
                "none",
                "none"
        );
        mDatabase.child("users").child(mFirebaseAuth.getCurrentUser().getUid()).setValue(user);

        mDatabase.child("users").child(mFirebaseAuth.getCurrentUser().getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                AuthenticatedUser user;
                user = dataSnapshot.getValue(AuthenticatedUser.class);
                AuthenticatedUserManager.getInstance().setUser(user);
                getStripeInformation();
                startActivity(new Intent(LoginActivity.this, CheckInActivity.class));
                finish();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.d("DatabaseCancelled", "Database returned canceled" + databaseError.toString());
            }
        });
    }

}

activity_login.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:background="@color/colorPrimary"
    tools:context="com.snapwebdevelopment.scanhappy.LoginActivity">

    <RelativeLayout
        android:id="@+id/loginProgressBar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:visibility="gone"
        android:gravity="center"
        android:background="@color/colorProgressOverlay"
        android:clickable="true">

        <ProgressBar
            android:id="@+id/progressBar"
            style="@android:style/Widget.ProgressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="57dp" />

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/logo"
            android:src="@drawable/logo3"
            android:layout_centerHorizontal="true"
            android:layout_width="240dp"
            android:layout_height="100dp"
            android:layout_marginTop="130dp"
            android:layout_marginBottom="5dp" />

        <EditText
            android:id="@+id/email"
            android:hint="@string/email"
            android:layout_below="@id/logo"
            android:inputType="textEmailAddress"
            style="@style/EditTextLong"/>

        <EditText
            android:id="@+id/password"
            android:hint="@string/password"
            android:inputType="textPassword"
            android:layout_below="@id/email"
            style="@style/EditTextLong"/>

        <Button
            android:id="@+id/signInButton"
            android:text="Sign In"
            android:layout_below="@+id/password"
            style="@style/ButtonStandard1"/>

        <TextView
            android:id="@+id/forgotPassword"
            android:layout_below="@id/signInButton"
            android:text="Forgot your login details? Get help signing in."
            android:textColor="#90FFFFFF"
            android:paddingTop="12dp"
            android:gravity="center"
            android:layout_centerHorizontal="true"
            style="@style/Subtext"/>

        <View
            android:id="@+id/divider"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="40dp"
            android:layout_below="@+id/forgotPassword"
            android:layout_width="fill_parent"
            android:layout_height=".5dp"
            android:background="#90FFFFFF"/>

        <RelativeLayout
            android:id="@+id/googleSignIn"
            android:layout_width="180dp"
            android:layout_height="30dp"
            android:layout_below="@+id/divider"
            android:layout_centerHorizontal="true">

            <ImageView
                android:id="@+id/googleIcon"
                android:layout_width="20dp"
                android:layout_height="30dp"
                android:src="@drawable/google"
                android:layout_marginRight="10dp"/>

            <TextView
                android:layout_toRightOf="@id/googleIcon"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Sign in with Google"
                android:textStyle="bold"
                android:textColor="@color/colorTextSecondary"
                android:layout_centerVertical="true"
                android:textSize="7pt"/>

        </RelativeLayout>

    </RelativeLayout>

    <Button
        android:id="@+id/signUpButton"
        android:text="Sign Up"
        style="@style/ButtonBottom3"/>

</RelativeLayout>

In order to better organize my code, I have looked at this resource, but I'm not sure where else to go.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.