如何在一个 Kotlin 中将共享首选项中的项目添加到两个微调器 Activity

How to add items from shared preferences to two spinners in one Kotlin Activity

我有一个带有两个旋转器的 activity。我为每个微调器制作了数组,其中包含流行食物的数据,但我希望用户能够将他们自己的三个 select 离子添加到列表中。该应用程序编译、安装和运行,但是当我 select 特定 activity 时,屏幕关闭并转到应用程序主屏幕或模拟器的主屏幕。 Logcat 显示:-

java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.kotlinsql/com.example.kotlinsql.CarbsInput}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.SharedPreferences android.content.Context.getSharedPreferences(java.lang.String, int)' on a null object reference

这是我调用共享首选项的地方。 我尝试了不同的上下文,但仍然会根据上下文出现略有不同的错误,我已将它们作为备注包含在代码中。

我已经尝试将所有内容都移到 onCreate 中,但这在 class 定义行中给我一个错误,因为函数“override fun onItemSelected”似乎必须是独立的,所以必须在外部onCreate.

请帮忙。我只学了不到一年,对于任何愚蠢的错误,我深表歉意。无意冒犯。


import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.*
import kotlinx.android.synthetic.main.input_carbs.*
import java.time.Clock
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import android.content.SharedPreferences
import android.content.res.Configuration
import java.security.AccessController.getContext
import kotlin.math.*

class CarbsInput : AppCompatActivity(),AdapterView.OnItemSelectedListener {


    var spinner:Spinner? = null
    var spinner2:Spinner? = null
    val sharedPrefFile = "greenbandbasicpreference"
    
