Help me with mixins

Yo, we need mixin guys. Or something like that.

I need to make a base class, but I don’t want to make it hard for delegation to the next developer. I want the BaseClass to explain whatever layers it has. Every layer should be explicit and mind its own business. Layers cannot contain extra APIs or shared methods.

I want to make it clear how Layer and Feature behave. I tried:

interface ActivityLoggerLayer
open class ActivityLoggerLayerDelegate : AppCompatActivity(), ActivityLoggerLayer {
    private var created: Long = 0L
    private var started: Long = 0L
    private var stopped: Long = 0L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ahsdvbjhadsbha ActivityLoggerLayer", "onCreate: Layer applied on ${this::class.java.simpleName}")
        created = System.currentTimeMillis()
    }

    override fun onStart() {
        super.onStart()
        started = System.currentTimeMillis()
    }

    override fun onStop() {
        super.onStop()
        stopped = System.currentTimeMillis()
    }

}

interface ActivityDummyLayer
open class ActivityDummyLayerDelegate : AppCompatActivity(), ActivityDummyLayer {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ahsdvbjhadsbha ActivityDummyLayer", "onCreate: Layer applied on ${this::class.java.simpleName}")
    }
}

// Base activity with layers
open class BaseActivity : AppCompatActivity(),
    ActivityLoggerLayer by ActivityLoggerLayerDelegate(),
    ActivityDummyLayer by ActivityDummyLayerDelegate() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // i expect the code call this on each layer automatically
        // this.onCreate(savedInstanceState)
        // this.onCreate(savedInstanceState)
    }
}

class MainActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
    }
}

I expect the code to execute onCreate from every layer when onCreate from the implementer is executed. I know delegation is not working like that, and I know there is some other way.

But the point is, I want to make the layers clear while working with an abstract or open class. I don’t want a BaseClass that is unclear about whatever composition it has. Or make the compositions have a chance to tangle together by not making it explicit.
For example by doing it like this inside the base class:

private val layer1: LayerOne = ....
private val layer2: LayerTwo = ...

Then handling the layer logic on the base class…
I don’t want it, i want to give the next developer a clear limitation (Layer should mind its own bussiness and should not tangled together) and eliminate possibility to make the logic tangled together…

I also want to make it clear for the next developer of what layer they are dealing with while using this base class. I want to make it clear by only looking at One Class. I don’t want to hide anything nor make it somehow invisible (Hidden Control / Logic).

Is there some approach to achieve this?

Also, don’t tell me about Layer2: Layer3(), then Layer1: Layer2() then BaseClass: Layer1()… no, I already mentioned above, no hidden logic or control or hidden composition.

Also, the code not allowing me to cal this@Layer1.onCreate(…).

NOTE:
This Case is specially targeting Open Class and Abstract Class cases. I know we can do it by using interface. But some SDK don’t have that feature, like what android sdk doing with Activity and Fragment class.

Do your individual layers really need to extend AppCompatActivity or do you simply want them to be lifecycle-aware components?

If it is the latter, maybe something like this achieves what you are looking for:


interface Layer : DefaultLifecycleObserver

interface HasLayers<L1, L2> : LifecycleOwner where L1 : Layer, L2 : Layer {
    val layers: List<Layer>

    fun registerLayers() = layers.forEach(lifecycle::addObserver)
}

abstract class LayeredActivity<L1, L2>(layer1: L1, layer2: L2) : AppCompatActivity(), HasLayers<L1, L2>
        where L1 : Layer, L2 : Layer
{
    override val layers: List<Layer> = listOf(layer1, layer2)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        registerLayers()
    }
}

interface Logger : Layer
object LoggerImpl : Logger {
    private var created: Long = 0L
    private var started: Long = 0L
    private var stopped: Long = 0L

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)

        Log.d("ActivityLoggerLayer", "onCreate: Layer applied on ${this::class.java.simpleName}")
        created = System.currentTimeMillis()
    }

    override fun onStart(owner: LifecycleOwner) {
        super.onStart(owner)
        started = System.currentTimeMillis()
    }

    override fun onStop(owner: LifecycleOwner) {
        super.onStop(owner)
        stopped = System.currentTimeMillis()
    }
}

interface Dummy : Layer
object DummyImpl : Dummy {
    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        Log.d("ActivityDummyLayer", "onCreate: Layer applied on ${this::class.java.simpleName}")
    }
}

open class BaseActivity : LayeredActivity<Logger, Dummy>(LoggerImpl, DummyImpl)

class MainActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
    }
}

LayeredActivity makes explicit what kind of layers are included with it and also automates the process of hooking each layer up to the activity lifecycle.
Unfortunately you would still have to deal with some boilerplate to provide LayeredActivity/HasLayers with different arities.

I didn’t spend too much time on your problem, but it generally feels like this is no scenario where relying on delegation would make your live much easier. The moment you delegate to more than one layer, you would need to provide explicit overrides and there doesn’t seem to be an obvious way of abstracting the process of making each leayer lifecycle-aware without exposing appropriate lifecycle-hooks.

Maybe some of this is helpful, but please keep in mind that I am no android expert, so take what I’ve said with a grain of salt.

2 Likes