I'm new to the Kotlin programming language. I have some questions regarding RecyclerView and ViewBinding. My English might not be good. I am sorry for that. But I will do my best to explain. I would love it if you could answer my questions. Please read the questions, looking the images
1.) We give the view to the constructor of the LandmarkHolder class. But when we send this view to the constructor of the RecyclerView.ViewHolder class, we make binding.root.
a) As far as I know, I need to write the object of RecyclerViewRowBinding (which I wrote as binding) in the constructor of Recycler.ViewHolder "in the same way" and send it there. But why do I have to write binding.root instead of "binding"? Why can't I just type "binding"? Because binding already has the design itself.
2.) While making a Layout Inflater, we normally access the XML file with the old method (R.layout. ....) and inflate it, that is, convert it to java code. The structure here has changed. Of course, it is converted to java code again, but there is a confused situation that I could not solve:
A class of the recycler_row.xml file is created called RecyclerRowBinding.
This class has an inflate method. I read from its website. In addition, this class directly references the ids of the views in the layout that are related to it. Now the thing that's stuck in my head is this: What am I inflating here? Because in the old usage (with finviewbyid), when we wrote the inflate method, we were adding a source xml file inside the iflate method.(Like CardView) But this new method does not have it. After the parent is written, attachtoParent is written as False.
3.)What we call this parent represents my RecyclerView?
4.) The holder object in the onBindViewHolder function belongs to the Landmarkholder class. So it uses properties of this class. But I see that it can access something called itemView. Here is how ItemView can relate to the Landmarkholder class. But I'm looking at the class itself, nothing related to this itemView is defined. How does this reach the itemView? Of course, the purpose of calling itemview is to call context. If the context exists in it, then the itemview also derives from another class. Does it derive from the View class? and the View class has this context I guess is it right? How can I call this itemView with "holder" object?
5.) This onCreateViewHolder returns the LandmarkHolder(binding) object. Then this function needs to be called elsewhere for it to work. (Normally it should be called, of course) Butwhere is it called from? On the emulator itself?
RecyclerViewAdapterRecycler_row.xmlMainActivity.xml
Why isn't the binding itself a view?
I didn't understand all your questions, but maybe this overview will help.
ViewBinding takes each of your XML layouts and creates a binding class for them that is comprised of properties matching each view that has an ID, plus one more property named root that holds the top-level view. It also has static functions called bind and inflate. So if you have a layout like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical">
<TextView
android:id="#+id/recyclerRowTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Test"
android:textColor="#0820aa"
android:textSize="20sp" />
</LinearLayout>
Then it creates a class that's the equivalent of this (in Java but I'll show a Kotlin version since that's what you're learning):
class RecyclerRowBinding private constructor(
val root: LinearLayout,
val recyclerRowTextView: TextView
) {
companion object {
fun bind(view: View): RecyclerRowBinding {
val root = view as LinearLayout
val recyclerRowTextView = root.findViewById<TextView>(R.id.recyclerRowTextView)
return RecyclerRowBinding(root, recyclerRowTextView)
}
fun inflate(layoutInflater: LayoutInflater): RecyclerRowBinding {
return inflate(layoutInflater, null, false)
}
fun inflate(layoutInflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean): RecyclerRowBinding {
val root = layoutInflater.inflate(R.layout.recycler_row, parent, attachToParent)
return bind(root)
}
}
}
I didn't exactly understand your questions, but a couple things to notice:
It's still inflating your views from XML with a LayoutInflater like you would if you were doing it without view binding.
The binding itself is not a view class. It only holds references to views.
The RecyclerView.ViewHolder abstract class's constructor requires an itemView object, which is the root view of the item layout, so you must pass binding.root to this constructor. The binding itself is not the view. RecyclerView.ViewHolder also has a property for this itemView, so when you create your own view holder that has a binding, there are two different ways to access the root view, either by using holder.binding.root or holder.itemView. However, binding.root will be of type LinearLayout, and itemView will be the less specific ViewGroup.
Adapter.onCreateViewHolder() is called by RecyclerView when it needs another view to display and doesn't have any previous views that it can recycle.
Related
Im trying to get some data out of other ViewModels inside another ViewModel to make my code smaller, but im having a problem trying to implement what already worked on a fragment or in a activity, this is what i got:
class ObraConMediaViewModel(private val context: ViewModelStoreOwner,
private val id: Int): ViewModel(), LifecycleObserver {
var allObras: LiveData<ArrayList<ObraConMedia>>
private lateinit var viewModelobras: ViewModelObras
private lateinit var viewModelMediaObra: ViewModelMediaObra
val repositoryobras =ObrasRepository()
val repositoryMediaObra = MediaObraRepository()
val viewModelFactoryobras = ViewModelFactoryObras(repositoryobras)
val viewModelMediaObraFactory = ViewModelMedIaObraFactory(repositoryMediaObra)
init{
viewModelobras = ViewModelProvider(context, viewModelFactoryobras)
.get(ViewModelObras::class.java) // requireActivity() when called
viewModelMediaObra = ViewModelProvider(context, viewModelMediaObraFactory)
.get(ViewModelMediaObra::class.java)
viewModelobras.getObras(id)
viewModelobras.myResponse.observe(this , Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.autor)
Log.d("Response", response.body()?.ans?.get(1)?.autor)
}else{
Log.d("Response", response.errorBody().toString())
}})
viewModelMediaObra.getMediaObra(Constantes.PRUEBA_ID)
viewModelMediaObra.myResponse.observe(this, Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.filePath)
}
})
}}
I was having trouble with the Observer but extending the class to LifecycleObserver fixed it, i have no idea if this will even work but the only error that i have right now its the owner of the .observe(this,....), i dont seem to find a way to pass a lifecycleowner from the fragment to this viewmodel. All the variables i need to make this viewmodel work are inside those two responses. If this is a very bad way to do it please tell me. Thanks for reading.
Kindly note that above approach is not correct.
One should not create a instance of ViewModel inside another ViewModel.
There is a possibility that one ViewModel may get destroyed before another. This will lead to garbage reference and memory leaks.
I would recommend you to create the instance of both View Models in an Activity/Fragment and then call respective methods of ViewModel from Activity/Fragment.
Also, as you want to make your code smaller and concise, I highly recommend you Shared ViewModel.
This Shared ViewModel can be used by two fragments.
Please refer to this link
I have stumbled upon a behaviour in TornadoFx that doesn't seem to be mentioned anywhere (I have searched a lot) and that I'm wondering about.
If I define a view like this with the TornadoFx builders for the labels:
class ExampleView: View() {
override
val root = vbox{ label("first label") }
val secondLabel = label("second label")
}
The result is:
That is, the mere definition of secondLabel automatically adds it to the rootof the scene.
However, if I place this definition BEFORE the definition of root...
class ExampleView: View() {
val secondLabel = Label("second label")
override
val root = vbox{ label("first label") }
}
... or if I use the JavaFx Labelclass instead of the TornadoFx builder ...
class ExampleView: View() {
override
val root = vbox{ label("first label") }
val secondLabel = Label("second label")
}
... then it works as I expect:
Of course, I can simply define all variables in the view before I define the rootelement but I'm still curious why this behaviour exists; perhaps I am missing some general design rule or setting.
The builders in TornadoFX automatically attach themselves to the current parent in the scope they are called in. Therefore, if you call a builder function on the View itself, the generated ui component is automatically added to the root of that View. That's what you're seeing.
If you really have a valid use case for creating a ui component outside of the hierarchy it should be housed in, you shouldn't call a builder function, but instead instantiate the element with it's constructor, like you did with Label(). However, the use cases for such behavior are slim to none.
Best practice is to store value properties in the view or a view model and bind the property to the ui element using the builders. You then manipulate the value property when needed, and the change will automatically update in the ui. Therefore, you very very seldom have a need to access a specific ui element at a later stage. Example:
val myProperty = SimpleStringProperty("Hello world")
override val root = hbox {
label(myProperty)
}
When you want to change the label value, you just update the property. (The property should be in an injected view model in a real world application).
If you really need to have a reference to the ui element, you should declare the ui property first, then assign to it when you actually build the ui element. Define the ui property using the singleAssign() delegate to make sure you only assign to it once.
var myLabel: Label by singleAssign()
override val root = hbox {
label("My label) {
myLabel = this
}
}
I want to stress again that this is very rarely needed, and if you feel you need it you should look to restructure your ui code to be more data driven.
Another technique to avoid storing references to ui elements is to leverage the EventBus to listen for events. There are plenty of examples of this out there.
Below is a example of a pattern from Android (Just an example, not interested in android specifics):
/*Im a kotlin file*/
class ListItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val text: = itemView.my_view
}
Then the pattern is that you access the text field as such:
/*Im a Java file*/
holder.text.setText("Metasyntactic variable");
It's unfortunate to have a large file with a set structure doing the above and then have:
/*Im a Java file, but this particular holder is a kotlin file*/
holder.getText().setText("Metasyntactic variable");
Is it possible to solve this? Maybe with some #Jvm annotation
It's #JvmField:
If you need to expose a Kotlin property as a field in Java, you need to annotate it with the #JvmField annotation. The field will have the same visibility as the underlying property. You can annotate a property with #JvmField if it has a backing field, is not private, does not have open, override or const modifiers, and is not a delegated property.
So, I have a case in which I need to have N rows in form of: Label TextView/Checkbox. Maybe I will have to have more than those two views, so I want to be able to support anything that is TornadoFx View.
I've created an interface that has one method that returns TornadoFx View and it looks like this:
interface ValueContainer {
fun getView() : View
}
One of the implementations looks like this:
class BooleanValueContainer(val checked: Boolean) : ValueContainer {
val valueProperty = SimpleBooleanProperty(checked)
override fun getView(): View {
return (object : View() {
override val root = checkbox {
bind(valueProperty)
}
})
}
}
Now, when I try to use it inside init block, it doesn't show in the layout. root is GridPane and parameters is a list of objects that have name and reference to ValueContainer implementation (BooleanValueContainer or other one which I haven't shown):
init {
with(root) {
parameters.map {
row(it.name) {
it.parameterContainer.getView()
}
}
}
}
I'm stuck here for quite a while and I've tried anything I could find but nothing really worked except putting textview or checkbox block instead of getView() call, but then I would have to have logic on what view should I show inside this class which represents a view and I don't want that.
The reason this is not working for you is that you simply call parameterContainer.getView() but you don't add the View to the row. I think what's confusing you is that for builders you can just say label() for example, and it's added to the current Node in the builder tree. In your case, you just say Label() (just create an instance of Label, not call the label builder), which would create a new Label, but not add it to the children list of the current Node. To solve your problem, do:
this += it.parameterContainer.getView()
This will add the View to the row.
Apart from this, I don't quite see the point of the ValueContainer. What does it solve to put a View inside this container object? I suspect this as well might be due to a misunderstanding and I'd like to understand why you feel that you need this construct.
Is there any way to access menu_item_search menu item defined in fragment_photo_gallery layout using synthetic properties instead of using findItem method?
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateOptionsMenu(menu, menuInflater)
menuInflater.inflate(R.menu.fragment_photo_gallery, menu)
//is there a way to access searchItem using synthetic properties?
val searchItem = menu.findItem(R.id.menu_item_search)
}
MenuInflater serves a fundamentally different purpose than LayoutInflater.
Despite both having "Inflater" part in its name and implementing methods that are named "inflate()", they do completely different things. MenuInflater inflates Menus, where LayoutInflater inflates Views.
Kotlin Android Extensions were created to simplify usage of Android Views, not Android Menus, or anything that has inflate() method.
Long story short - it is not possible to use KAE with Android Menus.