    val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
    val dataModel: CarbsInputModel by lazy { CarbsInputModel(sharedPreferences) }

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.input_carbs)

        spinner = this.gi_spinner
        spinner2 = this.carbs_per_spinner

        // Create an ArrayAdapter using a simple spinner layout and gIndices array
        val aa = ArrayAdapter(this, android.R.layout.simple_spinner_item, dataModel.gIndices)
        // Set layout to use when the list of choices appear
        aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        // Set Adapter to Spinner
        spinner!!.setAdapter(aa)
        //spinner!!.setSelection(9)//optional, better to leave favourites at top

        val aa2 = ArrayAdapter(this, android.R.layout.simple_spinner_item, dataModel.carbsPer)
        aa2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinner2!!.setAdapter(aa2)


        input_carbs_btn.setOnClickListener {//set an onclick listener
            enterCarbs()                   }

        backbtn.setOnClickListener {
            val fourth = Intent(this, MainActivity::class.java)//sets "fourth" to be MainActivity
            // start your next activity
            startActivity(fourth)  }

        btn_view_carbs.setOnClickListener { viewCarbs() }

        btn_carb_calc.setOnClickListener {
        var carbPer = et_carbsper.text.toString().toLong()
        var weight = et_weight.text.toString().toLong()
        var carbs = round((weight * carbPer) /100.0).toLong()
        et_carbs.setText(carbs.toString())
    }//end of button onClick listener

}//end of on create






    fun enterCarbs(){//get inputs from keys and calculate carbLife using GI



        var noow = ZonedDateTime.now(Clock.systemUTC())
        var noowSecs: Long = noow.toEpochSecond()
        var noowMins: Long = (noowSecs) / 60
        //var carbLife:Long = 220// this has to be calculated from GI
        var nowLocal = LocalDateTime.now()
        var carbTime: Long = noowMins-1
        var showCarbTime: String = nowLocal.format(DateTimeFormatter.ofPattern("E d MMM kk:mm "))+"local"
        var sharedPrefFile = "greenbandbasicpreference"
        val sharedPreferences: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)

        val databaseHandler: DatabaseHandler = DatabaseHandler(this)

        if (et_carbs.text.toString().trim() != "" && et_carbGI.text.toString().trim() != "") {

            val carbs = et_carbs.text.toString().toLong()
            val carbGi = (et_carbGI.text.toString().toLong())
            //val carbLife = 12_000 /carbGi.toLong()// to be replaced with 1-(X/L)^n calculation in stage2
            var carbDecayIndex:Double= sharedPreferences.getFloat("carbDecayIndex_key",0.8F).toDouble()//n
            //public fun carbLifeCalc():Double//L = 10^((log120^n-logGI)/n)
            var logLtoN = log10(120.00.pow(carbDecayIndex))//log120^n

            var logGi = log10(carbGi / 100.00)//logGI
            var carbLife = 10.00.pow((logLtoN - logGi) / carbDecayIndex).toLong()//gives L
            //end of carbLifeCalculation

            val status  =
                databaseHandler.saveCarbs(CarbsModelClass(carbTime, showCarbTime, carbs, carbGi, carbLife))
            if (status > -1) {
                Toast.makeText(applicationContext, "Carbohydrate saved", Toast.LENGTH_LONG).show()
                //MainActivity.evaluateCarbs //want to call this function from here without writing it again
                et_carbs.text.clear()
                et_carbGI.text.clear()

            }
        } else {
            Toast.makeText(
                applicationContext,
                "No field can be blank enter GI as 50 if unknown",
                Toast.LENGTH_LONG
            ).show()
        }
    }//end of function entercarbs



    fun viewCarbs() {
        //creating the instance of DatabaseHandler class
        val databaseHandler: DatabaseHandler = DatabaseHandler(this)
        //calling the viewCarbs method of DatabaseHandler class to read the records
        val carbohs: List<CarbsModelClass> = databaseHandler.viewCarbs()
        //val carbohsArraycarbTime     = Array<String>(carbohs.size) { "null" }//not needed
        val carbohsArrayshowCarbTime = Array<String>(carbohs.size) { "null" }
        val carbohsArraycarbs        = Array<String>(carbohs.size) { "null" }
        val carbohsArraycarbGI       = Array<String>(carbohs.size) { "null" }
        val carbohsArraycarbLife     = Array<String>(carbohs.size) { "null" }

        var index = 0
        for (e in carbohs) {
            //carbohsArraycarbTime[index] = e.carbTime.toString()//not needed
            carbohsArrayshowCarbTime[index] = e.showCarbTime
            carbohsArraycarbs[index] = e.carbs.toString()
            carbohsArraycarbGI[index] = e.carbGi.toString()//note small i inGi
            carbohsArraycarbLife[index] = e.carbLife.toString()
            //index--
            index++
        }
        //creating custom ArrayAdapter
        val myCarbListAdapter = CarbListAdapter(
            context = this,
            //carbTime = carbohsArraycarbTime,//not needed
            showCarbTime = carbohsArrayshowCarbTime,
            carbs = carbohsArraycarbs,
            carbGI = carbohsArraycarbGI,
            carbLife = carbohsArraycarbLife
        )
        lv_carb_view.adapter = myCarbListAdapter
    }//end of fun view carbs

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// see 
        if(parent?.getId() == R.id.gi_spinner) {
            var giFullSelected = dataModel.gIndices[position]
            var gIprelimString : String =
                giFullSelected[0].toString() + giFullSelected[1]//selecting just digits
            var GIprelim = gIprelimString.toLong()
            et_carbGI.setText(gIprelimString)

        }//end of first if
        else{ if (parent?.getId() == R.id.carbs_per_spinner) {
            var carbPerFullSelected = dataModel.carbsPer[position]
            var carbPerString: String =
                carbPerFullSelected[0].toString() + carbPerFullSelected[1]
            var carbPer = carbPerString.toLong()
            et_carbsper.setText(carbPerString)
            var weight = et_weight.text.toString().toLong()
            var carbs = round((weight * carbPer) /100.0).toLong()
            et_carbs.setText(carbs.toString())}//end of second if
            else { Toast.makeText(applicationContext, "parent id "+parent?.getId().toString(), Toast.LENGTH_LONG).show()
                    }//end of second else
        }//end of elseif OR /first else

    }//end of on item selected

    override fun onNothingSelected(parent: AdapterView<*>?) {  }

}//end of class carbs input

新Class CarbsInputModel Below


//start of CarbsInputModel
import android.content.SharedPreferences

class CarbsInputModel(private val sharedPreferences:SharedPreferences) {
   // val sharedPrefFile = "greenbandbasicpreference"
    //val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
    val sharedFav1Value: String? = sharedPreferences.getString("fav1_key", "50 50 defaultone")
    val sharedFav2Value: String? = sharedPreferences.getString("fav2_key", "50 50 defaultwo")
    val sharedFav3Value: String? = sharedPreferences.getString("fav3_key", "50 50 defaulthree")
    val favDescr1:String = sharedFav1Value?.takeLastWhile { !it.isDigit() }.toString().trim()
    val favDescr2:String = sharedFav2Value?.takeLastWhile { !it.isDigit() }.toString().trim()
    val favDescr3:String = sharedFav3Value?.takeLastWhile { !it.isDigit() }.toString().trim()
    val favData1:String = sharedFav1Value?.takeWhile { !it.isLetter() } .toString()
    val favData2:String = sharedFav2Value?.takeWhile { !it.isLetter() } .toString()
    val favData3:String = sharedFav3Value?.takeWhile { !it.isLetter() } .toString()
    val favCarbPerString1 = favData1.take(3).trim()
    val favCarbPerString2 = favData2.take(3).trim()
    val favCarbPerString3 = favData3.take(3).trim()
    val favGiString1 = favData1.takeLast(4).trim()
    val favGiString2 = favData2.takeLast(4).trim()
    val favGiString3 = favData3.takeLast(4).trim()

