lateinit variable not being initialized in a thread

Issue

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!!

Solution

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 = viewLifecycle.lifecycleScope.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 { viewLifecycle.lifecycleScope.launch {
            getSheets().setValues("A1", "this is a test", "USER_ENTERED")
            println(getSheets().getValues("A1").values)
        } }

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

        return view
    }

    //...
}

Answered By – Tenfour04

Answer Checked By – Marie Seifert (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *