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!!
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
}
//...
}