1. Treści
programowe:
Przechowywanie
danych podręcznych, mechanizm SharedPreferences, Preferences DataStore oraz format JSON.
2. Cel zajęć:
Celem zajęć jest
opanowanie ussmiejętności przechowywania i
współdzielenia danych w całej aplikacji w postaci pliku w pamięci urządzenia.
Student zdobędzie wiedzę niezbędną do programowania aplikacji w systemie
Android, która będzie zdolna do przechowywania danych np. konfiguracyjnych po
zamknięciu programu.
3. Materiały dydaktyczne
----------------------------------------------------------------------------------------
ü SharedPreferences
Jednym ze sposobów przechowywania
danych w aplikacji napisanej w OS Android jest mechanizm opisany w klasie SharedPreferences. Służy on do organizacji
niewielkich ilości danych (np. ustawienia programu, wynik - poziom gry, itd.).
Obsługuje następujące typy: boolean, float, int,
long, String.
Dane zapisane w ten sposób mogą być współdzielone przez wszystkie klasy
projektu (a nawet przez inne aplikacje) oraz nie zostaną utracone po wyłączeniu
programu. Jest to bezpieczny sposób nieobciążający system.
Przykład – zapis danych
Poniższy przykład
przedstawia sposób zapisania wartości dwóch zmiennych, całkowitej oraz
tekstowej.
int valueInt=111;
String valueString="OK";
SharedPreferences sharedPreferences=getSharedPreferences("SETTINGS", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt("KEY_ID_VALUE1", valueInt);
editor.putString("KEY_ID_VALUE2", valueString);
editor.commit();
Przykład – odczyt danych
Poniższy przykład przedstawia
sposób odczytania wartości oraz przypisanie ich do zmiennych, całkowitej i tekstowej.
int valueInt=0
String valueString="";
SharedPreferences prefMaxButton=getSharedPreferences("SETTINGS", Context.MODE_PRIVATE);
valueInt=prefMaxButton.getInt("KEY_ID_VALUE1", 10);
valueString=prefMaxButton.getString("KEY_ID_VALUE2", "FALSE");
Cykl życia aktywności pokazane na
poniższym rysunku pozwoli zrozumieć moment uruchomienia metody onResume() wykorzystany w przykładzie.

Przykład ukazujący wykorzystanie
klasy SharePreferences do zapisywania niedużych
danych w pamięci urządzenia oraz do przekazywania tych wartości pomiędzy
aktywnościami. Często wykorzystujemy ten mechanizm do zapisywania ustawień
aplikacji, do których dostęp mają wszystkie aktywność w ramach aplikacji.
Aplikacja zapisuje dwa typy danych. Pierwsza to wartość całkowita (na stałe
zapisaną w programie), drugą to wartość
tekstową pobraną z pola tekstowego.
Aktywność pierwsza:
package com.example.dwie_aktywnosci;
import
android.content.Context;
import
android.content.Intent;
import
android.content.SharedPreferences;
import
android.os.Bundle;
import
android.view.View;
import
androidx.appcompat.app.AppCompatActivity;
import
com.example.dwie_aktywnosci.databinding.ActivityMainBinding;
public
class MainActivity
extends AppCompatActivity
{
private
ActivityMainBinding
binding;
@Override
protected
void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
binding
=
ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.button.setOnClickListener(new View.OnClickListener()
{
@Override
public
void onClick(View view) {
Intent
intent = new
Intent(MainActivity.this, MainActivity2.class);
String wiadomosc = binding.editTextText.getText().toString();
int id=111;
SharedPreferences
sharedPreferences=getSharedPreferences("DATA", Context.MODE_PRIVATE);
SharedPreferences.Editor
editor = sharedPreferences.edit();
editor.putInt("KEY_ID_VALUE1",
id ); //
zapisanie wartości całkowitej
editor.putString("KEY_ID_VALUE2",
binding.editTextText.getText().toString()); // zapisanie wartości typu String
editor.commit();
startActivity(intent);
}
});
}
@Override
protected
void onResume()
{
super.onResume();
SharedPreferences
prefMaxButton=getSharedPreferences("DATA",
Context.MODE_PRIVATE);
if(!prefMaxButton.contains("KEY_ID_VALUE2")
|| !prefMaxButton.contains("KEY_ID_VALUE1"))
return;
int
valueInt=0;
String valueString=prefMaxButton.getString("KEY_ID_VALUE2",
"FALSE");
valueInt=prefMaxButton.getInt("KEY_ID_VALUE1",
0);
binding.editTextText.setText(valueString);
binding.editTextText.append("
"+valueInt+"\n");
}
}
Aktywność druga:
package com.example.dwie_aktywnosci;
import
android.content.Context;
import
android.content.SharedPreferences;
import
android.os.Bundle;
import
android.view.View;
import
androidx.appcompat.app.AppCompatActivity;
import
com.example.dwie_aktywnosci.databinding.ActivityMain2Binding;
public
class MainActivity2 extends
AppCompatActivity
{
private
ActivityMain2Binding
binding;
@Override
protected
void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
binding
=
ActivityMain2Binding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
SharedPreferences
prefMaxButton=getSharedPreferences("DATA",
Context.MODE_PRIVATE);
int
valueInt=0;
String valueString=prefMaxButton.getString("KEY_ID_VALUE2",
"FALSE");
valueInt=prefMaxButton.getInt("KEY_ID_VALUE1",
0);
binding.editTextText2.setText(valueString);
binding.editTextText2.setText(valueString);
binding.editTextText2.append("
"+valueInt+"\n");
binding.button3.setOnClickListener(new View.OnClickListener()
{
@Override
public
void onClick(View view) {
int id=222;
SharedPreferences
sharedPreferences=getSharedPreferences("DATA",
Context.MODE_PRIVATE);
SharedPreferences.Editor
editor = sharedPreferences.edit();
editor.putInt("KEY_ID_VALUE1",
id );
editor.putString("KEY_ID_VALUE2",
binding.editTextText2.getText().toString());
editor.commit();
finish();
}
});
}
}
Pliki widoków XML można pobrać z przykładu z instrukcji nr 4
Alternatywnie,
jeśli potrzebujesz tylko jednego współdzielonego pliku preferencji dla swojej
aktywności, możesz użyć metody getPreferences():
SharedPreferences
sharedPref = getPreferences(Context.MODE_PRIVATE);
Lokalizacja pliku DATA.xml
zapisanego przez SharePreferences:


----------------------------------------------------------------------------------------
ü Ustawienia
Poniżej opisano, jak utworzyć
prosty ekran ustawień za pomocą Biblioteka ustawień AndroidaX.

- Należy dodaj zależność z
biblioteki preferencji do swojego elementu (build.gradle)
plik:
Kotlin:
dependencies {
implementation("androidx.preference:preference-ktx:1.2.0")
}
Groovy:
dependencies {
implementation
"androidx.preference:preference-ktx:1.2.0"
}
-----------------------------------------------------------------------------------------------
<PreferenceScreen>
to główny kontener w pliku XML, w
którym definiuje się ustawienia aplikacji w Androidzie.
Działa jak „ekran ustawień” — zawiera inne elementy, takie jak <Preference>, <SwitchPreferenceCompat>,
<SeekBarPreference> itd.
Plik preferences.xml (res/xml/settings_preferences.xml):
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
app:key="user"
app:title="Użytkownik"
/>
<EditTextPreference
app:key="adres_email"
app:title="Adres
email"
/>
<SwitchPreferenceCompat
app:key="notification"
app:title="Włącz
powiadomienia"
/>
<SeekBarPreference
app:title="Wiek"
app:key="seek"
app:defaultValue="19"
android:max="100"
android:stepSize="1"
app:showSeekBarValue="true"
/>
</PreferenceScreen>
Przestrzenie nazw (namespaces), czyli informują Androida, skąd pochodzą
używane atrybuty:

xmlns:android - zawiera standardowe atrybuty Androida, np. android:layout_width, android:text,
android:background.
xmlns:app - zawiera dodatkowe (niestandardowe) atrybuty z
bibliotek AndroidX lub Material
Design, np. app:title, app:key,
app:showSeekBarValue.
Dzięki nim system wie, które
atrybuty należą do Androida, a które pochodzą z bibliotek aplikacji.
Opis komponentów i atrybutów
dla ustawień
Aby utworzyć obiekt widoku XML, należy
utworzyć klasę fragmentu dziedziczącą po PreferenceFragmentCompat
oraz nadpisać onCreatePreferences() podając
zasób XML:
plik: MySettingsFragment.java
import android.os.Bundle;
import
androidx.preference.PreferenceFragmentCompat;
public
class MySettingsFragment
extends PreferenceFragmentCompat
{
@Override
public void onCreatePreferences(Bundle savedInstanceState,
String rootKey) {
setPreferencesFromResource(R.xml.preferences,
rootKey);
}
}
plik: SettingsActivity.java
import android.os.Bundle;
import
androidx.activity.EdgeToEdge;
import
androidx.appcompat.app.AppCompatActivity;
import
androidx.core.graphics.Insets;
import
androidx.core.view.ViewCompat;
import
androidx.core.view.WindowInsetsCompat;
public
class SettingsActivity
extends AppCompatActivity
{
@Override
protected
void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_settings);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main),
(v, insets) -> {
Insets
systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return
insets;
});
//dodanie fragmentu do aktywności
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_settings,
new MySettingsFragment())
.commit();
}
}
plik: settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".SettingsActivity">
<FrameLayout
android:id="@+id/frame_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
Aktywność główna MainActivity.java
import android.content.Intent;
import
android.content.SharedPreferences;
import
android.os.Bundle;
import
android.widget.Button;
import
android.widget.EditText;
import
androidx.activity.EdgeToEdge;
import
androidx.appcompat.app.AppCompatActivity;
import
androidx.core.graphics.Insets;
import
androidx.core.view.ViewCompat;
import
androidx.core.view.WindowInsetsCompat;
import
androidx.preference.PreferenceManager;
public
class MainActivity
extends AppCompatActivity
{
@Override
protected
void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main),
(v, insets) -> {
Insets
systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return
insets;
});
Button buttonSettings
= findViewById(R.id.buttonSettings);
buttonSettings.setOnClickListener(v
-> {
Intent
intent = new
Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
});
var
preferences = PreferenceManager.getDefaultSharedPreferences(this);
if
(!preferences.contains("initialized"))
{
SharedPreferences.Editor
editor = preferences.edit();
editor.putString("user", "Nowy użytkownik");
editor.putString("adres_email", "email@poczta.com");
editor.putInt("seek", 25);
editor.putBoolean("notifications_enabled", true);
editor.putBoolean("initialized", true);
//
znacznik inicjalizacji
editor.apply();
}
}
@Override
protected
void onResume()
{
super.onResume();
var
preferences = PreferenceManager.getDefaultSharedPreferences(this).getAll();
EditText
editText = findViewById(R.id.editTextSettings);
preferences.forEach((key, value) -> {
editText.append(key + " " + value+"\n");
});
editText.append("\n");
}
}
----------------------------------------------------------------------------------------
ü Tablice w XML
Tablice deklarujesmy
w res/values/arrays.xml za
pomocą <string-array>, <integer-array> lub <array>.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="array_kolory_string">
<item>Czerwony</item>
<item>Zielony</item>
<item>Niebieski</item>
</string-array>
<string-array name="array_kolory_wartosci">
<item>red</item>
<item>green</item>
<item>blue</item>
</string-array>
<string-array name="array_kolory_domyslne">
<item>blue</item>
</string-array>
<integer-array name="punkty">
<item>10</item>
<item>20</item>
<item>30</item>
</integer-array>
<array name="flagi">
<item>true</item>
<item>false</item>
<item>true</item>
</array>
</resources>
Użycie w kodzie
w Javie: :
String[] kolory = getResources().getStringArray(R.array.kolory);
int[] punkty = getResources().getIntArray(R.array.punkty);
Wykorzystanie tablic w oknie ustawień:
<MultiSelectListPreference
app:key="kolory"
app:title="Wybierz
kolory motywu"
app:summary="Zaznacz
jeden lub więcej kolorów"
app:entries="@array/array_kolory_string"
app:entryValues="@array/array_kolory_wartosci"
app:defaultValue="@array/array_kolory_domyslne"
/>
<ListPreference
app:key="kolory"
app:title="Wybierz
kolor motywu"
app:summary="Zmień
kolor główny aplikacji"
app:entries="@array/array_kolory_string"
app:entryValues="@array/array_kolory_wartosci"
app:defaultValue="blue"
/>