    val favFullCarbPer1 = favCarbPerString1+" "+favDescr1+" "
    val favFullCarbPer2 = favCarbPerString2+" "+favDescr2+" "
    val favFullCarbPer3 = favCarbPerString3+" "+favDescr3+" "
    val favFullGi1 = favGiString1+" "+favDescr1+" "
    val favFullGi2 = favGiString2+" "+favDescr2+" "
    val favFullGi3 = favGiString3+" "+favDescr3+" "
}//end of class Carbs Input Model

尝试整理覆盖功能的代码。这仍然没有任何作用

//trying to tidy up code
 override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
 when {
     parent.id == R.id.gi_spinner -> {
         var giFullSelected = dataModel.gIndices[position]
         var gIprelimString: String =
             giFullSelected[0].toString() + giFullSelected[1]//selecting just leading digits
         et_carbGI.setText(gIprelimString)
     }

     parent.id == R.id.carbs_per_spinner -> {
         var carbPerFullSelected = dataModel.carbsPer[position]
         var carbPerString: String =
             carbPerFullSelected[0].toString() + carbPerFullSelected[1]
         var carbPer = carbPerString.toLong()
         et_carbsper.setText(carbPerString)
         var weight = et_weight.text.toString().toLong()
         var carbs = round((weight * carbPer) / 100.0).toLong()
         et_carbs.setText(carbs.toString())
     }

     else -> {
         Toast.makeText(applicationContext, "parent id " + parent?.getId().toString(),
             Toast.LENGTH_LONG ).show()
     }
    }//end of when
}//end of override fun onitemselected

当你像这样在声明站点赋值时:

 val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)

在 Activity 被 Android 实例化时调用您调用的函数来创建将分配给 属性 的对象。不幸的是,现在调用任何依赖于 Activity 被完全实例化和设置的东西还为时过早,例如,任何需要 Context 作为构造函数参数的东西。

解决这个问题的简单方法是让这些属性懒惰地实例化自己,因此它们是在 Activity 已经完全实例化之后创建的:

 val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }

另一种解决方案是使用 lateinit var 并准备 onCreate() 中的项目:

lateinit var sharedPreferences: SharedPreferences

// ...

override fun onCreate(bundle: SavedInstanceState) {
    super.onCreate(bundle)
    sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
}

我通常更喜欢惰性方法,因为它避免拆分声明和赋值,因此代码更易于阅读。它允许您使用 val 而不是 var,因此意图更清晰。

但是,您还有许多依赖于 SharedPreference 实例的属性,因此它们也都必须使用上述解决方案之一,这将导致代码非常冗长。我建议您将所有这些属性移动到一个单独的 class 中,该 class 使用 SharedPreferences 作为构造函数参数。例如:

class CarbsInputModel(private val sharedPreferences: SharedPreferences) {

    val sharedFav1Value: String? = sharedPreferences.getString("fav1_key", "50 50 defaultone")
    val sharedFav2Value: String? = sharedPreferences.getString("fav2_key", "50 50 defaultwo")
    val sharedFav3Value: String? = sharedPreferences.getString("fav3_key", "50 50 defaulthree")
    
    // etc...
}

然后在你的 activity:

class CarbsInput : AppCompatActivity(),AdapterView.OnItemSelectedListener {

    var spinner:Spinner? = null
    var spinner2:Spinner? = null
    val sharedPrefFile = "greenbandbasicpreference"
    val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
    val dataModel: CarbsInputModel by lazy { CarbsInputModel(sharedPreferences) }

}

然后通过 dataModel 属性 访问您的属性。将 UI 和修改数据的函数分开也是更好的设计实践,因此您可以将这些函数放在数据模型中 class.

您可能还想了解如何使用 ViewModel class。它可能是比我上面提出的更具可扩展性的解决方案。