'Using ViewPager2 to step-by-step SignUp form with Fragments

I'm trying to implement a SignUp form with viewpager2 where the user is asked to input its name, password, phone number and photo in a 4-step sequential prompt. So I have an activity that contains a ViewPager2, 4 different fragments and 4 different classes - one for each prompt.

Each layout has an EditText for the input and a "Next" Button for the submit and going to the next item on viewpager. For each fragment the input is validated at typing and its "Next" button becomes green and clickable when validated (ex: phone has more than 6 characters). This can be seen in the example-code I show below. (Please understand that I removed some code in order to be more clear)

example-layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/et_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:lines="1" />

        </com.google.android.material.textfield.TextInputLayout>

        <Button
            android:id="@+id/bt_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:text="next" />
    </LinearLayout>

</LinearLayout>

example-fragment-class.java: (example of password, but name, phone, are very similar)

public class PasswordFragment extends Fragment implements APIConnectionCallback {
    private FragmentPasswordBinding passwordBinding;
    private final AuthActivity parent;
    private boolean isValidInput = false;

    public PasswordFragment(AuthenticationActivity parent) {
        this.parent = parent;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return FragmentPasswordBinding.inflate(inflater, container, false).getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // saves password in shared preferences (to capture it at the last viewpager item and submit) and changes viewpager to the next item
        passwordBinding.btNext.setOnClickListener(view -> {
            SharedPreferences sharedPreferences = parent.getSharedPreferences(AuthenticationActivity.AUTHENTICATION_CREDENTIALS, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();

        editor.putString("password", Objects.requireNonNull(passwordBinding.etPassword.getText()).toString());
        editor.apply();

        parent.getViewPager().setCurrentItem(parent.getViewPager().getCurrentItem() + 1, true);});
    }

    @Override
    public void onStart() {
        super.onStart();

        passwordBinding.btNext.setOnClickListener(view -> {
            if(isValidInput) {
                // queries the server if submit is clicked and input is valid
            }
        });

        // validates input while typing
        passwordBinding.etPassword.addTextChangedListener(
                new Validator.TextValidator(passwordBinding.etLoginPassword) {

                    @Override
                    public void validate(TextView textView, String password) {
                        isValidInput = Validator.matches(password, Validator.VALIDATION_KEY.PASSWORD);
                        submitButtonStateChange(); // changes the "clickability" of the submit button
                    }

                }
        );
    }

    private void submitButtonStateChange() { // this is repeating code for all fragments
        passwordBinding.btNext.setBackgroundTintList(getActivity().getBaseContext().getColorStateList(
                isValidInput ? R.color.green : R.color.black
        ));
    }
}

My code is working but feels like a mess and too repetitive. For each Fragment class the button behaviour is very similar (only changes the type of validation). There must be a 'smarter way' of doing this without all the repeating code and all the different 4 classes + 4 layouts. Thought of using an abstract Fragment that generalizes some of the repeating code for the Fragment classes, but still it doesn't solve the necessity of having all those classes and layouts.

I could imagine doing this with a single Fragment class that changes implementation of its methods according to its position within the viewpager, but can't materialise this into a working code.

Is there any good idea or practice in order to tackle something like this?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source