Aplikacje mobilne · Sensory

GPS i lokalizacja w .NET MAUI

Nawigacja, pogoda, najbliższe punkty usługowe: wiele aplikacji korzysta z lokalizacji. Lokalizacja to dana wrażliwa, więc system wymaga zgody użytkownika. Nauczysz się prosić o zgodę, pobierać współrzędne, liczyć odległość i obsługiwać każdy rodzaj awarii.

  • około 60 minut
  • .NET MAUI · Geolocation · Permissions
  • INF.04.6.2

Po tej lekcji: zadeklarować uprawnienia w plikach platformowych, poprosić o zgodę (Permissions.RequestAsync), pobrać Location z limitem czasu i policzyć odległość (CalculateDistance), obsługując null i wyjątki.

Współrzędne i odległość do punktu

Telefon nie musi od razu oddać pozycji. Potrzebna jest zgoda, włączona usługa i obsługa sytuacji, gdy odczyt trwa albo kończy się bez wyniku.

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 · Sprawdź w działaniu

Pięć scenariuszy, jeden przycisk

Wybierz miejsce i kliknij „Pobierz lokalizację”. Potem sprawdź każdy scenariusz: odmowa zgody, GPS wyłączony, słaby sygnał (wynik null) i przekroczenie limitu czasu. Zmień próg „w pobliżu”, a zobaczysz rozwiązanie zadania trudniejszego.

Najpierw przewidź, potem sprawdź: W modelu GPS porównaj zgodę, odmowę i brak pozycji. Zmiana punktu na mapie w lekcji nie jest odczytem z Twojego prawdziwego GPS.

Ta interakcja wymaga włączonego JavaScriptu.

Każda z pięciu sytuacji kończy się innym komunikatem. Kod, który je rozróżnia, jest różnicą między aplikacją „zawieszoną” a aplikacją przyjazną. 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
AndroidManifest.xml (fragment)Fragment w istniejącym pliku
Info.plist (fragment)Fragment w istniejącym pliku
MainPage.xamlWygląd strony lub kontrolki
MainPage.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="Moja lokalizacja" FontSize="24" />
        <Button Text="Pobierz pozycję — jeszcze bez GPS" />
        <Label Text="Bez pomiaru nie pokazujemy współrzędnych." />
    </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.

Zacznijmy od turysty

Jak daleko jest do atrakcji?

Aplikacja turystyczna informuje, jak daleko jest do najbliższej atrakcji. Aplikacja dostawcza pokazuje kurierowi odległość do adresu. Obie opierają się na tym samym: prośbie o zgodę, pobraniu współrzędnych GPS i obliczeniu odległości.

Mechanizm uprawnień (Permissions) z tej lekcji jest identyczny dla wszystkich funkcji wymagających zgody (aparat, mikrofon, powiadomienia), więc przyda się w kolejnych lekcjach.

2 · Zanim pojawią się współrzędne

Przepis na pobranie lokalizacji

KrokCo robisz
1. DeklaracjaUprawnienia w pliku platformowym (Android: AndroidManifest.xml, iOS: Info.plist z opisem dla użytkownika).
2. Prośba o zgodęawait Permissions.RequestAsync<Permissions.LocationWhenInUse>(), potem sprawdzenie PermissionStatus.Granted.
3. PomiarGeolocation.Default.GetLocationAsync(request, token) zwraca Location (Latitude, Longitude, Accuracy).
4. UżycieSprawdź null, a potem np. Location.CalculateDistance(a, b, DistanceUnits.Kilometers).
Bez deklaracji w pliku platformowym system może w ogóle nie pokazać okna z prośbą o zgodę, tylko od razu odmówić.

3. Wskaż stronę startową ćwiczenia

Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje MainPage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.

App.xaml.cscsharp
namespace MojaAplikacja;

