Why there are Nullable view and NonNull view in same xm file - android

I use viewbinding in Activity
private val binding: ActivityThirdPartyRedirectBinding by lazy { ActivityThirdPartyRedirectBinding.inflate(LayoutInflater.from(this)) }
but somehow there are Nullable view and NonNull view
confirmView and llLoading are both ConstraintLayout but return different view?
I have tried invalid cache and clean and rebuild but the problem is still there.
How to solve it?

Related

Kotlin implement abstract properties correctly

For example java code
public abstract class BindingElement<T extends ViewDataBinding> {
T binding;
abstract public T createBinding(LayoutInflater inflater, ViewGroup parent);
public BindingElement(ViewGroup parent) {
binding = createBinding(LayoutInflater.from(parent.getContext()), parent);
binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
}
}
I need some necessary property that defined in constructor. And then i will do something with that property. What is the best way write it in kotlin?
This doesn’t directly answer your question but provides a safer alternative.
You should avoid calling an open or abstract function from the constructor in Java or Kotlin, even though it’s allowed. It is fragile and can cause weird bugs that are difficult to resolve. Read here: In Java, is there a legitimate reason to call a non-final method from a class constructor?
An alternative in this case would be to make your function into a constructor parameter. Your class doesn’t even need to be open or abstract to support this.
class ViewBindingParameter<T: ViewBindingData> (
parent: ViewGroup,
inflateBinding: (LayoutInflater, ViewGroup)->T
) {
val binding: T = inflateBinding(LayoutInflater.from(parent.context), parent)
}
Usage:
val bindingParam = ViewBindingParameter(parent, SomeBinding::inflate)
If you aren't planning to add features to this class, you might as well just use a function that directly returns a binding so you don't have to deal with the wrapper class. Maybe an extension function of the parent view:
fun <T: ViewBindingData> ViewGroup.inflateChildBinding(inflateBinding: (LayoutInflater, ViewGroup)->T): T =
inflateBinding(LayoutInflater.from(context), this)
and use it like:
val binding = parent.inflateChildBinding(SomeBinding::inflate)
Kotlin is no different from Java in case of abstractions, so I assume something like below will work
abstract class BindingElement<T: ViewDataBinding> {
val binding: T
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
init {
binding = createBinding(...)
}
}
UPD: I noticed that your method requires field provided in constructor, so instead of init block you will use
constructor(parent: ViewGroup) {
binding = createBinding(...)
}
It may look more like Kotlin code
abstract class BindingElement<T: ViewDataBinding>(
val parent: ViewGroup
) {
val binding = createBinding(..., parent)
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
}
And this code is calling the non-final function in the constructor which is an unsafe operation.

Where is layoutInflater defined in this android app tutorial code?

This Android tutorial introduces the concept of view binding, with this section demonstrating how to use it. In this case, the view binding is set up using the following code.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
The explanation for the call to ActivityMainBinding.inflate() is as follows:
This line initializes the binding object which you'll use to access
Views in the activity_main.xml layout.
What this does not explain is where the variable layoutInflater is defined.
When using Android Studio, the code completion suggests that the variable "comes from getLayoutInflater()":
getLayoutInflater() seems to be a method in Activity, but this doesn't help me understand what the reference to layoutInflater is doing, where it is defined, and how it is in scope at this point of the code. Can someone help me to understand this please?
ActivityMainBinding.java is the generated class by data binding which has a static method inflate(). When you pass the layoutInflater(it retrieve a standard LayoutInflater instance that is already hooked up to the current context) to inflate() it generates the same code under the code as we usually do while inflating the views and it fetches the layout name automatically.
So, the whole method is like
public static ActivityMainBinding inflate(#NonNull LayoutInflater inflater,
#Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
I hope this is what you are looking and sure can help you. Thanks
I came at the same question and found layoutInflater was declared in NavigationMenuPresenter.java.
package com.google.android.material.internal;
...
public class NavigationMenuPresenter implements MenuPresenter {
...
LayoutInflater layoutInflater;
...
public void initForMenu(#NonNull Context context, #NonNull MenuBuilder menu) {
layoutInflater = LayoutInflater.from(context);
...
In my case, full path to the java script was
%HOMEDRIVE%%HOMEPATH%\.gradle\caches\modules-2\files-2.1\com.google.android.material\material\1.7.0\289bbb3a7fea52532f1163487f9469217ee608a9\material-1.7.0-sources.jar!\com\google\android\material\internal\NavigationMenuPresenter.java
After a little digging I found out that the getLayoutInflater comes from the Activity class. Activity class is parent of androidx.core.app.ComponentActivity that is parent of ComponentActivity that is parent of FragmentActivity that is parent of AppCompatActivity that is parent of your Activity class.

recyclerview in fragment context not working in kotlin

Im creating a really basic recyclerview in fragment_home.xml which is linked to a <FrameLayout /> inserted in activity_main.xml
It works well...until I create the HomeFragment class in HomeFragment.kt, which inflates fragment_home.xml
Then I want to add up the arrayList (arrayHomeMenu) in the function onActivityCreated()
my problem is with this line
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this) //context "this" appears with red underline
It retrieves error, so I cant continue...
HomeFragment.kt
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View?{
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?){
super.onActivityCreated(savedInstanceState)
//create list of words
val arrayHomeMenu = ArrayList<HomeMenuModel>()
arrayHomeMenu.add(HomeMenuModel("Verbs","List of 461 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Nouns","List of 52 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Adjectives","List of 65 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Adverbs","List of 345 words", R.drawable.ic_logo))
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this) //context "this" appears with red underline
homeMenu_recyclerView.layoutManager = LinearLayoutManager(context)
homeMenu_recyclerView.adapter = homeMenuAdapter
}
This is a context problem....
I tried to replace this by activity, context, Context, applicationContext with not success at all...
HomeMenuAdapter.kt
class HomeMenuAdapter(val arrayList: ArrayList<HomeMenuModel>, val context: Context) :
RecyclerView.Adapter<HomeMenuAdapter.ViewHolder>() {
....//correct content
}
It works correctly when I create the recyclerview direct in activity_main.xml and work directly with MainActivity.kt
Once I move the recyclerview to a fragment...it throws the error described above.
HomeMenuModel.kt
class HomeMenuModel(val hm_title:String,val hm_description: String, val hm_image:Int)
I was checking this answer, but no success...
what am doing wrong?? thank you
The reason why you can use this as context inside an Activity, is because Activity extends Context.
Fragment however does not extend Context so you cannot use this.
The reason why activity, context also don't work is Kotlin's distinction of nullable types. While activity and context do return a Context, the return value is nullable. You can see this by paying close attention to the error message that appears when hovering over the red underline:
Type mismatch.
Required:
Context
Found:
Context? (or FragmentActivity?)
The question mark indicates that this is a nullable type, while a non-nullable Context is required. The reason why they're nullable is that the Fragment can only retrieve the Activity when it is attached to it, which is not always the case.
However, Fragment has a convenient method called requireContext() to work around this issue. It has a non-nullable return type but will instead throw an exception if it cannot retrieve the context, so it is on you to make sure to only call it when the Fragment is attached.
In short, you should be able to instantiate your adapter like this:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, requireContext())
Since you are in a Fragment this can't be passed as a context:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this)
In the above code you are trying to pass fragment as a context, so instead what you can do is get the context of the fragment. So you can do:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, requireContext())

