I have been trying to implement a checked box where is user says keep me signed in they stay signed in. The problem however is I am wondering how can I store this value in the pref and and if its positive they just log in.
in my viewModel I have this function
fun keepSignedIn(updatedValue: Boolean){
keepSignedIn.value = updatedValue
}
and in my content screen
#Composable
fun KeepMeSignedInCheckBox() {
Checkbox(checked = state.keepMeSignedIn, onCheckedChange = keepMeSignedIn.invoke(it))
}
I am wondering what is the best way to update this value in the view Model? I have my login function in the view model too.
What I have tried,
fun keepSignedIn(updatedValue: Boolean){
keepSignedIn.value = updatedValue
val signedIn = instance().keepUserSignedIn
if(signedIn){
loginUser
}
}
But my approach does not work when I leave the app it take me to login screen, and does not store that session.
Initialize the variable as
val sessionStored by mutableStateOf(
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.getBoolean("isSessionStored", false)
)
Where "prefs" is the name you assigned to the preferences, MODE_PRIVATE is the mode of accessing the SharedPreferences, "isSessionStored" the name of the preference field and false its default value, for obvious reasons.
Use it like so
Checkbox(checked = state.sessionStored, onCheckedChange = viewModel::setSessionStored)
Define setSessionStored like so
fun setSessionStored(sessionStored: Boolean) {
this.sessionStored = sessionStored
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit()
.putBoolean("isSessionStored", sessionStored)
.apply()
}
'll Work like a charm,
Here's a piece to code to https://youtu.be/RsEoX0FbETI
Related
I am experimenting around with Kotlin's sharedPreferences, however I cannot seem to get the updated value to stick.
val sharedPreferences = getSharedPreferences("Files", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putInt("numbers",1).apply()
val textview = findViewById<TextView>(R.id.textview)
textview.text = sharedPreferences.getInt("numbers",0).toString()
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
editor.putInt("numbers",2).apply()
textview.text = sharedPreferences.getInt("numbers",0).toString()
}
In the code above I set the initial value of the sharedPreference to 1, upon clicking the button the value will be updated to 2 and displayed.That works fine however when closing the app and reopening it, the value reverts to 1. Is there a way to permanatly keep the updated value?
You are setting it to that value every time you open the activity, since onCreate() is called every time it opens. You should check if the value is already set, and if it is, skip that line of code.
if ("numbers" !in sharedPreferences) {
val editor = sharedPreferences.edit()
editor.putInt("numbers",1).apply()
}
By the way, there is an extension function for editing without having to call apply and editor. repeatedly:
if ("numbers" !in sharedPreferences) {
sharedPreferences.edit {
putInt("numbers",1)
}
}
I think I haven't quite wrapped my head around how compose states work yet. I'm not able to trigger a recomposition when an item in the uiState changes.
I'm building an app that need notification access, so for that I'm navigating the user to the settings and after the user has granted permission they have to navigate back to the app. That's where I want to trigger the recomposition.
I have the permission check in onResume working and the variable in the uiState changes, but the recomposition doesn't get called. What am I missing here?
Composable
#Composable
private fun MainLayout(viewModel: SetupViewModel){
val uiState = viewModel.uiState.collectAsState()
SetupItem(
title = "Notification access",
summary = if(uiState.value.hasNotificationPermission) stringResource(R.string.granted) else stringResource(R.string.not_granted){}
}
SetupUiState.kt
data class SetupUiState(
var hasNotificationPermission: Boolean = false
)
I know for a fact that hasNotificationPermission gets set to true, but the summary in the SetupItem does not update. How do I accomplish that?
The problem here is that the hasNotificationPermission field is mutable (var and not val). Compose is not tracking inner fields for change. You have two options here:
Modify the SetupUiState as a whole, assuming you are using StateFlow in your ViewModel, it can look like this:
fun setHasNotificationPermission(value: Boolean) {
uiState.update { it.copy(hasNotificationPermission = value) }
}
You should also change hasNotificationPermission from var to val.
You can make use of compose's State and do something like this:
class SetupUiState(
initialHasPermission: Boolean = false
) {
var hasNotificationPermission: Boolean by mutableStateOf(initialHasPermission)
}
With this you can then simply do uiState.hasNotificationPermission = value and composition will be notified, since it's tracking State instances automatically.
I'm learning Jetpack compose and I'm having a hard time.
I have a DataStoreUtil class which sets and gets a Boolean value. Default value is true (if not found).
class DataStoreUtil(private val context: Context) {
// to make sure there's only one instance
companion object {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("settings")
val FORCE_DARK_THEME = booleanPreferencesKey("force_dark_theme")
}
//get the saved email
val getForceDarkTheme: Flow<Boolean> = context.dataStore.data
.map { preferences ->
preferences[FORCE_DARK_THEME] ?: true
}
//save email into datastore
suspend fun saveForceDarkTheme(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[FORCE_DARK_THEME] = value
}
}
}
I have tested it and it works.
On the other side I have a Switch which should manage this Boolean value.
val value = dataStoreUtil.getForceDarkTheme.collectAsState(initial = true).value
var checked by remember { mutableStateOf(value) }
Switch(checked = checked, onCheckedChange = {
checked = it
scope.launch {
dataStoreUtil.saveForceDarkTheme(checked)
}
})
Saving this value via Switch works but not initial state, which is ALWAYS set to true on screen open.
I guess it's because collectAsState(initial = true) but... how can achieve this goal? (Have it set to false if DataStoreUtil returns false.
Any help is appreciated.
You will need to read the value synchronously when you start the app, using runBlocking.
fun getForceDarkThemeSync() = runBlocking {
getForceDarkTheme.first()
}
Edit: if you want to hold the splash screen until you have read the data and made a determination on whether to use light or dark mode, check this.
Assume there is a FilterActivity with Switch and EditText controls. The latter has input disabled but is clickable. A click on it launches TypeActivity to pick a type value from and then gets the type name populated into the EditText.
There is a FilterViewModel with a StateFlow<FilterUiState>
data class FilterUiState(
var status: Boolean = false,
var type: String = "sometype"
)
which is collected by the activity in onCreate like this
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
binding.run {
statusSwitch.isChecked = it.status
typeEditText.text = it.type
}
}
}
}
The problem is if the user changes the statusSwitch state and then clicks on the typeEditText to pick a type, then on return back the FilterActivity gets resumed which triggers the uiState.collect and consequently resets the statusSwitch check state back to the initial value.
What is the right way to prevent UI changes from being overridden by StateFlow collect?
You need to actually update the state flow with the latest state you want it to hold, and then it will properly restore the latest state when collected.
Also, don't use a mutable class for this. It's error prone to mix mutable classes with StateFlows. The StateFlow cannot detect that a change has occurred if you mutate the instance it is already holding.
data class FilterUiState(
val status: Boolean = false,
val type: String = "sometype"
)
// in ViewModel class:
private val mutableUiState = with(PreferenceManager.getDefaultSharedPreferences(context)) {
val status = //...get these from shared preferences
val type = //...
val initialState = FilterUiState(status, type)
MutableStateFlow(initialState)
}
val uiState = mutableUiState.asStateFlow()
fun updateStatus(status: Boolean) {
mutableUiState.value = mutableUiState.value.copy(status = status)
// and update the SharedPreferences value
}
fun updateType(type: String) {
mutableUiState.value = mutableUiState.value.copy(type = type)
// and update the SharedPreferences value
}
And call these two update functions from the Activity when these values change. You can add a click listener to the checkbox to do this for the "status" and for the EditText, since you have input disabled, you can call the updateType() function instead of directly modifying its text when returning from the other Activity. Your existing collector will update the text in the widget for you when you update the state in the view model.
I need my users to be able to enter an API key in a "Setup" fragment and I need to use this API key in various other places such as other Fragments, Activities, Workers.
It is my understanding so far that getSharedPreferences is designed for this sort of purpose, much like the NSUserDefaults under iOS: save something somewhere, get it elsewhere.
Yet I can't seem to get the getSharedPreferences thing to work, I've had it initialized throughout the app with MainActivity.context but it always loses the data (the API key)
I am using ModelPreferencesManager https://gist.github.com/malwinder-s/bf2292bcdda73d7076fc080c03724e8a
I have an ApplicationState class as follows:
public class ApplicationState : Application() {
companion object {
// ...
lateinit var mContext: Context
var api_key : String = "undefined"
// ...
}
fun save(){
Log.e("ApplicationState", "save")
ModelPreferencesManager.with(mContext)
ModelPreferencesManager.put(api_key , key: "api_key_identifier")
}
fun load(){
Log.e("ApplicationState", "load")
ModelPreferencesManager.with(mContext)
api_key = ModelPreferencesManager.get<String>(key: "api_key_identifier") ?: "not read"
}
}
First, I store the application context on the first Activity (before anything else):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
ApplicationState.mContext = applicationContext
// ...
}
}
I now expect to be able to save the api_key as follows:
ApplicationState.api_key = "blablah" // from some input in a random fragment
ApplicationState.save()
And to load it later:
ApplicationState.load()
var api_key = ApplicationState.api_key // in some activity or random fragment or worker
However it doesn't produce the expected result, the api_key is not saved (or loaded? can't figure out)
I have also tried using a JSON file but still no luck, looks like it either doesn't write/read or just gets deleted for some reason.
I could use a helping hand from someone more experienced as I am new to Android development and can't seem to find my way through these intricacies
I don't know what you are doing with your ModelPreferencesManager.
But this is the standard way to save something in preferences.
val sharedPref = requireContext().getSharedPreferences(keyPrefIdentifier,
Context.MODE_PRIVATE) //get shared preferences
val editor = sharedPref.edit() //make modifications to shared preferences
editor.putString("userApiKeyIdent", "theActualKey")
editor.apply() //save shared preferences persitent.
This is how you read them again.
val sharedPref = requireContext().getSharedPreferences(keyPrefIdentifier,
Context.MODE_PRIVATE)
val apiKey = sharedPref.getString("userApiKeyIdent", "defaultValue")
Edit: You are saving the api key as the identifiery for the preference. But get it as "api key "
It should look like this
fun save(){
Log.e("ApplicationState", "save")
ModelPreferencesManager.with(mContext)
ModelPreferencesManager.put("api_key ", api_key) //like this
}