2
\$\begingroup\$

I am developing another bigger project, I want to try a simple DatingApp and currently I try to develop a Login and Registration Form. There is no special register / login button, the code will later decide wether email is registered (-> login) or not (-> register). But even though it's working I am not sure if I make things to complex:

First I want so show a very simple overview of the logic I try to develop:

enter image description here

The App launches with the LogoActivity as a simple SplashScreen. In here the Activity decides which Activity and Fragment is about to be displayed next. It has a sharedElementTransition connected to the AuthOptionActivity.

The AuthOptionActivity only contains the Logo and a fragment Container.
Depending on the AuthStatus it will show the AuthOptionFragment, containing a Layout with two Buttons. Here you can decide which way you want to use for your registration.

In case there is for example an email-registered user but he is not verified yet the AuthOptionActivity will show the MailAuthFragment instead of the AuthOptionFragment.

The MailAuthOptionFragment itself contains another two ChildFragments.
The EnterMailSubFragment contains two EditText-Views and the ConfirmMailSubFragment is just a Text containing the Email Adress and a clickable resend textview (not implemented yet).

The code so far implemented is working but it is not as clear as I want it to be.

Logo Activity

public class LogoActivity extends AppCompatActivity {

    private ImageView   mLogo;
    private Handler     mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_logo);
        this.init();
    }

    private void init(){
       mLogo = findViewById(R.id.activity_logo_logo);
        AuthHandler.performStartUpCheck(new AuthHandler.StartupAuthStatus() {
            @Override
            public void noUser() {
                Toast.makeText(LogoActivity.this, "No User", Toast.LENGTH_SHORT).show();
                performSharedElementTransactionToAuthOption();
            }

            @Override
            public void onError(String e) {
                Toast.makeText(LogoActivity.this, e, Toast.LENGTH_SHORT).show();
                performSharedElementTransactionToAuthOption();
            }

            @Override
            public void onPhone() {
                Toast.makeText(LogoActivity.this, "Phone", Toast.LENGTH_SHORT).show();
                //direct to loadingActivity
            }

            @Override
            public void onEmail(boolean isVerified) {
                Toast.makeText(LogoActivity.this, "Email" + String.valueOf(isVerified), Toast.LENGTH_SHORT).show();
                if(isVerified){
                    //direct to LoadingActivity
                }else{
                    performSharedElementTransactionToAuthOption();
                }
            }
        });
       //this.performSharedElementTransaction();
    }

    private void performSharedElementTransactionToAuthOption(){
        //check for auth and proceed to Loading
        Intent pIntent = new Intent(LogoActivity.this, AuthOptionActivity.class);
        ActivityOptions mOptions = ActivityOptions.makeSceneTransitionAnimation(LogoActivity.this, mLogo, mLogo.getTransitionName());
        startActivity(pIntent, mOptions.toBundle());

    }

    private void performTransactionToLoadingScreen(){
        /**dummy**/
    }

    @Override
    protected void onStop() {
        //Call finish here to avoid flickering 
        this.finish();
        super.onStop();
    }
}

Nothing so special here, I am quite happy with that class. Disregard some comments, they are just for me as a reminder.

AuthHandler

public class AuthHandler {

    public interface StartupAuthStatus{
        void noUser();
        void onError(String e);
        void onPhone();
        void onEmail(boolean isVerified);
    }

    public interface SignInStatus{
        void onEmail(boolean isVerified);
    }

    private FirebaseAuth mAuth;
    private FirebaseFirestore mFirestore;

    /**STARTUP PROCEDURE**/

    public static void performStartUpCheck(final StartupAuthStatus mCallback){
        final FirebaseAuth mAuth = FirebaseAuth.getInstance();
        checkStartupAuthStatus(mAuth, mCallback);

    }

    private static void checkStartupAuthStatus(FirebaseAuth mAuth, StartupAuthStatus mCallback){
        mAuth = FirebaseAuth.getInstance();
        checkForCurrentUser(mAuth,mCallback);
    }

    private static void checkForCurrentUser(FirebaseAuth mAuth, StartupAuthStatus mCallback){
        if(mAuth.getCurrentUser()!=null){
            checkRegistrationMethod(mAuth, mCallback);
        }else{
            mCallback.noUser();
        }
    }

