6

I have the following code in Java which I want to convert to Kotlin:

class RideHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    class HistoryItemHolder extends RecyclerView.ViewHolder {
        private static final int TYPE_IN_PROGRESS = 1
        private static final int TYPE_CANCELLED = 2
        private static final int TYPE_FINISHED = 3

        // class methods and properties are written
    }
}

I have come up with the following code:

class RideHistoryAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private inner class HistoryItemHolder(view: View)
        : RecyclerView.ViewHolder(view) {
        private companion object {
            private const val TYPE_IN_PROGRESS = 1
            private const val TYPE_CANCELLED = 2
            private const val TYPE_FINISHED = 3

            // class methods and properties are written
        }

    }

}

Android Studio is showing a red squiggly under "object" of the line companion object, saying:

Companion object is not allowed here

Note: I know I can convert it to non-inner class but I'd prefer keeping it one. I also checked I can't define an interface in an inner class too.

Sufian
  • 6,405
  • 16
  • 66
  • 120

2 Answers2

8

First of all you Java is not valid. If you fix it you get an error: Inner classes can not have static declarations

invalid java

You cant have a companion object in an inner class for the same reason that you can not have static members in an inner class in java

https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html:

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.

(highlight by me)

With the same reasoning it does not make sense to have a quasi-static companion object associated with instance.

Solutions (pick one):

  1. use val (similar to final).
  2. move the companion object to the enclosing class.
  3. make HistoryItemHolder not an inner class.
leonardkraemer
  • 6,573
  • 1
  • 31
  • 54
  • Thanks. I realize my mistake. I must have written like `public static final int TYPE_IN_PROGRESS = 1;`. Now this is a valid Java code. – Sufian Sep 13 '19 at 11:21
  • I tried your solution but it doesn't convert Java code ([using this answer](https://stackoverflow.com/questions/34957430/how-to-convert-a-kotlin-source-file-to-a-java-source-file)) of `public static final` line, instead it converts to `private final` (no static means it binds to the instance of the class and not the class and hence not a good practice). – Sufian Sep 13 '19 at 11:38
  • If you want to have it `static final` (the equivalent of `const` in a companion-object) you must move the companion object to the outer class for the reason given in my answer. Side note: I think a `private final` field is a good solution. Semantically it is very clear what it means. – leonardkraemer Sep 13 '19 at 11:42
  • Yes it will work but not an elegant solution ([here's why](https://stackoverflow.com/questions/8093230/why-are-java-constants-declared-static)). Making the class non-inner (as suggested by @NatigBabayev) seems to be the only valid solution to the problem. I guess I hit Kotlin's rare limits. – Sufian Sep 13 '19 at 11:50
  • Good point, that is a third way of solving the problem. I'll add it to my answer. – leonardkraemer Sep 13 '19 at 12:09
  • Yes please add it but also write the downsides for each solution to make it a complete answer. – Sufian Sep 13 '19 at 12:14
  • I will, when I find time. For the moment it will remain an exercise for the reader ;) – leonardkraemer Sep 13 '19 at 12:16
2

I can confirm that it is not possible to add constants in an inner-class (see my question on Kotlin forums).

However, there are two ways you can go about (I chose the first):

  1. Make ViewHolder a non-inner class - this means it can no longer access methods/properties of Adapter:

    public class Adapter {
    
        public class ViewHolder(private val adapter: Adapter) {
    
            private companion object {
                private const val TYPE_IN_PROGRESS = 1
                private const val TYPE_CANCELLED = 2
                private const val TYPE_FINISHED = 3
            }
        }
    }
    
  2. Define constants in the Adapter:

    public class Adapter {
        private const val TYPE_IN_PROGRESS = 1
        private const val TYPE_CANCELLED = 2
        private const val TYPE_FINISHED = 3
    
        public class ViewHolder(private val adapter: Adapter) {
            // ..
        }
    }
    

Note: do not convert your const val to val as it will bind it to your class instance (which is bad. More about it here).

Sufian
  • 6,405
  • 16
  • 66
  • 120
  • the second way here is only working because Adapter is not an inner class, you should get compile error `Const 'val' are only allowed on top level or in objects` – BabyishTank Nov 11 '21 at 19:03
  • 1
    @BabyishTank absolutely. That's why I wrote, "it is not possible to add constants in an inner-class" at the beginning of my answer. – Sufian Nov 12 '21 at 05:45