Viewbinding shows nullable view also crashing app when screen rotets - android

Here is my view in xml
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/et_user_name"
style="#style/text_bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="#dimen/_8sdp"
android:layout_weight="1"
android:backgroundTint="#color/activity_background_color"
android:cursorVisible="false"
android:maxLines="1"
android:imeOptions="actionDone"
android:minHeight="48dp"
android:padding="#dimen/_6sdp"
android:text="#string/guest_user"
android:textColor="#color/text_color_dark"
android:textSize="#dimen/_18sdp" />
here is my setText method
mView.etUserName!!.setText(sharePrefHelper.user?.name)
Here is init of mView
private lateinit var mView: FragmentProfileBinding
mView = FragmentProfileBinding.inflate(inflater)
this happens some of XML views
most views are non nullable but some views are shown as nullable
don't know what cause this issue

As per the View Binding documentation:
Null safety: Since view binding creates direct references to views, there's no risk of a null pointer exception due to an invalid view ID. Additionally, when a view is only present in some configurations of a layout, the field containing its reference in the binding class is marked with #Nullable.
If you have a nullable field in your binding, that means you have another configuration (for example, one in res/layout-land or some other folder) that does not contain a view with that android:id.
As soon as you add that View with the android:id to each configuration of that layout, it will no longer be nullable. The other option is to remove the alternate versions of that layout and use the same layout in all configurations.

Related

How to get a reference to an object in a NavigationView Header

In my activity, I am inflating the layout via data binding. The binding itself works correctly, and I am able to get references to objects in the layout file easily.
One of those objects is a NavigationView. It has a headerLayout:
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#android:color/white"
android:fitsSystemWindows="true"
app:itemIconTint="#color/navigation_view_color"
app:itemTextColor="#color/navigation_view_color"
app:headerLayout="#layout/nav_view_header" />
The header layout looks like this (extra objects removed for simplicity):
<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="wrap_content"
android:background="#color/menu_blue"
android:paddingBottom="32dp">
<ImageView
android:id="#+id/navViewLogOutButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="#drawable/logout"
app:tint="#color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:contentDescription="#string/sz.proApp.support.logOut" />
</androidx.constraintlayout.widget.ConstraintLayout>
I need to set a click listener on this image view, but I can't get a reference to it. This is what I am using:
binding.navView.findViewById<ImageView>(R.id.navViewLogOutButton)?.setOnClickListener{
doSomething()
}
I've verified that binding.navView is not null. Also, by setting a break point there, I can see what the memory address for this ImageView is. When I change that line to this:
binding.navView.findViewById<ImageView>(213123403)
I get the reference that I want. But obviously, I can't just use the integer since that is dynamic. The code compiles and the app launches. But when it hits that line, findViewById returns null every time.
I read that findViewById may not work with data binding in a activity, but if that is true, then how can I get this reference so that I can set the listener?
app:headerLayout="#layout/nav_view_header"
So, the generated binding class should beNavViewHeaderBinding, you can get the binding with:
val headerBinding = NavViewHeaderBinding.bind(binding.navView.getHeaderView(0))
And access the header views through headerBinding:
headerBinding.navViewLogOutButton

How to access child elements of a TextSwitcher using view binding?

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);

Android App setContentView(R.layout.activity_main) vs View binding - layout_gravity not respected

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.

How to include an Android layout but also access its elements in binding?