Możemy również
dodać ikony do ustawień:
android:icon="@drawable/outline_document_search_24"

Grupowanie ustawień:

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Kategoria: Wygląd aplikacji -->
<PreferenceCategory
app:title="Wygląd
aplikacji">
<MultiSelectListPreference
app:key="kolory"
app:title="Wybierz
kolory motywu"
app:summary="Zaznacz
jeden lub więcej kolorów"
app:entries="@array/array_kolory_string"
app:entryValues="@array/array_kolory_wartosci"
app:defaultValue="@array/array_kolory_domyslne"
/>
</PreferenceCategory>
<!-- Kategoria: Dane użytkownika -->
<PreferenceCategory
app:title="Dane
użytkownika">
<Preference
app:key="user"
app:title="Użytkownik"
/>
<EditTextPreference
app:key="adres_email"
app:title="Adres
email" />
<SeekBarPreference
app:title="Wiek"
app:key="seek"
app:defaultValue="19"
android:max="100"
android:stepSize="1"
app:showSeekBarValue="true" />
</PreferenceCategory>
<!-- Kategoria: Powiadomienia -->
<PreferenceCategory
app:title="Powiadomienia">
<SwitchPreferenceCompat
app:key="notification"
app:title="Włącz
powiadomienia" />
</PreferenceCategory>
</PreferenceScreen>
----------------------------------------------------------------------------------------
ü Toolbar
Toolbar to nowoczesny odpowiednik starego
ActionBar — pasek u góry aplikacji, który może
wyświetlać:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="8dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</LinearLayout>
Kod w klasie
MainActivity w klasie onCreate():
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Strzałka w stecz:

Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
Obsługa zdarzenia związanego z kliknięciem na strzałkę
@Override
public
boolean onSupportNavigateUp()
{
finish(); //
wróć do poprzedniej aktywności
return true;
}
----------------------------------------------------------------------------------------
ü Toolbar i Menu

