Android Dev in Kotlin, confused by tutorial - android

Im struggling with one of the android basics in kotlin code labs. Im a beginner who took up programming in lockdown for fun.
This is the tutorial in question
At the begining of the tutorial it says to get a nullable reference to binding named_binding, then use
private val binding get() = _binding!!
to get a version of it we can refer to without using the ? null safety thing. All good so far.
However in step four it shows the following code:
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentLetterListBinding.inflate(inflater, container, false)
val view = binding.rootreturn view}
Why do we refer to _binding to inflate the view then binding in the next line when assigning the view?

Why do we refer to _binding to inflate the view then binding in the next line when assigning the view?
Two concepts to understand here.
_binding is considered a backing property - that is, the actual variable reference that holds a value. In this case, the variable is a nullable type.
binding is a standard property - that is, a thing that provides access to an underlying backing field. In this case, it's using _binding as a backing property as a minor convenience to expose _binding as a non-null.
So - since binding just exposes _binding as a non-null value, _binding must be set first. So that's why it's assigned the value of the inflate call. Also note that _binding is a var which means it can be re-assigned while binding is a val which means it can't. So trying to use binding when inflating the view would not compile.
Finally, why they use binding to get the view is unclear. Probably just for the sake of the convenience / consistency of using binding as the single property to reference the class binding. Using _binding?.root would work fine too.

This is best practice to protect someone modify _binding like _binding = null and you got error when access to _binding,
You should use binding.root instead _binding.root

Related

Should ViewBinding properties in fragments always have a backing non-nullable property?

I've been reading https://developer.android.com/topic/libraries/view-binding and was thinking about how they define their view binding property in a fragment.
This is the sample code:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
So _binding is a nullable because the reference to the view will be destroyed in onDestroyView(). However, they use binding as a backing property, which retrieves _binding and makes it non-nullable (whatever the equivalent to force unwrapping is called in Kotlin).
The question is why should we have _binding and binding and which one should be used? It feels like if you are trying to make _binding nullable, then why make it essentially non-nullable with binding and risk accessing the view when it's destroyed?
_binding is nullable so that it can be set to null in onDestroyView, avoiding any memory leaks. but if you tried to access views using _binding then you will have to write _binding!!.someView every time you access any view, use of non-null assertion(!!) quickly becomes redundant and verbose.
So to remove this redundancy there is non-nullable binding. Now you can access your views as binding.someView, no need to use non-null assertion (!!). This can not cause any issue, since views should only be accessed between onCreateView and onDestroyView.
The reason behind this is to make code easier to read and maintain when you know for sure that you won't try to access the view before onCreateView and after onDestroyView. The non-null property avoids the unnecessary ? or !! null safety operators every time you're trying to reach the binding.
On the other hand if you're uncertain about your call sequences and may try to access the fragment view when the view is destroyed, you wouldn't need to take that extra step and put your binding instance in a backing property. The null safety is your dearest friend in this case.

Espresso launchFragmentInContainer<> binding issue

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.

ViewBinding - Included Layout Binding Resulting in Unresolved Reference

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

DataBindingUtil inflates layout as null

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

Databinding, Binding class can't find RealmObjects

I was using databinding with realm objects and after updating android studio and gradle the auto generated Binding classes can't find the RealmDb classes or directory.
I tried removing the realm object out of the databinding but the project won't override the existing auto generated class and i keep getting the same error.
This is how i'm binding the data
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate(inflater, R.layout.fragment_article_info, container, false) as FragmentArticleInfoBinding
val view = binding.root
viewModel = ArticleInfoVM(arguments?.getInt(KEY_ARTICLE_ID)!!)
binding.article = viewModel?.article.get()
return view
}
Why isn't the project able to find the classes?
Sometimes Android Studio has not found directories, if are there any error about different issues.
You can try to delete "RealmDB" package and clean All project then retry to insert emtyp RealmDB package. If are there any error, fix in order.

Categories

Resources