I'm doind this on my activity:
<include
android:id="#+id/withGyroLayout"
layout="#layout/with_gyro_layout"/>
Where with_gyro_layout.xml is
<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">
<com.example.util.FixedTransformerViewPager
android:id="#+id/viewPagerTop"
android:layout_width="0dp"
android:layout_height="143dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.util.FixedTransformerViewPager
android:id="#+id/viewPagerBottom"
android:layout_width="0dp"
android:layout_height="143dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/viewPagerTop" />
</androidx.constraintlayout.widget.ConstraintLayout>
However, I can't access the elements viewPagerBottom or viewPagerTop from the binding for my activity:
binding.viewPagerBottom.setVisibility(View.VISIBLE);
binding.viewPagerTop.setVisibility(View.VISIBLE);
I tried putting with_gyro_layout.xml around <merge>...</merge> but it also didn't solve.
I want to be able to change programatically between with_gyro_layout.xml and without_gyro_layout.xml and also access its inner elements by the binding. How can I do that?
Two things are required in order to use ViewBinding with an included layout.
<merge> is not supported
The documentation only covers Data Binding, and not View Binding, but it does appear to be applicable to both. See https://developer.android.com/topic/libraries/data-binding/expressions#includes
Data binding doesn't support include as a direct child of a merge element.
In other words, the layout must have a real, concrete view as its root element. The following is supported:
<LinearLayout ...>
<TextView ... />
<TextView ... />
</LinearLayout>
But a layout with a <merge> root is not supported:
<merge ...>
<TextView ... />
<TextView ... />
</merge>
The <include> tag must specify an ID
It is, in general, possible to include a layout without explicitly specifying an ID. View Binding does not support this:
<include layout="#layout/included_layout"/>
Even if the included layout has an ID on its root element, it is still not supported. Instead, you must explicitly specify the ID on the <include> tag:
<include
android:id="#+id/some_id"
layout="#layout/included_layout"/>
Once both of these conditions are satisfied, your generated binding for the outer layout will include a reference to the binding of the included layout. Let's say our two files are outer_layout.xml and included_layout.xml. Then these two files would be generated:
OuterLayoutBinding.java
IncludedLayoutBinding.java
And you could reference the included views like this:
val outerBinding = OuterLayoutBinding.inflate(layoutInflater)
val innerBinding = binding.someId // uses the id specified on the include tag
val innerView = innerBinding.viewPagerTop
Or, for short:
val binding = OuterLayoutBinding.inflate(layoutInflater)
val innerView = binding.someId.viewPagerTop

Under what circumstances will a layout element be null when fetched using findViewById(R.id.*)?

I have a layout as below:
Let's name it my_layout.xml:-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:background="#19396a"
/>
<TextView
android:id="#+id/textView2"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="#ffffff"
android:background="#19396a"
/>
</LinearLayout>
<ListView
android:id="#+id/uilistView"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
In my activity I have a method in which I initialize all the UI elements i.e I do mapping of activity UI variables to layout.
Assume, the method is as below:-
//used to initialize UI elements
//called in oncreate() method
public void initializeUIelements() {
......
activity_listView = (ListView) findViewById(R.id.uilistView);
}
Here, activity_listView is a class level variable of type ListView
In my project, I have res\layout as well as res\layout-land and my_layout.xml exists in both the folders.
But sometimes during activity restart/when it's created I get a NullpointerException while initializing activity_listView that comes from R.java.
I know:
We get NullpointerException similar to what I am asking when an
element does not exist in any of the layout folder(i.e it exists in layout-land but does not exist in layout folder).
But here,the element exists in both folders and even though I get this inconsistent error
i.e. I am not able to produce it always but sometimes it starts coming.
So,please help me in analyzing as what may be the reasons when we get this error apart from the one mentioned by me above.
Thanks in advance.
If you could provide a more specific example, it might be easier to debug the exact problem. A few reasons why your View might be null:
The layout you've set as the content view does not include a view with this id:
If you reference a View that exists in the R file (a file that holds references to all elements within the res folder), say uilistview, in an Activity where you are not setting it's layout container as the content view (in this case this would be my_layout.xml), you will not get an error in Eclipse or whatever IDE you are using. Android will look for a View with this id within the layout, and if it can't find it, it will return a null value.
You call findViewById too early
You must use the method setContentView(R.layout.some_layout) before you try to access any of the elements with findViewById(). Logically, it makes sense - you need to tell Android where it should look where it's find an element
Looking for an element inside an inflated View
This is similar to the first, but it's common. If you've inflated a view and then want to manipulate a View inside the inflated View, you must instruct Android to look in the right place. For example:
RLayoutInflater layoutInflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View smallerView = layoutInflater.inflate(R.layout.mylayout);
If you want to reference something within mylayout.xml, you would need to use this:
TextView text = (TextView) smallerView.findViewById(R.id.text_view);
rather than:
TextView text = (TextView) findViewById(R.id.text_view);
The first indicates to look into the inflated view by placing the findViewById method on the inflated view, while the second one applies it to the current View of the Activity, which will cause a null value to be return.
Clean your project
This is the most frustrating answer, because this is not your fault. Sometimes the R.java class messes up and causes wrong references to id's while it is being compiled/built. Try cleaning your project and correcting the wrong references by looking for Project -> Clean in the main bar of Eclipse (or the IDE you're using).

Categories

Resources