https://kotlinlang.org logo
Title
m

Mario Adam

05/08/2023, 1:44 PM
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

Joffrey

05/08/2023, 1:45 PM
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

Mario Adam

05/08/2023, 1:51 PM
OK - let’s imagine an interface like this:
interface IBarcodeProcessor {
    val service: ScanService
}
Then there is this
ScanService
with this portion of 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?
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

Joffrey

05/08/2023, 2:53 PM
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:
@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