public partial class App : Application
{
    public App() => InitializeComponent();
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(new MainPage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. new MainPage() Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.

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

Platforms/Android/AndroidManifest.xml

W AndroidManifest.xml dopisz elementy uprawnień wewnątrz istniejącego manifest. Nie usuwaj application ani innych deklaracji szablonu.

Dwa uprawnienia lokalizacji i dwa wpisy o sprzęcie (required="false", żeby aplikację można było zainstalować także na urządzeniach bez GPS).

AndroidManifest.xml (fragment)xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-feature android:name="android.hardware.location" android:required="false" />
  <uses-feature android:name="android.hardware.location.gps" android:required="false" />
</manifest>
  1. ACCESS_FINE_LOCATION Dokładna lokalizacja (GPS).
  2. ACCESS_COARSE_LOCATION Przybliżona lokalizacja (sieć, Wi‑Fi).
  3. uses-feature ... required="false" Aplikacja nie wymaga GPS do instalacji, tylko z niego korzysta, jeśli jest.

Budowa krok po kroku

Platforms/iOS/Info.plist (wewnątrz dict)

To para klucz–napis do istniejącego dict w Platforms/iOS/Info.plist. Nie zastępuj nią całego pliku plist.

Na iOS musisz podać powód, który zobaczy użytkownik w oknie z prośbą o zgodę.

Info.plist (fragment)xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Lokalizacja jest potrzebna do pokazania współrzędnych użytkownika.</string>

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.

Przycisk, spinner i dwie etykiety na wynik i odległość.

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"
             x:Class="MojaAplikacja.MainPage" Title="Moja lokalizacja">
    <ScrollView>
        <VerticalStackLayout Padding="24" Spacing="14">
            <Label Text="GPS: aktualna lokalizacja" FontSize="24" FontAttributes="Bold" />
            <Button Text="Pobierz lokalizację" Clicked="PobierzLokalizacje_Clicked" />
            <ActivityIndicator x:Name="Ladowanie" IsVisible="False" IsRunning="False" />
            <Label x:Name="WynikLabel" Text="Brak pomiaru." />
            <Label x:Name="OdlegloscLabel" />
        </VerticalStackLayout>
    </ScrollView>
</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

Prześledź: prośba o zgodę → żądanie z limitem czasu → sprawdzenie null → wynik → odległość. finally kończy spinner także po return i błędzie.

Zgoda, pomiar z limitem czasu, sprawdzenie null, odległość i trzy rodzaje wyjątków.

MainPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class MainPage : ContentPage
{
    // Punkt docelowy: centrum Warszawy
    private readonly Location _warszawa = new(52.2297, 21.0122);

    public MainPage()
    {
        InitializeComponent();
    }

    private async void PobierzLokalizacje_Clicked(object sender, EventArgs e)
    {
        try
        {
        // 1. Prośba o zgodę użytkownika (obowiązkowa)
        PermissionStatus status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

        if (status != PermissionStatus.Granted)
        {
            await DisplayAlertAsync("Brak zgody", "Bez zgody aplikacja nie może pobrać lokalizacji.", "OK");
            return;
        }

            Ladowanie.IsVisible = true;
            Ladowanie.IsRunning = true;

            // 2. Żądanie: średnia dokładność i limit czasu pomiaru
            var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
            using var anulowanie = new CancellationTokenSource(TimeSpan.FromSeconds(12));

            Location? lokalizacja = await Geolocation.Default.GetLocationAsync(request, anulowanie.Token);

            // 3. Przy słabym sygnale wynik może być null
            if (lokalizacja is null)
            {
                WynikLabel.Text = "Nie udało się ustalić lokalizacji.";
                return;
            }

            WynikLabel.Text =
                $"Szerokość: {lokalizacja.Latitude:F6}\n" +
                $"Długość: {lokalizacja.Longitude:F6}\n" +
                $"Dokładność: {lokalizacja.Accuracy:F0} m";

            double km = Location.CalculateDistance(lokalizacja, _warszawa, DistanceUnits.Kilometers);
            OdlegloscLabel.Text = $"Odległość od centrum Warszawy: {km:F1} km";
        }
        catch (FeatureNotEnabledException)
        {
            await DisplayAlertAsync("GPS wyłączony", "Włącz lokalizację w ustawieniach urządzenia.", "OK");
        }
        catch (PermissionException)
        {
            await DisplayAlertAsync("Brak uprawnienia", "Nadaj aplikacji dostęp do lokalizacji.", "OK");
        }
        catch (Exception ex)
        {
            await DisplayAlertAsync("Błąd", ex.Message, "OK");
        }
        finally
        {
            Ladowanie.IsRunning = false;
            Ladowanie.IsVisible = false;
        }
    }
}
  1. new Location(52.2297, 21.0122) Współrzędne celu (centrum Warszawy).
  2. Permissions.RequestAsync<...>() i status != Granted Bez zgody od razu kończymy. Próba pomiaru zakończyłaby się wyjątkiem PermissionException.
  3. GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10)) Żądana dokładność i czas pomiaru.
  4. CancellationTokenSource(TimeSpan.FromSeconds(12)) Przerywa oczekiwanie po 12 s, aby aplikacja nie zawisła, gdy sygnał jest słaby.
  5. Location? lokalizacja i is null Przy słabym sygnale wynik może być null. Bez sprawdzenia odczyt Latitude zgłosi NullReferenceException.
  6. Location.CalculateDistance(...) Odległość z uwzględnieniem krzywizny Ziemi.
  7. Kolejność catch Najpierw szczegółowe (FeatureNotEnabledException, PermissionException), na końcu ogólny Exception. Odwrotnie ogólny przechwyciłby wszystko.
  8. finally Ukrywa wskaźnik ładowania niezależnie od wyniku.
  9. Żądanie uprawnienia wewnątrz try Także samo sprawdzanie/proszenie o uprawnienie może zgłosić wyjątek. Obsługa błędów obejmuje całą operację, nie tylko pomiar.

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.

7 · Czego unikać

Trzy błędy z lokalizacją

Brak deklaracji uprawnienia w pliku platformowym
Tak nie
<!-- brak ACCESS_FINE_LOCATION w AndroidManifest.xml -->

Kod C# jest poprawny, ale system może nie pokazać prośby o zgodę, tylko od razu odmówić.

Tak
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Deklaracja w pliku platformowym poprzedza prośbę w kodzie.

Pomiar bez sprawdzenia zgody
Tak nie
var lok = await Geolocation.Default.GetLocationAsync();

Bez zgody zostanie zgłoszony PermissionException, którego łatwo uniknąć.

Tak
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted) return;

Najpierw prośba i sprawdzenie statusu.

Brak sprawdzenia `null`
Tak nie
WynikLabel.Text = $"{lokalizacja.Latitude}";

Przy słabym sygnale NullReferenceException.

Tak
if (lokalizacja is null) { ... return; }

GetLocationAsync może zwrócić null zamiast rzucić wyjątek.

Ćwiczenie z pomocą · samodzielna praca

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

Zmień punkt docelowy z Warszawy na Działdowo.

Oczekiwany rezultat
Zmień punkt docelowy z Warszawy na Działdowo.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Użyj zweryfikowanych współrzędnych punktu, zapisanych w kolejności szerokość,długość.

Krok 2 — pokaż podpowiedź

Zmień obiekt Location i opis wyniku, żeby nazwa zgadzała się z punktem.

Krok 3 — pokaż podpowiedź

Ponów test odległości; nie zamieniaj stopni na kilometry ręcznym mnożeniem.

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

8 · Sprawdź się

Zgoda, wyjątek czy null?

Cztery sytuacje.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Lokalizacja wymaga zgody: deklaracja w pliku platformowym, a potem Permissions.RequestAsync i sprawdzenie Granted.
  2. Geolocation.Default.GetLocationAsync(request, token) zwraca Location?. Użyj CancellationTokenSource jako limitu czasu.
  3. Sprawdzaj null oraz rozróżniaj FeatureNotEnabledException i PermissionException.
  4. Szczegółowe catch przed ogólnym, finally ukrywa wskaźnik ładowania.
  5. Odległość: Location.CalculateDistance(a, b, DistanceUnits.Kilometers).

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