    private static void checkRegistrationMethod(final FirebaseAuth mAuth, final StartupAuthStatus mCallback){
        if(mAuth.getUid() == null){
            mCallback.onError("NullPointerInUID");
            return;
        }
        FirebaseFirestore.getInstance().collection("Registration").document(mAuth.getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if(task.getResult() != null && task.getResult().exists()){
                    Map<String, Object> mData = task.getResult().getData();
                    String mRegistrationForm = (String) mData.get("reg");
                    if(mRegistrationForm.equals("mail")){
                        checkMailVerificationStatus(mAuth, mCallback);
                    }else if(mRegistrationForm.equals("phone")){
                        mCallback.onPhone();
                    }
                }
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                mCallback.onError(e.getMessage());
                mAuth.signOut();
            }
        });
    }

    private static  void checkMailVerificationStatus(FirebaseAuth mAuth, StartupAuthStatus mCallback){
        if(mAuth.getCurrentUser().isEmailVerified()){
            mCallback.onEmail(true);
        }else{
            mCallback.onEmail(false);
        }
    }

    /**LOGIN PROCEDURE**/

    public static void enterUserWithEmailAndPassword(String mEmail, String mPassword){
        FirebaseAuth mAuth = FirebaseAuth.getInstance();
        createUserWithEmailAndPassword(mAuth, mEmail, mPassword);
    }

    private static void createUserWithEmailAndPassword(FirebaseAuth mAuth, String mEmail, String mPassword){

    }

    private static void signUserWithEmailAndPassword(FirebaseAuth mAuth, String mEmail, String mPassword){

    }

    private static void sendVerificationEmail(FirebaseAuth mAuth){

    }

    private static void setupRegistrationModel(String mModelType){

    }

    /**DELETE PROCEDURE**/
}

This class is made to "outsource" the code mess. Here I am not sure if it's a common or even usefull way to handle this. The Database contains Users (with a lot of Data later) and a Pool of Registrations, here I only store the choosen registration form (field reg(String): "phone" or "mail").

I need to seperate it somehow as the email-registrated-user needs to verify the email.

AuthOptionActivity

public class AuthOptionActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_authoption);
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            getWindow().setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(android.R.transition.move));
        }
        this.checkAuthStatus();
    }

    @Override
    public void onBackPressed() {
        findViewById(R.id.activity_authoption_logo).setTransitionName(null);
        super.onBackPressed();
        /** Problem occured: SharedViewElement will be visible after App was closed.
         * Reason: The View will try to perform the ExitTransition
         * Solution 1: Delete super.onBackPressed(); and override with finish();
         * Solution 2: Set Transitionname to Null**/
    }

    /**The SlideEnter Transition is variable, on First Startup we want a smooth slide in,
     * for phoneauth we want a Left-Side Slide and for Email a Right-Side Slide.
     * The Exit Animation is always slide-out-bottom-smooth.
     * The Reenter Animation is always slide-in-bottom-smooth and due to overlapping
     * the ExitTransition of the View before BackPress will be hidden and is always 0**/

    private void performFragmentTransaction(Fragment mFragment, String mTag, Boolean mAddToBackStack, int mEnterTransition){
        FragmentTransaction mFragmentTransaction = getSupportFragmentManager().beginTransaction();
        mFragmentTransaction.setCustomAnimations(mEnterTransition, 0, R.anim.slide_in_bottom_smooth, 0);
        if(mAddToBackStack){
            mFragmentTransaction.addToBackStack(mTag);
        }
        mFragmentTransaction.replace(R.id.activity_authoption_container, mFragment, mTag);
        mFragmentTransaction.commit();
    }


    /**Check Auth Status: if CurrentUser != null but not Email verified -> Show MailAuth**/
    private void checkAuthStatus(){
        if(FirebaseAuth.getInstance().getCurrentUser()!=null && !FirebaseAuth.getInstance().getCurrentUser().isEmailVerified()){
            //very bad way to force a backstack, still looking for a solution
            this.showAuthOptionFragment();
            this.showMailAuth();
        }else{
            this.showAuthOptionFragment();
        }
    }

    /**Handle Fragment Transaction including Listener**/
    private void showAuthOptionFragment(){
        this.performFragmentTransaction(AuthOptionFragment.newInstance(new AuthOptionFragment.ClickListener() {
            @Override
            public void onMail() {
                showMailAuth();
            }

            @Override
            public void onPhone() {
                showPhoneAuth();
            }
        }), "AuthOption", false, R.anim.slide_in_bottom);
    }

    private void showPhoneAuth(){
        this.performFragmentTransaction(_PhoneAuthFragment.newInstance(new _PhoneAuthFragment.AuthStatusListener() {
            @Override
            public void onCancel() {
                //PhoneAuthFragment contains a "back" Button it will fire "onCancel()"
                onBackPressed();
            }

            @Override
            public void onSuccess() {
                //show LoadingScreen
            }
        }), "PhoneAuth", true, R.anim.slide_in_left_smooth);
    }

    private void showMailAuth(){
        this.performFragmentTransaction(_MailAuthFragment.newInstance(new _MailAuthFragment.AuthStatusListener() {
            @Override
            public void onCancel() {
                //MailAuthFragment contains a "back" Button it will fire "onCancel()"
                onBackPressed();
            }

            @Override
            public void onSuccess() {
                //show LoadingScreen
            }
        }), "MailAuth", true, R.anim.slide_in_right_smooth);
    }

}

