I am having trouble getting data-binding to work properly on a BottomSheetDialog layout. Here are the details:
Definition and setting of var:
private lateinit var myDrawerBinding: MyDrawerBinding
myDrawerBinding = MyDrawerBinding.bind(myDrawerContent) // crashes on this line
and later it's set and shown this way (although it never gets to this point)
myDrawerBinding.viewModel = theViewModel
val bottomSheet = BottomSheetDialog(context)
bottomSheet.setContentView(myDrawerBinding.myDrawerContent)
bottomSheet.show()
Here is a snippet of the XML layout (my_drawer.xml):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View"/>
<variable name="viewModel" type="path.to.my.viewModel"/>
</data>
<RelativeLayout
android:id="#+id/myDrawerContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<View
android:layout_width="50dp"
android:layout_height="3dp"
android:visibility="#{viewModel.shouldShowView() ? View.VISIBLE : View.GONE}"/>
....
The crash occurs when it calls the .bind() method above, and the error is:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.view.View.getTag()' on a null object reference
This same exact functionality works on a separate DrawerLayout that I am showing in the same Fragment, but for some reason the BottomSheetDialog layout is giving problems.
Finally found a fix for this. I had to treat this view a little differently from my DrawerLayout, although I have a feeling this approach may work for that view as well.
Here is the binding setup:
val myDrawerView = layoutInflater.inflate(R.layout.my_drawer, null)
val binding = MyDrawerBinding.inflate(layoutInflater, myDrawerView as ViewGroup, false)
binding.viewModel = theViewModel
And then to show the view:
val bottomSheetDialog = BottomSheetDialog(context)
bottomSheetDialog.setContentView(binding.myDrawerContent)
bottomSheetDialog.show()
Works like a charm now!
Related
There is a large data-bound view, which may take several seconds to inflate. I would like to display the user a splash screen and inflate the main view a delayed action. Android studio throws an exception "Failed to call observer method".
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.screen_splash)
Handler(Looper.getMainLooper()).postDelayed({
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this,
R.layout.activity_main
)
binding.lifecycleOwner = this // this line throws exception
}, 1000)
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.ViewModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/map_list"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" />
</RelativeLayout>
Exception:
2021-12-05 13:42:56.638 23701-23701/com.example E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example, PID: 23701
java.lang.RuntimeException: Failed to call observer method
at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:226)
at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194)
at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185)
at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:196)
at androidx.databinding.ViewDataBinding.setLifecycleOwner(ViewDataBinding.java:434)
at com.example.databinding.ActivityMainBindingImpl.setLifecycleOwner(ActivityMainBindingImpl.java:166)
at com.example.MainActivity.onCreate$lambda-3(MainActivity.kt:106)
at com.example.MainActivity.$r8$lambda$lffeScwTEbHi2B1isKEoQYU2po4(Unknown Source:0)
at com.example.MainActivity$$ExternalSyntheticLambda5.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:888)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:213)
at android.app.ActivityThread.main(ActivityThread.java:8178)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
Caused by: java.lang.NumberFormatException: s == null
at java.lang.Integer.parseInt(Integer.java:577)
at java.lang.Integer.valueOf(Integer.java:801)
at com.example.databinding.ControlPanelBindingImpl.executeBindings(ControlPanelBindingImpl.java:800)...
I am not sure about the structure of your application. In our case we had a similar requirement where we wanted to show a loader until the initial fragment is bound. So we created a viewStub in the activity. Then when the fragment is attached we set a liveData in the shared view model to SHOW which notifies the activity to inflate the viewStub. This way we inflate the view stub which hides the full screen displaying a splash image. Then once the view in the fragment is created and in the onViewCreated we again set the liveData in the shared view model to HIDE which hides the viewStub and the fragment is displayed.
Use fragmentContainerView inside your main activity. Show the view that you want to show in this container. Create a view in front of the container. Show splash message in this view. Make the visibility of the splash view gone when the main view is loaded. So the splash screen will use the activity life cycle and the main view will use the fragment lifecycle. This may be a solution for you.
Disclaimer: turns out that the issue is resolved in UPDATE 2 section
in the answer; the other sections are left if they could help future visitors in other potential issues
At first look, thought that Caused by: java.lang.NumberFormatException: s == null is related to the issue; although you told in comments that it's working synchronously.
And the exception java.lang.RuntimeException: Failed to call observer method won't help to know the error by tracing it in the code.
But your code successfully worked with me in simple layouts; probably the issue is related to the heavy layout that you try to load synchronously along while accessing the binding.lifecycleOwner; I guess the latter snippet requires a while before accessing the lifecycleOwner. So, you could post some delay in advance.
For that, I am going to use coroutines instead of posting a delay; as the code would be more linear and readable:
CoroutineScope(Main).launch {
delay(1000) // Original delay of yours
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this#MainActivity,
R.layout.activity_main
)
delay(1000) // try and error to manipulate this delay
binding.lifecycleOwner = this#MainActivity
}
If not already used, the coroutine dependency is
def coroutine_version = "1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
UPDATE
The posted delay in your code doesn't help in showing the splash/launch screen during that delay while the main activity is loading;
Handler(Looper.getMainLooper()).postDelayed({
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
this,
R.layout.activity_main
) // This won't be called unless the 1000 sec is over
binding.lifecycleOwner = this
}, 1000)
What your code does:
A splash screen is shown
A delay is posted (still the main layout is not loading in here)
main layout is shown when the delay is over
So, the posted delay is just accumulating to the time of loading the main layout; this even make it more lagged. Furthermore this is not the recommended way of using splash screen (This medium post would help in that)
Instead, I think what you intend to do:
Show a splash screen
Load main layout
Post a delay so that the main layout takes time to load during the delay
Show the main layout when the delay is over
But, the problem is that the thing need to be loaded is UI which requires to do that in the main thread, not in a background thread. So, we instead of using two different layout and call setContentView() twice; you could instead create a single layout for your main layout, and add some view that represents the splash screen which will obscure the main layout entirely (be in front of it) until the layout is loaded (i.e. the delay is over); then remove this splash view then:
Demo:
splash_screen.xml (Any layout you want that must match parent to obscure it):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/splash_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher" />
</LinearLayout>
Main activity:
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "LOG_TAG"
}
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Start Inflating layout")
binding = DataBindingUtil.setContentView(
this#MainActivity,
R.layout.activity_main
)
// Show only the first time app launches, not in configuration changes
if (savedInstanceState == null) {
CoroutineScope(IO).launch {
Log.d(TAG, "Start of delay")
delay(1000)
Log.d(TAG, "End of delay")
withContext(Main) {
hideSplash()
}
}
showSplash()
}
binding.lifecycleOwner = this#MainActivity
Log.d(TAG, "End Inflating layout")
}
private fun showSplash() {
supportActionBar?.hide()
// Inflate splash screen layout
val splashLayout =
layoutInflater.inflate(
R.layout.splash_screen,
binding.rootLayout,
false
) as LinearLayout
binding.rootLayout.addView(
splashLayout
)
}
private fun hideSplash() {
supportActionBar?.show()
binding.rootLayout.removeView(
findViewById(R.id.splash_screen)
)
}
}
Logs
2021-12-11 21:59:18.349 20681-20681/ D/LOG_TAG: Start Inflating layout
2021-12-11 21:59:18.452 20681-20707/ D/LOG_TAG: Start of delay
2021-12-11 21:59:18.476 20681-20681/ D/LOG_TAG: End Inflating layout
2021-12-11 21:59:20.457 20681-20707/ D/LOG_TAG: End of delay
Now the delay is running along with inflating the layout; the splash screen shown while it loads; and ends when the delay is over.
UPDATE 2
It's definitely not going to work: databinding = ... line takes 2.5 seconds to complete, can't add a view to "databinding.root" before it's ready. It works in the presented code because your main view is tiny.
Now try to separate inflating the layout from setContentView() in dataBinding; still both requires to be in the main thread
setContentView(R.layout.screen_splash)
CoroutineScope(Main).launch {
// Inflate main screen layout asynchronously
binding = ActivityMainBinding.inflate(layoutInflater)
delay(2500) // 2.5 sec delay of loading the mainLayout before setContentView
setContentView(binding.root)
binding.lifecycleOwner = this#MainActivity
}
Finally found the problem and, in retrospect, it was too elementary for the question:
Must assign ViewModel before lifecycleOwner
binding.viewModel = myViewModer
binding.livecycleOwner = this#MainActivity
Just changing order of these lines fixed it.
It's been a year since I used DataBinding last time. It seems something has changed since then or I've made a stupid mistake. Let's look at my problem.
UPD: please, also check the first comment
Long story short
I have a simple ProgressBar in my project:
<data>
<import type="android.view.View" />
<variable
name="vm"
type="/path/to/.SignInViewModel" />
</data>
<layout>
<androidx.constraintlayout.motion.widget.MotionLayout
<ProgressBar
...
android:visibility="#{vm.signInProgress ? View.VISIBLE : View.GONE}" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</layout>
which is visible on the screen until I press the button:
binding.login.setOnClickListener {
viewModel.signInProgress.value = false
}
where signInProgress is just a MutableLiveData
val signInProgress: MutableLiveData<Boolean> = MutableLiveData()
That's all about my interaction with ProgressBar. BUT when I click on login button, my ProgressBar becomes invisible for only a second, then it appears again and begins to rotate. Whaaaaat?
Also I checked generated Binding file and it seems like everything is fine:
androidxDatabindingViewDataBindingSafeUnboxVmSignInProgressGetValue = androidx.databinding.ViewDataBinding.safeUnbox(vmSignInProgressGetValue);
if(androidxDatabindingViewDataBindingSafeUnboxVmSignInProgressGetValue) {
dirtyFlags |= 0x10L;
} else {
dirtyFlags |= 0x8L;
}
vmSignInProgressViewVISIBLEViewGONE = ((androidxDatabindingViewDataBindingSafeUnboxVmSignInProgressGetValue) ? (android.view.View.VISIBLE) : (android.view.View.GONE));
if ((dirtyFlags & 0x7L) != 0) {
this.loginProgress.setVisibility(vmSignInProgressViewVISIBLEViewGONE);
}
Fragment:
binding.lifecycleOwner = this
binding.vm = viewModel
Gradle
// app
android {
buildFeatures {
viewBinding true
dataBinding true
}
}
// project
classpath "com.android.tools.build:gradle:7.0.2"
Can you help me to understand, what's going on here? I can't understand, why it can't just hide? It's my personal project, so I can share a link if you want to help me :(
Ok, I found the cause of the problem, but I haven't find a solution yet. Problem in my MotionLayout (I created the same layout with ConstraintLayout), but I wonder why
to sum up I am trying to change the src of ImageButtons inside my dialog_colors.xml file from MainActivity. However whatever I do I couldnt manage to change it. I tried the same code with ImageButtons inside my activity_main.xml but it doesnt work for buttons inside dialog_colors.xml file.
activity_main.xlm
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">
<include
android:id="#+id/dialog_colors"
layout="#layout/dialog_colors"/> ...
dialog_colors.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="#+id/ll_paint_colors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ImageButton
android:id="#+id/red"
android:layout_width="40sp"
android:layout_height="40sp"
android:src="#drawable/pallet_normal"
android:background="#color/Red"
android:layout_margin="1sp"
android:tag="redTag"
android:clickable="true"
android:onClick="selectColor"
/>...
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
colorDialog=findViewById<LinearLayout>(R.id.dialog_colors)
firstRowColors=colorDialog.findViewById<LinearLayout>(R.id.ll_paint_colors)
secondRowColors=colorDialog.findViewById<LinearLayout>(R.id.ll_paint_colors2)
drawingView=findViewById<DrawingView>(R.id.drawingView)
pressedColor=secondRowColors[0] as ImageButton
pressedColor!!.setImageResource(R.drawable.pallet_pressed)
}...
I tried the same thing with TextViews etc too. It seems like I cannot change anything inside the dialog_colors.xml file.
Do not get confused about the firstRows, secondRows etc, there are many ImageButtons, it doesnt work for any of them.
I found a solution to define an attribute in the MainActivity.kt through activity_main.xml to content_main.xml (included layout). The key word here is DataBinding. The project is completely reproducible and I provide first Kotlin and at the very end the JAVA files.
KOTLIN:
To enable DataBinding you need to go to your build.gradle(Module) and add following code:
//...
dataBinding{
enabled true
}
//...
You define a container called DrawableContainer as a Kotlin class. There you define a Drawable called customDrawable.
Thus DrawableContainer.kt:
import android.graphics.drawable.Drawable
data class DrawableContainer(val customDrawable: Drawable)
Now we will define our MainActivity.kt which will bind our chosen Drawable and pass it through our Container (DrawableContainer).
Our MainActivity.kt:
import android.app.Activity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.imagebuttonexperiment.databinding.ActivityMainBinding
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.drawable = DrawableContainer(resources.getDrawable(R.drawable.my_image))
}
}
The missing parts are our XML files. The code below shows our content_main.xml. It contains a variable (in <data>) we will define named drawable. The type guides to our DrawableContainer. So this is the first bridge between our Container and our layout we will <include. In the ImageButton you can see that as android:src we refer over our variable to our Drawable in our Container. That's why android:src="#{drawable.customDrawable}".
Thus content_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="drawable"
type="com.example.imagebuttonexperiment.DrawableContainer" />
</data>
<ImageButton
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="10dp"
android:src="#{drawable.customDrawable}"/>
</layout>
Now it's important to build our second bridge. Yet, we have DrawableContainer -> content_main. This will be the bridge content_main -> MainActivity. Therefor we have our <data/> and variable defined again. As you can see in <include we bind:drawable the exact same variable which is in both XML files.
The missing piece in our puzzle is the activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<data>
<variable
name="drawable"
type="com.example.imagebuttonexperiment.DrawableContainer" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/include_content"
layout="#layout/content_main"
bind:drawable = "#{drawable}"/>
</LinearLayout>
</layout>
RESULT:
The final result is an ImageButton in the MainActivity that was included by another Layout. But the Drawable was chosen from the MainActivity and passed a Container to be shown in the included Layout. Magic isn't it!? ;)
JAVA:
And I will keep my word and provide you the JAVA version also.
The XML files and gradle will be the same, you only need to use MainActivity.java instead of MainActivity.kt, the same for DrawableContainer.java instead of DrawableContainer.kt.
Here is the same in green, DrawableContainer.java:
import android.graphics.drawable.Drawable;
public class DrawableContainer {
public final Drawable customDrawable;
public DrawableContainer(Drawable customDrawable) {
this.customDrawable = customDrawable;
}
}
And of course our MainActivity.java:
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import com.example.imagebuttonexperiment.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
mainBinding.setDrawable(new DrawableContainer(getDrawable(R.drawable.my_image)));
}
}
DOWNLOAD PROJECT:
In addition I provide the project with both, JAVA and Kotlin classes in it. You just need to comment the content of the 2 classes grey you don't want. In example, if you want to use Kotlin, grey the content of .java files.
Github Project
Documentation & Tutorial:
Databinding Android Documentation
Youtube Tutorial: How to bind data?
NOTE: That's somewhat a solution of my problem:
I have tried this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
colorDialog2=Dialog(this)
colorDialog2.setContentView(R.layout.dialog_colors)
colorDialog2.setTitle("Colors")
firstRowColors=colorDialog2.findViewById<LinearLayout>(R.id.ll_paint_colors)
secondRowColors=colorDialog2.findViewById<LinearLayout>(R.id.ll_paint_colors2)
drawingView=findViewById<DrawingView>(R.id.drawingView)
pressedColor= secondRowColors[0] as ImageButton
pressedColor!!.setImageResource(R.drawable.pallet_pressed)
}
intead of this in Main.Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
colorDialog=findViewById<LinearLayout>(R.id.dialog_colors)
firstRowColors=colorDialog.findViewById<LinearLayout>(R.id.ll_paint_colors)
secondRowColors=colorDialog.findViewById<LinearLayout>(R.id.ll_paint_colors2)
drawingView=findViewById<DrawingView>(R.id.drawingView)
pressedColor=secondRowColors[0] as ImageButton
pressedColor!!.setImageResource(R.drawable.pallet_pressed)}
And it worked, I managed to change the src however I wanted. (Only did this change didnt do the changes that the others provided) However, I am not even sure if this is a good practice. It works as a quick fix for sure tho.
I will look into DEX7RA's answer more later on.
I've two layouts for a screen. Activity works fine while setting a layout for Mobile device but it's causing error while setting layout for tablet device. The main issue is:
Caused by: java.lang.RuntimeException: view must have a tag at
com.mypackage.DataBinderMapperImpl.getDataBinder(DataBinderMapperImpl.java:941)
Though, I don't face the problem when I install app on mobile device.
This way I'm setting layout on activity:
val resetPasswordActivityBinding = DataBindingUtil.setContentView<ResetPasswordActivityBinding>(this,
R.layout.reset_password_activity)
resetPasswordActivityBinding.resetPasswordViewModel = resetPasswordViewModel
Here is my XML layout for tablet screen:
<?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">
<data>
<variable
name="resetPasswordViewModel"
type="com.bhi.salesarchitect.user.password.reset.ResetPasswordViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/toolbar"
layout="#layout/app_toolbar_layout"
app:appTheme="#{resetPasswordViewModel.appTheme}"
app:appToolbar="#{resetPasswordViewModel.appToolbar}" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg_splash"
android:contentDescription="#null"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="#+id/imv_builder_logo_change_pswd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/dp_135"
android:layout_marginBottom="#dimen/space_xxlarge"
android:layout_weight=".5"
android:contentDescription="#null"
android:src="#drawable/ic_logo" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="#dimen/dp_80"
android:layout_weight=".4">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/space_normal"
android:layout_marginEnd="#dimen/space_normal"
android:contentDescription="#null"
android:scaleType="fitXY"
android:src="#drawable/bg_white_shadow" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="#dimen/space_xxxlarge"
android:paddingEnd="#dimen/space_xxxlarge">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/dp_55"
android:layout_marginBottom="#dimen/space_small"
android:gravity="center"
android:text="#string/a_one_time_password_reset_code_has_been_sent_to_your_email"
android:textColor="#color/blue_dark_main"
android:textSize="#dimen/text_size_normal" />
<EditText
android:id="#+id/et_otp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/space_normal"
android:background="#drawable/shape_rounded_white"
android:drawablePadding="#dimen/space_small"
android:hint="#string/password_reset_code"
android:inputType="textPersonName"
android:paddingStart="#dimen/space_small"
android:paddingTop="#dimen/space_xsmall"
android:paddingEnd="#dimen/space_xxsmall"
android:paddingBottom="#dimen/space_xsmall"
android:textColor="#android:color/black"
android:textSize="#dimen/sp_15" />
<EditText
android:id="#+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/space_small"
android:background="#drawable/shape_rounded_white"
android:drawableStart="#drawable/ic_password"
android:drawablePadding="#dimen/space_small"
android:hint="#string/new_password"
android:inputType="textPassword"
android:maxLength="#integer/max_password_length"
android:paddingStart="#dimen/space_small"
android:paddingTop="#dimen/space_xsmall"
android:paddingEnd="#dimen/space_xxsmall"
android:paddingBottom="#dimen/space_xsmall"
android:textColor="#android:color/black"
android:textSize="#dimen/sp_15" />
<EditText
android:id="#+id/et_confirm_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/space_small"
android:background="#drawable/shape_rounded_white"
android:drawableStart="#drawable/ic_password"
android:drawablePadding="#dimen/space_small"
android:hint="#string/confirm_new_password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLength="#integer/max_password_length"
android:paddingStart="#dimen/space_small"
android:paddingTop="#dimen/space_xsmall"
android:paddingEnd="#dimen/space_xxsmall"
android:paddingBottom="#dimen/space_xsmall"
android:textColor="#android:color/black"
android:textSize="#dimen/sp_15" />
<Button
android:id="#+id/bt_submit"
style="#style/ButtonNormal"
android:layout_marginTop="#dimen/space_normal"
android:backgroundTint="#color/blue_dark_main"
android:onClick="#{()-> resetPasswordViewModel.onSubmitClick()}"
android:text="#string/submit"
android:textColor="#android:color/white" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</layout>
I ran into this when I had:
A library module defining a layout resource
An app module that depended upon that library module defining the same layout resource
The library layout resource was set up for data binding (e.g., root <layout> element), but the app module's edition of that layout resource was not
In my case, the app module's layout was left over from when I created the project. Removing it cleared up the problem.
Keeping two layouts, one with layout tag of data-binding and another without it, is the common cause of this issue.
I got stuck when I renamed my two normal layout files with same name (/layout and /layout-sw720dp) and used tag. Then, it worked for mobile device but not for tablet. So, after cleaning project, it all started working.
I was having this problem when using an array adapter, having a crash due to a missing tag on convertView. In my getView(), I was doing:
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
if (convertView == null) {
DataBindingUtil.inflate<ItemSpinnerDropDownWorkPackageFilterBinding>(
LayoutInflater.from(parent.context),
R.layout.item_spinner_drop_down_work_package_filter,
parent,
false
)
} else {
binding = ItemSpinnerDropDownWorkPackageFilterBinding.bind(convertView)
binding.text1.setText(getItem(position))
setDividerVisibility(binding.divider, position)
return convertView
}
}
Which was crashing. The solution was to set the tag on the first run through getView:
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = if (convertView == null) {
DataBindingUtil.inflate<ItemSpinnerDropDownWorkPackageFilterBinding>(
LayoutInflater.from(parent.context),
R.layout.item_spinner_drop_down_work_package_filter,
parent,
false
)
} else {
convertView.tag as ItemSpinnerDropDownWorkPackageFilterBinding
}
binding.text1.setText(getItem(position))
setDividerVisibility(binding.divider, position)
binding.root.tag = binding
return binding.root
}
Using viewStub with databinding:
class MyFragment {
lateinit var binding: MyFragmentBinding
lateinit var viewStubBinding: MyViewStubBinding
private fun setViewStub() {
binding.myViewStub.setOnInflateListener { viewStub, view ->
viewStubBinding = binding.myViewStub.binding as MyViewStubBinding // property viewStubBinding is finally inflated
}
binding.myViewStub.viewStub?.inflate() // inflate viewStub with defined layout file in XML
}
}
When I tried to use MyViewStubBinding.bind(view / viewStub) it crash on View must have a tag. This happen because ViewStubProxy try to inflate binding class by itself, so when I try to call bind() by myself on same Binding class, it was already bound and crashed with this error.
You need to add layout tag at start of your app_toolbar_layout layout file
You must have <layout> tag into your all XML views (portrait, landscape, tablet, etc.). Even you have to include <layout> tag into included views ("#layout/app_toolbar_layout").
i was facing similar error so my work around was
DataBindingUtil.bind(holder.itemView)?.apply
{
item = items[position]
}
I just needed to clean and rebuild after deleting a resource file.
May be you have multiple xml layout files.
if you already use layout tag in your xml file, and you still give error, just rename your layout file name and clean/rebuild the project again it will fix
I had this error trying to bind separately as my view is inflated within a library but I supply the layout.
The doc says you can do this:
val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot)
The viewRoot I was passing was derived from the custom class but ended up being a parent of my layout file (the one that included the layout tag). So I had to specify the view as the root of my layout file via findViewById
I encounter this problem, the root case is there are duplicate layout resource file in different module. delete the redundant one to resolve the issue.
In my case worked this code to set content view with data binding.
binding = GenericItemDetailsListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
I started using Android Data binding but without success.I have done everything as proposed in the documentation but when I have to set method value I get null.
I am using Android Studio 2.1.2 and I put in gradle
dataBinding {
enabled = true
}
in layout I do exactly da same put layout and inside I put tag data:
<data>
<variable name="order" type="com.example.Order"/>
</data>
and in code when I want to have binding variable
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
ActivityOrderOnePaneBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_order_one_pane);
binding.setOrder(mOrder);
Binding is null,I don't have compile errors.
Since you're overriding setContentView in your Activity, you need to replace:
ActivityOrderOnePaneBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_order_one_pane);
with
ActivityOrderOnePaneBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_order_one_pane, getContentFrame(), false);
setContentView(binding.getRoot());
I had the same problem because I overrode setContentView in my base Activity and that fixed it.
If you overrode setContentView, getContentFrame() is the ViewGroup that contains your content, exclusive of the AppBarLayout and Toolbar. Here's an example of what getContentFrame() would look like if you had a base layout similar to what's below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
...>
<android.support.design.widget.CollapsingToolbarLayout
...>
<android.support.v7.widget.Toolbar
.../>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.ContentFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.ContentFrameLayout>
</android.support.design.widget.CoordinatorLayout>
getContentFrame() just returns the FrameLayout in the above layout.
protected ViewGroup getContentFrame() {
return (ViewGroup) findViewById(R.id.content_frame);
}
I hope this helps.
Try to rebuild gradle and clean project