Android: On BackPressed Fragment doesn't load content-ThrowExceptions

Exception or error:

so, I’m using the Android Navigation component inside my project. I have an Activity with 3 fragments that load perfectly and do everything they need to do. The problem is with one fragment that doesn’t load its content when its returned to from backstack.

I’m using navigate functions declared in the ViewModel with Directions from the Navigation.
example ( vm.navigate(SomeFragmentDirections.actionSomeFragmentToOtherFragment)

A(activity) -> B(fragment) -> C(fragment) -> D(fragment)

when I press back on the D fragmentto go back to the C fragment it shows the upper navbar but doesn’t load the content of it. I use the same principles on all other Activities/Fragments in my other projects (even in this one) and i don’t get that problem. All lifecycle functions are called and everything should work fine. The logcat doesn’t show any errors whatsoever. If anyone knows anything about this I would appreciate it.

EDIT:

This is the fragment that doesn’t load (Fragment C)
Fragment D is the webView fragment, fragment C navigates to it in the
vm.navigate(RegisterFragmentDirections.actionRegisterFragmentToWebViewFragment(webURL)) function

class RegisterFragment : BaseFragment() {


    private val vm: RegisterViewModel by viewModel()


    override fun getViewModel(): BaseViewModel = vm


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentRegisterBinding.inflate(inflater, container, false)
        context ?: return binding.root
        injectFeature()

        setToolbar(binding)

        subscribeUi(binding)

        return binding.root
    }

    /**
     * set toolbar
     * **/
    private fun setToolbar(binding: FragmentRegisterBinding) {
        if (activity is WelcomeActivity) {
            binding.appBarLayout.backClickListener = (activity as WelcomeActivity).createOnBackClickListener()
        } else if(activity is LoginActivity) {
            binding.appBarLayout.backClickListener = (activity as LoginActivity).createOnBackClickListener()
        }
    }

    /**
     * set ui
     * **/
    private fun subscribeUi(binding: FragmentRegisterBinding) {
        // set bindings
        binding.contentRegister.viewModel = vm
        binding.contentSuccess.viewOwner = this

        // set true full screen
        (activity as LoginActivity).setFullScreen(false)

        // set dark status bar icons
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            activity!!.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        }

        // set initial margin top
        ViewCompat.setOnApplyWindowInsetsListener(binding.rootLayout) { _, insets ->
            binding.appBarLayout.toolbar.setMarginTop(insets.systemWindowInsetTop)
            insets
        }

        // set phone number mask listener
        binding.contentRegister.etPhoneNumber.addTextChangedListener(PhoneNumberFormattingTextWatcher())
        // set licence agreement formatted text with hyperlinks
        setTextViewHTML(binding.contentRegister.licenceAgreement, getString(R.string.description_privacy_with_link))

        // set listener on form elements, error handling
        binding.contentRegister.etName.onFocusChangeListener = emptyInputValidationListener(
            binding.contentRegister.etName,
            binding.contentRegister.tilName,
            getString(R.string.error_empty_name)
        )

        binding.contentRegister.etLastName.onFocusChangeListener = emptyInputValidationListener(
            binding.contentRegister.etLastName,
            binding.contentRegister.tilLastName,
            getString(R.string.error_empty_last_name)
        )

        binding.contentRegister.etBirthDate.setOnFocusChangeListener { _, hasFocus ->
            if (!hasFocus) {

                when(isBirthDateValid(binding.contentRegister.etBirthDate.text.toString())) {

                    false -> binding.contentRegister.tilBirthDate.error = getString(R.string.error_date_not_valid)
                    true -> binding.contentRegister.tilBirthDate.isErrorEnabled = false
                }
            }
        }

        binding.contentRegister.etEmail.setOnFocusChangeListener { _, hasFocus ->
            if (!hasFocus) {
                when(!android.util.Patterns.EMAIL_ADDRESS.matcher(binding.contentRegister.etEmail.text!!.trim()).matches()) {
                    true -> binding.contentRegister.tilEmail.error = getString(R.string.error_email_not_valid)
                    false -> binding.contentRegister.tilEmail.isErrorEnabled = false
                }
            }
        }

        binding.contentRegister.etPassword.setOnFocusChangeListener { _, hasFocus ->
            if (!hasFocus) {
                when (binding.contentRegister.etPassword.text!!.trim().length < 6) {
                    true -> binding.contentRegister.tilPassword.error =
                        getString(R.string.error_password_not_valid)
                    false -> binding.contentRegister.tilPassword.isErrorEnabled = false
                }
            }
        }

        binding.contentRegister.registerButton.setOnClickListener{
            validateInputs(binding)
        }

        // set observables
        vm.userResponse.observe(viewLifecycleOwner, Observer { updateRegisterSuccess(binding, it) })
    }

    /**
     * update on success / failure
     * **/
    private fun updateRegisterSuccess(
        binding: FragmentRegisterBinding,
        resource: Resource<BaseResponseEntity>?
    ) {
        resource?.let {
            when (it.state) {
                ResourceState.LOADING -> {
                    binding.contentProgress.isLoading = true
                    setViewAndChildrenEnabled(binding.rootLayout, false)
                }
                ResourceState.SUCCESS -> {
                    binding.contentProgress.isLoading = false
                    setViewAndChildrenEnabled(binding.rootLayout, true)
                }
                ResourceState.ERROR -> {
                    binding.contentProgress.isLoading = false
                    setViewAndChildrenEnabled(binding.rootLayout, true)
                }
            }

            it.data?.let {
                when(it.responseCode) {
                    RESPONSE_CODE_SUCCESS -> {
                        binding.contentSuccess.isSucceeded = true
                        setViewAndChildrenEnabled(binding.rootLayout, true)
                    }

                    RESPONSE_CODE_ERROR -> {
                        if (it.message.isNotEmpty()) {
                            showSnackbar(it.message, Snackbar.LENGTH_SHORT)
                        } else {
                            showSnackbar(getString(R.string.error_unknown), Snackbar.LENGTH_SHORT)
                        }
                    }
                }
            }
            it.message?.let {
                showSnackbar(getString(R.string.error_unknown), Snackbar.LENGTH_SHORT)
            }
        }
    }

    /**
     * disable ui elements while loading
     * **/
    private fun setViewAndChildrenEnabled(view: View, enabled: Boolean) {
        view.isEnabled = enabled
        if (view is ViewGroup) {
            for (i in 0 until view.childCount) {
                val child = view.getChildAt(i)
                setViewAndChildrenEnabled(child, enabled)
            }
        }
    }

    /**
     * validate all inputs
     * **/
    private fun validateInputs(binding: FragmentRegisterBinding) {

        // check if all inputs are valid
        if(binding.contentRegister.etName.text!!.trim().isEmpty()) {
            binding.contentRegister.etName.requestFocus()
            binding.contentRegister.tilName.error = getString(R.string.error_empty_name)
            return
        }

        if(binding.contentRegister.etLastName.text!!.trim().isEmpty()) {
            binding.contentRegister.etLastName.requestFocus()
            binding.contentRegister.tilLastName.error = getString(R.string.error_empty_last_name)
            return
        }

        if (binding.contentRegister.etBirthDate.rawText.isNotEmpty()) {
            if (!isBirthDateValid(binding.contentRegister.etBirthDate.text.toString())) {
                binding.contentRegister.etBirthDate.requestFocus()
                binding.contentRegister.tilBirthDate.error =
                    getString(R.string.error_date_not_valid)
                return
            }
        }

        if(!android.util.Patterns.EMAIL_ADDRESS.matcher(binding.contentRegister.etEmail.text!!.trim()).matches()) {
            binding.contentRegister.etEmail.requestFocus()
            binding.contentRegister.tilEmail.error = getString(R.string.error_date_not_valid)
            return
        }

        if(binding.contentRegister.etPassword.text!!.trim().length < PASSWORD_MINIMUM_LENGHT) {
            binding.contentRegister.etPassword.requestFocus()
            binding.contentRegister.tilPassword.error = getString(R.string.error_password_not_valid)
            return
        }

        if(!binding.contentRegister.checkBox.isChecked) {
            showSnackbar(getString(R.string.error_terms_and_conditions), Snackbar.LENGTH_SHORT)
            return
        }

        // handle date of birth
        val dateOfBirth = if (binding.contentRegister.etBirthDate.rawText.trim().isNotEmpty()
            && isBirthDateValid(binding.contentRegister.etBirthDate.rawText)) {
            binding.contentRegister.etBirthDate.text.toString().replace("/", "-")
        } else {
            ""
        }

        binding.rootLayout.hideKeyboard()

        vm.register(
            username = binding.contentRegister.etEmail.text.toString(),
            password = binding.contentRegister.etPassword.text.toString(),
            name = binding.contentRegister.etName.text.toString(),
            lastName = binding.contentRegister.etLastName.text.toString(),
            phoneNumber = binding.contentRegister.etPhoneNumber.text.toString(),
            dateOfBirth = dateOfBirth)

        Timber.d(dateOfBirth)
    }

    //todo handle this and move to util class
    @Suppress("DEPRECATION")
    private fun setTextViewHTML(text: TextView, html: String) {
        // replace \n new line so android can show new line for text which we previously fetchCompanies from server
        val hmtlFormatted = html.replace("\n", "<br>")

        val sequence = Html.fromHtml(hmtlFormatted)
        val strBuilder = SpannableStringBuilder(sequence)
        val urls = strBuilder.getSpans(0, sequence.length, URLSpan::class.java)
        for (span in urls) {
            makeLinkClickable(strBuilder, span)
        }
        text.text = strBuilder
        text.movementMethod = LinkMovementMethod.getInstance()
    }

    private fun makeLinkClickable(strBuilder: SpannableStringBuilder, span: URLSpan) {
        val start = strBuilder.getSpanStart(span)
        val end = strBuilder.getSpanEnd(span)
        val flags = strBuilder.getSpanFlags(span)
        val clickable = object : ClickableSpan() {
            override fun onClick(view: View) {
                // Do something with span.getURL() to handle the link click...
                val webURL = span.url

                vm.navigate(RegisterFragmentDirections.actionRegisterFragmentToWebViewFragment(webURL))
            }
        }
        strBuilder.setSpan(clickable, start, end, flags)
        strBuilder.removeSpan(span)
    }
    // PUBLIC ACTIONS ---
    fun onRegisterDoneClick() {
        // navigate to welcome activity and finish it
        onRegisterSuccess()
    }


    /**
     * on register success
     * **/
    private fun onRegisterSuccess() {
        // navigate to welcome activity and finish it
        val returnIntent = Intent()
        (activity as LoginActivity).setResult(Activity.RESULT_OK, returnIntent)
        (activity as LoginActivity).finish()
    }
How to solve:

You only get a context once the fragment is attached to an activity.
When onCreateView is called you don’t have a context yet and it returns:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentRegisterBinding.inflate(inflater, container, false)
        context ?: return binding.root
        // ...
}

You should move your set up logic to onViewCreated:

lateinit var binding: FragmentRegisterBinding 

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = FragmentRegisterBinding.inflate(inflater, container, false)
    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    injectFeature()

    setToolbar(binding)

    subscribeUi(binding)
}

Leave a Reply

Your email address will not be published. Required fields are marked *