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 val
s 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 var
s it initialises from are assigned before it's accessed