Alternative to findviewbyid to find button - android

I have 26 buttons which all have an id like button_0 button_1 etc.
I've saw that findviewbyid is deprecated and I don't want to have to call all those 26 button line by line.
So my first idea was to make an array of them, but there is still the same problem I need to write them all at least one time.
I didn't find a way to achieve that with view binding or by calling the button with a string and need help.
Thanks!

Wrap your buttons inside ViewGroup (constraintLayout, LinearLayout, etc), give it an Id
call findViewById to the ViewGroup , not the button
get ViewGroup children by using children method
// just an example
val buttonContainer = findviewbyid<LinearLayout>()
val buttons = buttonContainer.children //return Sequence<View>
buttons.forEach { btn ->
if(btn is Button){
// do something
}
}

In the app level build.gradle file, just add viewBinding.
buildFeatures {
viewBinding true
}
In your activity create a variable for the binding of your activity layout. The binding will be camel notation in reverse.
lateinit var bindingActivity: ActivityMainBinding
Then add this to the onCreate() method.
bindingActivity = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindingActivity.root)
After you have that in place, you can access your views by just writing binding.viewId and it’s the equivalent of you using find view by id. If you want to access more than one view at the same time using Kotlin just use apply.
// access single view
val text = binding.textTitle.text
// access multiple views without calling binding each time
binding.apply {
textTitle.text = “hello”
val data = inputText.text.toString()
}

Related

How to get hiltViewModel from Activity?

I am trying to get a view model in two places, one in the MainActivity using:
val viewModel:MyViewModel by viewModels()
The Other place is inside a compose function using:
val viewModel:MyViewModel = hiltViewModel()
When I debug, it seems that those are two different objects. Is there anyway where I can get the same object in two places ?
Even though you solved your issue without needing the view model, the question remained unanswered so I am posting this in case someone else finds it helpful.
This answer explains how view model scopes are shared and how you can override it.
In case you are using Navigation component, this should help. However, if you don't want to pass down view models or override the provided ViewModelStoreOwners, you can access the parent activity's view model in any child composable like below.
val composeView = LocalView.current
val activityViewModel = composeView.findViewTreeViewModelStoreOwner()?.let {
hiltViewModel<MyViewModel>(it)
}

Viewbinding within Android and Exoplayer

I'm using Android Exoplayer in one of my Fragment.
Within Exoplayer I use a custom control layout"#layout/custom_player" for the controls.
I have different elements within the layout for example I have a button element"optionBtn" which I want to connect to onclicklistener from my Kotlin code. Unfortunately that doesn't go very smoothly with view binding.
This is the XML Exoplayer
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/playerVIew"
app:resize_mode="fill"
android:animateLayoutChanges="true"
app:controller_layout_id="#layout/custom_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
This is the kotlin code
...
private var binding: FragmentVideoBinding? = null
private var btnsheetOptions: SheetOptionsBinding? = null
private var sheetDialog: BottomSheetDialog? = null
private var customPlayer: CustomPlayerBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
btnsheetOptions = SheetOptionsBinding.inflate(inflater, null, false)
sheetDialog = BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme)
binding = FragmentVideoBinding.inflate(inflater, container, false)
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val simpleExoPlayer = SimpleExoPlayer.Builder(requireContext()).build()
binding!!.playerVIew.player = simpleExoPlayer
val mediaItem = MediaItem.fromUri(video.toString())
simpleExoPlayer.addMediaItem(mediaItem)
simpleExoPlayer.prepare()
simpleExoPlayer.playWhenReady = true
customPlayer!!.optionBtn.setOnClickListener {
...
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
btnsheetOptions = null
sheetDialog= null
customPlayer = null
}
}
...
This way the layout is double-inflated on top of each other and one layout works with onclick listener and the other does not, which is not very useful.
Does anyone know the correct solution for this, I've been working on this for almost all afternoon.
You cannot use view binding with ExoPlayer's custom HUD layout. View Binding only works with layouts specifically inflated for the activity/fragment layouts. The custom HUD layout does NOT belong to the parent layout which the player is set in. It is inflated in a stand-alone fashion and not included in the layout (Hence the double-inflation). Since the custom layout is inflated and is not part of the original layout, you can't use view binding with all the ids contained in it.
So what can you do if View Binding does not work with the custom layout's buttons ?
You should use findViewById which is a function that belongs to Activity class.
It's very easy to use and I assume you already know how as well:
findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
//The next line is for usage inside a fragment class
activity?.findViewById<ImageButton>(R.id.optionBtn).setOnClickListener {...}
Make sure you give the button an ID in your layout, for example:
android:id="#id/optionBtn"
What if you COULDN'T find the (R.id.optionBtn) ?
This is a common problem, there are two R directories to be aware of.
There is android.R which is usually used as R only. And there is the app's R directory. In order to distinguish between the two and avoid Unresolved reference issue, you should import your app's resources under a different name, this is done in the import section before your class code begins, add this:
import com.example.app.R as appR
Then you can try using appR.id.optionBtn instead. There is a very low chance you'd face this particular R.idissue but follow the solution above in case it happens.
Bottom Line:
1- Viewbinding only works for the activity/fragment layout connected to their context classes, it binds the parent layout's id and all their child views with actual binding variables.
2- If you wanna reach a layout that is not part of the activity/fragment layout directly, you should use findViewById instead.
3- If you have problems using the 'R.id', you should import your app's resources under a different name. I usually use 'X' instead of 'R'. But it's all a personal preference..
One shouldn't inflate the data-binding, while also applying attribute app:controller_layout_id:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
One can only have that either way.
Somehow the question is pointless, unless providing custom_player.xml ...because it may be lacking some mandatory resource IDs, which would be expected to be present (there are certain limitations to what "custom" may permit, which may include: having to provide certain resource ID, even if hiding these from the user). XML markup is quite important on Android - as all the code runs against it. ExoPlayer supports overriding layout files, unless giving them a different name.
Please refer to the original layout resources, in particular their file names and resId:
https://github.com/google/ExoPlayer/tree/release-v2/library/ui/src/main/res/layout
I'd assume, that when overriding by file name, it should also be possible to data-bind.
Because, when only the include has data-binding, then the parent still cannot bind it.
The parent layout XML would need to generate a data-binding, to begin with.
With .setControllerLayoutId(), one can actually data-bind the View before assigning it:
customPlayer = CustomPlayerBinding.inflate(inflater, binding!!.root, true)
binding.playerView.setControllerLayoutId(customPlayer!!.root)
In this case app:controller_layout_id must not be set.

