I'm using SharedPreferences in SettingsFragment:
val myPrefs = activity?.getSharedPreferences("myPrefs", Context.MODE_PRIVATE) ?: return
val testEnvEnabled = myPrefs.getBoolean(getString(R.string.saved_test_env_key), false)
This is working just fine.
Then I need to ready those preferences in object Api : KoinComponent {
I cannot get there context or activity.
Is there some another way to get information from shared preferences?
For me it's important just to save one information, if test env. is on or off.
I'm saving that information in SettingsFragment over switch.
Thank you
You can use Like this;
class Api : KoinComponent {
private val sharedPreferences by inject<SharedPreferences> {
parametersOf("myPref")
}
fun test() {
sharedPreferences.edit().putInt("Test", 1).commit()
}
fun get(): Int {
return sharedPreferences.getInt("Test", -1)
}
}
val sharedPreferences = module {
factory { key ->
androidContext().getSharedPreferences(key.get<String>(0), Context.MODE_PRIVATE)
}
factory {
Api()
}
}
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#App)
modules(sharedPreferences)
}
}
}
enter code here
Related
I am trying to save a name in SharedPreferences. The app is set up so that it recognizes devices by their MAC address. There's an EditText view that populates with the advertised name of the BLE peripheral, and then the user can alter the name inside of the EditText. Once the EditText loses focus, the Data class is supposed to save the user input in SharedPreferences, where the address of the device is the key and the name of the device is the value. Instead, when the EditText loses focus, the name just reverts to the original name found in the BLE advertisement.
I have already looked for answers from these resources (1, 2, 3, 4, 5).
As far as I can tell, I have everything set up correctly in my application, but I am not getting the results I'm looking for. I have three main classes: MainActivity, ScanResultAdapter, and Data. MainActivity is the driver of the program, ScanResultAdapter manages the incoming BLE advertisements from peripheral devices, and Data is a singleton class that manages information for the entire application.
This is how I have it set up in my application:
MainActivity
...
// declaration of the myPrefs variable
lateinit var myPrefs: SharedPreferences
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// instantiation of the myPrefs variable
myPrefs = PreferenceManager.getDefaultSharedPreferences(this)
// passing the context to the data-tracking Data class so that it can access MainActivity's SharedPreferences
data = Data.getInstance(this)
mainBinding.saveNamesButton.onClick {
mainBinding.saveNamesButton.requestFocusFromTouch()
}
rowScanResultBinding.deviceName.onFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (!hasFocus) {
val name = rowScanResultBinding.deviceName.text.toString()
val address = rowScanResultBinding.macAddress.text.toString()
if (data.sharedPref.contains(address) && rowScanResultBinding.deviceName.text.toString() != data.sharedPref.getString(address, null)) {
rowScanResultBinding.deviceName.setText(data.sharedPref.getString(address, null))
}
}
}
}
...
override fun onPause() {
super.onPause()
// TODO: Fix this line to resolve an initialization error
data.saveSharedPreference()
}
ScanResultAdapter
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (items[position].device.name == "EES-TEMP-1") {
val item = items[position]
if (!data.names.contains(item.device.address)) {
data.names[item.device.address] = item.device.name
data.sharedPref.edit().putString(item.device.address, item.device.name).apply()
}
println(data.names)
holder.bind(item)
} else return
}
...
inner class ViewHolder(
private val view: View,
private val onClickListener: ((device: ScanResult) -> Unit)
) : RecyclerView.ViewHolder(view) {
...
with(view) {
if (data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(data.getName(result.device.address))
} else if (!data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(result.device.address)
}
}
Data
class Data(private val context: Context) {
companion object {
private var instance: Data? = null
fun getInstance(context: Context): Data {
return instance ?: synchronized(this) {
instance ?: Data(context.applicationContext).also { instance = it }
}
}
}
val names: MutableMap<String, String> = mutableMapOf()
val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
val addresses = mutableSetOf<String>()
fun saveSharedPreference() {
sharedPref.edit {
for ((key, value) in names) {
if (!sharedPref.contains(key)) {
putString(key, value)
apply()
}
}
}
}
fun getName(address: String): String? {
if (sharedPref.contains(address)) {
return sharedPref.getString(address, "Unnamed")
}
return "failed"
}
fun hasName(key: String): Boolean {
return sharedPref.contains(key)
}
fun addName(key: String, value: String) {
names[key] = value
}
fun addAddress(address: String) {
addresses.add(address)
}
fun addItem(address: String, name: String) {
sharedPref.edit().putString(address, name).apply()
}
}
My DataStore keeps returning null even though I've set a default value on the preferences manager using the elvis operator. Also, my edit function to set a preference on a key-value pair isn't being called so I'm not even sure my datastore is properly setup in general. I'm pretty sure the class is properly injected though, because I can see it as a variable while using breakpoints.
Basically val countryCode = viewModel.countrySettings.value on the ViewModel always returns null
PreferencesManager class
const val TAG = "PreferencesManager"
const val DEFAULT_COUNTRY_PREFERENCE = "us"
const val DEFAULT_CATEGORY_PREFERENCE = "general"
private val Context.dataStore by preferencesDataStore(name = PREFERENCES_NAME)
#Singleton
class PreferencesManager #Inject constructor(#ApplicationContext appContext: Context) {
private val preferencesDataStore = appContext.dataStore
//Pairs are separated but I'll create an appropriate data class later.
val countrySettings = preferencesDataStore.data
.catch { exception ->
if (exception is IOException) {
Log.e(TAG, "Error while trying to read user preferences", exception)
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preference ->
val country = preference[PreferenceKeys.COUNTRY] ?: DEFAULT_COUNTRY_PREFERENCE
country
}
val categorySettings = preferencesDataStore.data
.catch { exception ->
if (exception is IOException) {
Log.e(TAG, "Error while trying to read user preferences", exception)
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
val category = preferences[PreferenceKeys.CATEGORY] ?: DEFAULT_CATEGORY_PREFERENCE
category
}
suspend fun setCountryPreference(country: String) {
preferencesDataStore.edit { preference ->
preference[PreferenceKeys.COUNTRY] = country
}
}
suspend fun setCategoryPreference(category: String) {
preferencesDataStore.edit { preference ->
preference[PreferenceKeys.CATEGORY] = category
}
}
private object PreferenceKeys {
val COUNTRY = stringPreferencesKey("country")
val CATEGORY = stringPreferencesKey("category")
}
}
ViewModel
class MainViewModel #Inject constructor(
private val repository: Repository,
private val preferencesManager: PreferencesManager
): ViewModel() {
val countrySettings = preferencesManager.countrySettings.asLiveData()
val categorySettings = preferencesManager.categorySettings.asLiveData()
/* .... */
fun setCountryPreference(country: String) {
viewModelScope.launch {
preferencesManager.setCountryPreference(country)
}
}
fun setCategoryPreference(category: String) {
viewModelScope.launch {
preferencesManager.setCategoryPreference(category)
}
}
}
Fragment
val viewModel: MainViewModel by activityViewModels()
private var _binding: FragmentSettingsCountryScreenBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentSettingsCountryScreenBinding.bind(view)
//Using breakpoints I've noticed this function isn't even called on the preferences manager to set the value, which is weird
viewModel.setCountryPreference("us")
val countryCode = viewModel.countrySettings.value
binding.radiogroup.check(adaptPreferenceFromDataStore(countryCode!!))
binding.radiogroup.setOnCheckedChangeListener { _, checkedId ->
viewModel.setCountryPreference(adaptPreferenceToDataStore(checkedId))
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Yes, ianhanniballake is correct, it was a newbie mistake - I completely forgot I had to observe the livedata value and just then set the UI parameters. I was trying to set the preferences based on the value of a few switches (and vice-versa). Here's the proper function for setting it up:
fun setupSwitches() {
viewModel.countrySettings.observe(viewLifecycleOwner, { preference ->
binding.radioGroup.check(adaptPreferenceFromDataStore(preference))
})
binding.radioGroup.setOnCheckedChangeListener { _, checkedId ->
viewModel.setCountryPreference(adaptPreferenceToDataStore(checkedId))
}
}
Then called setupSwitches() on onViewCreated.
I am learning Kotlin by trying to build a small app that find and and remember last connected BLE device. To recognize the last connected device I decide to save its MAC address using shared preferences (is that the best way to do that is also a question). I use a tutorial online and it worked well (I didn't remember the page) but today when I open the project to continue the job it gives me error - unresolved reference getSharedPreferences. My question is what is the problem - I get lost :) Here is the class where I have the error row 23.
import android.content.Context
import android.content.SharedPreferences
interface PreferencesFunctions {
fun setDeviceMAC(deviceMAC: String)
fun getDeviceMAC(): String
fun setLastConnectionTime(lastConnectionTime: String)
fun getLastConnectionTime(): String
fun clearPrefs()
}
class PreferenceManager(context: ScanResultAdapter.ViewHolder) : PreferencesFunctions{
private val PREFS_NAME = "SharedPreferences"
private var preferences: SharedPreferences
init {
preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
override fun setDeviceMAC(deviceMAC: String) {
preferences[DEVICE_MAC] = deviceMAC
}
override fun getDeviceMAC(): String {
return preferences[DEVICE_MAC] ?: ""
}
override fun setLastConnectionTime(lastConnectionTime: String) {
preferences[LAST_CONNECTION_TIME] = lastConnectionTime
}
override fun getLastConnectionTime(): String {
return preferences[LAST_CONNECTION_TIME] ?: ""
}
override fun clearPrefs() {
preferences.edit().clear().apply()
}
companion object{
const val DEVICE_MAC = "yyyyyyy"
const val LAST_CONNECTION_TIME = "zzzzzzz"
}
}
Your arguement context is not a acitivity or fragment, and you need those two to call getSharedPreferences method.
class PreferenceManager(context: Context) : PreferencesFunctions{
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
I'm starting to use Kotlin on a little demo android app. I've created a sharedpreferences helper class which i'm trying to test with Junit and Mockito. Below is my sharedprefshelper:
public class SharedPrefsHelperImp( cont : Context) : SharedPrefsHelper {
val prefsname: String = "prefs"
var prefs: SharedPreferences? = null
var edit: SharedPreferences.Editor? = null
init {
prefs = cont.getSharedPreferences(prefsname, Context.MODE_PRIVATE)
edit = prefs!!.edit()
}
override fun getPrefsStringValue(key: String) : String {
return prefs!!.getString(key, "")
}
override fun addPrefsStringVal( key : String, value: String) {
edit!!.putString(key, value).commit()
}
override fun getSharedPrefsBool(key : String): Boolean {
return prefs!!.getBoolean(key, false)
}
override fun addSharedPrefsBool(key : String, value : Boolean) {
edit!!.putBoolean(key, value).commit()
}
}
here is my test class:
class SharedPrefsHelperImpTest {
#Mock var cont : Context? = null
#Mock var mockprefs : SharedPreferences? = null
#Mock var mockprefsedit : SharedPreferences.Editor? = null
var prefshelper : SharedPrefsHelper? = null
#Before
fun setUp() {
//MockitoAnnotations.initMocks(this)
cont = Mockito.mock(Context::class.java)
mockprefs = Mockito.mock(SharedPreferences::class.java)
mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)
`when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
`when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)
prefshelper = SharedPrefsHelperImp(cont!!)
}
#Test
fun testNotNull(){
Assert.assertNotNull(cont)
Assert.assertNotNull(mockprefs)
Assert.assertNotNull(mockprefsedit)
}
#Test
fun testItemAdded()
{
prefshelper!!.addPrefsStringVal("thing", "thing")
verify(mockprefsedit)!!.putString(anyString(), anyString())
}
#Test
fun testGetString()
{
prefshelper!!.getPrefsStringValue("key")
verify(mockprefs)!!.getString("key", "")
}
}
Issue is when I call addPrefsValueString() in the helper. the line
edit!!.putString(key, value).commit()
throws a null pointer exception? not sure why? I've setup the mock sharedprefs and sharedpreferences.Edit in the test class method annotated with #Before (shown below)
#Before
fun setUp() {
//MockitoAnnotations.initMocks(this)
cont = Mockito.mock(Context::class.java)
mockprefs = Mockito.mock(SharedPreferences::class.java)
mockprefsedit = Mockito.mock(SharedPreferences.Editor::class.java)
`when`(cont!!.getSharedPreferences(anyString(), anyInt())).thenReturn(mockprefs!!)
`when`(mockprefs!!.edit()).thenReturn(mockprefsedit!!)
prefshelper = SharedPrefsHelperImp(cont!!)
}
i'm sure my code is less than optimal.
EDIT:
Here's my fix for the testItemAdded() method. need to return the mock preferences editor on the first call.
#Test
fun testItemAdded()
{
`when`(mockprefsedit?.putString(anyString(), anyString())).thenReturn(mockprefsedit)
`when`(mockprefsedit?.commit()).thenReturn(true)
prefshelper!!.addPrefsStringVal("thing", "thing")
verify(mockprefsedit)!!.putString(anyString(), anyString())
verify(mockprefsedit)!!.commit()
}
You should set expectations for the call below, on your mock object (mockprefsedit). As well for the object returned, on which commit is invoked.
edit!!.putString(key, value)
thanks
Sriram