Kotlin Android Extensions and Menu - kotlin

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.

Related

Access fragment view in parent activity

I have an activity which displays multiple fragments depending on which one is selected.
I also have a button in this activity and I want to obtain a value from a specific fragment when this button is clicked.
How can I obtain this value?
I tried to get the view I wanted from the fragment from the activity as the code shows below but I can understand that it doesn't work since the fragment is still to be created.
onOffButton.setOnClickListener {
if (onOffButton.text.contains("ON")) {
onOffButton.text = "TURN OFF"
var hoursPicker = findViewById<NumberPicker>(R.id.hoursPicker)
}
}
The short version is you shouldn't do this, there are all kinds of complications (especially when you're trying to access the Fragment's Views).
It's even more complicated if the Fragment might not even be added to the UI at all! If it's not there, what value are you supposed to use? If you want to somehow create the Fragment just so it exists, and so you can read the value from its text box, then that's a sign the value really needs to be stored somewhere else, so you don't need the Fragment if you want to access it.
The easiest, recommended, and modern way to share data like this is with a ViewModel:
class MyViewModel : ViewModel() {
// setting a default value here!
var currentHour: Int = 0
}
class MyActivity : AppCompatActivity() {
val model: MyViewModel by viewModels()
fun onCreate(...) {
...
onOffButton.setOnClickListener {
// access the data in the ViewModel
val currentHour = model.currentHour
}
}
}
class MyFragment : Fragment() {
// using activityViewModels so we get the parent Activity's copy of the VM,
// so we're all sharing the same object and seeing the same data
val model: MyViewModel by activityViewModels()
fun onViewCreated(...) {
...
hoursPicker.setOnValueChangeListener { _, _, newValue ->
// update the VM
model.currentHour = newValue
}
}
}
So basically, you have this ViewModel object owned by the Activity and visible to its Fragments. The VM outlives all of those components, so you don't lose data while an Activity is being destroyed on rotation, or when a Fragment isn't added to the UI, etc.
The VM is the source of data, everything else just reads from it, or updates it when something changes (like when the Fragment updates the variable when its number picker's value changes). This way, the Activity doesn't need to go "ask" the Fragment for info - it's stored in a central location, in the VM
This is the most basic way to use a ViewModel - you can start using LiveData and Flow objects to make different UI components observe data and react to changes too. For example, your button in your Activity could change some enabled state in the VM, and the Fragment (if it's added) will see that change and can do things like make the number picker visible or invisible.
It's way easier to coordinate this stuff with a ViewModel, so if you don't already know how to use them, I'd recommend learning it!

How to find where a Kotlin interface method is overridden in intellij?

Given this Foo interface
interface Foo {
fun whereIsThisImplemented(a: String): String
}
// How do I use the above interface to find the below implementation
class Bar: Foo {
override fun whereIsThisImplemented(a: String): String {
return a
}
}
is there a simple way of finding where the whereIsThisImplemented method is overridden through intellij?
While whereIsThisImplemented is highlighted, Using edit -> Find Usages -> Find Usage Settings, none of the options will find the Override fun whereIsThisImplemented.
I could find all places the interface is implemented, then look for the method, but this is extremely cumbersome and I was hoping there was a simpler way that I was missing.
Menu: Navigate -> Implementations . Note down the shortcut besides the menu.
Alternatively, you can click on the down arrow on the left of the method declaration
Tip: Install key promoter plugin so that you get a notification of keyboard shortcut everytime you use the mouse.
Hover over the green icon next to the interface function to see a list of places where it is implemented. If you click the icon, it jumps you to that spot in the code.

Kotlin, RecyclerView, ViewBinding

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.

Why are variables declared after root automatically added to UI in TornadoFx?

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.

Kotlin DSL scope control on external non-changeable classes (similar to #DslMarker)

I have lots of external classes (generated externally; not under my control), which do not come with a builder and which are rather cumbersome to create. However using apply it is rather easy to build them, e.g.:
SomeOfTheObjects().apply {
someProperty = SomeOtherComplexObject().apply {
someOtherProperty = "..."
}
}
Now I like the way it works with the receiver, but I would like to prevent that I can set someProperty within SomeOtherComplexObject. If the classes were under my control, it would suffice to put a #DslMarker on that class, but as they aren't, the only other way that came to my mind, was to use also instead without renaming the parameter, e.g.:
SomeOfTheObjects().also {
it.someProperty = SomeOtherComplexObject().also {
it.someOtherProperty = "..."
//it.someProperty will not work if SomeOtherComplexObject has no such property
}
}
While it works, it now has tons of it. in the code and I was wondering, whether it is possible to have some similar behaviour as with the #DslMarker in place.
What I tried is a mixture of the following:
#DslMarker
annotation class DemoMarker
#DemoMarker
inline fun <T> T.build(#DemoMarker builder : T.() -> Unit) = this.apply(builder)
"mixture", because I ended up putting the annotation everywhere, but this doesn't have any effect. If I put it on a class it works as expected. Did I miss something and it is actually possible somehow? Or does anyone have an appropriate workaround for this, besides using also?
For third party classes you can use the DslMarker annotation on receiver types as explained here.
#DslMarker
#Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class TestDsl
fun build1(builder: (#TestDsl DslReceiver1).() -> Unit) {}