Search code examples
androidkotlingenericsandroid-viewbinding

Why FragmentLoginBinding returns null?


I'm building a chat application using Stream SDK. I'm using a BindingFrament from which all fragments can inherit and get initialize the binding for each fragment in the bindingFragment class using Generics. But it give nullPointerException.

package com.example.streamchat.ui

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.example.streamchat.databinding.FragmentLoginBinding

abstract class BindingFragment<out T : ViewBinding> : Fragment() {

    private var _binding: ViewBinding? = null
    @Suppress("UNCHECKED_CAST")
    protected val binding: T
        get() = _binding as T

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = bindingInflater(inflater)
        return _binding!!.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    protected abstract val bindingInflater: (LayoutInflater) -> ViewBinding
}

Login Fragment :

package com.example.streamchat.ui.login

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewDebug.FlagToString
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.example.streamchat.R
import com.example.streamchat.databinding.FragmentLoginBinding
import com.example.streamchat.ui.BindingFragment
import com.example.streamchat.utils.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.security.cert.TrustAnchor

@AndroidEntryPoint
class LoginFragment : BindingFragment<FragmentLoginBinding>() {

    override val bindingInflater: (LayoutInflater) -> ViewBinding
        get() = FragmentLoginBinding::inflate

    private val viewModel : LoginViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding.btnConfirm.setOnClickListener{
            setUpConnectingUIState()
            viewModel.connectUser(binding.etUsername.text.toString())

        }

        binding.etUsername.addTextChangedListener {
            binding.etUsername.error = null
        }

        subscribeToEvents()

        return binding.root
    }

    private fun setUpConnectingUIState(){
        binding.progressBar.isVisible = true
        binding.btnConfirm.isVisible = false
    }

    private fun setUpIdealUIState(){
        binding.progressBar.isVisible = false
        binding.btnConfirm.isVisible = true
    }

    private fun subscribeToEvents(){
        lifecycleScope.launch {
            viewModel.logInEvent.collect{event ->
                when(event){
                    is LoginViewModel.LogInEvent.ErrorInputTooShort->{
                        setUpIdealUIState()
                        binding.etUsername.error =
                            getString(R.string.error_username_too_short,Constants.MIN_USERNAME_LENGTH)
                    }
                    is LoginViewModel.LogInEvent.ErrorLogIn->{
                        setUpIdealUIState()
                        Toast.makeText(
                            requireContext(),
                            event.error,
                            Toast.LENGTH_LONG
                        ).show()
                    }
                    is LoginViewModel.LogInEvent.Success->{
                        setUpIdealUIState()
                    }
                }

            }
        }
    }
}

But when I run the code it gives me this error.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.streamchat, PID: 6243
    java.lang.NullPointerException: null cannot be cast to non-null type T of com.example.streamchat.ui.BindingFragment
        at com.example.streamchat.ui.BindingFragment.getBinding(BindingFragment.kt:17)
        at com.example.streamchat.ui.login.LoginFragment.onCreateView(LoginFragment.kt:37)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3104)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
        at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1433)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2977)
        at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2888)
        at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
        at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1433)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2977)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2895)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
        at android.app.Activity.performStart(Activity.java:8018)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)


Solution

  • As LoginFragment is inheriting from BindingFragment and both these functions are overriding onCreateView(). So, in the LoginFragment we need to call super.onCreateView(inflater, container, savedInstanceState) So that the code we wrote in BindingFragment can run and initialize the binding variable.

    Binding Fragment:

    abstract class BindingFragment<T : ViewBinding> : Fragment() {
    
        private var _binding: T? = null
        lateinit var binding : T
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            super.onCreateView(inflater, container, savedInstanceState)
            _binding = bindingInflater(inflater, container)
            binding = _binding!!
            return binding.root
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            _binding = null
        }
    
        abstract fun bindingInflater(inflater: LayoutInflater, container: ViewGroup?): T
    }
    

    LoginFragment:

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            super.onCreateView(inflater, container, savedInstanceState)
    }