Home > other >  lateinit variable not being initialized in a thread
lateinit variable not being initialized in a thread

Time:11-17

So I'm working with the google sheets api in an android app and I'm trying to get the credentials in a separate thread. This is what I have:

GoogleSheets is a class I created to get credentials and cell values of my spreadsheet

private lateinit var sheets: GoogleSheets is a instance variable that I declare at the beginning of the class. I am trying to initialize here:

load.setOnClickListener(View.OnClickListener {
            Thread {
                sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
            }.start()
            println(sheets)
            println(sheets.getValues("A1"))
        })

but It's telling me that the sheets variable hasn't been initialized:

kotlin.UninitializedPropertyAccessException: lateinit property sheets has not been initialized

here is the full class:


import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.example.frcscout22.GoogleSheets
import com.example.frcscout22.R


// TODO: AUTOMATICALLY SWITCH TO DATA TAB AFTER LOAD OR CREATE NEW

class Home: Fragment(R.layout.fragment_home) {
    private lateinit var sheets: GoogleSheets
    private val STORAGE_PERMISSION_CODE = 100

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }

    @RequiresApi(Build.VERSION_CODES.P)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view: View = inflater.inflate(R.layout.fragment_home, container, false)

        val load = view.findViewById<Button>(R.id.button3)
        val new = view.findViewById<Button>(R.id.button4)
        val editText = view.findViewById<EditText>(R.id.editTextTextPersonName)

        if (!checkPermission()) {
            println("requested")
            requestPermission()
        }

        new.setOnClickListener(View.OnClickListener {
            val sheets = GoogleSheets(requireContext(),"1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
            sheets.setValues("A1", "this is a test", "USER_ENTERED")
            println(sheets.getValues("A1").values)
        })

        load.setOnClickListener(View.OnClickListener {
            Thread {
                sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
            }.start()
            println(sheets)
            println(sheets.getValues("A1"))
        })
        return view
    }

    private fun requestPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
            //Android is 11(R) or above
            try {
                val intent = Intent()
                intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
                val uri = Uri.fromParts("package", requireActivity().packageName, "Home")
                intent.data = uri
                storageActivityResultLauncher.launch(intent)
            }
            catch (e: Exception){
                val intent = Intent()
                intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
                storageActivityResultLauncher.launch(intent)
            }
        }
        else{
            //Android is below 11(R)
            ActivityCompat.requestPermissions(requireActivity(),
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE),
                STORAGE_PERMISSION_CODE
            )
        }
    }

    private val storageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
        //here we will handle the result of our intent
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
            //Android is 11(R) or above
            if (Environment.isExternalStorageManager()){
                //Manage External Storage Permission is granted
            }
        }
        else{
            //Android is below 11(R)
        }
    }

    private fun checkPermission(): Boolean{
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
            //Android is 11(R) or above
            Environment.isExternalStorageManager()
        }
        else{
            //Android is below 11(R)
            val write = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
            val read = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
            write == PackageManager.PERMISSION_GRANTED && read == PackageManager.PERMISSION_GRANTED
        }
    }
}

I can't figure out why the varible isn't being initialized. Does it have something to do with it being in a thread? How can I fix this problem? Thanks!!

CodePudding user response:

At a guess the error's telling you it's happening when you do this:

Thread {
    sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
}.start()
println(sheets)

lateinit is you promising the compiler you'll have assigned a value to sheets before anything tries to read it, which you're doing with println(sheets). You're assigning it in that thread you just started - but that's very unlikely to have completed before the println statement runs on the current thread!

You also have to worry about synchronisation if you're involving threading like this - just because your worker thread sets the value of sheets, it doesn't mean the current thread will see that updated value. You can have a look here if you're not familiar with that whole issue and the steps you have to take to ensure things stay consistent.

Your best bet with the code you have is to do your println stuff inside the thread after sheets is assigned. If you do anything more complicated than that, and need to get back on the main thread, you can post a Runnable on a view. Honestly, if you can use coroutines instead that would probably make your life easier in the long run

CodePudding user response:

You're starting another thread to initialize it, so if you check for it immediately, the other thread hasn't had time to initialize the property yet. This is a misuse of lateinit and you are also failing to utilize thread synchronization, so it is susceptible to other bugs.

I suggest loading the sheet with a coroutine and using a suspend function to retrieve the instance when you need to use it anywhere. When using only coroutines to access the property, you don't need to worry about thread synchronization.

Really, this should go in a class that outlives the Fragment so you don't have to reload it every time the Fragment is recreated, but for simplicity, I'll just keep it in your Fragment for this example.

class Home: Fragment(R.layout.fragment_home) {
    private val loadSheetsDeferred = GlobalScope.async(Dispatchers.IO) {
        GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
    }

    private suspend fun getSheets(): GoogleSheets = loadSheetsDeferred.await()

    private val STORAGE_PERMISSION_CODE = 100

    @RequiresApi(Build.VERSION_CODES.P)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //...

        new.setOnClickListener { viewLifecycleScope.launch {
            getSheets().setValues("A1", "this is a test", "USER_ENTERED")
            println(getSheets().getValues("A1").values)
        } }

        load.setOnClickListener { viewLifecycleScope.launch {
            println(getSheets())
            println(getSheets().getValues("A1"))
        } }

        return view
    }

    //...
}
  • Related