【问题标题】:How to add items from shared preferences to two spinners in one Kotlin Activity如何在一个 Kotlin Activity 中将共享首选项中的项目添加到两个微调器
【发布时间】:2021-02-12 23:21:58
【问题描述】:

我有两个旋转器的活动。我为每个微调器制作了包含流行食品数据的数组,但我希望用户能够将他们自己的三个选择添加到列表中。该应用程序编译并安装并运行,但是当我选择特定活动时,屏幕关闭并转到应用程序主屏幕或模拟器的主屏幕。 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 中,但这在类定义行中给我一个错误,因为函数“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 https://stackoverflow.com/questions/9262871/android-two-spinner-onitemselected
        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

下面的新类 CarbsInputModel


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

【问题讨论】:

    标签: android kotlin sharedpreferences spinner


    【解决方案1】:

    当您像这样在声明站点分配值时:

     val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
    

    您为创建将分配给属性的对象而调用的函数在 Android 实例化 Activity 时被调用。不幸的是,现在调用任何依赖于 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 实例的属性,因此它们都必须使用上述解决方案之一,这将导致代码非常冗长。我建议您将所有这些属性移动到一个单独的类中,该类使用 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...
    }
    

    然后在你的活动中:

    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 和修改数据的函数分开也是更好的设计实践,因此您可以将这些函数放入数据模型类中。

    您可能还想了解如何使用 ViewModel 类。这可能是一个比我上面提到的更具可扩展性的解决方案。

    【讨论】:

    • 非常感谢您的建设性回答。我还没有实现它,但我会的。我还将阅读有关 ViewModel 的信息。
    • 刚刚尝试实现这个答案,但它不起作用。相同的可见结果仍然给出 NPE 异常,但在不同的行。如何提供完整的详细信息?
    • 您可以编辑您的问题以添加新代码或将其放在 Pastebin 或 Github Gist 上,我会看看。
    • 当我输入“class CarbsInputModel(private val sharedPreferences: SharedPreferences)”时,“val”显示为灰色,并且出现错误消息“constructor parameter is never used as a property”
    • 我试图将修改添加到问题中的代码中,这是粘贴箱中错误的 URL(对我来说是新的,所以不知道如何分享)pastebin.com/9rA8feQC
    猜你喜欢
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    相关资源
    最近更新 更多