Ustawienie nazwy w androidmanifest.xml

Plik menu_1.xml zapisany w utworzonym folderze menu:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_ustawienia"
android:icon="@drawable/outline_document_search_24"
android:title="Ustawienia"
app:showAsAction="always"/>
<item
android:id="@+id/opcja_menu1"
android:icon="@drawable/baseline_headset_24"
android:title="Tytuł"
app:showAsAction="ifRoom"/>
<item android:id="@+id/opcja_menu2"
android:title="Ustawienia"
app:showAsAction="never"/>
</menu>
Metoda onCreateOptionsMenu nadpisana w pliku aktywności MainActivity.java:
@Override
public
boolean onCreateOptionsMenu(Menu
menu) {
MenuInflater
inflater = getMenuInflater();
inflater.inflate(R.menu.menu_1,
menu);
return true;
}
Obsługa
przycisków menu:
@Override
public
boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection.
if
(item.getItemId()==R.id.menu_ustawienia){
Intent intent = new
Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
}else
if (item.getItemId()==R.id.manu_opcja1)
{
EditText
editText = findViewById(R.id.editTextSettings);
editText.append("Wybrano
opcję 1\n");
}else
if (item.getItemId()==R.id.menu_opcja2)
{
EditText
editText = findViewById(R.id.editTextSettings);
editText.append("Wybrano
inną opcje 2\n");
}
return super.onOptionsItemSelected(item);
}
Generowanie
ikon:


Domyślna
lokalizacja pliku utworzonej ikony:

-----------------------------------------------------------------------------------------
4.
Zadania
Zadanie 1.
Napisz program wg własnego pomysłu wykorzystujący mechanizm
zarządzania danymi SharedPreferences.
Aplikacja powinna zapisywać i pobierać dane z poziomu różnych Aktywności.
Zadanie 2.
Napisz program, w którym utwórz okno ustawień zawierające typy kontrolek
pozwalające ustawić parametry dla TextView, jak w
kodzie poniżej. W oknie ustawień wstaw pasek toolBar
ze strzałką pozwalającą wyjść z aktywności ustawień. Pobierz ustawienia i ustaw
je dla pola tekstowego TextView, które powinno być
widoczne w głównej aktywności.
Można wykorzystać z poziomu kodu Java takie funkcje jak:
// Zmiana koloru tła TextView
public
void ustawKolorTla(TextView textView, int kolor) {
textView.setBackgroundColor(kolor);
}
//
Zmiana koloru tekstu
public
void ustawKolorTekstu(TextView textView, int kolor) {
textView.setTextColor(kolor);
}
//
Zmiana rozmiaru czcionki (w SP)
public
void ustawRozmiarCzcionki(TextView textView, float rozmiarSp)
{
textView.setTextSize(rozmiarSp);
}
//
Zmiana stylu tekstu (np. pogrubienie)
public
void ustawStylTekstu(TextView textView, boolean pogrubiony) {
if
(pogrubiony)
textView.setTypeface(null, Typeface.BOLD);
else
textView.setTypeface(null, Typeface.NORMAL);
}
//
Zmiana treści tekstu
public
void ustawTekst(TextView textView, String nowyTekst) {
textView.setText(nowyTekst);
}
Można
przypisać kolor jako int np. do TextView:
textView.setTextColor(Color.parseColor("#2196F3")); //
niebieski
// lub
textView.setTextColor(0xFF2196F3);