Home > database >  app crashes while opening main Activity because of getString(R.string.xxx)
app crashes while opening main Activity because of getString(R.string.xxx)

Time:09-07

I am working in app with two languages

in autocomplatetextview i want to change values according to the language of device

i try this code

var EGP = getString(R.string.egyptian_pound_egp)
    var USD = getString(R.string.american_dollar_usd)
    var SAR = getString(R.string.Saudia_Ryal)
    var KWD = getString(R.string.Kuwaiti_Dinar)

and full code of MainActivity

package com.example.currency

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.widget.Adapter
    import android.widget.ArrayAdapter
    import android.widget.AutoCompleteTextView
    import android.widget.Button
    import androidx.annotation.StringRes
    import androidx.core.widget.addTextChangedListener
    import com.google.android.material.internal.ContextUtils.getActivity
    import com.google.android.material.textfield.TextInputEditText
    
    class MainActivity : AppCompatActivity() {
    
        var EGP = getString(R.string.egyptian_pound_egp)
        var USD = getString(R.string.american_dollar_usd)
        var SAR = getString(R.string.Saudia_Ryal)
        var KWD = getString(R.string.Kuwaiti_Dinar)
    
        lateinit var convertButton: Button
        lateinit var amount: TextInputEditText
        lateinit var result: TextInputEditText
        lateinit var from: AutoCompleteTextView
        lateinit var to: AutoCompleteTextView
    
        val listValue = mapOf(
            USD to 0.052356,
            EGP to 1.0,
            SAR to 0.197040,
            KWD to 0.0166838
        )
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            initalizeViews()
            populateMenu()
    
    
            convertButton.setOnClickListener {
    
                calcResault()
            }
            amount.addTextChangedListener {
                calcResault()
            }
        }
    
        private fun initalizeViews() {
            convertButton = findViewById(R.id.button)
            amount = findViewById(R.id.AmountTIET)
            result = findViewById(R.id.ResultTIET)
            from = findViewById(R.id.FromACTV)
            to = findViewById(R.id.ToACTV)
    
    
    
        }
    
        private fun populateMenu() {
            val currencyList = listOf(EGP, USD, SAR, KWD)
            val adapter = ArrayAdapter(this, R.layout.list_currency, currencyList)
            from.setAdapter(adapter)
            to.setAdapter(adapter)
    
    
        }
    
        private fun calcResault(){
            if (amount.text.toString().isNotEmpty()) {
                result.setText(
                    String.format(
                        "%.2f", listValue.get(to.text.toString())!!.times(
                            amount.text.toString().toDouble()
                                .div(listValue.get(from.text.toString())!!)
                        )
                    )
                )
            } else {
                amount.setError(getString(R.string.amount_required))
            }
        }
    
    
    }

after testing some codes , i found that getString(R.string.xxx) the reason of the crashing

when change getString(R.string.xxx) with string value the app opening with no problem

but i want to change values according to the language of device

CodePudding user response:

Make those be either lateinit var or use by lazy {} to defer initialization. You cannot call getString() until after super.onCreate() has been called in your onCreate() function.

CodePudding user response:

Try this

(CAUTION: You values will be re-initialized every time onCreate() method called)

lateinit var EGP: String
lateinit var USD: String
lateinit var listValues: Map<String, Double>
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    EGP = getString(R.string.egyptian_pound_egp)
    USD = getString(R.string.american_dollar_usd)

    listValues = mapOf(
        USD to 0.052356,
        EGP to 1.0
    )

  //Rest of your code  
} 

CodePudding user response:

getString requires your Activity to have a Context, and at construction time it doesn't have one. So when you define those top-level variables that are initialised at construction time, your getString calls fail. The error log will tell you this, that you're trying to do something with a null Context or similar.

The context shows up somewhere around onCreate, so if you can guarantee those values won't be used until the Activity is CREATED (i.e. you won't be reading them until onCreate or later) then you could use a lazy delegate. That only initialises them when they're first read - so if you're reading them when the Activity has its Context, the getString call works fine!

val EGP = by lazy { getString(R.string.egyptian_pound_egp) }
val USD = by lazy { getString(R.string.american_dollar_usd) }
val SAR = by lazy { getString(R.string.Saudia_Ryal) }
val KWD = by lazy { getString(R.string.Kuwaiti_Dinar) }

But the problem here is you're not first reading these in onCreate or later - it happens in the next line where you build a Map using those values, which is another top-level variable that's initialised at construction. So you don't get the benefit of the lazy because it's called too early.

You can fix this by making that map lazy too:

val listValue by lazy { mapOf(
    USD to 0.052356,
    EGP to 1.0,
    SAR to 0.197040,
    KWD to 0.0166838
)}

Now listValue won't be initialised until it's read either! So it won't try to read those other values until it's actually accessed - so same deal, as long as listValue isn't read by something before onCreate, it should be fine.

This is the kind of thing you have to watch out for with lazy delegates in Android lifecycle components like Activity and Fragment, or anywhere you need lazy initialisation really. Make sure it's not being read too early by something else, and make those lazy too if appropriate.


Using lazy delegates requires your variables to be vals though - if you need to be able to change them, make them lateinit instead and initialise them manually as soon as you can. You could keep listValue as a lazy if you want, just make sure the lateinit vars it initialises from are assigned before it's accessed

  • Related