Search code examples
androidkotlinandroid-fragmentskotlin-android-extensionskotlin-extension

How to detect cause of Null Pointer Exception on Android Extensions inside fragments


In my android application, I am using Kotlin Android Extensions for accessing views from XML layout inside fragments and activities. This works well in almost all scenarios but I am constantly getting a null pointer crash report on crashlytics when the app is in use of actual users. I am unable to reproduce this issue or understand the cause of this issue.

The crash is on a fragment that has Kotlin Android Extensions usage.

Below is my code:

Kotlin File

import kotlinx.android.synthetic.main.fragment_documents.*

@Keep
class BottomNavFragment : Fragment() {
    private val activityViewModel: MainActivityViewModel by activityViewModels()
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val rootView = inflater.inflate(R.layout.fragment_documents, container, false)
        mainActivity = activity as MainActivity
        return rootView
    }

    companion object {
        @JvmStatic
        fun newInstance() =
            BottomNavFragment().apply {

            }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        try {
            setUpTabLayout()
            handleViewModelObservers()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }

    private fun handleViewModelObservers() {
        activityViewModel.isBackToHomeEnabled.observe(viewLifecycleOwner,
            { isHomeEnabled ->
                if (isHomeEnabled!!) {
                    tabLayout.getTabAt(1)?.select()

                }
            })
    }


    private fun setUpTabLayout() {
        try {
            if (mainActivity != null && activityViewModel != null && viewPager != null &&tabLayout!=null) {
                val adapter = FilesViewPagerAdapter(childFragmentManager)
                adapter.addFragments()
                viewPager.adapter = adapter
                viewPager.offscreenPageLimit = 4
                viewPager.currentItem = 1
                viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
                    override fun onPageScrolled(
                        position: Int,
                        positionOffset: Float,
                        positionOffsetPixels: Int
                    ) {

                    }

                    override fun onPageSelected(position: Int) {
                        mainActivity.clearSearch()
                    }

                    override fun onPageScrollStateChanged(state: Int) {
                    }

                })
                tabLayout.setupWithViewPager(viewPager)
                tabLayout.getTabAt(0)?.icon = mainActivity.getDrawable(R.drawable.test1)
                tabLayout.getTabAt(1)?.icon = mainActivity.getDrawable(R.drawable.test2)
                tabLayout.getTabAt(2)?.icon = mainActivity.getDrawable(R.drawable.test3)
                tabLayout.getTabAt(3)?.icon = mainActivity.getDrawable(R.drawable.test3)
                tabLayout.getTabAt(4)?.icon = mainActivity.getDrawable(R.drawable.test5)

                tabLayout.getTabAt(0)?.text = getString(R.string.test1)
                tabLayout.getTabAt(1)?.text = getString(R.string.test2)
                tabLayout.getTabAt(2)?.text = getString(R.string.test3)
                tabLayout.getTabAt(3)?.text = getString(R.string.test4)
                tabLayout.getTabAt(4)?.text = getString(R.string.test5)
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }


}

XML Layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".views.fragments.BottomNavFragment">

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/_50sdp"
        android:background="@color/colorPrimary" />


    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/_60sdp"
        android:layout_marginStart="@dimen/_10sdp"
        android:layout_marginTop="@dimen/_20sdp"
        android:layout_marginEnd="@dimen/_10sdp"
        android:background="@drawable/bg_round_view_white"
        app:tabSelectedTextColor="@color/colorPrimary"
        app:tabTextAppearance="@style/ThemeTextAppearanceTab">

    </com.google.android.material.tabs.TabLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tabLayout" />

</RelativeLayout>

Crash Report

Fatal Exception: java.lang.NullPointerException: viewPager must not be null
       at mypackagename.views.fragments.BottomNavFragment$setUpTabLayout$1.run(BottomNavFragment.java)
       at android.os.Handler.handleCallback(Handler.java:739)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:145)
       at android.app.ActivityThread.main(ActivityThread.java:6946)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

What I have learned so far:

Kotlin Android Extensions actually have findViewById , findViewCache and clearFindViewByIdCache implementation. And as mentioned in this article

The problem with fragments is that the view can be recreated but the fragment instance will be kept alive. What happens then? This means that the views inside the cache would be no longer valid.

So, how I can know that the view is no longer valid? Or, what I can do to prevent this crash?

Any help will be appreciated.


Solution

  • Android extensions plugin is deprecated and the official proposal is to use ViewBinding instead which provided compile time access to the views of an xml layout without the need to do viewFindById on each view you have.

    This page here will provide you with the steps to set up your code to use view binding, make sure to check the code snippet on a fragment to clear the reference you have on the views