Search code examples
javakotlininheritanceoverriding

Subtyping "some" Java classes in the form of derived Kotlin classes doesn't seem to work


Since you can't subtype LinearProgressIndicator, I've been trying to subtype BaseProgressIndicator (for fun) as a derived Kotlin class I'm calling AdvancedLinearProgressIndicator. I stumbled on the following issue:

'public open fun createSpec(context: Context, attrs: AttributeSet): LinearProgressIndicatorSpec defined in AdvancedLinearProgressIndicator' has no access to 'public/*package*/ abstract fun createSpec(context: Context, attrs: AttributeSet): LinearProgressIndicatorSpec! defined in com.google.android.material.progressindicator.BaseProgressIndicator', so it cannot override it

The Java code for BaseProgressIndicator is here: BaseProgressIndicator.java, but I think the important parts to consider are:

public abstract class BaseProgressIndicator<S extends BaseProgressIndicatorSpec>
    extends ProgressBar {

   ...

  S spec;

   ...

  protected BaseProgressIndicator(
      @NonNull Context context,
      @Nullable AttributeSet attrs,
      @AttrRes final int defStyleAttr,
      @StyleRes final int defStyleRes) {
    super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);

   ...

    spec = createSpec(context, attrs);

   ...

  }

  abstract S createSpec(@NonNull Context context, @NonNull AttributeSet attrs);

   ...

}

Here is the Kotlin code I started creating:

class AdvancedLinearProgressIndicator(
    context: Context,
    attrs: AttributeSet?,
    @AttrRes defStyleAttr: Int
) : BaseProgressIndicator<LinearProgressIndicatorSpec>(
    context,
    attrs,
    defStyleAttr,
    com.google.android.material.R.style.Widget_Material3_LinearProgressIndicator
) {

   override fun createSpec(context: Context, attrs: AttributeSet): LinearProgressIndicatorSpec {
        return LinearProgressIndicatorSpec(context, attrs)
    }
}

No matter what I tried, Kotlin won't let me override createSpec()

Also, I can't access the spec member field from BaseProgressIndicator in AdvancedLinearProgressIndicator either...

Any thoughts?

By the way, I tried to mimic this issue as follows:

// BaseSpec.java
import android.content.Context;

public abstract class BaseSpec {

    protected BaseSpec(Context context) {

    }

    abstract void validateSpec();
}

// AdvancedSpec.java
public final class AdvancedSpec extends BaseSpec {

    public AdvancedSpec(Context context) {
        super(context);
        validateSpec();
    }

    @Override
    void validateSpec() {

    }
}

// Basic.java
import android.content.Context;
import android.widget.ProgressBar;

import androidx.annotation.NonNull;

public abstract class Basic<S extends BaseSpec> extends ProgressBar {
    S spec;

    protected Basic(@NonNull Context context) {
        super(context);
        spec = createSpec(context);
    }

    abstract S createSpec(@NonNull Context context);
}

// Advanced.kt
import android.content.Context

class Advanced(context: Context) : Basic<AdvancedSpec>(context) {

    override fun createSpec(context: Context): AdvancedSpec {
        spec.validateSpec()
        return AdvancedSpec(context)
    }
}

This test code works fine... no issue with the createSpec() method in my derived Kotlin class - and no issue access the spec member field...


Solution

  • In Java, members of a class that are not marked with any access modifiers are package-private. They can only be accessed within the package in which the class is declared. The createSpec method is such a member.

    Since your Kotlin code is in another package, not com.google.android.material.progressindicator, you cannot access createSpec, so you cannot override it.

    Also, if you were allowed to override it, you would be exposing the method to the code in your own package, which is not supposed to happen.

    Your attempt at mimicking the problem compiles just fine, presumably because you put all the code in the same package.

    Since what you are doing is just "for fun", you can create your own com.google.android.material.progressindicator package and put the BaseProgressIndicator subclass in there.

    // move the file to this package too!
    package com.google.android.material.progressindicator
    
    class AdvancedLinearProgressIndicator(...) : BaseProgressIndicator(...) {
        // ...
    }