I am developing a single activity application using the Android Jetpack navigation component. On one of the fragments I utilize the built-in data binding tools. Strangely enough, even though it was working just the week before, it just completely broke today for seemingly no reason.
The setup:
The fragment I use with binding has the following layout file:
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<data>
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/example_layout_root"
tools:context=".example.ExampleFragment"
android:background="#color/main_1"
>
...
</ScrollView>
</layout>
I have stripped the main content but it shows that I have a <layout> element as the root with both the data and the fragment layout part defined.
The fragment code is the following:
class ExamleFragment : Fragment() {
private val viewModel: ExampleViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val binding : FragmentExampleBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_example, container, false)
// This one also does not work
// val binding = FragmentExampleBinding.inflate(inflater, container, false)
binding.vm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
I use Koin to inject the viewmodel into the fragment.
When I try to inflate the layout (either with DataBindingUtil or the generated FragmentExampleBinding class) it results in the following exception:
java.lang.IllegalStateException: FragmentExampleBin…flater, container, false) must not be null at
com.example.fragments.ExampleFragment.onCreateView(ExampleFragment.kt:38)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
at ...
And the stack goes straight down into OS territory so basically it doesn't give any useful information. I can't figure out why the result is null.
What I have tried:
I have tried inflating the layout with inflater.inflate(...) and it
works just fine but that way I can't use the data bindings.
I have tried removing EVERYTHING from the layout description that's
not absolutely neccessary and it still does not work.
I have tried reverting to previous commits (as far back as the first
working version with binding) and even though it used to work before,
it does not work now.
I even restarted and reset the emulator, tried different images, but
to no avail.
Has anyone encountered anything similar to this? The code really 'broke' over the weekend as I have just tested it the Friday before and it worked properly.
EDIT:
Okay, so I somehow managed to fix it.
I started experimenting with creating another fragment with data binding to see whether things are broken for all fragments or just that specific one. The new one seemed to work just fine but strangely, the old, broken fragment still refused to work even though they were basically the same.
So I did Clean + Rebuild (which I swear I performed as a first attempt to fix the issue) and it kind of just fixed it.
In my multi-module project, the reason for this problem was the fact that there was some kind of package name clash.
If you are getting this error without an apparent reason, double check the AndroidManifest.xml files of your data binding enabled modules.
Making sure all of the data binding enabled modules use unique package names solved the problem for me.
I spent hours to find the root of this problem hopefully others read this answer and won't spend that much.
Dont use DataBindingUtil for fragments, run your app once and android studio will generate a binding class for that fragment based off the name of your xml file, in your case the xml file is call fragment_example, so the class generated will be FragmentExampleBinding, then you call FragmentExampleBinding.inflate(....) like this:
val binding = FragmentExampleBinding.inflate(inflater, container, false);
Do it this way :
class ExamleFragment : Fragment() {
private val viewModel: ExampleViewModel by sharedViewModel()
lateinit binding:FragmentExmaple
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_example, container, false)
binding.vm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
please I need you to paste your error here
in my case I just forgot to add #AndroidEntryPoint to my fragment class
Related
I'm using Android Exoplayer in one of my Fragment.
Within Exoplayer I use a custom control layout"#layout/custom_player" for the controls.
I have different elements within the layout for example I have a button element"optionBtn" which I want to connect to onclicklistener from my Kotlin code. Unfortunately that doesn't go very smoothly with view binding.
This is the XML Exoplayer
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/playerVIew"
app:resize_mode="fill"
android:animateLayoutChanges="true"
app:controller_layout_id="#layout/custom_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
This is the kotlin code
...
private var binding: FragmentVideoBinding? = null
private var btnsheetOptions: SheetOptionsBinding? = null
private var sheetDialog: BottomSheetDialog? = null
private var customPlayer: CustomPlayerBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
btnsheetOptions = SheetOptionsBinding.inflate(inflater, null, false)
sheetDialog = BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
binding = FragmentVideoBinding.inflate(inflater, container, false)
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val simpleExoPlayer = SimpleExoPlayer.Builder(requireContext()).build()
binding!!.playerVIew.player = simpleExoPlayer
val mediaItem = MediaItem.fromUri(video.toString())
simpleExoPlayer.addMediaItem(mediaItem)
simpleExoPlayer.prepare()
simpleExoPlayer.playWhenReady = true
customPlayer!!.optionBtn.setOnClickListener {
...
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
btnsheetOptions = null
sheetDialog= null
customPlayer = null
}
}
...
This way the layout is double-inflated on top of each other and one layout works with onclick listener and the other does not, which is not very useful.
Does anyone know the correct solution for this, I've been working on this for almost all afternoon.
You cannot use view binding with ExoPlayer's custom HUD layout. View Binding only works with layouts specifically inflated for the activity/fragment layouts. The custom HUD layout does NOT belong to the parent layout which the player is set in. It is inflated in a stand-alone fashion and not included in the layout (Hence the double-inflation). Since the custom layout is inflated and is not part of the original layout, you can't use view binding with all the ids contained in it.
So what can you do if View Binding does not work with the custom layout's buttons ?
You should use findViewById which is a function that belongs to Activity class.
It's very easy to use and I assume you already know how as well:
findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
//The next line is for usage inside a fragment class
activity?.findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
Make sure you give the button an ID in your layout, for example:
android:id="#id/optionBtn"
What if you COULDN'T find the (R.id.optionBtn) ?
This is a common problem, there are two R directories to be aware of.
There is android.R which is usually used as R only. And there is the app's R directory. In order to distinguish between the two and avoid Unresolved reference issue, you should import your app's resources under a different name, this is done in the import section before your class code begins, add this:
import com.example.app.R as appR
Then you can try using appR.id.optionBtn instead. There is a very low chance you'd face this particular R.idissue but follow the solution above in case it happens.
Bottom Line:
1- Viewbinding only works for the activity/fragment layout connected to their context classes, it binds the parent layout's id and all their child views with actual binding variables.
2- If you wanna reach a layout that is not part of the activity/fragment layout directly, you should use findViewById instead.
3- If you have problems using the 'R.id', you should import your app's resources under a different name. I usually use 'X' instead of 'R'. But it's all a personal preference..
One shouldn't inflate the data-binding, while also applying attribute app:controller_layout_id:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
One can only have that either way.
Somehow the question is pointless, unless providing custom_player.xml ...because it may be lacking some mandatory resource IDs, which would be expected to be present (there are certain limitations to what "custom" may permit, which may include: having to provide certain resource ID, even if hiding these from the user). XML markup is quite important on Android - as all the code runs against it. ExoPlayer supports overriding layout files, unless giving them a different name.
Please refer to the original layout resources, in particular their file names and resId:
https://github.com/google/ExoPlayer/tree/release-v2/library/ui/src/main/res/layout
I'd assume, that when overriding by file name, it should also be possible to data-bind.
Because, when only the include has data-binding, then the parent still cannot bind it.
The parent layout XML would need to generate a data-binding, to begin with.
With .setControllerLayoutId(), one can actually data-bind the View before assigning it:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
binding.playerView.setControllerLayoutId(customPlayer!!.root)
In this case app:controller_layout_id must not be set.
I have a simple test function like this:
#Test
fun testfunc() {
val fragScenario = launchFragmentInContainer<MyFragment>()
}
launchFragmentInContainer provides me with this error:
Binary XML file line #10: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f040004 a=-1}
Caused by: java.lang.UnsupportedOperationException
which takes me to something wrong with the binding inflater.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
I assume view binding doesn't work with isolated fragments like this? Do I need to create an entire new fragment without view binding just for testing?
In the fragment testing documentation there is a note:
Note: Your fragment might require a theme that the test activity doesn't use by default. You can provide your own theme as an additional argument to launch() and launchInContainer().
This error normally occurs when there are style values that the activity isn't aware of. Be sure to pass your app or fragment style via the themeResId parameter.
I am implementing ViewBinding in one of my fragments. This fragment has a layout included like so:
...
<androidx.core.widget.NestedScrollView
android:id="#+id/sv_sudf_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/btn_sudf_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/eav_sudf_avatar">
<include
android:id="#+id/l_sudf_details"
layout="#layout/layout_sign_up_details_fields"/>
</androidx.core.widget.NestedScrollView>
...
I have followed this answer but it also does not work.
The generated view binding class for the fragment has the binding inside, however, the type for the attribute is View. When I then reference the View using binding.lSudfDetails the type is LayoutSignUpDetailFieldsBinding. Where this type is coming from I can't work out as there is no generated class with that name however, I would expect it would assign it the proper binding type. Here is the attribute in the FragmentSignUpDetailsBinding.java.
#NonNull
public final View lSudfDetails;
The bindings are all correctly setup however and it allow me to reference views within the nested layout but when I come to build I get unresolved reference errors. Lint does not complain when I reference them like this:
binding.lSudfDetails.etSudfDob
The compiler does fail however with errors such as this
Unresolved reference: etSudfDob
The binding itself is created according to the Android docs:
private var _binding : FragmentSignUpDetailsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentSignUpDetailsBinding.inflate(inflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvSudfWelcome.text = getString(R.string.sign_up_welcome,getString(R.string.app_name))
binding.lSudfDetails.etSudfDob.setOnClickListener {
showYearSelection()
}
}
The tvSudfWelcome binding works its the nested binding it doesn't like.
If you're using Android Studio 3.6.0 sometimes gradle plugin fails to generate ViewBinding fields for included layouts. Please update to Android Studio 3.6.1 and gradle plugin version to 3.6.1.
If someone has similar problem... I solved my problem with adding width and height for this included view. It helped, I do not have any idea why, but this would be my solution:
<include
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/l_sudf_details"
layout="#layout/layout_sign_up_details_fields"/>
If someone has a similar problem - I solved mine using:
execute gradle task : prepareKotlinBuildScriptModel
changing the id name to the same as the layout name worked for me.
For eg:
<include
android:id="#+id/layout_sign_up_details_fields"
layout="#layout/layout_sign_up_details_fields"/>
I was working with databinding instead of viewbinding though
I'm using Android Studio 3.0 RC2 & Kotlin.
When I try to access a UI component the app crashes unless I first write findViewById. I thought Kotlin was supposed to get rid of having to write findViewById line? The UI is a fragment and I'm trying to access from the same fragment code. Is there a way to not have to write findViewById?
These lines work:
var userNameField = view?.findViewById<EditText>(R.id.userNameTextField) as EditText
userNameField.setText("hello world")
This line doesn't work without findViewById line
userNameTextField.setText("hello world")
I even have
import kotlinx.android.synthetic.main.fragment_sign_in.*
The onCreateView() code:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater!!.inflate(R.layout.fragment_sign_in, container, false)
var userNameField = view?.findViewById<EditText>(R.id.userNameTextField) as EditText
userNameField.setText("hello world")
return view
}
In the onCreateView just return the inflated view.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_sign_in, container, false)
}
In the onViewCreated you can access your view components
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
userNameField.setText("hello world")
}
I was brought here by searching for the same issue. I missed to add the kotlin-android-extensions to the build.gradle:
apply plugin: 'kotlin-android-extensions'
Was unable to access the ui components like OP faced looks like below was needed inside app gradle:
plugins {
...
id 'kotlin-android-extensions'
}
Even though after adding this line android studio was still not able to auto resolve the imports for kotlin synthetics so you may need to invalidate cache and restart.
If still not working then import manually depending on views
activity /fragment view: import
kotlinx.android.synthetic.main.<your_activity_view>.*
normal views :
import kotlinx.android.synthetic.main.<your_layout_view>.view.*
I faced a similar problem.
I had a private function. I'd also imported the kotlinx library. But I was not able to access the editText from that function.
I just removed the function and defined it again.
Voila! It worked.
For those who have this problem in 2021:
I know this question is asked in the past but since it is still relevant here I want to help others:
today I ran to this problem and tried the suggested ways and unfortunately, the
plugins { ... id 'kotlin-android-extensions' }
is not working anymore or more accurately is deprecated, so try to use the Jetpack view binding approach and you'll be good.
The message I got after adding the Gradle Kotlin Android extension dependency is:
The 'kotlin-android-extensions' Gradle plugin is deprecated. Please use this migration guide (click here) to start working with View Binding. (click here) and the 'kotlin-parcelize' plugin.
I am using Kotlin Android extensions in my project and I came across some behavior that I am unable to understand. I use this code to retain my fragment in the activity:
val fragment = fragmentManager.findFragmentByTag("hello") ?: HelloFragment()
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment, "hello")
.commit()
This is the retained Fragment:
import kotlinx.android.synthetic.hello.*
public class HelloFragment : Fragment() {
val text = "Hello world!"
override fun onCreate(savedInstanceState: Bundle?) {
super<Fragment>.onCreate(savedInstanceState)
setRetainInstance(true)
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.hello, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super<Fragment>.onViewCreated(view, savedInstanceState)
text_view.setText(text) // <- does not work when retained
}
}
and its XML layout hello.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" />
Everything works as expected – the text_view.setText() displays Hello world! on screen on the first launch. But when you rotate the screen the text_view.setText() does not work. This is weird because text_view is not nullable and has to reffer to some view. If you remove setRetainInstance(true) and leave the fragment recreate every time this problem disappears. Any thoughts what might cause this problem?
UPD: The issue is fixed now. You don't have to call clearFindViewByIdCache() manually anymore.
View cache is not cleared after calling onDestroyView(). There is an open issue.
For now, you can explicitly invoke clearFindViewByIdCache() in onDestroyView() to clear the cache. This method is part of the synthetic package so you have to import it
import kotlinx.android.synthetic.*
I have found the answer myself. The Fragment class does not inflate the layout directly – it has property view: View? which holds it. This should be pretty obvious since it is created with onCreateView. In order to access the properties within the view you have to set the import
import kotlinx.android.synthetic.hello.view.*
and then access the properties as follows
view?.text_view?.setText(text)
Note that these properties are nullable.
Just to clarify. The issue is fixed now. You don't have to pass clearFindViewByIdCache() anylonger. Please see issue tracker: https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-8073