Home > Net >  implementing an interface with callback on a fragment
implementing an interface with callback on a fragment

Time:02-18

I'm learning android programming and for practicing I'm trying to do a controller for some dc motors, then I did a customview for making a virtual joypad, which uses an interface and a callback for the ontouch listener.

The problem is, I'm working on my app using a single MainActivity as a navhost and then I'm navigating through different fragments, My customview just works when I override the interface method on my MainActivity but I can't make it works on my fragment, where I want to handle all the logic of the joypad.

I've a couple of days researching but most of the post that I've found are written on Java and I just can't make it work on Kotlin.

My custom view class


class KanJoypadView: SurfaceView, SurfaceHolder.Callback, View.OnTouchListener{  

    ...
    private var joypadCallback: joypadListener? = null

    //the main constructor for the class
    constructor(context: Context): super(context){
        ...

        getHolder().addCallback(this)
        setOnTouchListener(this)

        try { 
            //trying to initialize the callback
            if (context is joypadListener){
                joypadCallback = context
                Log.d("TAG", "Callback has been initialized")
            } else {
                Log.d("TAG", "Callback is still null")
            }
        } catch (e: IllegalStateException){
            Log.e("Tag", "MainActivity must implement YourInterface")
        }

        //the interface for the main functionally of the view
        interface joypadListener {
            fun onJoypadMove(x: Float, y: Float, src: Int){}
        }

        ...

}

My MainActivity


class NavActivity : AppCompatActivity(), KanJoypadView.joypadListener {
    ...
    
    //Overriding the Function from the interface
    override fun onJoypadMove(x: Float, y: Float, src: Int) {
        Log.d(src.toString(), y.toString()) //** I wanna do this in my Fragment, not in my activity **
    }

}

My Fragment


class JoystickFragment : Fragment(), KanJoypadView.joypadListener {

    ...

    var enginesArray = arrayOf(0.toFloat(), 0.toFloat(), 0.toFloat(), 0.toFloat())

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
    
    ...
    
        val binding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_joystick, container, false
        )
        rJoypad = binding.rightJoypad.id
        lJoypad = binding.leftJoypad.id
    }

    /*what I really want to do, but it is not happening as it is just happenning the
 override from the NavActivity, which I dont need, and not from here which I need*/
    override fun onJoypadMove(x: Float, y: Float, src: Int) {
        if (src == lJoypad) {
            if (y >= 0) {
                enginesArray[0] = 1.toFloat()
                enginesArray[1] = y
            } else if (y < 0) {
                enginesArray[0] = 0.toFloat()
                enginesArray[1] = y
            }
            if (src == rJoypad) {
                if (y >= 0) {
                    enginesArray[0] = 1.toFloat()
                    enginesArray[1] = y
                } else if (yAxis < 0) {
                    enginesArray[0] = 0.toFloat()
                    enginesArray[1] = y
                }
                Log.d("Engines array", enginesArray.toString())
            }
        }
    }

}

Also I've tried to make a function in the fragment, and then call that function from the onMoveJoypad method from the Activity, but also I couldn't made it work. I'll appreciate any help or advice on how to implement this, thanks in advance !

CodePudding user response:

Where is your KanJoypadView initialized and which context is used for the constructor ?

If you want to use its callback, you should use the context of JoystickFragment.

CodePudding user response:

This:

if (context is joypadListener){

is a very hacky and error prone way to get a listener reference. It’s also very limiting because it makes it impossible for the listener to be anything but the Activity that created the view. Don’t do this!

You already have a joypad listener property. Just remove the private keyword so any class can set any listener it wants from the outside. Remove that whole try/catch block. When it’s time to call the listener’s function, use a null-safe ?. call to do it so it gracefully does nothing if a callback hasn’t been set yet.

Side note: all class and interface names should start with a capital letter by convention. Your code becomes very hard to read and interpret if you fail to follow this convention.

Also, I advise you to avoid using the pattern of making a class implement an interface to serve as a callback for one of the objects it is manipulating or for its own internal functions. You’re doing this twice in your code above. Your custom view does it for its own touch listener, and your Activity does it to serve as the joypad listener.

The reason is that it publicly exposes the class’s inner functionality and reduces modularity. It can make unit testing more difficult. It needlessly exposes ways to misuse the class you’ve designed. It’s not as big deal for Activities to do this because you rarely if ever work with Activity instances from the outside anyway. But it’s ugly for a view class to do it.

The alternative is to implement the interface as an anonymous object or lambda so the functionality of the callback is hidden from outside classes.

  • Related