Aplikacje mobilne · Projekt końcowy

Projekt końcowy: aplikacja „Moje Zadania”

Czas połączyć cały dział w jeden projekt, w formie podobnej do zadania na egzaminie INF.04: lista, formularz z walidacją, baza SQLite i ustawienia w Preferences. Najpierw plan na kartce, potem kod.

  • około 3 godziny
  • .NET MAUI · SQLite · Preferences · nawigacja
  • INF.04.6.2

Po tej lekcji: samodzielnie zaprojektować i zbudować kompletną aplikację mobilną, rozdzielić logikę od interfejsu oraz przygotować projekt do publikacji.

Moje zadania: lista, formularz i ustawienia

Połączymy poznane elementy w jedną małą aplikację. Nie zaczniemy od wszystkich przycisków naraz: najpierw dane i walidacja, potem zapis, lista i przejścia między ekranami.

Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

1 · Tak ma działać

Aplikacja, którą zbudujesz

Dodaj zadanie z pustą nazwą lub datą z przeszłości, a zobaczysz komunikat walidacji. Odznacz zadania jako wykonane, zmień sortowanie i ukryj wykonane. Na koniec uruchom aplikację ponownie: dane (baza) i ustawienia (Preferences) zostają.

Najpierw przewidź, potem sprawdź: W modelu aplikacji dodaj zadanie, zmień jego status i sprawdź filtr. Zastanów się, które wartości trafią do bazy, a które do Preferences.

Ta interakcja wymaga włączonego JavaScriptu.

Cztery mechanizmy dają kompletną aplikację: nawigacja, walidacja, baza i preferencje. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.

1. Przygotuj miejsce pracy

Co dodajesz, a co podmieniasz?

Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.

PlikRola w ćwiczeniu
Modele/Zadanie.csLogika i obsługa danych
Logika/WalidatorZadania.csLogika i obsługa danych
Data/ZadaniaDatabase.csLogika i obsługa danych
App.xaml.csLogika i obsługa danych
MainPage.xamlWygląd strony lub kontrolki
MainPage.xaml.csLogika i obsługa danych
DodajZadaniePage.xamlWygląd strony lub kontrolki
DodajZadaniePage.xaml.csLogika i obsługa danych
UstawieniaPage.xamlWygląd strony lub kontrolki
UstawieniaPage.xaml.csLogika i obsługa danych

Zanim uruchomisz: trzy połączenia

x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.

x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.

Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.

2. Uruchom sam wygląd — jeszcze bez logiki

W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.

MainPage.xaml — etap tymczasowyxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="24" Spacing="14">
        <Label Text="Moje zadania" FontSize="24" />
        <Entry Placeholder="Nowe zadanie" />
        <DatePicker />
        <Label Text="Najpierw ekran. Za chwilę walidacja i zapis." />
    </VerticalStackLayout>
</ContentPage>
  1. Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.

Jak na egzaminie

Cała aplikacja od zera

Menedżer zadań do zrobienia to niemal dokładnie to, co może pojawić się na egzaminie: formularz, lista, baza i ustawienia w jednej, spójnej aplikacji, jak miliony aplikacji „to-do” w sklepach.

Najpierw spróbuj samodzielnie, a dopiero potem porównaj z rozwiązaniem referencyjnym w dalszej części lekcji. To jedno z wielu możliwych rozwiązań. Uruchamiaj je etapami i wykonaj próby odbioru opisane niżej.

2 · Analiza

Wymagania funkcjonalne

WymaganieTechniki z działu
Lista zadań w CollectionView (nazwa, termin, status) z przełączaniem „wykonane”Listy danych, BindingContext klikniętego elementu
Osobna strona dodawania zadania (PushAsync / PopAsync)Nawigacja
Walidacja: nazwa niepusta, termin nie z przeszłości; klasa zwraca krotkę (bool ok, List<string> bledy, Zadanie? model)Modele i walidacja
Trwały zapis w SQLiteSQLite, CRUD
Ustawienia: sortowanie (termin / nazwa) i pokazywanie wykonanychPreferences
Logika oddzielona od interfejsu; code-behind jako cienki „klej”Architektura, MVVM
Kolejność pracy: analiza wymagań → projekt (szkice ekranów, model, metody) → implementacja.

4 · Po własnej próbie

Rozwiązanie referencyjne

Poniżej główne pliki do projektu utworzonego szablonem MAUI. Zachowaj App.xaml, MauiProgram.cs, katalog Platforms i zasoby szablonu. Wymaga pakietów NuGet sqlite-net-pcl w wersji1.11.285. Porównaj go ze swoim rozwiązaniem: gdzie masz inny podział odpowiedzialności?

4. Połącz wygląd z działaniem

Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.

Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.

Budowa krok po kroku

Zalecana struktura projektu

Otwórz Struktura. Podmień wskazany plik po utworzeniu typów, do których się odwołuje. Objaśnienia pod kodem pokazują, które wartości przechodzą do następnego etapu.

Zanim napiszesz kod, narysuj na kartce: (1) wygląd trzech ekranów, (2) pola klasy Zadanie, (3) metody klasy bazy i walidatora. Dopiero potem implementuj.

Strukturatext
MojeZadaniaApp/
├── Modele/Zadanie.cs
├── Logika/WalidatorZadania.cs
├── Data/ZadaniaDatabase.cs
├── App.xaml.cs                 (inicjalizacja bazy, App.Database)
├── MainPage.xaml(.cs)          (lista zadań, CollectionView)
├── DodajZadaniePage.xaml(.cs)  (formularz z walidacją)
└── UstawieniaPage.xaml(.cs)    (Preferences)

Budowa krok po kroku

Modele/Zadanie.cs

Najpierw model i atrybutySQLite. TerminOpis i Status mają Ignore, bo wynikają z innych wartości i nie są osobnymi kolumnami.

Właściwości zapisywane w tabeli oraz dwie wyliczane, oznaczone [Ignore], bo nie są kolumnami.

Modele/Zadanie.cscsharp
using SQLite;

namespace MojaAplikacja.Modele;

public class Zadanie
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    [MaxLength(100)]
    public string Nazwa { get; set; } = "";

    public DateTime Termin { get; set; }

    public bool Wykonane { get; set; }

    [Ignore]
    public string TerminOpis => Termin.ToString("dd.MM.yyyy HH:mm");

    [Ignore]
    public string Status => Wykonane ? "Wykonane" : "Do zrobienia";
}
  1. [PrimaryKey, AutoIncrement] Klucz główny nadawany przez bazę.
  2. [Ignore] Właściwości TerminOpis i Status istnieją tylko do wyświetlania i nie trafiają do bazy.

Budowa krok po kroku

Logika/WalidatorZadania.cs

Sprawdź nazwę oraz termin przed dostępem do bazy. Krotka przenosi wynik, listę błędów i model tylko wtedy, gdy dane są poprawne.

Cała walidacja w jednym miejscu. Zwraca wynik, listę błędów i gotowy model.

Logika/WalidatorZadania.cscsharp
using MojaAplikacja.Modele;

namespace MojaAplikacja.Logika;

public static class WalidatorZadania
{
    public static (bool ok, List<string> bledy, Zadanie? model) Waliduj(string? nazwa, DateTime termin)
    {
        var bledy = new List<string>();

        if (string.IsNullOrWhiteSpace(nazwa))
            bledy.Add("Podaj nazwę zadania.");
        else if (nazwa.Trim().Length > 100)
            bledy.Add("Nazwa może mieć najwyżej 100 znaków.");

        if (termin < DateTime.Now)
            bledy.Add("Termin nie może być z przeszłości.");

        if (bledy.Count > 0)
            return (false, bledy, null);

        return (true, bledy, new Zadanie { Nazwa = nazwa!.Trim(), Termin = termin });
    }
}
  1. Krotka (bool ok, List<string> bledy, Zadanie? model) Wynik, komunikaty i obiekt (albo null przy błędach).
  2. Zbieranie wszystkich błędów Użytkownik widzi wszystkie problemy naraz, a nie po jednym.

Budowa krok po kroku

Data/ZadaniaDatabase.cs

Ten plik odpowiada za trwałość, nie za kontrolki. Każda operacja czeka na jedną inicjalizację tabeli. Przeczytaj osobno pobieranie, zapis i usuwanie.

Konstruktor uruchamia tworzenie tabeli i zachowuje Task, bez blokowania wątku przez .Wait(). Każda operacja najpierw await InitAsync(), więc czeka na to samo ukończenie inicjalizacji. Błąd inicjalizacji trafia do wywołującej metody; ekran powinien go przechwycić i pokazać komunikat.

Data/ZadaniaDatabase.cscsharp
using SQLite;
using MojaAplikacja.Modele;

namespace MojaAplikacja.Data;

public class ZadaniaDatabase
{
    private readonly SQLiteAsyncConnection _database;
    private readonly Task _inicjalizacja;

    public ZadaniaDatabase(string dbPath)
    {
        _database = new SQLiteAsyncConnection(dbPath);
        _inicjalizacja = _database.CreateTableAsync<Zadanie>();
    }

    private Task InitAsync() => _inicjalizacja;

    public async Task<List<Zadanie>> PobierzAsync()
    {
        await InitAsync();
        return await _database.Table<Zadanie>().ToListAsync();
    }

    public async Task<int> DodajAsync(Zadanie zadanie)
    {
        await InitAsync();
        return await _database.InsertAsync(zadanie);
    }

    public async Task<int> AktualizujAsync(Zadanie zadanie)
    {
        await InitAsync();
        return await _database.UpdateAsync(zadanie);
    }

    public async Task<int> UsunAsync(Zadanie zadanie)
    {
        await InitAsync();
        return await _database.DeleteAsync(zadanie);
    }
}
  1. private readonly Task _inicjalizacja Zapamiętana jedna operacja tworzenia tabeli. Nie uruchamiamy jej ponownie przy każdym zapytaniu.
  2. await InitAsync() Oczekiwanie asynchroniczne, przed odczytem i każdą zmianą. Nie używaj .Result ani .Wait() do blokowania UI.
  3. return await ... Po inicjalizacji wykonujemy właściwą operację i zwracamy jej wynik.

Budowa krok po kroku

App.xaml.cs

App przygotowuje bazę i uruchamia NavigationPage. To wspólny punkt startu listy, formularza i ustawień.

Jedna współdzielona instancja bazy i okno z NavigationPage, dzięki któremu działa PushAsync.

App.xaml.cscsharp
using MojaAplikacja.Data;

namespace MojaAplikacja;

public partial class App : Application
{
    public static ZadaniaDatabase Database { get; private set; } = null!;

    public App()
    {
        InitializeComponent();

        string dbPath = Path.Combine(FileSystem.AppDataDirectory, "zadania.db3");
        Database = new ZadaniaDatabase(dbPath);
    }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        return new Window(new NavigationPage(new MainPage()));
    }
}
  1. public static ZadaniaDatabase Database Dostęp z każdej strony przez App.Database.
  2. new NavigationPage(new MainPage()) Stos stron potrzebny do nawigacji.

Budowa krok po kroku

MainPage.xaml

Dodaj lub otwórz MainPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

Dwa przyciski u góry i lista zadań z przyciskami statusu i usuwania w szablonie.

MainPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:modele="clr-namespace:MojaAplikacja.Modele"
             x:Class="MojaAplikacja.MainPage"
             Title="Moje Zadania">

    <Grid Padding="16" RowDefinitions="Auto,*" RowSpacing="12">

        <HorizontalStackLayout Spacing="12">
            <Button Text="Nowe zadanie" Clicked="Dodaj_Clicked" />
            <Button Text="Ustawienia" Clicked="Ustawienia_Clicked" />
        </HorizontalStackLayout>

        <CollectionView Grid.Row="1" x:Name="ListaZadan" EmptyView="Brak zadań.">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="modele:Zadanie">
                    <Grid Padding="8" ColumnDefinitions="*,Auto,Auto" ColumnSpacing="8">
                        <VerticalStackLayout>
                            <Label Text="{Binding Nazwa}" FontAttributes="Bold" />
                            <Label Text="{Binding TerminOpis}" FontSize="12" />
                        </VerticalStackLayout>
                        <Button Grid.Column="1" Text="{Binding Status}" Clicked="Status_Clicked" />
                        <Button Grid.Column="2" Text="Usuń" Clicked="Usun_Clicked" />
                    </Grid>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </Grid>
</ContentPage>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

Budowa krok po kroku

MainPage.xaml.cs

OnAppearing odświeża widok po powrocie. BezpiecznieAsync przyjmuje operację Func<Task>, czyli funkcję bez argumentów zwracającą zadanie, i obsługuje jej błąd.

Odświeżanie przy każdym pojawieniu się strony, filtrowanie i sortowanie wg ustawień oraz obsługa kliknięć.

MainPage.xaml.cscsharp
using MojaAplikacja.Modele;