Kotlin: How to rid of function overload boilerplate?

I am working with overload functions in Kotlin.
In this schematic example, suppose a function whose only difference is the type of view that I pass to the function. One uses TextView, the other uses Button, so I have 2 different functions.
fun workWithViews(v:TextView,...){
// code
}
fun workWithViews(v:Button,...){
// same code!
}
In this case, the properties I use are the same (isAllCaps, gravity, etc.). The problem is that I have to place the same code twice, i.e., the whole code is exactly the same.
It happens because isAllCaps (just like many other properties) it is not a general property of a view, but of some types of views
So it doesn't work, because obviously the compiler see the function parameter, not the real parameter.
I also can make function with a view type parameter, with a big when with my type possibilities:
fun workWithTextView(v:View,...){
when{
(v is TextView) -> {
// code
}
(v is Button) {
// same code
}
} // when
}
The 2 solutions are terrible and generate duplicate code or boilerplate.
I can also do the when before each access to some field, which makes things even worse. Now imagine if one has 5 similar types instead of 2, with many fields in common.
I read some suggestions to create union types in Kotlin. It would be great!
For instance:
fun workWithViews(v:(TextView, Button),...){
// just one code repetition....
}
or
union textBut = TextView , Button
fun workWithViews(v:textBut ,...){
// just one code repetition....
}
In that case I would only have to test a certain type (if (v is typeX)) if I used something specific for that type.
Is there some best solution?
Button is a subclass of TextView, so you can make the function signature take a TextView and put Button-specific stuff in an if-block.
fun workWithTextView(textView: TextView) {
// Do stuff common to TextViews and Buttons.
if (textView is Button) {
// Do extra stuff only for Buttons.
}
}
If Views have common methods, then most likely one view extends the other.
For example, Button extends TextView and you can do this:
fun workWithViews(v:TextView){
// TextView code
}
fun workWithViews(v:Button){
// Button specific code
workWithViews(v as TextView)
}

How to pass UI data to ViewModel function when using data binding for onClickListeners?

I have extensively searched for this issue but have not yet found a reasonable solution.
What I'm trying to do is simply set up an onClickListener using the data binding format in the layout:
android:onClick="#{() -> subjectsViewModel.onClickAdd()}"
where subjectsViewModel is a layout variable to which I pass the ViewModel in the fragment.
What I want is to pass the currently entered edit text data to onClickAdd() function.
The only solution I have found yet is to use two way data binding but I do not think it should be required for setting up such a basic fuctionality.
you can pass the view reference and get the text from TextView after casting it back to TextView from View. something like this.
android:onClick="#{(view) -> subjectsViewModel.onClickAdd(view)}"
and in your view model just create something like this.
fun onClickAdd(view: View){
val myText = (view as TextView).text ?: ""
//here use text
}
First give an id to your EditText. Lets say the id is edittext.
Then do it like this.
android:onClick="#{() -> subjectsViewModel.onClickAdd(edittext.getText().toString())}"

Kotlin Android - Is there a way to define the Views only one time in a class?

In my code I make use of the following Views in XML:
val googleButton: Button = findViewById<View>(R.id.google_login) as Button
val loginWithEmailText: TextView = findViewById(R.id.login_with_email_text)
val emailLoginButton: Button = findViewById(R.id.email_login_button)
val createAccountButton: Button = findViewById(R.id.email_create_account_button)
This code is extracted from a function inside my Kotlin class. Whenever I have to access these views, I need to write this code all over again.
Is there any way that I can access them from only one place in my class code? I tried putting them outside but the app won't start.
Thank you
You need to define these fields as a part of your class and initialize them once you set the layout resource for your Activity/Fragment. If you put these lines 1:1 in the class body, the initialization will fail, since the layout has not been inflated yet.
Please get familiar with the concept of lifecycle, so that you can understand how to approach View related topics: https://developer.android.com/guide/components/activities/activity-lifecycle
Please check out this snippet for a sample code:
class MyActivity: Activity() {
lateinit var textView: TextView
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// initialize your views here
textView = findViewById(R.id.text_view_id)
button = findViewById(R.id.button_id)
}
fun someOtherFunction(){
// you can reference your views here like normal properties
button.setOnClickListener { v -> callAnotherFunction() }
// ...
}
}
Since you are on Android, you might be interested in using Kotlin synthetic properties for referencing views without the whole boilerplate of finding them: https://antonioleiva.com/kotlin-android-extensions/. It's no longer a recommended practice to make use of it, but it's handy in some cases anyway.

Categories

Resources