I tried to follow this tutorial, but an error occured when I try to assign a value to my Sh.Preference (prefs.token = "sometoken"):
kotlin.UninitializedPropertyAccessException: lateinit property prefs has not been initialized
I don't understand where's the bug, I also checked this thread.
Here are my code snippets
Prefs.kt :
class Prefs(context: Context) {
private val PREFS_FILENAME = "com.example.myapp.prefs"
private val PREFS_TOKEN = "token"
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0)
var token: String?
get() = prefs.getString(PREFS_TOKEN, "")
set(value) = prefs.edit().putString(PREFS_TOKEN, value).apply()
}
App.kt :
val prefs: Prefs by lazy {
App.prefs
}
class App : Application() {
companion object {
lateinit var prefs: Prefs
}
override fun onCreate() {
prefs = Prefs(applicationContext)
super.onCreate()
}
}
prefs.token has a default value of "", so why the logs said that has not been initialized?
Ok, problem found... The code was alright, I just missed to add this line
android:name=".App"
in the tag
<application in my Android Manifest.
In my case, i just initialize SharedPreference in onCreate(), and all works
For example: MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppPreferences.init(this) // added this
}
SharedPreferences object:
object AppPreferences {
private const val NAME = "SpinKotlin"
private const val MODE = Context.MODE_PRIVATE
private lateinit var preferences: SharedPreferences
// list of app specific preferences
private val IS_FIRST_RUN_PREF = Pair("is_first_run", false)
fun init(context: Context) {
preferences = context.getSharedPreferences(NAME, MODE)
}
/**
* SharedPreferences extension function, so we won't need to call edit() and apply()
* ourselves on every SharedPreferences operation.
*/
private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
val editor = edit()
operation(editor)
editor.apply()
}
// getter and setter with Shared Preference
var temperature: Float
// custom getter to get a preference of a desired type, with a predefined default value
get() = preferences.getFloat("temp",1f )
// custom setter to save a preference back to preferences file
set(value) = preferences.edit {
it.putFloat("temp", value)
}
}
Related
There are two classes MainActivity and PickTimeForNotif in my project. In MainActivity getSharedPreferences works just fine, i can save my data and get it back. In PickTimeForNotif, however, the same method seems to do nothing.
Here's my simplified MainActivity class:
class MainActivity : AppCompatActivity(), ChangeCupDialogFragment.StringListener {
#SuppressLint("SetTextI18n")
//this is variable i'm saving
private var drankToday = 0
//function in which i save my value to SharedPreferences
private fun saveWaterCountToInternalStorage(clickCounter: Int) {
val sharedPref = this.getSharedPreferences("something", Context.MODE_PRIVATE)
with (sharedPref.edit()){
putInt(getString(R.string.clickCount), clickCounter)
apply()
}
}
//and here i get it from there
private fun loadWaterCountToInternalStorage(): Int {
val sharedPref = this.getSharedPreferences("something", Context.MODE_PRIVATE)
return sharedPref.getInt(getString(R.string.clickCount), drankToday)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
val setupNotifButton = findViewById<Button>(R.id.setupNotifButton)
setupNotifButton.setOnClickListener{
val notifIntent = Intent(applicationContext, PickTimeForNotif::class.java)
startActivity(notifIntent)
}
}
}
In setOnClickListener i intend my second activity PickTimeForNotif, here it is.
class PickTimeForNotif: AppCompatActivity(), TimePickerFragment.OnCompleteListener {
val APP_PREFERENCES = "settings"
private val SAVED_FROM_HOUR = "SetFromHour"
private var FROM_HOUR = 99
private fun saveTimeToInternalStorage(prefName1: String, Hour:Int) {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
with (sharedPref.edit()){
putInt(prefName1, Hour)
apply()
}
}
private fun loadTimeFromInternalStorage() {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
if (sharedPref.contains(APP_PREFERENCES)) {
sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.pick_time_activity)
saveTimeToInternalStorage(SAVED_FROM_HOUR, 1)
loadTimeFromInternalStorage()
Toast.makeText(applicationContext,"$FROM_HOUR", Toast.LENGTH_SHORT).show()
}
}
In the code above i'm trying to set value (1 for example ) to a SAVED_FROM_HOUR key and then get it back and assign to FROM_HOUR variable. However, the Toast shows 99, which means that new data wasn't loaded properly. I tried putting all code from loadTimeFromInternalStorage and saveTimeToInternalStorage to onCreate, but the result is same.
I also tried checking if the Preferences file exists after i call getSharedPreferences with
if (sharedPref.contains(APP_PREFERENCES))
but it does not.
So i'm asking to explain what am i doing wrong and why i can save the data in my MainActivity, but not in the second one. Thanks alot to anyone in advance!!
In loadTimeFromInternalStorage(), you are fetching the value but not assigning to variable like this:
private fun loadTimeFromInternalStorage() {
val sharedPref = this.getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE)
if (sharedPref.contains(APP_PREFERENCES)) {
FROM_HOUR = sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR)
}
}
Also, in this line FROM_HOUR = sharedPref.getInt(SAVED_FROM_HOUR, FROM_HOUR), the last parameter in getInt() method is the default value so you should make another constant for it or supply it 0.
I am writing Junit test for shared preferences but facing below issue
Method
public void storeUser(User user) {
final SharedPreferences userPrefs = context.getSharedPreferences(USER_PREFS, Context.MODE_PRIVATE);
userPrefs.edit().putString(USER_ID_KEY, user.getUserID()).apply();
}
Junit Class
class UserRepoTest {
private lateinit var context: Context
private lateinit var userRepo: UserRepo
private lateinit var userPrefs: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
#Before
fun setup() {
context = mock()
userRepo = UserRepo(context)
userPrefs = mock()
editor = mock()
}
#Test
fun `check mocked instances are not null`() {
context assertNotEquals null
userRepo assertNotEquals null
}
#Test
fun `when store user then load user`() {
whenever(context.getSharedPreferences(USER_PREFS, Context.MODE_PRIVATE)).thenReturn(userPrefs)
whenever(userPrefs.edit()).thenReturn(editor)
val user = getUser()
verify(editor, times(1)).putString(USER_ID_KEY, user!!.userID).apply()
// verify(userPrefs.edit(), times(1)).putString(USER_ID_KEY, user!!.userID).apply()
userRepo.storeUser(user)
}
private fun getUser(): User? {
return User().apply {
userID = "test#yopmail.com"
}
}
companion object {
const val USER_PREFS = "userprefs"
const val USER_ID_KEY = "UserID";
}
}
but getting below error not sure why its saying not invoked
Wanted but not invoked:
editor.putString(
"UserID",
"test#yopmail.com"
);
-> at com.example.UserRepoTest.when store user then load user(UserRepoTest.kt:41)
Actually, there were zero interactions with this mock.
Order is important. Call the action storeUser() before you try to verify what it did.
I am trying to make a simple Kotlin Class user for Android and i would like to save the username to keep it when closing and reopening the application
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
class User (var username : String){
private val PREFERENCE_FILE_KEY = "AppPreference"
private val KEY_USERNAME= "prefUserNameKey"
private val sharedPref : SharedPreferences = activity?.getSharedPreferences(PREFERENCE_FILE_KEY, MODE_PRIVATE)
fun Save(){
with (sharedPref.edit()) {
putString(KEY_USERNAME, username)
commit()
}
}
fun Load(){
teamname = sharedPref.getString(KEY_SAVENAME, "Default")?:"Default"
}
}
I have a first question :
android studio says sharedPref.getString(KEY_SAVENAME, "Default") is a String?, but then what is the purpose of the default value here then ?
And secondly, my real problem here is that the activity? keyword has an unresolved reference.
Better practice is to pass shared preference instance when constructing user object because user class shouldn't care how shared preferences is built or about activity:
class User (var username : String, private val sharedPref : SharedPreferences){
}
Also, when getting shared preference instance, use application context not activity to prevent memory leaks:
val sharedPref : SharedPreferences = applicationContext.getSharedPreferences(PREFERENCE_FILE_KEY, MODE_PRIVATE)
val user = User("username", sharedPref)
user.Save()
Firstly, sharedPref.getString(KEY_SAVENAME, "Default") returns a String?, because the "Default"- String can be null. For more information look here.
Secondly, try using
preferences = PreferenceManager.getDefaultSharedPreferences(context)
to initiate the preference instance.
private lateinit var sharedpreferences: SharedPreferences
companion object {
val mypreference = "mypref"
val Name = "nameKey"
}
private fun Save(userName : String) {
val editor = sharedpreferences.edit()
editor.putString(Name, userName)
etName.setText("username")
editor.apply()
}
private fun Get(userName: String) {
sharedpreferences = getSharedPreferences(mypreference, Context.MODE_PRIVATE)
if (sharedpreferences.contains(Name)) {
val setString = sharedpreferences.getString(Name, userName)
etName.setText(getString)
}
}
//Call funtion in onCreate method
private fun containUserString(){
sharedpreferences = getSharedPreferences(mypreference, Context.MODE_PRIVATE)
if (sharedpreferences.contains(Name)) {
val setString = sharedpreferences.getString(Name, "")
etName.setText(getString)
}
}
Extension functions are great for the SharedPreference api in android. Jake Wharton has an interesting implementation at time code 32:30 of this video tutorial where he implements SharedPreferences extension function like so:
preferences.edit{
set(USER_ID /*some string key constant somewhere*/, 42)
//...
}
while this is ok, its kind of verbose.
This tutorial by Krupal Shah explains how you can reduce the getter/setter extension functions of SharedPreferences to:
preferences[USER_ID] = 42
Log.i("User Id", preferences[USER_ID]) //User Id: 42
This is pretty good, but the brackets imply iterable semantics, IMO. While not the worst thing in the world, you just wish that you could implement a field extension of a SharedPreferences value by the key constant itself.
My question is, is there any way to implement this type of extension on SharedPreferences?
preferences.USER_ID = 42
Log.i("User Id", preferences.USER_ID) //User Id: 42
First, let's create general interface for providing instance of SharedPreferences:
interface SharedPreferencesProvider {
val sharedPreferences: SharedPreferences
}
After we have to create delegate for property which will read/write value to preferences:
object PreferencesDelegates {
fun string(
defaultValue: String = "",
key: String? = null
): ReadWriteProperty<SharedPreferencesProvider, String> =
StringPreferencesProperty(defaultValue, key)
}
private class StringPreferencesProperty(
private val defaultValue: String,
private val key: String?
) : ReadWriteProperty<SharedPreferencesProvider, String> {
override fun getValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>
): String {
val key = key ?: property.name
return thisRef.sharedPreferences.getString(key, defaultValue)
}
override fun setValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>,
value: String
) {
val key = key ?: property.name
thisRef.sharedPreferences.save(key, value)
}
}
PreferencesDelegates needed to hide implementation and add some readability to code. In the end it can be used like this:
class AccountRepository(
override val sharedPreferences: SharedPreferences
) : SharedPreferencesProvider {
var currentUserId by PreferencesDelegates.string()
var currentUserName by string() //With import
var currentUserNickname by string(key = "CUSTOM_KEY", defaultValue = "Unknown")
fun saveUser(id: String, name: String) {
this.currentUserId = id
this.currentUserName = name
}
}
Similar can be implemented int, float or even custom type:
open class CustomPreferencesProperty<T>(
defaultValue: T,
private val key: String?,
private val getMapper: (String) -> T,
private val setMapper: (T) -> String = { it.toString() }
) : ReadWriteProperty<SharedPreferencesProvider, T> {
private val defaultValueRaw: String = setMapper(defaultValue)
override fun getValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>
): T {
val key = property.name
return getMapper(thisRef.sharedPreferences.getString(key, defaultValueRaw))
}
override fun setValue(
thisRef: SharedPreferencesProvider,
property: KProperty<*>,
value: T
) {
val key = property.name
thisRef.sharedPreferences.save(key, setMapper(value))
}
}
I wrote small library which covers such case. You can find rest of implemented preferences here
EDIT. In case if you are using dagger:
class AccountRepository #Injcet constructor() : SharedPreferencesProvider {
#Inject
override lateinit var sharedPreferences: SharedPreferences
var currentUserId by PreferencesDelegates.string()
...
}
You could define a simple extension property with a getter and a setter
var SharedPreferences.userId
get() = getInt(USER_ID, 0)
set(value: Int) { edit().putInt(USER_ID, value).apply() }
So, basically I have a class:
class App : Application() {
lateinit var prefs: SharedPreferences
}
Now, I want to add a delegated property:
var isInitialized: Boolean by prefs.boolean()
The problem is that this, isInitialized property must be initialized lazily since I'm using Android Dagger2 framework, which performs injection after App creation (during calling onCreate() method):
class App : Application() {
lateinit var prefs: SharedPreferences
var isInitialized: Boolean = false
override fun onCreate() {
super.onCreate()
// how can I assign a delegate to isInitialized?
}
}
I would like it to be done either via:
lazy initialization during declaration (which is delegate in delegate - wondering whether this possible?)
lazy initialization during assignment
Is there any way of doing this?
Thanks!
You could do it with an indirection:
class DoubleDelegate<R, T>(var realDelegate: ReadWriteProperty<R, T> = /* some default */) : ReadWriteProperty<R, T> by realDelegate
then
val isInitializedDelegate = DoubleDelegate<App, Boolean>()
var isInitialized: Boolean by isInitializedDelegate
override fun onCreate() {
super.onCreate()
isInitializedDelegate.realDelegate = prefs.boolean()
}
Somehow I don't think this is actually a good idea.
Use Lazy
From the document Lazy Gets the lazily initialized value of the current Lazy instance. Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
Application class
val prefs: Prefs by lazy {
App.prefs!!
}
class App : Application() {
companion object {
var prefs: Prefs? = null
}
override fun onCreate() {
prefs = Prefs(applicationContext)
super.onCreate()
}
}
your data model class should be like this
class Prefs (context: Context) {
val PREFS_FILENAME = "com.teamtreehouse.colorsarefun.prefs"
val IsInitialized = "isInitialized"
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0);
var initialized: Boolean
get() = prefs. getBoolean(IsInitialized, false)
set(value) = prefs.edit(). putBoolean(IsInitialized, value).apply()
}
then use Activity or fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val initialized = prefs.initialized //getvalue
selectvalue(false)// set value
}
private fun selectvalue(value: Boolean) {
prefs.initialized = value
}
}
more details refer this example SharedPreferences Easy with Kotlin