i try to use SharedPreferences but it's only saving the last value.
MainActivity:
myPreferences.setPrice(txtPrice.text.toString().toFloat())
myPreferences.setSABV(txtABV.text.toString().toFloat())
SharedPreferences handler:
class myPreferences(context: Context){
val PREFERENCENAME = "BeerNote"
val PRICE = 0.0f
val ALCOHOLBYVOLUME = 0.0f
val preference = context.getSharedPreferences(PREFERENCENAME,Context.MODE_PRIVATE)
fun setPrice(price:Float){
preference.edit().putFloat(PRICE.toString(),price).apply()
}
fun getPrice():Float{
return preference.getFloat(PRICE.toString(),0.0f)
}
fun setSABV(abv:Float){
preference.edit().putFloat(ALCOHOLBYVOLUME.toString(),abv).apply()
}
fun getABV():Float{
return preference.getFloat(ALCOHOLBYVOLUME.toString(),0.0f )
}
}
When i try to recover the data:
Toast.makeText(this, "Price:"+mypreference.getPrice(), Toast.LENGTH_LONG).show()
Toast.makeText(this, "ABV:"+mypreference.getABV(), Toast.LENGTH_LONG).show()
It only saves the ABV value in Price and ABV.
You probably want to use constants as keys instead of floats converted to strings as you do now. This would look like:
class myPreferences(context: Context){
val PREFERENCENAME = "BeerNote"
val PRICE = 0.0f
val ALCOHOLBYVOLUME = 0.0f
val priceKey = "price"
val SABVKey = "sabv"
val preference = context.getSharedPreferences(PREFERENCENAME,Context.MODE_PRIVATE)
fun setPrice(price:Float){
preference.edit().putFloat(priceKey,price).apply()
}
fun getPrice():Float{
return preference.getFloat(priceKey,0.0f)
}
fun setSABV(abv:Float){
preference.edit().putFloat(SABVKey,abv).apply()
}
fun getABV():Float{
return preference.getFloat(SABVKey,0.0f )
}
}
Related
I have a token like this:
hereeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyYWRvciI6eyJpZCI6NzAsIm5vbWUiOiJERUlWSVRJIiwidXN1YXJpbyI6IkRFSVZJVEkifSwiaWF0IjoxNjI5ODEyNDA1fQ.JqzQnFSbG6gFsnlJu3-bezxZ_N5e5FEzc9QvpRGu0u4
hide it:
alg: "HS256",
typ: "JWT"
}.
operador: {
id: 20,
nome: "JOAO",
usuario: "JOAO"
},
iat: 1629812405
}
Question is how do I get on android kotlin only user id to use in certain tasks?
You could use this,
https://github.com/auth0/JWTDecode.Android
Assuming the iat value is the user id,
var jwt: JWT = JWT(YOUR_TOKEN_STRING)
var claim: Claim = jwt.getClaim("iat")
//or as a string
var claim: String = jwt.getClaim("iat").asString()
I just fix the issue thanks to this:
private fun decodeToken(jwt: String): String {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return "Requires SDK 26"
val parts = jwt.split(".")
return try {
val charset = charset("UTF-8")
val header = String(Base64.getUrlDecoder().decode(parts[0].toByteArray(charset)), charset)
val payload = String(Base64.getUrlDecoder().decode(parts[1].toByteArray(charset)), charset)
"$header"
"$payload"
} catch (e: Exception) {
"Error parsing JWT: $e"
}
}
Then :
val mDecode = decodeToken(mToken)
val test = JSONObject(mDecode).getString("operador")
val mDecodeTokenOk = JSONObject(test).getString("id")
/** SALVANDO ID_OPERADOR */
mSharedPreferences.saveString(WmsConstantes.ID_OPERADOR,mDecodeTokenOk)
Log.e("------------------>", mDecodeTokenOk.toString());
You don't have to install any libraries. You can try something like this.
Class(s) reflecting your JWT payload
data class JwtPayload(
#SerializedName("iat")
val iat: Int,
#SerializedName("operador")
val operador: Operador
)
data class Operador(
#SerializedName("id")
val id: Int,
#SerializedName("nome")
val nome: String,
#SerializedName("usuario")
val usuario: String
)
You can use this class as a wrapper for your token
class Jwt(private val token: String) {
private val userData: JsonObject by lazy {
val userData = String(Base64.decode(token.split(".")[1], Base64.DEFAULT), StandardCharsets.UTF_8)
JsonParser.parseString(userData).asJsonObject
}
fun getUserData(): JwtPayload{
gson.toJson(userData, Jwt::class.java)
return gson.fromJson(userData, JwtPayload::class.java)
}
fun isExpired(): Boolean {
return userData.asJsonObject.get("exp").asLong < (System.currentTimeMillis() / 1000)
}
companion object {
#JvmStatic
private val gson = Gson()
}
}
Usage
val token = Jwt("YOUR_TOKEN")
val operatorID = token.operator.id
I have a problem for now in JetpackCompose.
The problem is, when I'm collecting the Data from a flow, the value is getting fetched from firebase like there is a listener and the data's changing everytime. But tthat's not that.
I don't know what is the real problem!
FirebaseSrcNav
suspend fun getName(uid: String): Flow<Resource.Success<Any?>> = flow {
val query = userCollection.document(uid)
val snapshot = query.get().await().get("username")
emit(Resource.success(snapshot))
}
NavRepository
suspend fun getName(uid: String) = firebase.getName(uid)
HomeViewModel
fun getName(uid: String): MutableStateFlow<Any?> {
val name = MutableStateFlow<Any?>(null)
viewModelScope.launch {
navRepository.getName(uid).collect { nameState ->
when (nameState) {
is Resource.Success -> {
name.value = nameState.data
//_posts.value = state.data
loading.value = false
}
is Resource.Failure<*> -> {
Log.e(nameState.throwable, nameState.throwable)
}
}
}
}
return name
}
The probleme is in HomeScreen I think, when I'm calling the collectasState().value.
HomeScreen
val state = rememberLazyListState()
LazyColumn(
state = state,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(post) { post ->
//val difference = homeViewModel.getDateTime(homeViewModel.getTimestamp())
val date = homeViewModel.getDateTime(post.timeStamp!!)
val name = homeViewModel.getName(post.postAuthor_id.toString()).collectAsState().value
QuestionCard(
name = name.toString(),
date = date!!,
image = "",
text = post.postText!!,
like = 0,
response = 0,
topic = post.topic!!
)
}
}
I can't post video but if you need an image, imagine a textField where the test is alternating between "null" and "MyName" every 0.005 second.
Check official documentation.
https://developer.android.com/kotlin/flow
Flow is asynchronous
On viewModel
private val _name = MutableStateFlow<String>("")
val name: StateFlow<String>
get() = _name
fun getName(uid: String) {
viewModelScope.launch {
//asyn call
navRepository.getName(uid).collect { nameState ->
when (nameState) {
is Resource.Success -> {
name.value = nameState.data
}
is Resource.Failure<*> -> {
//manager error
Log.e(nameState.throwable, nameState.throwable)
}
}
}
}
}
on your view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
viewModel.name.collect { name -> handlename
}
}
}
Currently, I am writing some test for my proto datastore. The only problem I have here is that I can't call a specific function because then my test fails / crashes. I find this very confusing, because all my other functions seem to work, except resetDatastore
Here is my code:
Repository
private companion object {
private const val SHOP_FILTER_PRODUCT_DATASTORE: String = "shop_filter_product_datastore_test"
private const val SHOP_FILTER_LIST_DATASTORE: String = "shop_filter_list_datastore_test"
private const val SHOP_FILTER_BTN_DATASTORE: String = "shop_filter_btn_datastore_test"
}
private val testNonVolatileProductDataStore = context.createDataStore(
fileName = SHOP_FILTER_PRODUCT_DATASTORE,
serializer = ShopFilterProductSerializer
)
private val testNonVolatileListDataStore = context.createDataStore(
fileName = SHOP_FILTER_LIST_DATASTORE,
serializer = ShopFilterListSerializer
)
private val testNonVolatileBtnDataStore = context.createDataStore(
fileName = SHOP_FILTER_BTN_DATASTORE,
serializer = ShopFilterBtnSerializer
)
override suspend fun setValueProduct(newProduct: ShopFilterTempHolder) {
if (newProduct.id == null || newProduct.mQuery == null) return
testNonVolatileProductDataStore.updateData { preferences ->
preferences.toBuilder().apply {
positionId = newProduct.id!!
query = newProduct.mQuery
}.build()
}
}
override suspend fun setValueList(newList: ShopFilterTempHolder) {
if (newList.id == null || newList.mQuery == null) return
testNonVolatileListDataStore.updateData { preferences ->
preferences.toBuilder().apply {
positionId = newList.id!!
query = newList.mQuery
mQueryDirection = newList.mQueryDirection
}.build()
}
}
override suspend fun setShopFilterBtn(value: Boolean) {
testNonVolatileBtnDataStore.updateData { preferences ->
preferences.toBuilder().apply {
isChecked = value
}.build()
}
}
override suspend fun peekProductValue(): ShopFilterTempHolder {
val temp = shopFilterProduct.first()
return ShopFilterTempHolder(temp.positionId, temp.query)
}
override suspend fun peekListValue(): ShopFilterTempHolder {
val temp = shopFilterList.first()
return ShopFilterTempHolder(temp.positionId, temp.query, temp.mQueryDirection)
}
override suspend fun peekBtnValue(): Boolean = mappedShopFilterBtn.first()
override suspend fun resetDatastore() {
testNonVolatileProductDataStore.updateData { preferences ->
preferences.toBuilder().apply {
positionId = Constants.SHOP_FILTER_DEFAULT_PRODUCT_ID
query = Constants.SHOP_FILTER_DEFAULT_PRODUCT_QUERY
}.build()
}
testNonVolatileListDataStore.updateData { preferences ->
preferences.toBuilder().apply {
positionId = Constants.SHOP_FILTER_DEFAULT_LIST_ID
query = Constants.SHOP_FILTER_DEFAULT_LIST_QUERY
mQueryDirection = Constants.SHOP_FILTER_DEFAULT_LIST_QUERY_DIRECTION
}.build()
}
testNonVolatileBtnDataStore.updateData { preferences ->
preferences.toBuilder().apply {
isChecked = true
}.build()
}
}
Test
#Test
fun `values should be set to default`() = runBlocking {
val newBtn = false
val newList = ShopFilterTempHolder(0, "testString", 0)
val newProduct = ShopFilterTempHolder(0, "testString", 0)
shopFilterValidator.tempBtnFilterValue = newBtn
shopFilterValidator.tempListFilter = newList
shopFilterValidator.tempProductFilter = newProduct
shopFilterValidator.setNewBtnFilter()
shopFilterValidator.setNewListFilter()
shopFilterValidator.setNewProductFilter()
assertEquals(newProduct, shopFilterDataStoreRepository.peekProductValue())
assertEquals(newList, shopFilterDataStoreRepository.peekListValue())
assertEquals(newBtn, shopFilterDataStoreRepository.peekBtnValue())
shopFilterValidator.deleteAllValues()
assertEquals(defautTempProductFilter, shopFilterDataStoreRepository.peekProductValue())
assertEquals(defaultTempListFilter, shopFilterDataStoreRepository.peekListValue())
assertEquals(defaultTempBtnFilterValue, shopFilterDataStoreRepository.peekBtnValue())
}
Stacktrace
Exception in thread "DefaultDispatcher-worker-2 #coroutine#5" java.io.IOException: Unable to rename C:\Users\Censored\AppData\Local\Temp\robolectric-Method_values_should_be_set_to_default1366629743868428403\com.example.app-dataDir\files\datastore\shop_filter_product_datastore_test.tmp.This likely means that there are multiple instances of DataStore for this file. Ensure that you are only creating a single instance of datastore for this file.
at androidx.datastore.core.SingleProcessDataStore.writeData$datastore_core(SingleProcessDataStore.kt:303)
at androidx.datastore.core.SingleProcessDataStore.transformAndWrite(SingleProcessDataStore.kt:280)
at androidx.datastore.core.SingleProcessDataStore$actor$1.invokeSuspend(SingleProcessDataStore.kt:165)
(Coroutine boundary)
at kotlinx.coroutines.CompletableDeferredImpl.await(CompletableDeferred.kt:86)
at androidx.datastore.core.SingleProcessDataStore$updateData$2.invokeSuspend(SingleProcessDataStore.kt:96)
at androidx.datastore.core.SingleProcessDataStore.updateData(SingleProcessDataStore.kt:96)
at com.example.app.repository.FakeDataStoreRepositoryImpl.deleteDataStore(FakeDataStoreRepositoryImpl.kt:86)
at com.example.app.data.models.validator.ShopFilterValidator$deleteAllValues$1.invokeSuspend(ShopFilterValidator.kt:80)
not sure if that could help you, but in my case the problem occurred when running tests on Windows machine and wasn't there when switching to Linux or executing the test on the emulator instead
I have an activity with two spinners. I have made arrays for each spinner containing data from popular foods, but I want the user to be able to add three of their own selections to the lists. The app compiles and installs and runs, BUT when I select the specific activity, the screen closes and either goes to the apps main screen or to the emulator's home screen. Logcat shows:-
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
It's where I call the shared preferences.
I have tried different contexts but still get errors that vary slightly according to the context and I have included them in the code as remarks.
I have tried moving everything into onCreate, but this gives me an error in the class definition line, because the function "override fun onItemSelected" seems to have to be stand-alone, so must be outside onCreate.
Please help. I have only been learning this for less than a year, and I apologise for any stupid mistakes. No offence is intended.
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
New 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
Attempted tidy code for override function. This still does absolutely Nothing
//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
When you assign a value at the declaration site like this:
val sharedPreferences:SharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
the function(s) you are calling to create the object that will be assigned to the property is getting called at the time the Activity is instantiated by Android. Unfortunately, this is too early to be calling anything that relies on the Activity being fully instantiated and set up, for example, anything that needs the Context as a constructor parameter.
The easy fix for this is to make these properties instantiate themselves lazily, so they are created after the Activity is already fully instantiated:
val sharedPreferences: SharedPreferences by lazy { getSharedPreferences(sharedPrefFile, MODE_PRIVATE) }
An alternate solution is the use a lateinit var and prepare the item in onCreate():
lateinit var sharedPreferences: SharedPreferences
// ...
override fun onCreate(bundle: SavedInstanceState) {
super.onCreate(bundle)
sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
}
I usually prefer the lazy method because it avoid splitting the declaration and the assignment so the code is easier to read. And it allows you to use val instead of var so the intent is clearer.
However, you also have many properties that are reliant on the SharedPreference instance, so they would all have to use one of the above solutions as well, which will lead to very verbose code. I recommend that you move all of these properties into a separate class that uses the SharedPreferences as a constructor paraamter. For example:
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...
}
and then in your 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) }
}
And then access your properties through the dataModel property. It is also better design practice to separate your UI and your functions that modify the data, so you could put those functions in your data model class.
You might also want to read up on how to use a ViewModel class. It would possibly be a more scalable solution than what I put above.
I am not able to retrieve data.
Is there any way by which we can access our database and we can check what we have inserted so far.
In this code I am trying to insert the latest calculation I did in my calculator with the number of my transaction. Using Coroutines, Room and View Model.
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
import com.kotlin_developer.calculator.database.CalculationDatabaseDao
import com.kotlin_developer.calculator.database.CalculatorHistory
import kotlinx.coroutines.*
import timber.log.Timber
class CalculatorViewModel(
val
database: CalculationDatabaseDao,
application: Application
) : AndroidViewModel(application) {
var operatorEnabled: Boolean = false
var firstResult: Double = 0.0
var operator: Char = ' '
var ifNumeric: Boolean = true
var secondResultLenght = 0
// First step of coroutines is to create job, this can cancel all the coroutines started by this view model
private var viewModelJob = Job()
// Second step is to create the scope where we want to run our code
// Scope determines what thread the coroutines will run on, it also needs to know about the job
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
// private val history = database.getAllCalculation()
private var _totalTransaction = MutableLiveData<Int>()
val totalTransaction: LiveData<Int>
get() = _totalTransaction
//Getting currentCalculation
private var _currentCalculation = MutableLiveData<String>()
val currentCalculation: LiveData<String>
get() = _currentCalculation
//Getting current result
private var _currentResult = MutableLiveData<String>()
val currentResult: LiveData<String>
get() = _currentResult
val navControler = MutableLiveData<NavController>()
private val _secondResult = MutableLiveData<Double>()
val secondResult: LiveData<Double>
get() = _secondResult
private var _resultTextValue = MutableLiveData<Double>()
val resultTextValue: LiveData<Double>
get() = _resultTextValue
private var _lastHistory = MutableLiveData<CalculatorHistory>()
val lastHistory: LiveData<CalculatorHistory>
get() = _lastHistory
val totalCalculation = mutableListOf<String>()
init {
Timber.i("Calculator View Model created")
_resultTextValue.value = 0.0
_secondResult.value = 0.0
_totalTransaction.value = 0
}
fun insertData() {
uiScope.launch {
val newHistory = CalculatorHistory(
totalTransaction.value?.toLong()!!,
totalCalculation[totalTransaction.value!! - 1]
)
insert(newHistory)
}
}
private suspend fun insert(newHistory: CalculatorHistory) {
withContext(Dispatchers.IO) {
database.insert(newHistory)
Timber.i("Data Inserted")
}
}
internal fun initializeHistory() {
uiScope.launch {
_lastHistory.value = getHistoryFromDatabase()
}
Timber.i("${lastHistory.value?.transactionNumber} and ${lastHistory.value?.calculation}")
}
private suspend fun getHistoryFromDatabase(): CalculatorHistory? {
return withContext(Dispatchers.IO) {
var lastCalculation = database.get(1)
lastCalculation
}
Timber.i("${lastHistory.value?.transactionNumber} and ${lastHistory.value?.calculation}")
}
fun calculator(operator: Char): Double {
return when (operator) {
'+' -> firstResult.plus(secondResult.value ?: 0.0)
'-' -> firstResult.minus(secondResult.value ?: 0.0)
'*' -> firstResult.times(secondResult.value ?: 1.0)
'/' -> firstResult.div(secondResult.value ?: 1.0)
else -> firstResult.rem(secondResult.value ?: 1.0)
}
}
fun createCalculation() {
ifNumeric = false
operatorEnabled = false
_resultTextValue.value = calculator(operator)
//This we can use in future to create a list of calculation
totalCalculation.add(
totalTransaction.value!!,
"${currentCalculation.value}=${_resultTextValue.value}"
)
_currentResult.value = totalCalculation[totalTransaction.value!!]
insertData()
firstResult = _resultTextValue.value ?: 0.0
_totalTransaction.value = _totalTransaction.value?.plus(1)
_secondResult.value = 0.0
secondResultLenght = 0
}
fun seprateNumber(number: Double) {
if (operatorEnabled) {
if (ifNumeric) {
_secondResult.value = number + (secondResult.value?.times(10.0))!!
} else {
_secondResult.value = number
}
} else {
firstResult = number + (firstResult * 10)
}
}
fun clearText() {
_resultTextValue.value = 0.0
_currentResult.value = ""
firstResult = 0.0
_secondResult.value = 0.0
secondResultLenght = 0
operator = ' '
operatorEnabled = false
ifNumeric = false
_currentCalculation.value = ""
}
fun ifSeprateNumber(number: Double) {
seprateNumber(number)
if (operatorEnabled) {
secondCalculationText()
} else {
_currentCalculation.value = firstResult.toString()
}
ifNumeric = true
}
fun secondCalculationText() {
_currentCalculation.value = _currentCalculation.value
?.removeRange(
_currentCalculation.value!!.length -
secondResultLenght, _currentCalculation.value!!.length
)
_currentCalculation.value =
"${_currentCalculation.value}${secondResult.value.toString()}"
secondResultLenght = secondResult.value.toString().length
ifNumeric = true
}
fun addTextToField() {
ifNumeric = false
operatorEnabled = true
_currentCalculation.value = "${_currentCalculation.value}${operator}"
}
override fun onCleared() {
super.onCleared()
//This cancels all the coroutines when the view model is getting closed
viewModelJob.cancel()
Timber.i("Calculator ViewModel destroyed")
}
}
I think you are doing too many things in your ViewModel. If you want to see what the data in your database is, you should have an activity (the View, the activity which creates, updates and handles UI events) that observes your livedata in the viewmodel. Everytime you insert values into your database, regardless of the time it takes, once it's done it will trigger a callback to the observer and you will get the new values. I would start with this, so you can keep track of what you are inserting in your database, and it's the starting point to then using those values.
Would be something like this, from your View/Activity:
yourViewModel.variableYouWant.observe(this, Observer { yourValue ->
//do something with yourValue
})