dsf
dsf

Reputation: 1215

How to initialize lateinit variable used for object expression in Kotlin?

It is not a duplication. The "object" expression is the keyword.

I am trying to integrate Timber Android Library with https://github.com/orhanobut/logger and I need a customLogStrategyForTimber for my needs.

I came up with this logic. But I am having difficulty on initializing the customLogStrategyForTimber

class App : Application() {
    // I CREATED THE lateinit VARIABLE
    lateinit var customLogStrategyForTimber: LogStrategy
    
    override fun onCreate() {
        super.onCreate()
        Timber.plant(object : Timber.DebugTree() {
            override fun log(
                priorityTimber: Int, tagTimber: String?, message: String, t: Throwable?
            ) {
                
                // USED THE lateinit HERE
                customLogStrategyForTimber = object : LogcatLogStrategy() {
                    override fun log(priority: Int, tag: String?, message: String) {
                        super.log(priorityTimber, tagTimber, message)
                    }
                }

 println("customLogStrategyForTimber: ${::customLogStrategyForTimber.isInitialized}") // PRINTS TRUE
            }
        })

        println("customLogStrategyForTimber OUTSIDE: ${::customLogStrategyForTimber.isInitialized}") // PRINTS FALSE - WHY??


            var formatStrategy1 = PrettyFormatStrategy.newBuilder()
                
                // TRYING TO CALL THE lateinit VARIABLE HERE
                // NOW, HERE THE lateinit IS NOT INITIALIZED.
                .logStrategy(customLogStrategyForTimber)
                .build()
            Logger.addLogAdapter(AndroidLogAdapter(formatStrategy1))
    }
}

The customLogStrategyForTimber is never initialized. Is there a better way to do this logic? Trying to add the entire formatStrategy code inside the first override fun log method results in unexpected behaviour when using Timber logging, so that does not seem to be the easy option.

Crash when running the App

  Caused by: kotlin.UninitializedPropertyAccessException: lateinit property customLogStrategyForTimber has not been initialized

Tried using isInitialized as well.

The code inside it never runs.

EDIT:

Created a sample project: https://github.com/shipsywor/demotimberlogger

EDIT 2:

I added println statements to the code above. You will see that ::customLogStrategyForTimber.isInitialized returns False at one point of code and True at another

NOTE: I cannot put formatStrategy code inside of Timber.plant {... }. It results in unexpected behavior.

Upvotes: 1

Views: 1186

Answers (4)

Tenfour04
Tenfour04

Reputation: 93609

Based on your comments on other answers, I think I understand what you're trying to do.

It looks like this would be less fragile code if you had a concrete implementation of LogcatLogStrategy where you have properties you can set.

class MyLogcatLogStrategy: LogcatLogStrategy() {

    var timberTag: String? = null
    var timberPriority: Int = -1

    override fun log(priority: Int, tag: String?, message: String) {
        super.log(timberPriority, timberTag, message)
    }
}

Then you can have a single instance and can update it safely from anywhere.

class App : Application() {
    val customLogStrategyForTimber = MyLogcatLogStrategy()
    
    override fun onCreate() {
        super.onCreate()
        Timber.plant(object : Timber.DebugTree() {
            override fun log(
                priority: Int, tag: String?, message: String, t: Throwable?
            ) {
                  customLogStrategyForTimber.apply {
                      timberTag = tag
                      timberPriority = priority
                  }
            }
        }
        val formatStrategy1 = PrettyFormatStrategy.newBuilder()
            .logStrategy(customLogStrategyForTimber)
            .build()
        Logger.addLogAdapter(AndroidLogAdapter(formatStrategy1))
    }
}

What I don't know is if this updates your tag value on the correct thread or in the correct order for the tag to show up in your log correctly.

It would probably be easier to eliminate Timber entirely and just copy its method of determining the tag out of its source code into your LogcatLogStrategy, but I'm not familiar with this other logging library you're using. Maybe something like this:

class MyLogcatLogStrategy: LogcatLogStrategy() {
    private val ignoredClassNames = listOf(
        MyLogcatLogStrategy::class.java.name,
        /* The names of all the classes in your logging library */
    )

    //Adapted from Timber:
    private val tag: String?
      get() = Throwable().stackTrace
          .first { it.className !in ignoredClassNames }
          .let(::createStackElementTag)

    override fun log(priority: Int, ignoredTag: String?, message: String) {
        super.log(priority, this.tag, message)
    }

    // From Timber: (https://github.com/JakeWharton/timber/blob/master/timber/src/main/java/timber/log/Timber.kt#L216)
    private fun createStackElementTag(element: StackTraceElement): String? {
      var tag = element.className.substringAfterLast('.')
      val m = ANONYMOUS_CLASS.matcher(tag)
      if (m.find()) {
        tag = m.replaceAll("")
      }
      // Tag length limit was removed in API 24.
      return if (tag.length <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        tag
      } else {
        tag.substring(0, MAX_TAG_LENGTH)
      }
    }

    companion object {
      private val ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$")
    }
}

Upvotes: 0

Muhammad Zahab
Muhammad Zahab

Reputation: 1105

Let me explain to you. Timber.plant is a thread which will take some time to complete its work. Your code where you are getting false in log it suddenly run after Timber.plan and print false meanwhile Timper.plan thread is executing parallel and initialized variable and then print where you are getting true.

You should do like this:

     class App : Application() {
 
    lateinit var customLogStrategyForTimber: LogStrategy
 
    override fun onCreate() {
        super.onCreate()
 
        Timber.plant(object : Timber.DebugTree() {
 
            override fun log(
                priorityTimber: Int, tagTimber: String?, message: String, t: Throwable?
            ) {
                customLogStrategyForTimber = object : LogcatLogStrategy() {
                    override fun log(priority: Int, tag: String?, message: String) {
                        super.log(priorityTimber, "global_tag_$tagTimber", message)
                    }
                }
                    Logger.d(message)
            }
        })
 
 
        thread {
            val formatStrategy: FormatStrategy = PrettyFormatStrategy.newBuilder()
                .showThreadInfo(false)
                .methodCount(1)
                .methodOffset(5)
                .logStrategy(customLogStrategyForTimber)
                .build()
 
            Logger.addLogAdapter(AndroidLogAdapter(formatStrategy))
 
            println("global_tag INSIDE thread: ${::customLogStrategyForTimber.isInitialized}")
 
        }
 
        Timber.d("Initialize Timber")
    }
}

Upvotes: 1

Joffrey
Joffrey

Reputation: 37710

The code where you initialize your lateinit var is in the log() of the DebugTree implementation you created on the spot. It is not executed when calling Timber.plant() here, you're just registering an instance of DebugTree with some implementation.

So when you reach the println with "OUTSIDE", that log method has never been called yet, so the customLogStrategyForTimber was not initialized.

Upvotes: 1

PurrgnarMeowbro
PurrgnarMeowbro

Reputation: 338

It looks like you have needlessly created a local val customLogStrategyForTimber, which is only accessible within the Timber.plant() method. In effect, you're not really using the lateinit var that you have declared at the top of your class. If you just removed val from object declaration inside the Timber.plant() method, your code would work as you intend it to.

As it is now, the object that you have declared stays inside the Timber.plant() method and isn't accessible on the outside.

Upvotes: 1

Related Questions