The ViewBinding implementation is unable to render the layout configuration for LinearLayout used in my code, while the same layout works with the older technique of findViewById()
I have setup the gradle to use ViewBinding
android {
...
buildFeatures {
viewBinding = true
}
Below is the activity which uses a LinearLayout
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
... />
<Button
... />
</LinearLayout>
The corresponding Kotlin class includes the binding instance with its root view passed to setContentView()
MainActivity.kt
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
...
setContentView(binding.root)
}
While the Layout editor renders a correct alignment of the app, once it is launched onto the emulator the layout configuration such as center_vertical alignment set using layout_gravity of LinearLayout is ignored.
Is something incorrect or missing the way ViewBinding is implemented?
in your activity_main.xml give id to your LinearLayout
There are one possibility is that your LinearLayout have not any id.
When you are doing binding.root it keeps a reference of root view which is LinearLayout in your case.
As per the official document:
If view binding is enabled for a module, a binding class is generated
for each XML layout file that the module contains. Each binding class
contains references to the root view and all views that have an ID.
Give id to your LinearLayout android:id="rootView, rebuild your project and try to run.
I hope, it will solve your issue.
Thanks & Happy coding..!
Had the same issue, Solved with adding android:gravity="center_vertical" to LinearLayout in activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:id="#+id/root_view"
tools:context=".MainActivity">
</LinearLayout>
I had the same problem doing an exercise in the "Developing Android Apps with Kotlin" course.
I did not find a solution because I suppose that without using view binding, a view above the one in main_activity.xml must be taken as root (probably the same full screen), so when using layout_gravity without view binding, it works, because it positions the LinearLayout relative to the screen. However, when using view binding, I assume that it takes the LinearLayout as its root, so layout_gravity does nothing (again, I'm not sure, I don't know very well how view binding works).
The solution I used was to simply try to achieve the same view with something different. For this in activity_main.xml I used:
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
tools:context=".MainActivity">
Related
I'm using the Android view binding to get an automatically generated binding class of my XML. In my XML definitions I'm using a TextSwitcher control with two child elements of the type TextView.
In code I access the child views of the TextSwitcher like this:
...
_binding = MyViewBinding.inflate((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE), this, true);
((TextView)_binding.myTextSwitcher.getChildAt(0)).setTextColor(_mySpecialColor);
((TextView)_binding.myTextSwitcher.getChildAt(1)).setTextColor(_mySpecialColor);
...
Is there an easier way to access the child views of the myTextSwitcher directly with the MyViewBinding class without the need to cast them?
The XML definitions looks like this:
<TextSwitcher
android:id="#+id/myTextSwitcher"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</TextSwitcher>
All the time I was searching at the wrong location in the generated binding class.
Those TextView's are there directly accessible on my _binding variable.
// this does work
_binding.text1.setTextColor(_headerLabelColor);
_binding.text2.setTextColor(_headerLabelColor);
I thought they need to be accessible as child's of my TextSwitcher but it seems I learned something now.
// this does not work but I expected it to
_binding.myTextSwitcher.text1.setTextColor(_headerLabelColor);
_binding.myTextSwitcher.text2.setTextColor(_headerLabelColor);
I have parent layout with databinding. I want to add child layout depends on logic.
I expected to need similar code:
<include
android:id="#+id/layout_id"
layout="#{model.isOk ? #layout/layout_container_1 : #layout/layout_container_2}"
app:model="#{model}" />
But I get error:
android.databinding.tool.processing.ScopedException: [databinding] {"msg":"included value (#{model.isOk ? #layout/layout_container_1 : #layout/layout_container_2}) must start with #layout/.","file":"x.xml","pos":[]}
Is it possible to include child layout dynamically through "if-else" condition? If yes what is the best way? Any samples are welcome!
Is it possible to include child layout dynamically through "if-else" condition?
So far, this is not possible, because the layout attribute must point to a certain layout before the data-binding works:
(Data Binding) As its name implies, it binds data to a layout, so there must be a layout first, in order to bind data to it.
In other words, the layout="#{model.isOk ? #layout/layout_container_1 : #layout/layout_container_2}" doesn't work because there should be an existing layout before examining model.isOk, and also the layout attribute expects a layout resource after the # symbol.
So, we need to do that programmatically instead of the XML layout. And this an be done by replacing the <include> with ViewStub which is a sort of deferring layout inflation to be done in the activity.
So before inflating the ViewStub layout, you can have a chance to check whether the model.isOk in the activity before deciding which layout can be inflated on the ViewStub.
And hence based on the model.isOk value, we can set the layout with binding.myLayoutStub.myViewStub?.layoutResource = R.layout.layout_container_1
Eventually we can inflate the new ViewStub layout using binding.myLayoutStub.myViewStub?.inflate() method.
Using the ViewStub, will make you have a couple of binding object, one for the activity (or fragment), and the other for the inflated layout of the ViewStub.
Here is an example like yours:
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">
<data>
<variable
name="model"
type="com.example.android.databindingexample.ActivityViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="#+id/layout_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And we've layout_container_1 & layout_container_2 that only differ in the the textview value:
layout_container_1.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="#color/purple_200">
<TextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Layout 1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ViewModel:
class ActivityViewModel : ViewModel() {
var isOk = true
}
Then decide which layout in the activity based on the model.isOk whenever you inflate the ViewStub:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
// This is of Any type as it will be casted later to the proper DataBinding generated class when the ViewStub layout inflated
private lateinit var stubBinding: Any
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val model = ViewModelProvider(this).get(ActivityViewModel::class.java)
binding.model = model
// Listener to ViewStub inflation so that we can change its layout
binding.layoutStub.setOnInflateListener { _, inflated ->
stubBinding =
(if (model.isOk) DataBindingUtil.bind<LayoutContainer1Binding>(
inflated
)
else DataBindingUtil.bind<LayoutContainer2Binding>(
inflated
))!!
// Changing the text of the inflated layout in the ViewStub using the ViewStubBinding
if (model.isOk)
(stubBinding as (LayoutContainer1Binding)).textview.text = "This is Layout container 1"
else
(stubBinding as (LayoutContainer2Binding)).textview.text = "This is Layout container 2"
}
// Inflating the ViewStub
if (!binding.layoutStub.isInflated) {
binding.layoutStub.viewStub?.layoutResource = if (model.isOk)
R.layout.layout_container_1 else R.layout.layout_container_2
binding.layoutStub.viewStub?.inflate()
}
}
}
Preview:
This is impossible with the current implementation of <include> tag. By "implementation" I mean how it is actually used by the SDK.
<include> tag is parsed in the process of layout inflation which means layout attribute of <include> tag must have a layout resource ID defined before any of your code logic can be executed. While data-binding is what you can use after a layout was successfully inflated.
I decided to finally jump into Kotlin this month as a hobby project. I'm working through a Udacity course (Android app dev with Kotlin).
I was just trying to replace the viewById with View binding (as a test for me). Which works fine.
But why if I use setContentView(view) from the binding does my layout no longer respect the gravity?
My layout file for my main activity is linear, with center-vertical layout_gravity
<<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="30sp"
android:layout_gravity="center_horizontal"/>
<Button
android:id="#+id/roll_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/roll"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
When I use the old setContentView(R.layout.main_activity) in my activity, this displays as expected in the center of the screen
class MainActivity : AppCompatActivity() {
#RequiresApi(Build.VERSION_CODES.P)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Let's do it the trad way without binding
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.roll_button)
Image with central gravity
If I swap this out for view binding instead and replace the setContentView as shown below
var binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
The app still runs, but now the text and button are at the top of the screen.
image top aligned
I was just testing that I could make this work in general, as I'd prefer to use in real life (anything to reduce nullPointer exceptions). But if I can't even get a simple example to work properly I'm stuffed.
Does anyone know what I am doing wrong? Or what concept I'm missing?
Try to change android:layout_gravity="center_horizontal" to android:layout_gravity="center" or maybe try to use RelativeLayout I heard that it's one of the best layouts.
I'm having trouble with this same problem. I was looking at the documentation for View Binding and it states:
"View binding doesn't support layout variables or layout expressions,
so it can't be used to declare dynamic UI content straight from XML
layout files."
I'm wondering if it's related to that? I'm still very new to this, but it's the only thing I can find so far that makes some sense to me.
UPDATE: Found another question asking the same thing (How to use View Binding with Linear Layout?)
Changed android:layout_gravity="center_vertical" to android:gravity="center", but then had an issue with the preview in Layout Editor not showing it centered; to remedy that I added tools:layout_gravity="center_vertical" so the activity_main.xml shows this now at the top:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
tools:layout_gravity="center_vertical"
android:orientation="vertical"
tools:context=".MainActivity">
Might not be the best solution, but works to show you the preview and then when you actually run it!
XML layout_-prefixed attributes work with the parent view. When you inflate a view without a parent, the layout_* attributes have no effect.
An activity's setContentView(int) normally delegates to PhoneWindow#setContentView(int) that implicitly uses a parent container layout when inflating the XML layout. That's why the 1-arg inflation works in an activity.
View binding does not implicitly supply any parent layouts. You need to explicitly supply it with the three-arg inflate(int, View, boolean) method call where the first arg is the layout id, second one is the parent and the third controls whether the inflated layout should be added to the parent when inflating.
The usual an easy use case is to use fragments where the onCreateView() callback supplies you with a parent container layout and you can just return inflate(id, container, false).
The PhoneWindow content layout is lazily generated, so you kinda have a chicken-and-egg problem. Calling setContentView() generates the content layout but when calling it you kinda want to already have inflated the view binding with a parent content layout.
My suggestion is to move the layout code away from your activity and instead use a fragment for it. Then you can use view binding in your fragment.
I am using a RecylerView inside a layout l1.xml. I am including this l1.xml inside l2.xml using include tag.
I update this RecyclerView after an api call but l2.xml is not showing the updated RecyclerView.
Is there a way to forcibly ask the parent to refresh?
invalidate(), refreshDrawableState(); on the parent layout didn't help?
Is there a smarter way to use a rendered layout in multiple places?
l1.xml
...
...
<LinearLayout
android:id="#+id/feed"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:background="#color/light_primary_background">
<include layout="#layout/events_list"/>
</LinearLayout>
...
...
events_list.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/events_recycler_view"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
I update the events_recycler_view after an API call and the events_list.xml is updated but the include in l1.xml is not updated
Yes you can always use LayoutInflater to inflate a view, but the view must have the ids and type matching the id and type defined in your java code.
Check out this link for how to use layout inflater
http://www.programcreek.com/java-api-examples/android.view.LayoutInflater
In my layout xml file, I have included other layout xml file (each
with a different android id).
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<include layout="#layout/view_contact_name" android:id="+id/test2"/>
But when I run it in the emulator, and start Hierarchy Viewer, each of
the layout still shows 'NO_ID', and in my code, I have
findViewById(R.id.test1) and findViewById(R.id.test2) both returns null.
Can anyone please help me with my problem ?
Specify the ID in the <include>
<include layout="#layout/test" android:id="#+id/test1" />
Then use two findViewById to access fields in the layout
View test1View = findViewById(R.id.test1);
TextView test1TextView = (TextView) test1View.findViewById(R.id.text);
Using that approach, you can access any field in any include you have.
I found out, that if you are using <merge> tag in your include layout, then the ID of include transfers to the merge tag which is not real view.
So either remove merge, or replace it with some layout.
Tor Norbye wrote:
The <include> tag is not a real view, so findByView will not find it. The #id attribute (and any other attributes you've set on the include tag) gets applied on the root tag of the included layout instead. So your activity.getView(R.id.included1) should in fact be the <TextView> itself.
Romain Guy indicates that you can override the ID of an included layout by putting an android:id attribute inside the <include> tag.
<include android:id="#+id/cell1" layout="#layout/workspace_screen" />
I think the top answer misses the most important point and might mislead people into thinking the <include/> tag creates a View that holds the include contents.
The key point is that include's id is passed to the root view of the include's layout file.
Meaning that this:
// activity_main.xml
<include layout="#layout/somelayout" android:id="#+id/someid"/>
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Becomes this:
// activity_main.xml
<ImageView
android:id="#+id/someid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
yes is like this, but careful when the layout inserted in include field is a custom one and you want to access that root layout. That layout in this case #layout/test test, is actually returned in first line.
test test1View = (test)findViewById(R.id.test1);
you must set id each include tag
included child element set a
new id. if you look how to generate new id, look at this entry:
https://stackoverflow.com/a/15442898/1136117
Problem is we try to use id which is not declared in current layout file.
Instead of declaring again, id can be simply referred using #+id/. If you refactor original id name through Android Studio it does refactor in included layout as well.
<include layout="#layout/toolbar"/>
<TextView
android:id="#+id/txt_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
**android:layout_below="#+id/toolbar"**
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"/>
In a case of using <RecyclerView> find the id of <include> by using an instance of inflated view or else it will return null.
public class ViewHolder extends RecyclerView.ViewHolder {
private mTextView;
public ViewHolder(View view) {
super(view);
View include_1 = view.findViewById(R.id.include_1);
mTextView = (TextView) include_1.findViewById(R.id.text_id);
}
}
If you have set id to either root tag of included layout then you can use that id
or you can set id to included layout.
But you can not set id to both it may throw exception.
<include layout="#layout/view_contact_name" android:id="+id/test1"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
Or
<include layout="#layout/view_contact_name"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/llBottomMainView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
....
</LinearLayout>
When talking about include you either have an id on the root view inside the included layout file or on the include line itself and not on both. For example:
<include layout="#layout/layout1" android:id="#+id/layout1"/>
Layout 1 file
<RelativeLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout2">
</RelativeLayout>
The above example is wrong because technically you have two id's declared for the same layout. So what you have to do is pick which element will have the id.
To specify the id when you are including a xml file is like setting it to any xml element
Example:
*list_layout.xml*
`<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvNames"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>`
*activity_main.xml*
`<RelativeLayout
tools:context=".MainActivity">
<include
layout="#layout/list_layout"
android:id="#+id/myList" />
</RelativeLayout>`
Now if you want to get that to use in .kt file, just use normally findViewById
Exemplo
*MainActivity.kt*
`val myList: RecycleView = findViewById(R.id.myList)`
Wow, I can't believe this question doesn't have the right answer yet. It's simple tags suck. You can only change things that start with android:layout_ which android:id doesn't match. So the answer is you can't. Sorry. What you can do instead is create a class that will be a ViewGroup which will inflate the included views inside, then add that as a tag in your layout, but that's about it.