0

I am doing an app using a single activity architecture. The navigation is done by the navigation component. The relevant snippet of my navigation graph looks like that:

nav_graph

On the transition from listLensesFragment to generalLensInformationFragment a bundle is added.

The three fragments generalLensInformationFragment, leftLensDetailsFragment and rightLensDetailsFragment are using a shared ViewModel.

So far all of the things work - but as soon as I navigate for a second time from listLensesFragment to generalLensInformationFragment the app crashes at the point of receiving the bundle by using getArguments():

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

        manageLensViewModel = new ViewModelProvider(getActivity()).get(ManageLensViewModel.class);

        Bundle bundle = this.getArguments();

In debug mode, checking "this", bundle with its object inside is there.

But the logcat output looks the follwoing:

java.lang.IllegalStateException: Fragment LeftLensDetailsFragment{f731448} (30ef95ba-f165-4385-8b84-c63a7eb435c5) not attached to a context.
        at androidx.fragment.app.Fragment.requireContext(Fragment.java:900)
        at androidx.fragment.app.Fragment.getResources(Fragment.java:964)
        at androidx.fragment.app.Fragment.getString(Fragment.java:986)

I have no clue what to do whit those errors. Below the most relevant parts of my code.

Clicking edit object in listLensesFragment:

    adapter.setOnItemEditClickListener(new LensAdapter.OnItemEditClickListener() {
        @Override
        public void onItemClick(LensWithWears lensWithWears) {

            Bundle bundle = new Bundle();
            Lens lens = lensWithWears.lens;
            bundle.putParcelable(BUNDLE_LENS, lens);
            navController.navigate(R.id.action_listLensesFragment_to_generalLensInformationFragment, bundle);
            }
        });

On Create View of my GeneralLensInformationFragment:

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

    manageLensViewModel = new ViewModelProvider(getActivity()).get(ManageLensViewModel.class);

    Bundle bundle = this.getArguments();

    Lens lens;
    if (bundle.getParcelable(BUNDLE_LENS) != null) {
        manageLensViewModel.action = ACTION_UPDATE;
        lens = bundle.getParcelable(BUNDLE_LENS);
        manageLensViewModel.setLens(lens);
    }
    return view;
}

leftLensDetailsFragment

public class LeftLensDetailsFragment extends Fragment {
    public static final String BUNDLE_LENS = "com.studioz.lenstimer.BUNDLE_LENS";
    public static final String STRING_ACTION = "com.studioz.lenstimer.STRING_ACTION";

    public static final int ACTION_CREATE = 1;
    public static final int ACTION_UPDATE = 2;

    SliderConverter sl;

    private TextView txtLensName, txtShowDiopters, txtShowDiameter, txtShowBaseCurve;
    private TextInputLayout edtTextBrandLayout, edtTextModelLayout;
    private TextInputEditText edtTextBrand, edtTextModel;
    private Slider sliderSelectDiopters, sliderSelectDiameter, sliderSelectBaseCurve;
    private CheckBox cbUniformLenses;
    private MaterialButton btnNextLens, btnCreateLens;
    private ManageLensViewModel manageLensViewModel;

    public LeftLensDetailsFragment() {
    }

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

        manageLensViewModel = new ViewModelProvider(getActivity()).get(ManageLensViewModel.class);
        sl = new SliderConverter();

