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.
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.
| Plik | Rola w ćwiczeniu |
|---|---|
| AndroidManifest.xml (fragment) | Fragment w istniejącym pliku |
| Info.plist (fragment) | Fragment w istniejącym pliku |
| MainPage.xaml | Wygląd strony lub kontrolki |
| MainPage.xaml.cs | Logika 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.
<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>
- 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
| Krok | Co robisz |
|---|---|
| 1. Deklaracja | Uprawnienia 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. Pomiar | Geolocation.Default.GetLocationAsync(request, token) zwraca Location (Latitude, Longitude, Accuracy). |
| 4. Użycie | Sprawdź null, a potem np. Location.CalculateDistance(a, b, DistanceUnits.Kilometers). |
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.
namespace MojaAplikacja;
public partial class App : Application
{
public App() => InitializeComponent();
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new NavigationPage(new MainPage()));
}
CreateWindowTworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.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).
<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>
ACCESS_FINE_LOCATIONDokładna lokalizacja (GPS).ACCESS_COARSE_LOCATIONPrzybliżona lokalizacja (sieć, Wi‑Fi).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ę.
<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ść.
<?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>
- 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.
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;
}
}
}
new Location(52.2297, 21.0122)Współrzędne celu (centrum Warszawy).Permissions.RequestAsync<...>()istatus != GrantedBez zgody od razu kończymy. Próba pomiaru zakończyłaby się wyjątkiemPermissionException.GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10))Żądana dokładność i czas pomiaru.CancellationTokenSource(TimeSpan.FromSeconds(12))Przerywa oczekiwanie po 12 s, aby aplikacja nie zawisła, gdy sygnał jest słaby.Location? lokalizacjaiis nullPrzy słabym sygnale wynik może byćnull. Bez sprawdzenia odczytLatitudezgłosiNullReferenceException.Location.CalculateDistance(...)Odległość z uwzględnieniem krzywizny Ziemi.- Kolejność
catchNajpierw szczegółowe (FeatureNotEnabledException,PermissionException), na końcu ogólnyException. Odwrotnie ogólny przechwyciłby wszystko. finallyUkrywa wskaźnik ładowania niezależnie od wyniku.- Żą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
<!-- 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ć.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />Deklaracja w pliku platformowym poprzedza prośbę w kodzie.
Pomiar bez sprawdzenia zgody
var lok = await Geolocation.Default.GetLocationAsync();Bez zgody zostanie zgłoszony PermissionException, którego łatwo uniknąć.
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted) return;Najpierw prośba i sprawdzenie statusu.
Brak sprawdzenia `null`
WynikLabel.Text = $"{lokalizacja.Latitude}";Przy słabym sygnale NullReferenceException.
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.
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.
Podsumowanie
Co warto zapamiętać
- Lokalizacja wymaga zgody: deklaracja w pliku platformowym, a potem
Permissions.RequestAsynci sprawdzenieGranted. Geolocation.Default.GetLocationAsync(request, token)zwracaLocation?. UżyjCancellationTokenSourcejako limitu czasu.- Sprawdzaj
nulloraz rozróżniajFeatureNotEnabledExceptioniPermissionException. - Szczegółowe
catchprzed ogólnym,finallyukrywa wskaźnik ładowania. - 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ę →