For this activity I have some questions:

  1. I am checking the Auth Status & email again to decide which fragment to show. It's easy and simple, but repetitive (it will occur again in MailAuthFragment). Is it a common way to repetitive use FirebaseAuth instances?
  2. I have seen a couple of fragment -> activity communication solutions using onAttach etc. Is my solution to pass a CustomListener(interface) in the newInstance method okay? Is there any bug that could occur using it that way?
    I pretty much like it as it makes the handling easier.

MailAuthOptionFragment

public class _MailAuthFragment extends Fragment {

    public interface AuthStatusListener{
        void onCancel();
        void onSuccess();
    }

    private AuthStatusListener mCallback;
    private String mActiveWindow;
    private CheckCancelButton mCheckCancelButton;

    private String mEmail;
    private String mPassword;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View mView = inflater.inflate(R.layout.fragment_mailauth, container, false);
        this.checkAuthStatus();
        this.initButton(mView);
        return mView;
    }

    /**PUBLIC METHODS**/

    /**Set the Fragment Listener for AuthOptionActivity**/
    public void setListener(AuthStatusListener mCallback){
        this.mCallback = mCallback;
    }

    /**Replaces hard coded Instances as Listener will be passed directly**/
    public static _MailAuthFragment newInstance(AuthStatusListener mCallback){
        _MailAuthFragment mFragment = new _MailAuthFragment();
        mFragment.setListener(mCallback);
        return mFragment;
    }

    /**PRIVATE METHODS**/

    /**Check AuthStatus, if CurrentUser!=null but not verified -> Show VerificationScreen**/
    private void checkAuthStatus(){
        if(FirebaseAuth.getInstance().getCurrentUser() != null && !FirebaseAuth.getInstance().getCurrentUser().isEmailVerified()){
            this.showConfirmEmailSubFragment(R.anim.slide_in_right_smooth);
        }else{
            this.showEnterEmailSubFragment(R.anim.slide_in_right_smooth);
        }
    }

    /**Handle Registration Process Fragments**/
    private void performFragmentTransaction(Fragment mFragment, String mTag, int mEnterAnim){
        mActiveWindow = mTag;
        FragmentTransaction mFragmentTransaction = getChildFragmentManager().beginTransaction();
        mFragmentTransaction.setCustomAnimations(mEnterAnim, 0);
        mFragmentTransaction.replace(R.id.fragment_mailauth_container, mFragment, mTag);
        mFragmentTransaction.commit();
    }

    /**MailAuth receives any valid TextChange from SubFragment**/
    private void showEnterEmailSubFragment(int mEnterAnim){

        this.performFragmentTransaction(EnterMailSubFragment.newInstance(new EnterMailSubFragment.EnterMailListener() {
            @Override
            public void onEmailChanged(String mEmail) {
                _MailAuthFragment.this.mEmail = mEmail;
            }

            @Override
            public void onPasswordChanged(String mPassword) {
                _MailAuthFragment.this.mPassword = mPassword;
            }
        }), "EnterMail", mEnterAnim);

    }

    /**MailAuth receives resend VerificationEmailRequest from SubFragment**/
    private void showConfirmEmailSubFragment(int mEnterAnim){
        this.performFragmentTransaction(new ConfirmMailSubFragment(), "ConfirmMail", R.anim.slide_in_right_smooth);
    }

    private void initButton(final View mView){
        /**Custom Button using AfterEffects, Bodymovin and LottieView, contains some commands like "transform to tick"**/
        mCheckCancelButton = new CheckCancelButton((LottieAnimationView)mView.findViewById(R.id.fragment_mailauth_nextBtn));
        mCheckCancelButton.setCustomClickListener(new CheckCancelButton.CheckCancelButtonClickListener() {
            @Override
            public void onClick() {
                switch(mActiveWindow){
                    case "EnterMail":
                        // the received Email and Password will be passed to AuthHandlerClass - not implemented yet
                        break;
                    case "ConfirmMail":
                        // the AuthHandler will reloard the currentUser and check for email verification Status - not implemented yet
                        break;
                }
            }
        });
    }

}

