Andrea
Andrea

Reputation: 566

Kotlin inheritance and variables visibility

I have this situation:

abstract class BaseWebClient(baseUrl: String) {

    abstract val defaultHeaders: HttpHeaders

    val client = WebClient
        .builder()
        .baseUrl(baseUrl)
        .defaultHeaders { it.addAll(defaultHeaders) }
        .build()
}

class AnimalsPortalClient(val config: Config, baseUrl: String) : BaseWebClient(baseUrl) {

    override val defaultHeaders: HttpHeaders
        get() {
            val headers = HttpHeaders()
            headers.add("app-name", config.appName)
            headers.add("app-version", config.appVersion)

            return headers
        }

    fun getAnimals(): String {
        return client.get( // ... etc
    }
}

This solution doesn't work, because - when defaultHeaders are attempted to be retrieved from overriding property in derived class - the variable config is null.

A possible solution is to pass the config object to the base class' constructor:

abstract class BaseWebClient(val config: Config, baseUrl: String) { 

    abstract val defaultHeaders: HttpHeaders
    
    // ... etc
}

class AnimalsPortalClient(localConfig: Config, baseUrl: String) : BaseWebClient(localConfig, baseUrl) {
    
    override val defaultHeaders: HttpHeaders
        get() {
            val headers = HttpHeaders()
            headers.add("app-name", config.appName)
            headers.add("app-version", config.appVersion)

            return headers
        }
    
    // ... etc
}

But this solution has a drawback: not all extending classes need a config object. In most of derived class I have empty default headers. Like this:

class SoccerPortalClient(baseUrl: String) : BaseWebClient(baseUrl) {

    override val defaultHeaders: HttpHeaders
        get() = HttpHeaders()

Using the solution I proposed, I would be forced to always have a config object to pass to the base class, even if there is no need for it.

So basically:

  1. I'm a bit puzzled about the behavior: why the variable is null? Is it a matter of visibility, or...?
  2. What's the correct implementation to get around this problem?

Thank you!

Upvotes: 0

Views: 284

Answers (2)

You are trying to access defaultHeaders property at the moment when derived class is not yet initialized.

Consided either convert client property initializer to getter:

val client
    get() = WebClient
        .builder()
        .baseUrl(baseUrl)
        .defaultHeaders { it.addAll(defaultHeaders) }
        .build()

or use lazy delegate:

val client by lazy {
    WebClient
        .builder()
        .baseUrl(baseUrl)
        .defaultHeaders { it.addAll(defaultHeaders) }
        .build()
}

Upvotes: 2

Xid
Xid

Reputation: 4951

The problem is that your client field in the base class is initialized before anything else. You can initialize it lazily. Like so:

val client by lazy {
    WebClient
        .builder()
        .baseUrl(baseUrl)
        .defaultHeaders { it.addAll(defaultHeaders) }
        .build()
}

Upvotes: 2

Related Questions