|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "SharedPreferences" |
| 4 | +date: 2019-08-05 |
| 5 | +categories: ["Przechowywanie"] |
| 6 | +image: store/sharedpreferences |
| 7 | +github: store/tree/master/sharedpreferences |
| 8 | +description: "Przechowywanie danych" |
| 9 | +keywords: "store, data, datastore, sharedpreferences, preferences, key, value, primitive, settings, get, put, editor, android, programowanie, programming" |
| 10 | +--- |
| 11 | + |
| 12 | +## Charakterystyka |
| 13 | +`SharedPreferences` umożliwiają przechowywanie i zarządzanie prostymi danymi prymitywów tj.: wartości logiczne, liczby całkowite i zmiennoprzecinkowe oraz teksty w postaci pary `klucz - wartość`. Dane przechowywane są na urządzeniu w plików `xml` niezależnie od cyklu życia aplikacji. Istnieją tak długo dopóki nie zostaną usunięte przez kod programu lub wyczyszczone ręcznie przez użytkownika z danych aplikacji. Aplikacja może posiadać wiele instancji `SharedPreferences`, które najczęściej są prywatne lecz mogą być także publiczne dla innych aplikacji. |
| 14 | + |
| 15 | +## Przeznaczenie |
| 16 | +Nazwa `SharedPreferences` może sugerować przeznaczenie do przechowywania informacji nt ustawień użytkownika, co jest po części prawdą. Jednakże wszystkie informacje, które można sprowadzić do postaci klucz - wartość nadają się do umieszczenia w `SharedPreferences`. Mogą więc to być ustawienia czy preferencje użytkownika, ale równie dobrze także inne dane stanu aplikacji (np. najlepszy wynik) czy dane autoryzacyjne. Pomimo, że wiele informacji może być technicznie zapisanych w `SharedPreferences` (np. konwersja do `String`) to nie wszystkie powinny się tam znaleźć. Umieszczając wartości należy kierować się zasadą prostych, pojedynczych informacji (nie kolekcji). W pozostałych przypadkach warto rozważyć alternatywy w postaci m.in. plików i bazy danych. |
| 17 | + |
| 18 | +## Implementacja |
| 19 | +Aby uzyskać referencję do wskazanego pliku `SharedPreferences` należy wywołać metodę `getSharedPreferences` lub `getPreferences`. Czytanie wartości odbywa się za pomocą metod get danego typu, natomiast zapisywanie wartości przebiega jako transakcja na obiekcie `SharedPreferences.Editor`. |
| 20 | + |
| 21 | +{% highlight kotlin %} |
| 22 | +class MainActivity : AppCompatActivity() { |
| 23 | + |
| 24 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 25 | + super.onCreate(savedInstanceState) |
| 26 | + } |
| 27 | + |
| 28 | + fun getSharedPreferences(name : String) : SharedPreferences { |
| 29 | + //must be called on Context |
| 30 | + return getSharedPreferences(name, Context.MODE_PRIVATE) //pass public to allow access outside app |
| 31 | + } |
| 32 | + |
| 33 | + fun getSharedPreferencesForActivity() : SharedPreferences { |
| 34 | + //SharedPreferences file only for this activity |
| 35 | + return getPreferences(Context.MODE_PRIVATE) |
| 36 | + } |
| 37 | + |
| 38 | + fun write(key : String, value : Int) { |
| 39 | + val sharedPref = getSharedPreferences("pl.androidcode.app.FILE_KEY") |
| 40 | + val editor = sharedPref.edit() |
| 41 | + |
| 42 | + //do some transactions |
| 43 | + editor.putInt(key, value) //or use another method to put different types |
| 44 | + editor.commit() //to save immediately or apply to save in background |
| 45 | + } |
| 46 | + |
| 47 | + fun read(key : String) : Int { |
| 48 | + val sharedPref = getSharedPreferences("pl.androidcode.app.FILE_KEY") |
| 49 | + val default = 0 |
| 50 | + return sharedPref.getInt(key, default) |
| 51 | + } |
| 52 | +} |
| 53 | +{% endhighlight %} |
| 54 | + |
| 55 | +## Preference |
| 56 | +W przypadku standardowych ustawień aplikacji edytowanych przez interfejs graficzny dobrym pomysłem mogłoby być wykorzystanie biblioteki `Preference`, która ułatwia pracę z `SharedPreferences` poprzez dostarczenie kontrolek widoku dla kluczy ustawień. W tym celu należy stworzyć Fragment rozszerzający `PreferenceFragmentCompat` i w metodzie `onCreatePreferences` przekazać plik `xml` z preferencjami. Wprowadzane zmiany dotyczą globalnych domyślnych ustawień możliwych do uzyskania przez wywołanie `getDefaultSharedPreferences`. |
| 57 | + |
| 58 | +{% highlight kotlin %} |
| 59 | +class SettingsActivity : AppCompatActivity() { |
| 60 | + |
| 61 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 62 | + super.onCreate(savedInstanceState) |
| 63 | + setContentView(R.layout.activity_settings) |
| 64 | + |
| 65 | + //add Preference Fragment |
| 66 | + supportFragmentManager |
| 67 | + .beginTransaction() |
| 68 | + .replace(R.id.container, SettingsFragment()) |
| 69 | + .commit() |
| 70 | + } |
| 71 | + |
| 72 | + //read some pref when needed, use getDefaultSharedPreferences |
| 73 | + fun readPref() : Boolean { |
| 74 | + val sharedPref = getDefaultSharedPreferences(this) |
| 75 | + return sharedPref.getBoolean("pref2", false) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +class SettingsFragment : PreferenceFragmentCompat() { |
| 80 | + |
| 81 | + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { |
| 82 | + setPreferencesFromResource(R.xml.preferences, rootKey) |
| 83 | + } |
| 84 | + |
| 85 | + override fun onPreferenceTreeClick(preference: Preference?): Boolean { |
| 86 | + return when (preference?.key) { |
| 87 | + "pref2" -> { |
| 88 | + //do something when pref has changed if needed |
| 89 | + true |
| 90 | + } |
| 91 | + else -> { |
| 92 | + super.onPreferenceTreeClick(preference) |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + //implement more methods to react for interactions |
| 98 | +} |
| 99 | +{% endhighlight %} |
| 100 | + |
| 101 | +{% highlight kotlin %} |
| 102 | +<!-- PreferenceScreen must be parent --> |
| 103 | +<androidx.preference.PreferenceScreen |
| 104 | + xmlns:app="http://schemas.android.com/apk/res-auto"> |
| 105 | + |
| 106 | + <Preference |
| 107 | + app:key="pref1" |
| 108 | + app:title="Some title" |
| 109 | + app:summary="More details"/> |
| 110 | + |
| 111 | + <SwitchPreferenceCompat |
| 112 | + app:key="pref2" |
| 113 | + app:title="Some message" |
| 114 | + app:summary="More details"/> |
| 115 | + |
| 116 | + <!-- use more preferences from Preference class and subclasses --> |
| 117 | + |
| 118 | +</androidx.preference.PreferenceScreen> |
| 119 | +{% endhighlight %} |
| 120 | + |
| 121 | +## DataStore |
| 122 | +Biblioteka `Preference` domyślnie zarząda danymi wykorzystując implementację `SharedPreferences`. Nierzadko jednak taka realizacja zapisu i odczytu danych może nie być wystarczająca ponieważ może np. zachodzić potrzeba dodatkowego zapisu danych w chmurze ze względu na synchronizację preferencji między urządzeniami. W takiej sytuacji z pomocą przychodzi `PreferenceDataStore` dostępny od wersji systemu `Android O`. |
| 123 | + |
| 124 | +{% highlight kotlin %} |
| 125 | +//use custom PreferenceDataStore inside onCretePreferences |
| 126 | +class DataStoreFragment : PreferenceFragmentCompat() { |
| 127 | + |
| 128 | + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { |
| 129 | + setPreferencesFromResource(R.xml.preferences, rootKey) |
| 130 | + |
| 131 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| 132 | + |
| 133 | + //enable data store only for specific preference |
| 134 | + val preference = findPreference("pref2") |
| 135 | + preference.preferenceDataStore = DataStore() |
| 136 | + |
| 137 | + //ore enable for entire hierarchy |
| 138 | + preferenceManager.preferenceDataStore = DataStore() |
| 139 | + } |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +//override only used operations |
| 144 | +@RequiresApi(Build.VERSION_CODES.O) |
| 145 | +class DataStore : PreferenceDataStore { |
| 146 | + |
| 147 | + //make sure to provide proper non blocking threading during extra work |
| 148 | + |
| 149 | + override fun getBoolean(key: String?, defValue: Boolean): Boolean { |
| 150 | + //try to get pref from remote |
| 151 | + //if failed or doesn't exists then try to get from local |
| 152 | + return super.getBoolean(key, defValue) |
| 153 | + //log operation |
| 154 | + } |
| 155 | + |
| 156 | + override fun putBoolean(key: String?, value: Boolean) { |
| 157 | + //try to put pref to remote |
| 158 | + //save also in local |
| 159 | + super.putBoolean(key, value) |
| 160 | + //log operation |
| 161 | + } |
| 162 | +} |
| 163 | +{% endhighlight %} |
0 commit comments