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.
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.