2

I have a dynamic features module called favorite. This module has an install-time delivery. In this module, I am using data binding. However, when I want to access the ID of a view, it says that the view is null.

Take a look at the XML layout present in my favorite module that I am using.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:colorBackground">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/frag_favorite_tab_layout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:elevation="@dimen/keyline_0"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/frag_favorite_view_pager"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/frag_favorite_tab_layout" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

As you can see, there are two views there with their respective IDs. Then, I want to reference those views in my favorite module fragment. I did it like this.

class FavoriteFragment : BaseFragment() {
    private lateinit var binding: FragFavoriteBinding
    private val viewmodel: FavoriteViewModel by viewModel()
    override var bottomNavigationViewVisibility = View.VISIBLE

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupViewPager()
        observeNavigationEvent()
    }

    private fun setupViewPager() {
        val viewPagerAdapter =
            FavoriteViewPagerAdapter(
                childFragmentManager,
                viewLifecycleOwner.lifecycle
            )
        // THIS PART OVER HERE v
        binding.fragFavoriteViewPager.adapter = viewPagerAdapter
        TabLayoutMediator(binding.fragFavoriteTabLayout, binding.fragFavoriteViewPager) { tab, position ->
            when (position) {
                0 -> tab.text = getString(ResR.string.movie_toolbar_text)
                1 -> tab.text = getString(ResR.string.show_toolbar_text)
            }
        }.attach()
        binding.fragFavoriteViewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
            override fun onPageScrollStateChanged(state: Int) {
                super.onPageScrollStateChanged(state)
                if (state == ViewPager.SCROLL_STATE_DRAGGING || state == ViewPager.SCROLL_STATE_SETTLING)
                    EspressoIdlingResource.increment()
                else
                    EspressoIdlingResource.decrement()
            }
        })
    }

    private fun observeNavigationEvent() {
        viewmodel.navigateToDetails.observe(viewLifecycleOwner, Observer {
            if (it.first != -1) {
                val action =
                    if (it.second == ListFragment.TYPE_MOVIE) FavoriteFragmentDirections.actionFavoriteFragmentToMovieDetailsFragment2(
                        it.first
                    )
                    else FavoriteFragmentDirections.actionFavoriteFragmentToShowDetailsFragment2(it.first)
                findNavController().navigate(action)
                viewmodel.finishNavigatingToDetails()
            }
        })
    }
}

I am referencing the ViewPager using binding.fragFavoriteViewPager as can be seen above. During build time, there is no error. However, in run time, when I try to navigate to this FavoriteFragment, it returns a null pointer exception error. The error tells me that binding.fragFavoriteViewPager is a null object reference. The full error can be seen below.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.mobile.moviecatalogue, PID: 13131
    java.lang.NullPointerException: binding.fragFavoriteViewPager must not be null
        at com.mobile.favorite.ui.FavoriteFragment.setupViewPager(FavoriteFragment.kt:50)
        at com.mobile.favorite.ui.FavoriteFragment.onViewCreated(FavoriteFragment.kt:40)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.mobile.moviecatalogue, PID: 13131
    java.lang.NullPointerException: binding.fragFavoriteViewPager must not be null
        at com.mobile.favorite.ui.FavoriteFragment.setupViewPager(FavoriteFragment.kt:50)
        at com.mobile.favorite.ui.FavoriteFragment.onViewCreated(FavoriteFragment.kt:40)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

I have set up my Gradle file for that favorite module like so.

plugins {
    id 'com.android.dynamic-feature'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

apply from: '../shared_dependencies.gradle'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.mobile.favorite"
        minSdkVersion 27
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    buildFeatures {
        dataBinding true
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation project(":core")
    implementation project(":app")
    implementation project(":res")
}

To navigate to that dynamic feature fragment, in my other module called app, I have this navigation graph.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_navigation"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/movieFragment">

    ...
    <fragment
        app:moduleName="favorite"
        android:id="@+id/favoriteFragment"
        android:name="com.mobile.favorite.ui.FavoriteFragment"
        android:label="@string/favorite_toolbar_text"
        tools:layout="@layout/frag_favorite">
        <action
            android:id="@+id/action_favoriteFragment_to_movieDetailsFragment2"
            app:destination="@id/movieDetailsFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
        <action
            android:id="@+id/action_favoriteFragment_to_showDetailsFragment2"
            app:destination="@id/showDetailsFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>
    ...
    <action
        android:id="@+id/action_global_favoriteFragment"
        app:destination="@id/favoriteFragment"
        app:enterAnim="@anim/nav_default_enter_anim"
        app:exitAnim="@anim/nav_default_exit_anim"
        app:popEnterAnim="@anim/nav_default_pop_enter_anim"
        app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</navigation>

I did the changes to the navigation graph based on Android's doc regarding dynamic navigation. I then deleted all codes related to the call of binding.AnyView and used Layout Inspector. It turns out that all the views did not have any ID anymore. See this picture below.

enter image description here

Why did the ID disappear in Layout Inspector? What can I do to be able to reference the views using data binding in the fragment file?

EDIT

Using binding.root.findViewById, I can access the views. This is weird.

Richard
  • 7,037
  • 2
  • 23
  • 76
  • Thanks for providing the solution in the Edit, same problem for me so I'll stick to findViewById... – Merthan Erdem Apr 20 '21 at 13:59
  • 2
    @MerthanE I still hope that someone can provide an answer while not using `findViewById`, though, because using [`binding` should allow us to extract views faster](https://developer.android.com/topic/libraries/data-binding/generated-binding). – Richard Apr 21 '21 at 03:00
  • even findViewById not work on my code – Zainal Fahrudin Jun 11 '22 at 07:46

0 Answers0