4

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.

Riccoh
  • 359
  • 1
  • 4
  • 19

2 Answers2

5

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..

yuroyami
  • 814
  • 6
  • 24
  • 1
    Thank you for your answer not only did you give text and explanation but also the solution. It's pretty clear now. Again, thank you very much. – Riccoh Nov 01 '21 at 19:11
  • getting null pointer exception whenever I am trying to use the findViewbyid in fragment. Any solution to this ? – Yesha Shah Jul 20 '23 at 18:38
2

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.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • in what way can I bind those element within my kotlin code? Normally when I use "binding!!.playerVIew. optionBtn.setOnClickListener..."(saveBtn) everything works fine with synthetic. As soon as I remove synthetic there is no way to bind that element again via bindings. I tried several ways within Bindings but no luck so far. I know my way with binding within "include" but with Exoplayer it seems not working. – Riccoh Oct 26 '21 at 19:42
  • 1
    setControllerLayoutId method not found – Jithish P N Aug 21 '22 at 13:48