Search code examples
androidandroid-fragmentsandroid-viewpager2

How to get text from an EditText in a ViewPager2?


I have a ViewPager2, with two pages. One the get an email and another to get a phone number. To obtain the values I added two get methods to the adapter but I'm not sure this is the correct and safest way to do it. Here is my code:

  public class PhoneEmailFragment extends Fragment {

private View view;
private final String[] tabs = {"Phone", "Email"};
private User user;
private int pageSelected = 0;


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

    return view;
}

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

    assert getArguments() != null;
    user = getArguments().getParcelable("user");
}

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

private void viewpagerSetup() {

    TabLayout tabLayout = view.findViewById(R.id.register_phone_email_tab);
    ViewPager2 viewPager2 = view.findViewById(R.id.register_viewpager);

    SwipeAdapter adapter = new SwipeAdapter(this);
    viewPager2.setAdapter(adapter);

    viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
        @Override
        public void onPageSelected(int position) {
            super.onPageSelected(position);
            pageSelected = position;
        }
    });

    new TabLayoutMediator(
            tabLayout,
            viewPager2,
            (tab, position) -> tab.setText(tabs[position])
    ).attach();

}

public void saveValue() {
        switch (pageSelected) {
            case 0:
                user.setUserPhoneNumber();
                break;
            case 1:
                user.setUserEmail();
                break;
        }

}

static class SwipeAdapter extends FragmentStateAdapter {

    public SwipeAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {

        Fragment fragment = new Fragment();

        switch (position) {
            case 0:
                fragment = new PhoneFieldFragment();
                break;
            case 1:
                fragment = new EmailFieldFragment();
                break;
        }
        return fragment;
    }

    @Override
    public int getItemCount() {
        return 2;
    }

}

}

I want to use the value from the EditText on the saveValue() method.


Solution

  • Obtaining the values of the EditText should be done inside your fragment classes. In order to get the values of the EditText from the fragments, the 2 get methods should be removed from your SwipeAdapter class and findViewById() calls should be placed in your fragment classes like below:

    public class EmailFieldFragment extends Fragment {
    
        private MainViewModel mainViewModel;
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_email_field, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            // Do this after an action, example - after a button click
            EditText emailEt = view.findViewById(R.id.email_et);
            emailEt.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) {
                   if (count < 1) { // i.e - no user input
                        mainViewModel.setEmail("");
                    } else {
                        mainViewModel.setEmail(s.toString());
                    }
                }
    
                @Override
                public void afterTextChanged(Editable s) {
    
                }
            });
        }
    }
    

    I updated the SwipeAdapter to accept list of fragments as follows:

    public class SwipeAdapter extends FragmentStateAdapter {
    
        private final List<Fragment> fragments;
    
        public SwipeAdapter(@NonNull FragmentActivity fragmentActivity, 
        List<Fragment> fragmentList) {
            super(fragmentActivity);
            this.fragments = fragmentList;
        }
    
        @NonNull
        @Override
        public Fragment createFragment(int position) {
            return fragments.get(position);
        }
    
        @Override
        public int getItemCount() {
            return fragments.size();
        }
    }
    

    I created a viewModel class that holds the values of the EditText fields in LiveData objects. These fields are then observed from the activity (or fragment). See the code below:

    public class MainViewModel extends ViewModel {
        private final MutableLiveData<String> _email = new 
        MutableLiveData<>();
        public LiveData<String> email = _email;
    
        private final MutableLiveData<String> _phone = new 
        MutableLiveData<>();
        public LiveData<String> phone = _phone;
    
        public void setEmail(String email) {
            _email.setValue(email);
        }
    
        public void setPhone(String phone) {
            _phone.setValue(phone);
        }
    }
    

    I also added the lifecycle dependencies.

    Finally, this is the code for the Activity below:

    public class MainActivity extends AppCompatActivity {
    
    private String email = "";
    private String phone = "";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        MainViewModel mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
    
        PhoneFieldFragment phoneFieldFragment = new PhoneFieldFragment();
        EmailFieldFragment emailFieldFragment = new EmailFieldFragment();
        List<Fragment> fragmentList = new ArrayList<>();
        fragmentList.add(phoneFieldFragment);
        fragmentList.add(emailFieldFragment);
        SwipeAdapter swipeAdapter = new SwipeAdapter(this, fragmentList);
        ViewPager2 viewPager = findViewById(R.id.viewPager);
        viewPager.setAdapter(swipeAdapter);
    
        Button button = findViewById(R.id.get_field_button);
    
        mainViewModel.email.observe(MainActivity.this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                email = s;
            }
        });
    
        mainViewModel.phone.observe(MainActivity.this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                phone = s;
            }
        });
    
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "Email: " + email + "\nPhone: " + phone, Toast.LENGTH_SHORT).show();
                // Both email and phone number are available here, so you can call your setValue() method here
            }
        });
      }
    }
    

    This is a link to the entire codebase on github

    EDIT

    I used a shared viewModel class that connects the viewPager fragments and their host activity (or another fragment as the case may be). I get the text from the EditText as the user is typing using a TextWatcher.