What are my options with interfaces to ensure some certain code to be executed when a class implemen...
m
What are my options with interfaces to ensure some certain code to be executed when a class implements given interface? I’d like to avoid some abstract base class as this interface is intended to be implemented by viewmodels which already can inherit from two possible base classes. (sometimes I really miss multi-inheritance 🙂 )
j
When you said
code be executed when a class implements a given interface
- when exactly did you mean? Like at instantiation time? If that's the case, I don't know of any way (apart from using an abstract class instead). Could you please expand on your use case for needing this?
m
OK - let’s imagine an interface like this:
Copy code
interface IBarcodeProcessor {
    val service: ScanService
}
Then there is this
ScanService
with this portion of code
Copy code
class ScanService {
    val scanObservers = mutableListOf<(Scan) -> Unit>()

    var scan: Scan by Delegates.observable(
        Scan(
            "",
            "",
            ""
        )
    ) { _, _, new -> scanObservers.forEach { it(new) } }
}
I’d like to add the class which implements
IBarcodeProcessor
to add a method to the
ScanService.scanObservers
list
So - I fear, that there is (as you said) no way for this… But as I’m new to kotlin I thought, that maybe there is some secret feature unknown to me as a rookie
Point is: each implementing viewmodel class might behave different when processing a barcode (e.g. only allowing a certain list of barcode types)
So I cannot have a single processing class as it is the responsibility of the viewmodel class to provide the concrete processing logic
OK - I might have found something with composition and delegate. What do you think?
Copy code
interface IBarcodeProcessor {
    val service: ScanService
    val allowedTypes: List<BarcodeType>
    fun process(scan: Scan)
}

class BarcodeProcessor(
    override val service: ScanService,
    override val allowedTypes: List<BarcodeType>
) : IBarcodeProcessor {
    init {
        service.scanObservers.add {
            process(it)
        }
    }
    override fun process(scan: Scan) {}
}

abstract class SomeBaseClass {}

@HiltViewModel
class MyViewModel
@Inject
constructor(
    service: ScanService,
    barcodeProcessor: IBarcodeProcessor = BarcodeProcessor(connector, listOf(BarcodeType.CONTAINER))
) : SomeBaseClass(), IBarcodeProcessor by barcodeProcessor {
    override fun process(scan: Scan) {
        TODO("Not yet implemented")
    }
}
j
I don't quite see why you need to go through so much trouble. I mean adding the
IBarcodeProcessor
interface seems as much hassle as just registering a hook. Like in which way would your suggested setup be better than, say, simply this:
Copy code
@HiltViewModel
class MyViewModel
@Inject
constructor(
    service: ScanService,
) : SomeBaseClass() {
    init {
        service.registerObserver(BarcodeType.CONTAINER, BarcodeType.SOMETHING_ELSE) {
            someHandlerFunNamedHoweverIWant(it)
        }
    }

    private fun someHandlerFunNamedHoweverIWant(scan: Scan) {
        TODO("Not yet implemented")
    }
}
People can forget things, but they can forget implementing
IBarcodeProcessor
just as much as forgetting to register the hook. Not sure why favor one over the other. IMO if you want your viewmodel to do something when a barcode event is emitted, it doesn't seem too farfetched to use the snippet above. Also, in this code the viewmodel authors control when they want to register