        return view;
    }


    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        initializeViews(view);
        setFieldValues();

        edtTextBrand.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) {
                Lens lens = manageLensViewModel.getLens().getValue();
                lens.setBrandLeft(edtTextBrand.getText().toString());
                manageLensViewModel.setLens(lens);
            }
        });

        edtTextModel.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) {
                Lens lens = manageLensViewModel.getLens().getValue();
                lens.setModelLeft(edtTextModel.getText().toString());
                manageLensViewModel.setLens(lens);
            }
        });

        sliderSelectDiopters.addOnChangeListener(new Slider.OnChangeListener() {
            @Override
            public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
                double diopters = sl.convertToDoubleFromDioptersSlider(sliderSelectDiopters.getValue());
                txtShowDiopters.setText(String.valueOf(diopters) + " " + getString(R.string.unitDiopters));
            }
        });

        sliderSelectDiameter.addOnChangeListener(new Slider.OnChangeListener() {
            @Override
            public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
                double diameter = sl.convertToDoubleFromDiameterSlider(sliderSelectDiameter.getValue());
                txtShowDiameter.setText(String.valueOf(diameter) + " " + getString(R.string.unitDiameter));
            }
        });

        sliderSelectBaseCurve.addOnChangeListener(new Slider.OnChangeListener() {
            @Override
            public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
                double basecurve = sl.convertToDoubleFromBaseCurveSlider(sliderSelectBaseCurve.getValue());
                txtShowBaseCurve.setText(String.valueOf(basecurve) + " " + getString(R.string.unitBaseCurve));
            }
        });

        cbUniformLenses.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    btnCreateLens.setVisibility(View.VISIBLE);
                    btnNextLens.setVisibility(View.GONE);
                } else {
                    btnCreateLens.setVisibility(View.GONE);
                    btnNextLens.setVisibility(View.VISIBLE);
                }
            }
        });

        NavController navController = Navigation.findNavController(view);

        btnNextLens.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                double d = 0;
                Lens lens = manageLensViewModel.getLens().getValue();

                d = sl.convertToDoubleFromDioptersSlider(sliderSelectDiopters.getValue());
                lens.setDioptersLeft(d);

                d = sl.convertToDoubleFromDiameterSlider(sliderSelectDiameter.getValue());
                lens.setDiameterLeft(d);

                d = sl.convertToDoubleFromBaseCurveSlider(sliderSelectBaseCurve.getValue());
                lens.setBasecurveLeft(d);

                lens.setUniform(false);

                manageLensViewModel.setLens(lens);

                navController.navigate(R.id.action_leftLensDetailsFragment_to_rightLensDetailsFragment);
            }
        });

        btnCreateLens.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                double d = 0;
                Lens lens = manageLensViewModel.getLens().getValue();

                d = sl.convertToDoubleFromDioptersSlider(sliderSelectDiopters.getValue());
                lens.setDioptersLeft(d);

                d = sl.convertToDoubleFromDiameterSlider(sliderSelectDiameter.getValue());
                lens.setDiameterLeft(d);

                d = sl.convertToDoubleFromBaseCurveSlider(sliderSelectBaseCurve.getValue());
                lens.setBasecurveLeft(d);

                lens.setBrandRight(lens.getBrandLeft());
                lens.setModelRight(lens.getModelLeft());
                lens.setDioptersRight(lens.getDioptersLeft());
                lens.setDiameterRight(lens.getDiameterLeft());
                lens.setBasecurveRight(lens.getBasecurveLeft());

                lens.setUniform(true);

                manageLensViewModel.setLens(lens);

                if (manageLensViewModel.action == ACTION_CREATE) {
                    manageLensViewModel.insert(manageLensViewModel.getLens().getValue());
                }
                if (manageLensViewModel.action == ACTION_UPDATE) {
                    manageLensViewModel.update(manageLensViewModel.getLens().getValue());
                }

                navController.navigate(R.id.action_leftLensDetailsFragment_to_listLensesFragment);
            }
        });
    }

    private void setFieldValues() {
        Lens lens = manageLensViewModel.getLens().getValue();

        txtLensName.setText(lens.getName());
        edtTextBrandLayout.getEditText().setText(lens.getBrandLeft());
        edtTextModelLayout.getEditText().setText(lens.getModelLeft());

        if (lens.getDioptersLeft() != 0) {
            txtShowDiopters.setText(String.valueOf(lens.getDioptersLeft()) + " " + getString(R.string.unitDiopters));
            sliderSelectDiopters.setValue(sl.convertToFloatFromLensDiopters(lens.getDioptersLeft()));
        }

        if (lens.getDiameterLeft() != 0) {
            txtShowDiameter.setText(String.valueOf(lens.getDiameterLeft()) + " " + getString(R.string.unitDiameter));
            sliderSelectDiameter.setValue(sl.convertToFloatFromLensDiameter(lens.getDiameterLeft()));
        }

        if (lens.getBasecurveLeft() != 0) {
            txtShowBaseCurve.setText(String.valueOf(lens.getBasecurveLeft()) + " " + getString(R.string.unitDiopters));
            sliderSelectBaseCurve.setValue(sl.convertToFloatFromLensBaseCurve(lens.getBasecurveLeft()));
        }

        cbUniformLenses.setChecked(lens.isUniform());
    }

    private void initializeViews(View view) {
        txtLensName = view.findViewById(R.id.txtLensName);
        txtShowDiopters = view.findViewById(R.id.txtShowDiopters);
        txtShowDiameter = view.findViewById(R.id.txtShowDiameter);
        txtShowBaseCurve = view.findViewById(R.id.txtShowBaseCurve);
        edtTextBrandLayout = view.findViewById(R.id.edtTextBrandLayout);
        edtTextBrand = view.findViewById(R.id.edtTextBrand);
        edtTextModelLayout = view.findViewById(R.id.edtTextModelLayout);
        edtTextModel = view.findViewById(R.id.edtTextModel);

        sliderSelectDiopters = view.findViewById(R.id.sliderSelectDiopters);
        sliderSelectDiopters.setValue(sl.getSliderDioptersDefault());
        sliderSelectDiopters.setValueTo(sl.getSliderDioptersEnd());
        txtShowDiopters.setText(String.valueOf(sl.DEFAULT_DIOPTERS) + " " + getString(R.string.unitDiopters));

        sliderSelectDiameter = view.findViewById(R.id.sliderSelectDiameter);
        sliderSelectDiameter.setValue(sl.getSliderDiameterDefault());
        sliderSelectDiameter.setValueTo(sl.getSliderDiameterEnd());
        txtShowDiameter.setText(String.valueOf(sl.DEFAULT_DIAMETER) + " " + getString(R.string.unitDiameter));

        sliderSelectBaseCurve = view.findViewById(R.id.sliderSelectBaseCurve);
        sliderSelectBaseCurve.setValue(sl.getSliderBaseCurveDefault());
        sliderSelectBaseCurve.setValueTo(sl.getSliderBaseCurveEnd());
        txtShowBaseCurve.setText(String.valueOf(sl.DEFAULT_BASECURVE) + " " + getString(R.string.unitBaseCurve));

        btnNextLens = view.findViewById(R.id.btnNextLens);
        btnCreateLens = view.findViewById(R.id.btnCreateLens);

        cbUniformLenses = view.findViewById(R.id.cbUniformLenses);
        cbUniformLenses.setChecked(false);
        btnCreateLens.setVisibility(View.GONE);
        btnNextLens.setVisibility(View.VISIBLE);
    }
}

In the code above, I stopped using an observer for my MutableLiveData in my ViewModel. This seemed to fix the issue but is not the reccommended way.

This way the data were observed when the app used to crash:

    nameObserver = new Observer<Lens>() {
        @Override
        public void onChanged(Lens lens) {
            // do all the stuff from setFieldValues()...
        }
    };

    manageLensViewModel.getNameData().observe(this, nameObserver);

Can anybody support me on fixing this errors? I really have no idea what to do now. If further code snippets are required, please let me know!

Zudy
  • 49
  • 7
  • You say that "The three fragments generalLensInformationFragment, leftLensDetailsFragment and rightLensDetailsFragment are using a shared ViewModel." From the error message it looks like an instance of LeftLensDetailsFragment is still tied to the shared ViewModel somehow *and* it is trying to update its UI. Could you post enough code to show what happens in LeftLensDetailsFragment and in the ViewModel before the error occurs? – Bö macht Blau Dec 11 '21 at 18:38
  • @BömachtBlau I have added the full code of leftLensDetailsFragment. I think I could limit the possiblities of the root cause. The code in my initial post is working now - the major change was that previously I was observing a MutableLiveData in my shared manageLensViewModel. I was observing that MutableLIveData in all three fragments. By not using an observer, the error is not there any more. But still it would be interesting to know why it is crashing - maybe you have an idea. Because from what I have learned, the MutableLiveData is the reccomended approach for shared view models. – Zudy Dec 12 '21 at 17:26
  • Your `ViewModel` can still expose data to the `Fragment` via `LiveData`, but you should use `Fragment.viewLifecycleOwner` for the `Observer`, see also the accepted answer to [Use viewlifecycleowner as the Lifecycleowner](https://stackoverflow.com/questions/59521691/use-viewlifecycleowner-as-the-lifecycleowner) – Bö macht Blau Dec 13 '21 at 17:11

2 Answers2

0

kotlin

lifecycleScope.launchWhenResumed {
// do your work here 
}
Qamar khan
  • 179
  • 1
  • 7
0

The answer fro Bö macht Blau helped me - using Fragment.viewLiefecycleOwner did the job.

viewModel.getAllData().observe(getViewLifecycleOwner(), new Observer<List<LensWithWears>>() {
    ...do the job here...
});
Zudy
  • 49
  • 7