PowerApps offline bez utraty danych: jak projektować zapisy, które nie znikają
Jak projektować aplikacje PowerApps offline, by zapisy nie ginęły? Praktyczne podejście do kolejek zmian, identyfikatorów, synchronizacji, konfliktów, błędów Patch i czytelnego UX pracy bez internetu.
Jakie są najczęstsze przyczyny gubienia zapisów w aplikacjach PowerApps offline?
Najczęściej zapisy „giną” nie dlatego, że PowerApps sam usuwa dane, ale dlatego, że aplikacja offline nie ma poprawnie zaprojektowanego mechanizmu buforowania, trwałego przechowywania i ponownej synchronizacji. W praktyce problem zwykle pojawia się wtedy, gdy dane są zapisywane tylko do pamięci tymczasowej aplikacji lub kolekcji, ale nie trafiają do lokalnego magazynu przez SaveData. Jeśli użytkownik zamknie aplikację, system ubije proces albo urządzenie zrestartuje się przed trwałym zapisem, wpis przepada.
Drugą częstą przyczyną jest błędna logika synchronizacji po odzyskaniu połączenia. Dane lokalne bywają nadpisywane świeżo pobraną zawartością z serwera, zanim zostaną wysłane, albo aplikacja czyści kolejkę zmian zbyt wcześnie, na przykład po samym wywołaniu Patch, bez potwierdzenia, że operacja zakończyła się sukcesem. W efekcie użytkownik widzi formularz jako zapisany, ale rekord nie trafia do źródła danych albo zostaje zastąpiony starszą wersją.
Istotnym źródłem problemów są też błędy identyfikacji rekordów. Jeżeli aplikacja nie nadaje lokalnym zapisom jednoznacznych identyfikatorów i nie rozróżnia rekordu nowego od aktualizacji istniejącego, podczas synchronizacji może dojść do duplikacji, nadpisania niewłaściwego wpisu albo pominięcia części zmian. To samo dotyczy sytuacji, gdy kilka operacji na tym samym rekordzie wykonuje się offline bez zachowania kolejności.
Często gubienie zapisów wynika również z braku obsługi błędów i konfliktów. Gdy połączenie jest niestabilne, żądanie może zakończyć się częściowym niepowodzeniem, przekroczeniem limitu czasu albo odrzuceniem danych przez źródło danych. Jeśli aplikacja nie rejestruje wyniku operacji i nie przechowuje niezsynchronizowanych rekordów do ponownej próby, użytkownik traci dane mimo pozornego zapisu.
Dodatkowym czynnikiem są ograniczenia samego urządzenia i sesji mobilnej. Aplikacja może zostać zamknięta w tle, pamięć może zostać zwolniona przez system, a lokalny stan może nie zostać odtworzony, jeśli nie był zapisany w sposób trwały. Dlatego w kontekście offline najczęstsza przyczyna utraty danych to nie pojedynczy błąd, lecz brak pełnego cyklu: zapis lokalny, potwierdzenie przechowania, bezpieczna kolejka synchronizacji i usuwanie danych dopiero po potwierdzonym sukcesie.
Jak zbudować lokalną kolejkę zmian w PowerApps (SaveData/LoadData), żeby nic nie zginęło?
Najbezpieczniejszy wzorzec polega na tym, żeby nie zapisywać zmian od razu do źródła danych, tylko najpierw dodawać je do lokalnej kolekcji pełniącej rolę kolejki operacji. Taka kolejka jest następnie utrwalana na urządzeniu przez SaveData(), a po ponownym uruchomieniu aplikacji odtwarzana przez LoadData(). Dzięki temu wpis nie znika po zamknięciu aplikacji ani przy chwilowej utracie połączenia.
Każdy rekord w kolejce powinien zawierać nie tylko dane biznesowe, ale też metadane potrzebne do bezpiecznej synchronizacji: lokalny unikalny identyfikator, typ operacji (np. utworzenie, aktualizacja, usunięcie), czas utworzenia wpisu, identyfikator rekordu w źródle danych jeśli już istnieje, status synchronizacji oraz ewentualny komunikat błędu. Kluczowe jest to, żeby traktować kolejkę jako dziennik zmian, a nie jako zwykły cache formularza.
Praktycznie wygląda to tak: po zapisie formularza tworzysz rekord zmiany i dodajesz go do kolekcji, na przykład przez Collect(colQueue, {...}), a zaraz potem wywołujesz SaveData(colQueue, "queue"). Nie warto odkładać utrwalania na później, bo jeśli aplikacja zostanie ubita przez system między Collect() a SaveData(), ostatnia zmiana może przepaść. Zasada jest prosta: najpierw zapisz do kolejki lokalnej, potem natychmiast utrwal kolejkę na dysku urządzenia.
Przy starcie aplikacji trzeba odtworzyć stan lokalny przez LoadData(colQueue, "queue", true). Trzeci parametr ustawiony na true pozwala uruchomić aplikację nawet wtedy, gdy plik jeszcze nie istnieje. Po wczytaniu kolejki aplikacja może próbować wysłać oczekujące zmiany do źródła danych, ale dopiero po potwierdzeniu sukcesu dla konkretnego wpisu wolno usunąć go z kolejki i ponownie wykonać SaveData(). To ważne, bo usuwanie wpisu przed rzeczywistym powodzeniem synchronizacji jest najprostszą drogą do utraty danych.
Żeby nic nie zginęło, kolejka powinna być odporna na częściowe niepowodzenia. Jeśli wysyłasz 10 zmian i piąta kończy się błędem, pierwsze cztery można oznaczyć jako zsynchronizowane i usunąć dopiero po utrwaleniu nowego stanu kolejki, a piątą oraz kolejne zostawić do następnej próby. Nie należy czyścić całej kolejki po jednej udanej synchronizacji zbiorczej, jeśli nie masz pewności, że każda operacja została zaakceptowana osobno.
Istotna jest też kolejność przetwarzania. Kolejkę należy wysyłać w tej samej kolejności, w jakiej powstały zmiany, szczególnie jeśli użytkownik najpierw utworzył rekord, a potem go edytował. Lokalny identyfikator pozwala powiązać te operacje, zanim źródło danych zwróci docelowe ID. Gdy rekord zostanie utworzony po stronie serwera, warto zaktualizować odpowiadające mu lokalne wpisy tak, aby dalsze operacje odnosiły się już do właściwego identyfikatora z backendu.
Jeżeli użytkownik może wielokrotnie edytować ten sam rekord offline, dobrze jest scalać lokalne zmiany, zamiast tworzyć serię zbędnych wpisów. Przykładowo, jeśli rekord został utworzony lokalnie i jeszcze nie zsynchronizowany, kolejne edycje powinny raczej aktualizować istniejący wpis typu „create” niż dopisywać osobne „update”. Taka kompresja kolejki zmniejsza ryzyko błędów i upraszcza późniejszą synchronizację.
Nie należy trzymać w kolejce wyłącznie danych z kontrolek formularza bez kontekstu. Rekord kolejki musi być samowystarczalny: po restarcie aplikacji powinno dać się z niego odtworzyć, co trzeba wysłać, na jakim rekordzie i w jakim typie operacji. Jeśli do wykonania synchronizacji potrzebujesz dodatkowego stanu przechowywanego tylko w pamięci, to po zamknięciu aplikacji powstaje luka, przez którą dane realnie mogą zniknąć.
W praktyce bezpieczny schemat jest więc następujący: użytkownik wykonuje akcję, aplikacja zapisuje kompletny wpis do lokalnej kolekcji, natychmiast wykonuje SaveData(), przy starcie odtwarza kolejkę przez LoadData(), a podczas synchronizacji usuwa tylko te wpisy, dla których ma pewne potwierdzenie sukcesu, po czym znów utrwala kolejkę. Jeśli trzymasz się tej zasady atomowości na poziomie aplikacji, ryzyko utraty zmian w scenariuszu offline spada do minimum.
Jak projektować identyfikatory rekordów, gdy użytkownik tworzy dane bez internetu?
W trybie offline nie można opierać identyfikatora nowego rekordu na numerze nadawanym przez źródło danych po zapisie, bo w momencie tworzenia rekordu użytkownik nie ma połączenia z serwerem. Dlatego rekord powinien dostać lokalny, unikalny identyfikator już na urządzeniu, a dopiero po synchronizacji można powiązać go z identyfikatorem docelowym nadanym przez backend.
Najbezpieczniejszy wzorzec to rozdzielenie dwóch pól: LocalId i ServerId. LocalId służy do jednoznacznego rozpoznania rekordu w aplikacji offline, śledzenia edycji, załączników i relacji lokalnych. ServerId pozostaje puste do czasu skutecznego zapisu online, a po synchronizacji jest uzupełniane wartością zwróconą przez system docelowy. Dzięki temu aplikacja nie traci odniesienia do rekordu ani przed, ani po wysłaniu danych.
Jako LocalId należy stosować identyfikator o bardzo niskim ryzyku kolizji, najlepiej GUID/UUID. Nie należy używać liczników lokalnych, numerów wierszy, bieżącej daty jako jedynego klucza ani identyfikatorów „następny numer”, bo przy wielu urządzeniach lub ponownych instalacjach łatwo o duplikaty. Identyfikator musi być stabilny: raz nadany nie może się zmieniać, także po restarcie aplikacji i po synchronizacji.
- Twórz LocalId po stronie aplikacji w chwili utworzenia rekordu i zapisuj go razem z danymi w lokalnej kolejce lub kolekcji.
- Traktuj LocalId jako klucz techniczny do wyszukania rekordu przy ponownej próbie synchronizacji, aktualizacji statusu i łączenia rekordów zależnych.
- Nie zastępuj LocalId identyfikatorem serwerowym; zamiast tego przechowuj oba pola, bo rekord lokalny może być referencją dla innych obiektów jeszcze przed pełną synchronizacją.
- Jeśli rekord ma relacje, zapisuj je tymczasowo przez LocalId, a po synchronizacji mapuj na ServerId.
W praktyce oznacza to, że identyfikator widoczny dla użytkownika, na przykład numer dokumentu, nie powinien pełnić roli głównego klucza offline. Taki numer bywa nadawany według reguł biznesowych dopiero na serwerze i może zależeć od kolejności zapisów. Do pracy offline potrzebny jest osobny identyfikator techniczny, który nie zależy od połączenia z internetem.
Jeśli źródło danych samo używa GUID jako klucza głównego i pozwala dostarczyć go z aplikacji, można użyć jednego identyfikatora od początku do końca. Jeżeli jednak backend generuje własne ID, aplikacja powinna utrzymywać mapowanie LocalId -> ServerId. To jest kluczowy warunek, aby rekordy utworzone bez internetu nie „gubiły się” podczas późniejszego zapisu.
Jak bezpiecznie synchronizować dane po powrocie online i rozwiązywać konflikty?
Bezpieczna synchronizacja po odzyskaniu połączenia polega na tym, aby nie nadpisywać od razu danych w źródle, tylko wysyłać lokalne zmiany w kontrolowany sposób i dla każdego rekordu sprawdzać, czy od momentu jego pobrania nie został zmieniony gdzie indziej. W praktyce aplikacja powinna zapisywać lokalnie nie tylko dane do wysłania, ale też identyfikator rekordu, typ operacji oraz informację o wersji rekordu znanej w chwili edycji, na przykład znacznik czasu modyfikacji, numer wersji albo wartość pola używanego do kontroli współbieżności.
Po powrocie online synchronizację najlepiej wykonywać sekwencyjnie, rekord po rekordzie. Dla każdego elementu należy najpierw pobrać aktualny stan z serwera lub porównać zapisany lokalnie znacznik wersji z bieżącym. Jeżeli wersje się zgadzają, można bezpiecznie wykonać zapis. Jeżeli się różnią, wystąpił konflikt i taki rekord nie powinien być automatycznie nadpisywany bez dodatkowej decyzji. To najważniejsza zasada: brak zgodności wersji oznacza wstrzymanie zapisu, a nie wymuszenie aktualizacji.
Konflikty warto rozdzielić na dwa typy. Pierwszy to konflikt techniczny, gdy zapis nie dochodzi do skutku z powodu błędu połączenia, ograniczeń API albo walidacji danych. Taki przypadek zwykle nadaje się do ponownej próby i powinien pozostać w lokalnej kolejce z oznaczeniem błędu. Drugi to konflikt biznesowy, gdy ten sam rekord został zmieniony przez innego użytkownika lub proces. Tutaj potrzebna jest reguła rozstrzygania: albo użytkownik wybiera, którą wersję zachować, albo aplikacja scala tylko niekolidujące pola, albo obowiązuje stała polityka, na przykład priorytet wersji serwerowej. Najmniej bezpieczne jest podejście typu last write wins, bo łatwo prowadzi do cichej utraty danych.
Żeby rozwiązywanie konfliktów było przewidywalne, lokalny zapis powinien przechowywać również wartości „sprzed edycji”. Dzięki temu można porównać trzy stany: wersję bazową, lokalne zmiany użytkownika i aktualny stan na serwerze. Taki model pozwala wykryć, czy konflikt dotyczy dokładnie tego samego pola, czy tylko innej części rekordu. Jeżeli użytkownik offline zmienił pole Status, a w międzyczasie na serwerze zmieniono pole Opis, aplikacja może scalić zmiany bezpieczniej niż przy prostym nadpisaniu całego rekordu.
W PowerApps oznacza to zwykle pracę na lokalnej kolekcji lub pamięci urządzenia jako kolejce zmian oraz wysyłanie ich dopiero po potwierdzeniu dostępności sieci. Po udanym zapisie rekord należy oznaczyć jako zsynchronizowany i usunąć z kolejki dopiero wtedy, gdy źródło danych zwróci sukces. Nie wolno czyścić lokalnej kolejki „na wszelki wypadek” przed potwierdzeniem, bo właśnie wtedy najczęściej dochodzi do utraty danych przy niestabilnym połączeniu.
Bezpieczeństwo synchronizacji zwiększa też idempotencja operacji, czyli możliwość ponownego wysłania tej samej zmiany bez tworzenia duplikatów. Najprościej osiąga się to przez własny identyfikator operacji albo stabilny identyfikator rekordu tworzonego offline. Jeśli aplikacja po błędzie spróbuje wysłać dane ponownie, źródło danych powinno rozpoznać, że to ta sama operacja, a nie nowy wpis.
Użytkownik powinien widzieć stan synchronizacji każdego rekordu: oczekuje, zapisano, błąd techniczny, konflikt wymagający decyzji. To nie jest kwestia wygody, tylko kontroli nad danymi. Jeżeli konflikt zostanie wykryty, aplikacja powinna pokazać różnicę między wersją lokalną a serwerową i umożliwić świadome rozstrzygnięcie zamiast ukrywać problem za ogólnym komunikatem o błędzie.
Najkrócej: bezpieczna synchronizacja po powrocie online wymaga kolejki lokalnych zmian, kontroli wersji rekordu, zapisu dopiero po weryfikacji zgodności oraz jawnej obsługi konfliktów. Jeśli aplikacja zapisuje „w ciemno” i nie odróżnia błędu transmisji od konfliktu danych, ryzyko utraty informacji jest wysokie nawet wtedy, gdy sama praca offline wydaje się działać poprawnie.
Jak obsłużyć błędy Patch i ponowienia, żeby użytkownik nie musiał wpisywać wszystkiego od nowa?
Kluczowa zasada jest prosta: formularz nie może być jedynym miejscem przechowywania danych do momentu wysłania. Jeśli dane istnieją tylko w kontrolkach i wywołanie Patch() zakończy się błędem, użytkownik łatwo traci stan wpisanego formularza po odświeżeniu ekranu, zamknięciu aplikacji albo ponownym wejściu. Dlatego przed próbą zapisu do źródła dane trzeba zapisać w lokalnym modelu roboczym, najczęściej w kolekcji lub rekordzie przechowywanym lokalnie, a dopiero potem wysyłać je do źródła.
W praktyce warto rozdzielić proces na dwa etapy: zachowanie danych użytkownika i próbę synchronizacji. Użytkownik zapisuje formularz do lokalnej kolejki lub bufora, a aplikacja wykonuje Patch() na podstawie tego zapisanego rekordu. Jeśli zapis zdalny się nie uda, rekord pozostaje w buforze razem ze statusem błędu, dzięki czemu można go wysłać ponownie bez ponownego wpisywania pól.
Dobra obsługa błędów opiera się na trzech elementach:
- Trwały identyfikator rekordu lokalnego – każdy zapis oczekujący powinien mieć własny identyfikator, status i znacznik czasu. To pozwala odróżnić nowy rekord od ponawianego oraz uniknąć duplikatów przy kolejnych próbach.
- Jawne przechwycenie wyniku Patch – użyj
IfError(),Errors()albo sprawdzenia wyniku operacji i zapisz status typu:Pending,Synced,Error. Nie zakładaj, że brak komunikatu oznacza sukces. - Ponowienie z tych samych danych – retry powinno korzystać z wcześniej zapisanego rekordu lokalnego, nie z aktualnej zawartości kontrolek. Dzięki temu nawet po zamknięciu aplikacji można wznowić wysyłkę.
Jeżeli używasz formularza edycyjnego, nie czyść go od razu po kliknięciu „Zapisz”. Czyszczenie lub ResetForm() powinno nastąpić dopiero po potwierdzonym sukcesie zapisu albo po bezpiecznym przeniesieniu danych do lokalnego bufora. W przeciwnym razie błąd sieciowy lub błąd walidacji spowoduje, że użytkownik straci treść wpisaną przed chwilą.
Warto też rozdzielić błędy na takie, które mają sens ponawiać, i takie, które wymagają poprawki danych. Chwilowy brak połączenia, timeout czy przejściowy błąd usługi to kandydaci do retry. Natomiast błąd walidacji, brak wymaganego pola albo konflikt danych wymaga komunikatu pokazującego, co trzeba poprawić, bez usuwania już wpisanych wartości.
Dobry wzorzec to zapisanie lokalnego rekordu, np. z polami biznesowymi oraz metadanymi Status, ErrorMessage, RetryCount. Następnie aplikacja próbuje wykonać Patch(). Gdy operacja się powiedzie, zmienia status na Synced i ewentualnie usuwa rekord z kolejki. Gdy się nie powiedzie, zostawia rekord w kolejce i zapisuje komunikat błędu. Użytkownik widzi wtedy, że dane są zachowane i czekają na ponowienie, zamiast wracać do pustego formularza.
Najważniejsze z punktu widzenia UX jest to, aby komunikat nie brzmiał tylko „błąd zapisu”, ale także potwierdzał stan danych, na przykład: dane zostały zachowane lokalnie, spróbujemy ponownie albo popraw oznaczone pola, Twoje wpisy nie zostały utracone. To usuwa największy problem: niepewność, czy trzeba wpisywać wszystko jeszcze raz.
Jeśli aplikacja ma działać także po zamknięciu, lokalny bufor powinien być dodatkowo utrwalany mechanizmem lokalnego zapisu, tak aby rekordy oczekujące przetrwały restart aplikacji. Wtedy ponowienie może nastąpić automatycznie przy następnym uruchomieniu albo ręcznie przez użytkownika, ale zawsze na podstawie już zapisanych danych, a nie pamięci użytkownika.
Jak zaprojektować UX offline, żeby użytkownik rozumiał stan synchronizacji?
UX offline musi przede wszystkim jasno oddzielać trzy stany: dane zapisane lokalnie, dane oczekujące na synchronizację i dane zsynchronizowane z systemem docelowym. Użytkownik nie może domyślać się, czy kliknięcie „Zapisz” oznacza trwały zapis na serwerze, czy tylko bezpieczne zachowanie rekordu na urządzeniu. Jeśli aplikacja działa bez internetu, komunikaty i etykiety powinny mówić wprost, co się stało, na przykład że rekord został zapisany na urządzeniu i zostanie wysłany po odzyskaniu połączenia.
Najczęstszy błąd projektowy to użycie jednego ogólnego komunikatu „zapisano”, który nie rozróżnia zapisu lokalnego od synchronizacji. W praktyce trzeba pokazywać stan na poziomie rekordu lub formularza, a nie tylko globalnie. Użytkownik powinien widzieć, czy dany wpis jest w kolejce, czy został już wysłany, czy wymaga uwagi z powodu błędu. Taki stan powinien być widoczny stale, a nie wyłącznie w krótkim powiadomieniu, które znika po kilku sekundach.
Dobry wzorzec to połączenie dwóch warstw informacji: bieżącego statusu połączenia oraz statusu synchronizacji danych. Sam brak sieci nie mówi jeszcze, czy użytkownik ma niezsynchronizowane rekordy. Z kolei przywrócenie połączenia nie oznacza automatycznie, że wszystko już zostało przesłane. Dlatego interfejs powinien osobno informować, że urządzenie jest offline lub online, i osobno pokazywać, ile elementów czeka na wysyłkę, kiedy była ostatnia udana synchronizacja oraz czy wystąpiły błędy.
Nazewnictwo ma kluczowe znaczenie. Lepiej używać precyzyjnych określeń takich jak Zapisano lokalnie, Oczekuje na synchronizację, Zsynchronizowano, Błąd synchronizacji niż ogólnych komunikatów typu Gotowe albo Sukces. Dzięki temu użytkownik rozumie różnicę między bezpieczeństwem danych na urządzeniu a ich faktycznym przesłaniem do źródła danych.
Ważne jest też projektowanie na sytuacje problemowe. Jeśli synchronizacja się nie powiedzie, interfejs nie powinien tylko sygnalizować błędu technicznego, ale pokazywać, co to oznacza operacyjnie: czy rekord nadal jest bezpiecznie zapisany lokalnie, czy będzie ponawiany automatycznie, czy użytkownik musi wykonać działanie ręczne. Brak tej informacji powoduje największą utratę zaufania, bo użytkownik nie wie, czy ma wprowadzać dane ponownie.
Z perspektywy UX najlepszy efekt daje stan widoczny i trwały, a nie oparty wyłącznie na jednorazowych komunikatach. Użytkownik powinien móc w każdej chwili sprawdzić, które rekordy czekają, które się wysłały i które wymagają reakcji. Jeśli ten model jest konsekwentny w całej aplikacji, użytkownik rozumie, że praca offline nie oznacza utraty danych, tylko inny moment ich synchronizacji.
Jak testować scenariusze terenowe (tryb samolotowy, słaby zasięg), żeby uniknąć niespodzianek?
Takie testy trzeba prowadzić jak próbę awarii, a nie jak zwykłe sprawdzenie formularza. Celem nie jest potwierdzenie, że aplikacja działa offline „w idealnym laboratorium”, tylko wykrycie momentów, w których użytkownik traci dane, widzi mylący komunikat albo nie wie, czy zapis się powiódł. Dlatego należy sprawdzać nie tylko pełny brak internetu w trybie samolotowym, ale też niestabilne przejścia między stanami: chwilowy zanik sieci, bardzo słaby sygnał, odzyskanie połączenia w trakcie zapisu oraz zamknięcie aplikacji przed synchronizacją.
Najlepsza praktyka to testowanie na realnym urządzeniu mobilnym, w warunkach zbliżonych do pracy terenowej. Sam emulator nie pokaże wszystkich problemów, zwłaszcza tych związanych z opóźnieniem, ubijaniem aplikacji przez system i lokalnym przechowywaniem danych. W każdym scenariuszu trzeba wykonać pełny cykl: uruchomić aplikację online, pobrać dane startowe, przejść offline, dodać i edytować rekordy, zamknąć aplikację, uruchomić ją ponownie bez sieci, a potem przywrócić połączenie i sprawdzić, czy wszystko zostało poprawnie zsynchronizowane bez duplikatów i bez nadpisania nowszych danych.
Kluczowe jest testowanie momentów granicznych. Jeśli użytkownik zapisuje formularz dokładnie wtedy, gdy sieć znika, aplikacja musi zachować spójne zachowanie: albo jednoznacznie zapisać rekord lokalnie, albo jasno zakomunikować brak zapisu. Nie może pozostawiać stanu pośredniego, w którym ekran sugeruje sukces, ale dane nie istnieją ani lokalnie, ani w źródle. To samo dotyczy słabego zasięgu: zapis może trwać długo, wywołania mogą kończyć się przekroczeniem czasu, a użytkownik może nacisnąć przycisk drugi raz. W testach trzeba więc świadomie powtarzać akcje i sprawdzać, czy mechanizm kolejkowania i identyfikacji rekordów zapobiega wielokrotnemu utworzeniu tego samego wpisu.
Warto przygotować prosty zestaw przypadków testowych oparty na stanach sieci. Należy sprawdzić, czy aplikacja poprawnie rozpoznaje brak łączności przy starcie, czy umożliwia pracę na lokalnych danych, czy blokuje funkcje wymagające połączenia oraz czy po odzyskaniu internetu sama lub na żądanie użytkownika uruchamia synchronizację. Równie ważne jest potwierdzenie, że po restarcie urządzenia lub wymuszonym zamknięciu aplikacji lokalna kolejka zmian nadal istnieje i daje się bezpiecznie wysłać później.
Żeby test miał wartość, trzeba obserwować konkretne rezultaty: czy użytkownik widzi status offline/online, czy liczba oczekujących zmian jest zgodna ze stanem faktycznym, czy po synchronizacji rekord pojawia się tylko raz, czy załączniki i pola obowiązkowe zachowują się tak samo jak online oraz czy błędy są zapisywane w sposób pozwalający ustalić, co dokładnie nie wyszło. Jeśli aplikacja nie daje takiej obserwowalności, problemy terenowe zwykle wychodzą dopiero w produkcji.
Najprostsza zasada brzmi: testuj nie „czy offline działa”, tylko „czy dane przetrwają każdy realistyczny błąd połączenia i powrót aplikacji do życia”. Dopiero wtedy można uznać scenariusz terenowy za bezpieczny.
Majczęściej zadawane pytania i odpowiedzi odnośnie PowerApps offline bez utraty danych: jak projektować zapisy, które nie znikają
Nie, same SaveData i LoadData nie wystarczą. Te funkcje rozwiązują tylko problem trwałego przechowania danych na urządzeniu, ale nie zastępują poprawnej kolejki zmian, obsługi błędów, identyfikatorów rekordów i bezpiecznej synchronizacji. Dopiero połączenie zapisu lokalnego, odtworzenia stanu i usuwania wpisów wyłącznie po potwierdzonym sukcesie daje realną ochronę przed utratą danych.
Rekord można usunąć dopiero po potwierdzonym sukcesie synchronizacji. Samo wywołanie Patch nie oznacza jeszcze, że dane trafiły do źródła danych. Najpierw aplikacja powinna otrzymać potwierdzenie powodzenia, potem zaktualizować stan kolejki i ponownie utrwalić ją lokalnie. Usunięcie wpisu wcześniej jest jedną z najczęstszych przyczyn cichej utraty danych.
Najlepiej pokazywać osobne statusy dla zapisu lokalnego i synchronizacji. Użytkownik powinien widzieć, czy rekord jest tylko zachowany na urządzeniu, czy już wysłany do systemu docelowego. Pomagają w tym jasne etykiety i trwałe oznaczenia stanu, na przykład:
- Zapisano lokalnie
- Oczekuje na synchronizację
- Zsynchronizowano
- Błąd synchronizacji
Nie zawsze, bo często lepiej scalać zmiany. Jeśli rekord został dopiero utworzony offline i jeszcze nie został wysłany, kolejne poprawki zwykle powinny aktualizować ten sam lokalny wpis zamiast tworzyć osobne operacje. Taka kompresja kolejki upraszcza synchronizację, zmniejsza ryzyko błędów i ogranicza liczbę sytuacji, w których można przypadkowo nadpisać dane.
Bo rekord offline musi być rozpoznawalny przed i po synchronizacji. LocalId pozwala aplikacji śledzić rekord od momentu utworzenia bez internetu, a ServerId jest uzupełniane dopiero po zapisie do backendu. Przechowywanie obu pól zapobiega gubieniu odniesień, ułatwia mapowanie relacji i pozwala bezpiecznie ponawiać operacje na tym samym rekordzie.
Rekord kolejki powinien być samowystarczalny. Po restarcie aplikacji musi dać się z niego odtworzyć, co trzeba zsynchronizować i w jakim stanie jest operacja. Zwykle warto przechowywać:
- lokalny unikalny identyfikator
- typ operacji
- dane biznesowe
- powiązanie z rekordem źródłowym
- status synchronizacji
- czas utworzenia i komunikat błędu
Najpierw trzeba zapisać dane lokalnie, a dopiero potem próbować Patch. Dzięki temu błąd zapisu zdalnego nie usuwa treści formularza ani nie zmusza użytkownika do wpisywania wszystkiego od nowa. Rekord powinien zostać w lokalnym buforze ze statusem błędu lub oczekiwania, aby można było go ponowić z tych samych danych także po ponownym uruchomieniu aplikacji.
Trzeba testować pełne scenariusze awarii i powrotu połączenia. Sama próba w idealnym trybie offline nie wystarczy, bo problemy zwykle pojawiają się na granicy stanów. W praktyce należy sprawdzić między innymi:
- zapis przy zaniku sieci
- restart aplikacji bez internetu
- ponowne uruchomienie z zachowaną kolejką
- synchronizację po odzyskaniu połączenia bez duplikatów i nadpisań