Checking AuthStatus again and choosing whether to show the EnterEmailSubFragment or the ConfirmEmailSubFragment. This class contains a button handling both SubFragment pages. This leads to that switch-case method. Not sure if that is a proper way.

EnterMailSubFragment

public class EnterMailSubFragment extends Fragment {

    public interface EnterMailListener{
        void onEmailChanged(String mEmail);
        void onPasswordChanged(String mPassword);
    }

    private EnterMailListener   mCallback;
    private EditText            mEmail, mPassword;
    private ImageView           mEmailWarning, mPasswordWarning;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View mView = inflater.inflate(R.layout.subfragment_mailauth, container, false);
        this.initUiWidgets(mView);
        this.initEditText(mView);
        return mView;
    }

    public void setEnterMailListener(EnterMailListener mMailListener){
        this.mCallback = mMailListener;
    }

    public static EnterMailSubFragment newInstance(EnterMailListener mMailListener){
        EnterMailSubFragment mFragment = new EnterMailSubFragment();
        mFragment.setEnterMailListener(mMailListener);
        return mFragment;
    }

    private void initUiWidgets(View mView){
        mEmail =            mView.findViewById(R.id.subfragment_mailauth_email_ET);
        mPassword =         mView.findViewById(R.id.subfragment_mailauth_password_et);
        mEmailWarning =     mView.findViewById(R.id.subfragment_mailauth_email_warning);
        mPasswordWarning =  mView.findViewById(R.id.subfragment_mailauth_password_warning);
    }

    /**Any Valid Entry will be passed to MailFragment**/

    private void initEditText(final View mView){
        mEmail.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                if(mEmail.getText() != null && Patterns.EMAIL_ADDRESS.matcher(mEmail.getText().toString()).matches()){
                    mEmailWarning.setVisibility(View.GONE);
                    mCallback.onEmailChanged(mEmail.getText().toString());
                }else{
                    mEmailWarning.setVisibility(View.VISIBLE);
                }
            }
        });

        mPassword.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                if(mPassword.getText() != null && mPassword.getText().length() >= 6){
                    mPasswordWarning.setVisibility(View.GONE);
                    mCallback.onPasswordChanged(mPassword.getText().toString());
                }else{
                    mPasswordWarning.setVisibility(View.VISIBLE);
                }
            }
        });
    }
}

The Textwatcher checks every entry and transfers it to the MailAuthFragment as MailAuthFragment handels the button event (this is for design puposes, I want the content to move and the button is meant to stay in its position).

ConfirmMailAuthSubFragment

public class ConfirmMailSubFragment extends Fragment {

    public interface ConfirmMailListener{
        void onResendEmail();
    }

    private ConfirmMailListener mCallback;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View mView = inflater.inflate(R.layout.subfragment_confirmmail, container, false);

        return mView;
    }

    public void setConfirmMailListener(ConfirmMailListener mCallback){
        this.mCallback = mCallback;
    }

    public static ConfirmMailSubFragment newInstance(ConfirmMailListener mCallback){
        ConfirmMailSubFragment mFragment = new ConfirmMailSubFragment();
        mFragment.setConfirmMailListener(mCallback);
        return mFragment;
    }
}

Nothing special here yet. It will handle the Button Event "Resend Verification Mail" and transfer it to MailAuthFragment.

I know it's a very early stage of my code but as beautiful as it looks with that slide in animations I am not sure if I am running in the wrong direction or making stuff unnecessary complex.

Update
I decided to handle the onBackPress manually (no more backstack) as the ChildFragmentManager does not allow to get a step back using the backpress button. Every fragment and childfragment now has a public method "onBackPress" called from AuthOptionActivity.

AuthOptionActivity - onBackPressed()

  @Override
public void onBackPressed() {

    /**Checking for Fragments and then checking of which instance so you can acess methods**/
    if(getSupportFragmentManager().findFragmentById(R.id.activity_authoption_container) != null)
    {
        Fragment mCurrent = getSupportFragmentManager().findFragmentById(R.id.activity_authoption_container);
        if(mCurrent instanceof AuthOptionFragment){
            this.finish();
        }
        if(mCurrent instanceof _MailAuthFragment){
            ((_MailAuthFragment) mCurrent).onBackPress();
        }
        if(mCurrent instanceof _PhoneAuthFragment){
            ((_PhoneAuthFragment) mCurrent).onBackPress();
        }
    }
    findViewById(R.id.activity_authoption_logo).setTransitionName(null);
    //super.onBackPressed();      
}
\$\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.