Search code examples
androidandroid-fragmentsnullpointerexceptionandroid-viewmodel

How to use ModelView on different Fragments to get the input of an EditText and set a TextView to its value?


So basically what I want to achieve is to display the input of an EditText into a TextView of another Fragment. So a user answers a question and clicks on a button which gets him to the next fragment where in a TextView his answer should be displayed. Since I have 3 questions with the same method I am using ModelView to achieve this. Since I am relatively new to programming I don't know if this is the right way I implemented this. Your help is much appreciated!

Here is my ViewModel:

public class SharedViewModel extends ViewModel {


private HashMap<Integer, MutableLiveData<String>> answers = new HashMap<>();

public MutableLiveData<String> getAnswer(int questionId) {
    return answers.get(questionId);
}

public void setAnswer(int questionId, String answer) {
    if (answers.get(questionId) != null) {
        answers.get(questionId).setValue(answer);
    }
}
}

Here is my first Fragment with the first Question:

   ...

    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_question_1, container, false);

        btnNavFrag1 = view.findViewById(R.id.btn_question1);

        mEditTextQuestion1 = view.findViewById(R.id.edit_text_question_1);

        btnNavFrag1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                viewModel.getAnswer(1);

                ((GameActivity)getActivity()).setViewPager(2);

            }
        });

        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        viewModel.getAnswer(1).observe(getViewLifecycleOwner(), new Observer<CharSequence>() {
            @Override
            public void onChanged(@Nullable CharSequence charSequence) {
                mEditTextQuestion1.setText(charSequence);
            }
        });
    }

And here is my Answer overview Fragment which is displayed after the first question Fragment and the typed in answer should replace the default value of a TextView:

   ...

    public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_question_answer_1, container, false);

        btnNavFragAns1 = view.findViewById(R.id.next_question_1);

        tv_answer1 = view.findViewById(R.id.answer_player_1_text_view);

        btnNavFragAns1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                ((GameActivity)getActivity()).setViewPager(3);

            }
        });

        return view;
    }

    @Override
    public void onActivityCreated(Bundle bundle) {
        super.onActivityCreated(bundle);

        viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        viewModel.getAnswer(1).observe(getViewLifecycleOwner(), new Observer<CharSequence>() {
            @Override
            public void onChanged(@Nullable CharSequence charSequence) {
                tv_answer1.setText(charSequence);
            }
        });
    }
    }

Currently I get a NullPointerException at this line:

    viewModel.getAnswer(1).observe(getViewLifecycleOwner(), new Observer<CharSequence>() {


Solution

  • After long trying I found the right solution to my problem:

    I am using a ViewModel so the 6 different fragments can store and retrieve data in the functions of the ViewModel. So basically if you have a fragment where you have for example an EditText and want to display its input in another fragment, this is the way that worked for me:

    1. Create a ViewModel with set and get methods:
    public class SharedViewModel extends ViewModel {
    
    
        private MutableLiveData<String> mAnswer1 = new MutableLiveData<>();
        private MutableLiveData<String> mAnswer2 = new MutableLiveData<>();
        private MutableLiveData<String> mAnswer3 = new MutableLiveData<>();
    
        public void setAnswer1 (String answer1){
            mAnswer1.setValue(answer1);
        }
    
        public void setAnswer2 (String answer2){
            mAnswer2.setValue(answer2);
        }
    
        public void setAnswer3 (String answer3){
            mAnswer3.setValue(answer3);
        }
    
        public LiveData<String> getAnswer1(){
            return mAnswer1;
        }
        public LiveData<String> getAnswer2(){
            return mAnswer2;
        }
        public LiveData<String> getAnswer3(){
            return mAnswer3;
        }
    }
    
    
    1. In the fragment where you have an EditText and want to use its input, you have to call the set method of the ViewModel.:
    // This method gets the input of the EditText and "sends" it to the SharedViewModel where the next fragment can access it
        @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            EditText nameEditText = view.findViewById(R.id.edit_text_question_1);
    
            // Add Text Watcher on name input text
            nameEditText.addTextChangedListener(new TextWatcher() {
                @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
                }
    
                @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    viewModel.setAnswer1(charSequence.toString());
                }
    
                @Override public void afterTextChanged(Editable editable) {
    
                }
            });
        }
    

    And before the onCreate method you have to call this:

    public FragmentQuestion1() {
            // Required empty public constructor
        }
    
        /**
         * Create a new instance of this fragment
         * @return A new instance of fragment FirstFragment.
         */
        public static FragmentQuestion1 newInstance() {
            return new FragmentQuestion1();
        }
    
        @Override public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // init ViewModel
            viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel.class);
        }
    
    1. In the fragment where you want to use the input, you just have to call the get method of the ViewModel:
    // Gets the answer from player 1 from the previous fragment and set the TextView to the answer
        @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            tv_answer1 = view.findViewById(R.id.answer_player_1_text_view);
    
            viewModel.getAnswer1().observe(requireActivity(), new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
                    tv_answer1.setText(s);
                }
            });
        }
    

    And like in the other fragment before you have to call the following before the onCreate method:

    /**
         * Use this factory method to create a new instance of this fragment.
         *
         * @return A new instance of fragment SecondFragment.
         */
        public static FragmentAnswer1 newInstance() {
            return new FragmentAnswer1();
        }
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // initialise ViewModel here
            viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel.class);
        }
    

    I hope this will somehow help somebody because I struggled for a long time to find the right answer to my problem.