My application sometimes resets the UI into the initial UI state just like after the first initialization.
The event is occurred when I use another application while the problematic application is sent to background / is sleeping. Then when I change back to the problematic application, it displays the initial state of UI (The initial state of the main page).
I suppose I need to save its state before I minimize the application on OnSleep() event and restore it back on OnResume() event.
How can I do this?
I have solved the problem by using global variable to store whether the application is still running or not.
public MainPage()
{
InitializeComponent();
if(Config.Config._RunningGetLocationThread == true)
{
lbl_Title.Text = "Tracker is running...";
btn_StartTracking.Text = "Stop Tracking!";
}
else
{
btn_StartTracking.Text = "Start Tracking!";
lbl_Title.Text = "Tracker is not running";
}
}
Config.Config._RunningGetLocationThread == true is the global variable and set to true when the application is first started.
I think there should be a better way to save the state of the UI elements. If anyone know the best way to save state UI elements, please share here.
Related
I have a View (with ViewModel, both Singleton) that reads and writes to a text file.
I would like it to be opened and closed in a controlled manner and am trying to work out where this should happen in the Maui app lifecycle.
In normal operation I assume that I should open it in OnAppearing and close in OnDisappearing.
If however the app is moved into the background and killed, OnDisappearing for the View does not fire.
It does fire OnPause, so I could close the file here and reopen it OnResume but would rather not use these for simplicity (although I will if recommended).
My questions are:
Is there any event that fires when the app is killed by the user (i.e. put in background and swiped away).
If the app is killed, will my text file be tidily flushed and closed?
Does anyone have any recommendations for better ways to do this. I am new to MAUI and may be missing something fundamental.
" Is there any event that fires when the app is killed by the user?"
No. Your app code does not get a chance to run at that time.
As you've discovered, there is a bit of a disconnect between App lifecycle (e.g Deactivated or OnPaused) and Page lifecycle (Appearing/Disappearing).
This is fundamental to Android itself, whether usng Maui or not. On Android, the only event that is guaranteed to happen before an app disappears is OnPaused; Maui app's Deactivated event runs when Android activity's OnPaused is called.
Unfortunately after OnPaused, its possible that your app will not get to run any other code later. You'll have to design everythng with that in mind.
Regardless of what page or popup your app is showing, or what it is in the middle of, your "app Deactivated" code is responsible for saving whatever information needs to be persisted.
Below follows one conceptual way to handle the situation.
"If the app is killed, will my text file be tidily flushed and closed?"
No. Unfortunately, there is no standard mechanism that does this for you.
The burden is on you to keep track of what "clean-up" is needed, depending on what user has done / is doing in app.
Consider defining public class BasePage : ContentPage class, that has some methods that manage this. Have your pages inherit from BasePage.
An example:
// In App.xaml.cs:
protected override void OnDeactivated()
{
CurrentPage?.SaveData();
...
}
private static BasePage CurrentPage;
public static void PageAppearing(BasePage page)
{
CurrentPage = page;
}
public static void PageDisappearing(BasePage page)
{
// Null CurrentPage, but skip that if has already changed to a different page.
// (Fixes a bug on some XForms implementations; haven't tested whether this is still an issue in Maui.)
if (ReferenceEquals(CurrentPage, page)
CurrentPage = null;
}
public class BasePage : ContentPage
{
// NO "InitializeContext". No XAML. Each subclass does that.
public BasePage(){ }
protected override void OnAppearing()
{
base.OnAppearing();
App.PageAppearing(this);
}
protected override void OnDisappearing()
{
App.PageDisappearing(this);
base.OnDisappearing();
}
public virtual void SaveData() {}
}
public partial class MyTextEditPage : BasePage
{
public override void SaveData()
{
... code to save text file being edited ...
}
}
The first thing I do in Deactivated/OnPaused is set a flag: DidPause = true; (refers to declaration private static bool DidPause;). I check that flag in Activated/OnResume, to know that app is continuing after going into background (but was not killed).
Next, have some mechanism to save data that "may have changed" (aka "dirty flag" is set on them), but have not been saved to local files. Save them. (If there is changed information that has not yet been successfully sent to a server, that is more complex. I save locally first; what to do after that is beyond scope of this answer.)
Third, pause or shutdown anything that doesn't need to run while app is in background.
In OnResume, if (DidPause) { DidPause = false; ... } code to restore anything that got shutdown or paused.
In this case, whatever page was showing should still be showing. Data saving was just a precaution, in case the app never comes back.
else { ... } (DidPause not set): The app is starting for first time, or after being killed.
I'd like to persist the scroll location of a ListView so it is restored between app restarts.
So I guess it should be saved on disk using something like shared_preferences (in NSUserDefaults on iOS and SharedPreferences on Android)
Is there a builtin way (or plugin) to do that?
If I have to do it manually, when should I save the scroll position? (In a native Android app I would do it in the onPause Activity life-cycle method.)
I searched a lot, and I can't seem to find any post or sample code that shows how to save and restore the scroll position of a ListView.
If I have to do it manually, when should I save the scroll position? (In a native Android app I would do it in the onPause Activity life-cycle method.)
You can use mixin WidgetsBindingObserver to detect when your app goes to background and when it comes to foreground by listening AppLifecycleState
The observable lifecycle events are:
paused — The application is not currently visible to the user, not responding to user input, and running in the background. It is like onPause() in Android
inactive — The application is in an inactive state and is not receiving user input. (only IOS)
resumed — The application is visible and responding to user input. It is like onPostResume() in Android
suspending — The application will be suspended momentarily. (only Android)
In example I save date instead of scroll position
class _TestPageState extends State<TestPage> with WidgetsBindingObserver {
SharedPreferences prefs;
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('state = $state'); // resumed, paused, inactive, suspending
if (state == AppLifecycleState.paused) {
prefs.setString('date', DateTime.now().toIso8601String());
}
}
_TestPageState() {
initPrefs();
}
void initPrefs() async {
prefs = await SharedPreferences.getInstance();
print(prefs.getString('date') ?? 'no date');
}
...
We now have out of the box support for state restoration in Flutter.
Check out restorationId property inside ScrollView widget.
Check out this link to understand how to implement restoration in Flutter.
https://dev.to/pedromassango/what-is-state-restoration-and-how-to-use-it-in-flutter-5blm
As you said in your question - usage of https://pub.dev/packages/shared_preferences is the best option since it utilized the native way of handling such stuff - SharedPreferences for Android and NSUserDefaults for iOs. The new way to do in Android it PersistentBundle which can be also used in flutter, but there is no analog for iOs. Also you may use all kinds of databases and file storages but it will be overkill in this case.
Storing scroll state always was a manual task on mobile - there is no shortcuts for that out of the box - so only handmade stuff or libraries.
You can store the whole state of your stateful widget there and then restore it as a whole.
I want to show a Dialog or a UI when user first uses the android app. When a condition is satisfied (registration to an application server is done), the UI (or the Dialog) disappears, and a flag is set in SharedPreferences telling that android app is registered so that by each new app start the UI related to registration does not appear.
I do not know what to use since I am beginner in Android development? and which one is better from Performance perspective?
UI Fragments and show/hide fragments based on condition (check SharedPref Flag)?
Or switch between Activities ? Or is there a UI Intro that can be showed or disabled based on a Flag value?
This is pretty straightforward solution, first you just need to check if there's a flag saved in the prefs. Best to do it in onCreate of your launch activity
val settings = PreferenceManager.getDefaultSharedPreferences(context)
val shouldDisplayDialog = settings.getBoolean("first-launch", true)
if(shouldDisplayDialog) // your logic here
The second value in method getBoolean is the default one, so it will set value of shouldDisplayDialog true if there's no saved value.
After succesfull login or whatever, just write the value to SharedPrefs.
with (settings.edit()) {
putBoolean("first-launch", false)
commit()
}
That's it!
I have a cordova (ionic) app that facilitates entering a lot of form data. This form data is saved to localStorage until it is ready to be published.
So keep the app quick I am not saving to the disk everytime and input changes. I am saving when the user navigates away from the page.
The problem I'm having is that the user may enter a lot of data on one page, and close the app without navigating. This is an obvious use case but I'm not sure how to get in front of it without frequently going back to the disk.
Is there a way I can quickly save when the app is exited? I know I can listen to the "pause" event in cordova apps but is that the same when the app is exited? Does an exit emit "pause" ?
If not, what are some strategies for handling this?
TLDR: Listening to the pause event is the right strategy.
The pause event fires when the native platform puts the application
into the background, typically when the user switches to a different
application.
Source
The pause event is the only way to let you know your app is being put in the background. On some mobile platforms you don't even have the possibility to exit your app (in iOS for example), as the platform is managing this for you. So listening to the pause event is your only choice as you loose control once the app is paused. Therefore listening to the pause event is the right strategy.
See following code snippet:
document.addEventListener("pause", onPause, false);
function onPause() {
// Save the application state here
}
In detail I have implemented a storage service with a save-method, which is called in the OnPause-handler.
Pause only fires when going to background. Why you say "to keep the app quick"? it's javascript, you can save to disk every field when is filled and it can be asynchronous.
function onLoad() {
document.addEventListener("deviceready", onDeviceReady, false);
document.addEventListener("pause", onPause, false);
function onDeviceReady() {
var pageNow = document.getElementsByTagName(body);
var currentPage = JSON.stringify(pageNow);
localStorage.setItem("uiState", currentPage);
}
function onPause() {
//retrieve info here
var pageShow = document.getElementsByTagName(body);
let techStack = localStorage.getItem("uiState");
// JSON.parse(techStack); //unnecessary because the parser will detect HTML
pageShow.innerHTML(techStack);
}
}
you can get the current state of the UI in any element, just use a different selector.
I have two static tables with about 500 records each which provide lookup material for some ListViews in my app. When the app first starts and the tables are created I run a process to populate the tables which takes less than a minute but I've been looking to run the process in background using Async Task with a progress dialog letting the user know what is happening.
My concern is that while the process is running and the data is being added and then the user tilts the phone the process will cancel. What would be the state of my database then? Is Async Task the best solution for this or should I use Threading?
So when you rotate or change the orientation of the phone, the activity is the only thing destroyed. You don't necessarily have to get rid of the async task. In fact it will live on. Just don't let another task come in and work on it ad-hocly.
So if you want to have your activity act as if upon rotating that you can start right back up, where you left off, there is a method called onRetainNonConfigurationInstance(). It's basically the method that stashes objects which can't be parceled like in saveInstanceState()
So the idea being:
public void onCreate(Bundle a) {
...
AsyncTask myTask = getNonConfigurationInstance();
if (myTask == null) {
myTask = new AsyncTask();
myTask.execute();
}
...
}
public Object onRetainNonConfigurationInstance() {
return myTask;
}
This will keep the async task running, and when you get your onCreate called after the rotation you just pick it back up and do what needs to be done.
One thing to be conscious of is the progressView. It will have to be destroyed and reinitialized to the new state. Also the overall dismissing of it and showing it in the first place should be done outside the AsyncTask. But nothing is to say that the AsyncTask can't call some callback that you always set in your onCreate() so that it will notify to tell to update the UI or play a sound of completion, etc.
You could also decide to handle the configuration changes on your own through the use of the android:configChanges in your manifest.
You then implement the onConfigurationChanged method and perform any actions inside.
See the developer doc.
When a configuration change occurs at
runtime, the activity is shut down and
restarted by default, but declaring a
configuration with this attribute will
prevent the activity from being
restarted. Instead, the activity
remains running and its
onConfigurationChanged() method is
called.