Obraz zawierający tekst, Czcionka, Grafika

Opis wygenerowany automatycznie 

Kierunek Informatyka

 

Instrukcja do ćwiczeń laboratoryjnych nr:

6

Nazwa przedmiotu:
Programowanie aplikacji mobilnych

Temat: Organizacja danych w pamięci urządzenia poprzez klasę SharedPreferences, PreferencesScreen, pasek TollBar, Menu, ikony

Tryb studiów: stacjonarne

Czas trwanie ćw.

2x45 min

Autor materiałów: dr Marcin Skuba

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.

 

 

Obraz zawierający tekst, zrzut ekranu, diagram, Czcionka

Zawartość wygenerowana przez AI może być niepoprawna.

 

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.

 

Obraz zawierający tekst, zrzut ekranu

Zawartość wygenerowana przez AI może być niepoprawna.

 

- 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:

 

Obraz zawierający zrzut ekranu, tekst, Czcionka, linia

Zawartość wygenerowana przez AI może być niepoprawna.

 

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"

 

 

Obraz zawierający tekst, zrzut ekranu, Czcionka, biały

Zawartość wygenerowana przez AI może być niepoprawna.

 

Grupowanie ustawień:

 

Obraz zawierający tekst, zrzut ekranu, Czcionka, numer

Zawartość wygenerowana przez AI może być niepoprawna.

 

<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:

Obraz zawierający tekst, zrzut ekranu

Zawartość wygenerowana przez AI może być niepoprawna.

 

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:

 

Obraz zawierający tekst, zrzut ekranu, oprogramowanie, Oprogramowanie multimedialne

Zawartość wygenerowana przez AI może być niepoprawna.

 

 

 

 

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);