Android, Koin: How to prevent interface bound by viewModel from creating new viewModel instance?

It's hard to understand what the problem is from the headline - I'll try my best explaining:
I'm using Koin for dependency injection. I'm injecting my HomeViewModelinto my HomeFragment (the viewModel has parameters, but that should be unrelated to the problem):
// fragment code
private var viewModelParameters: ParametersDefinition? = null
lateinit var viewModel: VM
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, layout, container, false)
binding?.lifecycleOwner = viewLifecycleOwner
viewModel = getViewModel(HomeViewModel::class, parameters = viewModelParameters)
return binding?.root ?: inflater.inflate(layout, container, false)
}
The fragment contains a RecyclerView. The recycler's ViewHolder declares an interface, that is injected via Koins by inject()`:
class MyRecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), KoinComponent {
private val callback by inject<Callback>()
fun bind(item: MyItemType) {
itemView.setOnClickListener { callback.myCallbackFunction(item) }
}
interface Callback {
fun myCallbackFunction(item: MyItemType)
}
}
My HomeviewModel implements this interface, and I bind it to the viewModel in my KoinGraph module via Koin's bind DSL method:
private val baseModule = module {
single { androidApplication().resources }
single { PermissionHelper(androidApplication()) }
...
viewModel { HomeViewModel() } bind MyRecyclerviewHolder.Callback::class
}
Now, when I click on my recycler item, the callback's myCallBackFunction is called, which should trigger the implementation in my HomeViewModel. Which it does, but: It is not the same instance, but a new HomeViewmodel.
My understanding is that Android's ViewModelclass, if used in the typical way (currently using, without Koin, by viewModels() - see here), should only exist once. But with Koin's viewModel{} call, I can create multiple instances, which I think I shouldn't be able to? Or should I (and if yes, why)?
Anyway, I'd like to bind my callback to the view model I already have (the one the fragment knows of) and not a new instance my fragment doesn't know about.
How can I achieve that using Koin and its injection patterns?
By the way, If I use
single { HomeViewModel() } bind MyRecyclerviewHolder.Callback::class
instead of
viewModel { HomeViewModel() } bind MyRecyclerviewHolder.Callback::class
my code works as intended - since I'm forcing my view model to be a singleton that way - which is what I want. But what is the point of the viewModel{} command then? And are there any downsides to it? It doesn't feel like what I should be supposed to do but maybe it's totally fine?

kotlin -access view from non activity class

I've a class that needs to be include in lots of my activities , so I've made a class that needs to access the view .
this is my code :
class Toolbar{
private lateinit var typeface:Typeface;
private lateinit var context:Context
private lateinit var tvToolbar:TextView;
fun MakeToolbar(context:Context, title:String){
this.context=context
init();
}
private fun init() {
typeface= Func.getTypeFace(context)!!
tvToolbar=((Activity)context).findViewById
}
the problem is , in java I can easily access the context and use findViewById to get my view , Buy I can't do it in kotlin and it couldn't find the view .
How can I access a view in a non activity class?
Is tvToolbar=((Activity)context).findViewById really your code? It won't event compile in Kotlin, or Java for that matter.
The syntactically correct way would be this:
tvToolbar = (context as Activity).findViewById(R.id.tvToolbar)
Though I recommend against passing your entire activity object to another class. Why don't you just pass in a reference to your view? And be sure to read about WeakReferences.

Categories

Resources