namespace MojaAplikacja;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    protected override async void OnAppearing()
    {
        base.OnAppearing();
        await BezpiecznieAsync(OdswiezAsync);
    }

    private async Task OdswiezAsync()
    {
        List<Zadanie> zadania = await App.Database.PobierzAsync();

        if (!Preferences.Default.Get("pokazuj_wykonane", true))
            zadania = zadania.Where(z => !z.Wykonane).ToList();

        zadania = Preferences.Default.Get("sortowanie", 0) == 0
            ? zadania.OrderBy(z => z.Termin).ToList()
            : zadania.OrderBy(z => z.Nazwa).ToList();

        ListaZadan.ItemsSource = zadania;
    }

    private async void Dodaj_Clicked(object sender, EventArgs e)
        => await Navigation.PushAsync(new DodajZadaniePage());

    private async void Ustawienia_Clicked(object sender, EventArgs e)
        => await Navigation.PushAsync(new UstawieniaPage());

    private async void Status_Clicked(object sender, EventArgs e)
    {
        if ((sender as BindableObject)?.BindingContext is Zadanie zadanie)
        {
            zadanie.Wykonane = !zadanie.Wykonane;
            await BezpiecznieAsync(() => App.Database.AktualizujAsync(zadanie));
            await BezpiecznieAsync(OdswiezAsync);
        }
    }

    private async void Usun_Clicked(object sender, EventArgs e)
    {
        if ((sender as BindableObject)?.BindingContext is Zadanie zadanie)
        {
            await BezpiecznieAsync(() => App.Database.UsunAsync(zadanie));
            await BezpiecznieAsync(OdswiezAsync);
        }
    }
    private async Task BezpiecznieAsync(Func<Task> operacja)
    {
        try { await operacja(); }
        catch (Exception)
        {
            await DisplayAlertAsync("Baza", "Operacja się nie powiodła. Odśwież listę i spróbuj ponownie.", "OK");
        }
    }
}
  1. OnAppearing Lista odświeża się także po powrocie z formularza lub ustawień.
  2. Preferences.Default.Get("sortowanie", 0) Odczyt ustawienia z wartością domyślną.
  3. (sender as BindableObject)?.BindingContext is Zadanie zadanie Pobiera obiekt wiersza, w którym kliknięto przycisk.
  4. BezpiecznieAsync(Func<Task>) Metoda przyjmuje operację asynchroniczną jako argument i przechwytuje jej wyjątek, aby błąd bazy nie uciekł z handlera async void. Po błędzie lista jest odświeżana.

Budowa krok po kroku

DodajZadaniePage.xaml

Dodaj lub otwórz DodajZadaniePage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

Nazwa, data i godzina oraz przycisk zapisu.

DodajZadaniePage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.DodajZadaniePage"
             Title="Nowe zadanie">

    <VerticalStackLayout Padding="16" Spacing="12">
        <Entry x:Name="NazwaEntry" Placeholder="Nazwa zadania" />
        <DatePicker x:Name="DataPicker" />
        <TimePicker x:Name="CzasPicker" />
        <Button Text="Zapisz" Clicked="Zapisz_Clicked" />
    </VerticalStackLayout>
</ContentPage>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

Budowa krok po kroku

DodajZadaniePage.xaml.cs

Walidator tworzy model, baza go zapisuje, a dopiero udany zapis pozwala wykonać PopAsync. Nie zamykaj formularza w finally.

Złożenie terminu, wywołanie walidatora i zapis do bazy.

DodajZadaniePage.xaml.cscsharp
using MojaAplikacja.Logika;

namespace MojaAplikacja;

public partial class DodajZadaniePage : ContentPage
{
    public DodajZadaniePage()
    {
        InitializeComponent();
        DataPicker.Date = DateTime.Today.AddDays(1);
    }

    private async void Zapisz_Clicked(object sender, EventArgs e)
    {
        if (DataPicker.Date is not DateTime data || CzasPicker.Time is not TimeSpan czas)
        {
            await DisplayAlertAsync("Termin", "Wybierz datę i godzinę.", "OK");
            return;
        }
        DateTime termin = data.Date + czas;
        var (ok, bledy, zadanie) = WalidatorZadania.Waliduj(NazwaEntry.Text, termin);

        if (!ok)
        {
            await DisplayAlertAsync("Popraw dane", string.Join("\n", bledy), "OK");
            return;
        }

        try
        {
            await App.Database.DodajAsync(zadanie!);
            await Navigation.PopAsync();
        }
        catch (Exception)
        {
            await DisplayAlertAsync("Baza", "Nie udało się zapisać zadania. Formularz zachował dane.", "OK");
        }
    }
}
  1. var (ok, bledy, zadanie) = ... Dekonstrukcja krotki do trzech zmiennych.
  2. string.Join("\n", bledy) Wszystkie błędy w jednym komunikacie.
  3. PopAsync() Powrót do listy po udanym zapisie.

Budowa krok po kroku

UstawieniaPage.xaml

Dodaj lub otwórz UstawieniaPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

Przełącznik i lista wyboru sortowania.

UstawieniaPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.UstawieniaPage"
             Title="Ustawienia">

    <VerticalStackLayout Padding="16" Spacing="14">
        <HorizontalStackLayout Spacing="10">
            <Label Text="Pokazuj wykonane" VerticalOptions="Center" />
            <Switch x:Name="WykonaneSwitch" Toggled="Zmiana" />
        </HorizontalStackLayout>

        <Picker x:Name="SortPicker" Title="Sortowanie" SelectedIndexChanged="Zmiana">
            <Picker.Items>
                <x:String>Po terminie</x:String>
                <x:String>Alfabetycznie</x:String>
            </Picker.Items>
        </Picker>
    </VerticalStackLayout>
</ContentPage>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

Budowa krok po kroku

UstawieniaPage.xaml.cs

Zdarzenia kontrolek mogą odpalić już przy odczyciePreferences. _wczytano chroni przed nadpisaniem zapisanych ustawień wartościami startowymi.

Wczytanie ustawień i zapis przy każdej zmianie.

UstawieniaPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class UstawieniaPage : ContentPage
{
    private readonly bool _wczytano;

    public UstawieniaPage()
    {
        InitializeComponent();
        WykonaneSwitch.IsToggled = Preferences.Default.Get("pokazuj_wykonane", true);
        SortPicker.SelectedIndex = Preferences.Default.Get("sortowanie", 0);
        _wczytano = true;
    }

    private void Zmiana(object? sender, EventArgs e)
    {
        if (!_wczytano) return;

        Preferences.Default.Set("pokazuj_wykonane", WykonaneSwitch.IsToggled);
        Preferences.Default.Set("sortowanie", SortPicker.SelectedIndex);
    }
}
  1. Pole _wczytano Zdarzenia Toggled i SelectedIndexChanged odpalają się już podczas wczytywania wartości. Bez flagi zapisałyby domyślne wartości przed odczytem.

5. Odbiór: czy naprawdę działa?

Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.

5 · Czego unikać

Cztery błędy w projekcie końcowym

Kod bez planu
Tak nie
// od razu piszę MainPage.xaml.cs

Brak planu przy kilku stronach i klasach prowadzi do przepisywania połowy kodu.

Tak
// kartka: ekrany, model, metody bazy i walidatora → dopiero kod

Analiza, projekt, implementacja.

Wszystko w code-behind
Tak nie
// MainPage.xaml.cs: SQL, walidacja i obsługa UI w jednym pliku

Setki linijek wymieszanej logiki; trudno to testować i zmieniać.

Tak
// Data/ZadaniaDatabase.cs, Logika/WalidatorZadania.cs, cienki code-behind

Oddzielenie logiki od interfejsu.

Pominięta walidacja
Tak nie
await App.Database.DodajAsync(new Zadanie { Nazwa = NazwaEntry.Text });

Pusta nazwa lub data z przeszłości psują aplikację przy pierwszym nietypowym wpisie.

Tak
var (ok, bledy, zadanie) = WalidatorZadania.Waliduj(...);

Użytkownik i egzaminator wpiszą coś nieoczekiwanego.

Brak odświeżenia listy po operacji na bazie
Tak nie
await App.Database.UsunAsync(zadanie);

Element nadal widać na ekranie, bo ItemsSource się nie zmienił.

Tak
await App.Database.UsunAsync(zadanie);
await OdswiezAsync();

Po Insert, Update i Delete pobierz dane ponownie.

Ćwiczenie z pomocą · samodzielna praca

6. Mała zmiana, którą zrobisz samodzielnie

Dodaj priorytet zadania jako nową właściwość modelu i element formularza.

Oczekiwany rezultat
Dodaj priorytet zadania jako nową właściwość modelu i element formularza.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Najpierw wybierz typ oraz domyślną wartość priorytetu; zaplanuj zachowanie istniejących rekordów.

Krok 2 — pokaż podpowiedź

Przenieś wartość z formularza do modelu przekazanego do zapisu.

Krok 3 — pokaż podpowiedź

Dodaj odczyt w szablonie wiersza; sprawdź nowy i stary rekord oraz restart.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

6 · Powtórka działu

Zadanie kontrolne: 7 pytań

Pytania z części pisemnej egzaminu: layouty, kontrolki, zdarzenia i nawigacja.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Pracuj w kolejności: analiza → projekt → implementacja.
  2. Rozdziel odpowiedzialności: model, walidator, baza i cienki code-behind.
  3. Po każdej operacji na bazie odśwież listę, najwygodniej w OnAppearing.
  4. Walidacja zwraca (ok, bledy, model), a użytkownik widzi wszystkie błędy naraz.
  5. Łączysz XAML, model, walidację, bazę i ustawienia. Przed publikacją wykonaj jeszcze testy na docelowym urządzeniu i przygotuj podpisany pakiet.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka