Flutter: L'état de l'instance n'est pas enregistré lorsque l'application est supprimée par le système d'exploitation

Créé le 12 nov. 2016  ·  139Commentaires  ·  Source: flutter/flutter

Qu'est-ce que l'état d'instance et pourquoi il existe

Sur Android, une activité peut être tuée à tout moment par le système. Cela se produit généralement lorsqu'Android a besoin de mémoire lorsque votre activité n'est pas au premier plan, ou en raison d'un changement de configuration non géré, tel qu'un changement de paramètres régionaux.
Pour éviter que l'utilisateur n'ait à recommencer ce qu'il a fait à partir de zéro lorsqu'Android a tué l'activité, le système appelle onSaveInstanceState(…) lorsque l'activité est en pause, où l'application est censée enregistrer ses données dans un Bundle , et transmet le bundle enregistré à la fois dans onCreate(…) et onRestoreInstanceState(…) lorsque la tâche est reprise si l'activité a été arrêtée par le système.

La question à ce sujet en flutter

Dans les applications de flutter que j'ai essayées (Flutter Gallery et le projet de base avec le compteur de pression FAB), si j'ouvre suffisamment d'applications pour qu'Android tue l'activité de l'application de flutter, tout l'état est perdu lorsque je reviens à l'activité de flutter (alors que ne pas avoir supprimé la tâche des récentes).

Étapes pour reproduire

  1. Installer la galerie Flutter
  2. Ouvrez les paramètres de l'appareil et, dans les options du développeur, activez « Ne pas conserver les activités ». (voir la capture d'écran)
    dev_option
    Cela permettra de simuler quand Android tue Activités car il manque de mémoire et vous n'êtes pas au premier plan.
  3. Ouvrez l'application Flutter Gallery et allez n'importe où ailleurs que sur l'écran principal.
  4. Accédez au lanceur en appuyant sur le bouton d'accueil de l'appareil.
  5. Appuyez sur le bouton Aperçu et revenez à Flutter Gallery. Voici le bogue.

Ce qui est attendu : l'application est dans le même état que là où nous nous sommes arrêtés, avec une interface utilisateur intacte.
Que se passe-t-il : l'activité est redémarrée à partir de zéro, perdant tout l'état de l'interface utilisateur, même les formulaires vraiment très longs.

P2 annoyance crowd routes framework new feature

Commentaire le plus utile

Mise à jour rapide : je travaille actuellement activement à l'exploration de solutions à ce problème.

Tous les 139 commentaires

https://github.com/flutter/flutter/issues/3427 est également probablement lié.

@eseidelGoogle C'est vrai. Dans quel format l'état d'activité flutter peut-il être enregistré, s'il peut l'être à partir de la version actuelle ?

En ce moment, nous ne faisons rien pour sauver quoi que ce soit.

Le framework lui-même a très peu d'état qui mérite d'être sauvegardé - c'est toute l'animation et des trucs comme ça - il se peut donc que nous laissions toujours le soin à l'application de le faire. Nous devrions probablement l'exposer au niveau de Dart cependant. (Pour le moment, il n'est exposé qu'au niveau Java.)

@Hixie Sur Android, toutes les vues du framework ont ​​leur état automatiquement enregistré et restauré par le système, ce qui nécessite uniquement que le développeur enregistre manuellement la partie non UI de l'état de l'instance. Le flottement ne devrait pas fonctionner de la même manière pour tous les widgets de l'interface utilisateur afin d'éviter aux développeurs d'avoir à écrire tout le passe-partout à chaque fois pour enregistrer l'emplacement de l'utilisateur, la position de défilement, l'état activé de certains boutons, l'état de l'écran précédent, etc. au…?

Dans Flutter, il y a très peu d'état du framework à sauvegarder. Par exemple, l'état activé d'un bouton n'est pas un état, c'est une entrée fournie par l'application. L'historique de route actuel est stocké dans le framework, mais pas dans un état que le framework peut reconstruire (puisqu'il s'agit de toutes les instances d'objets fournies par le code de l'application). La position de défilement est à peu près la seule chose que nous puissions réellement stocker (et nous l'enregistrons actuellement dans un magasin, mais pas celui qui survit à l'application).

Mais dans tous les cas, nous devrions certainement faire mieux qu'aujourd'hui.

En tant que développeur Android expérimenté, j'aimerais également participer à la conversation avec les développeurs iOS lorsqu'on en discutera afin que Flutter puisse être le cadre idéal pour créer des applications iOS et Android.
Je pense que la capacité de persistance du framework peut être importante pour enregistrer les données, les préférences, les données de cache et les états de la manière la plus conviviale possible pour les développeurs.

Whoa je ne savais pas que c'était le cas? C'est en fait critique, et il convient de mentionner que sur les appareils avec moins de mémoire, le processus pourrait être tué assez facilement !

Qu'en est-il de l'enregistrement du PageStore (ou associé) en tant que flux d'octets via serialization.dart dans onSaveInstanceState ?

@Takhion Pourriez-vous lier "PageStore" que vous mentionnez ? Je suis intéressé à essayer de contourner ce problème car il semble qu'il ne sera pas résolu de sitôt (le 31 décembre 2029 est un peu loin à mon humble avis)

@LouisCAD bien sûr c'est ici : https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/page_storage.dart

Je pense que vous aurez besoin d'économiser plus que cela: au moins la ou les routes actuelles et peut-être un état?
Qu'en penses-tu @Hixie ?

Nous ne faisons actuellement rien pour rendre cela facile. Nous n'avons pas encore étudié ce problème en détail. Pour l'instant, je vous recommande de stocker manuellement les informations que vous souhaitez conserver et de les réappliquer lorsque l'application est restaurée.

Exposons-nous les événements de cycle de vie nécessaires (« vous êtes sur le point d'être tué !", « félicitations, vous êtes maintenant restauré ») dans le code Dart, afin que les développeurs puissent gérer l'état persistant ? Cela semble être les capacités suffisantes pour déverrouiller les développeurs pour explorer des solutions. Les pensées?

@sethladd Il ne suffit pas de l'exposer aux développeurs à

@LouisCAD merci pour le retour. Je pense par étapes... quelle est la première étape ? Avons-nous déjà exposé les événements du cycle de vie ?

@sethladd tout ce que j'ai pu trouver est ceci , qui n'expose aucun rappel pour la sauvegarde/restauration de l'état de l'instance

@Hixie, vous avez mentionné au #3427 que nous avons des crochets pour le cycle de vie. Vouliez-vous dire https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html ?

Flutter utilise-t-il plusieurs activités par défaut ? (par exemple si vous passez à un nouvel écran).

La mort du processus est assez courante, l'utilisateur appuie sur le bouton Accueil et lance une autre application gourmande en mémoire/CPU et votre application sera tuée en arrière-plan (ou votre application a été en arrière-plan pendant trop longtemps). Cela fait partie intégrante du système d'exploitation Android - chaque écran (activité) doit pouvoir persister et restaurer son état et le processus peut être arrêté à tout moment après onStop().

Android recréera le backstack des activités si l'utilisateur revient à une application supprimée, la première activité est créée en premier, puis les activités du backstack sont recréées à la demande si vous revenez dans l'historique du backstack. Cela peut être un problème plus important si Flutter utilise plusieurs activités (je ne sais pas si c'est le cas).

Cela signifie que si vous n'avez pas mis en œuvre la sauvegarde d'état, le système recréera les activités, mais tout le reste sera perdu (le processus a été tué), entraînant ainsi des incohérences et des plantages.

J'ai écrit un article sur la mort du processus sur Android https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

De plus, il n'existe aucun moyen (propre) d'empêcher Android de tuer votre application une fois qu'elle a dépassé l'état onStop() (après avoir cliqué sur Accueil ou si l'application n'est tout simplement pas l'application actuelle au premier plan). Donc, d'une manière ou d'une autre, vous devez vous en occuper. Les widgets Android par défaut enregistrent leur état (par exemple, le texte saisi dans EditText) automatiquement dans l'instance du Bundle. Votre activité vous informera qu'il est nécessaire de stocker votre état en appelant onSaveInstanceState(Bundle) . Alors peut-être que Flutter devrait pouvoir transférer ce rappel onSaveInstanceState de l'activité vers vos "écrans". Vous récupérerez le Bundle avec l'état enregistré dans le rappel de cycle de vie onCreate(Bundle saveInstanceState) ou onRestoreInstanceState(Bundle saveInstanceState) dans votre activité.

Donc, pour récapituler, Flutter pourrait peut-être transmettre les rappels onSaveInstanceState() et onRestoreInstanceState() au développeur. Idéalement, vous envelopperiez également l'objet Android Bundle dans quelque chose qui peut également être utilisé dans Flutter. La prochaine étape serait que tous les widgets Flutter à l'intérieur de l'écran soient également informés de ces rappels et les utilisent pour conserver leur état actuel.
Le système d'exploitation Android prendra alors le Bundle et le conservera sur le disque afin qu'il ne soit pas perdu au cas où le processus serait tué et pourrait être à nouveau désérialisé.

Bonne chance avec ça :-). S'il vous plaît faites attention et ne présentez pas trop cet enfer d'état / cycle de vie Android à Flutter.

Je ne sais pas comment cela fonctionne sur iOS - mais je pense qu'il y a quelque chose de similaire mais ce n'est pas "nécessaire" pour l'utiliser (?).

Un rappel de cycle de vie serait bien pour moi. Je voudrais simplement stocker/charger l'état redux sérialisé.

Merci pour tous les commentaires! @Takhion a également proposé son aide ici. Peut-être qu'une conception d'API et une bibliothèque sont un bon début ? Si cette bibliothèque fonctionne, nous pouvons envisager une intégration plus formelle. De plus, la bibliothèque aidera à identifier ce que nous devons faire au niveau du moteur de bas niveau (le cas échéant). En gros : quelle est la proposition concrète d'API ?

Flutter utilise-t-il plusieurs activités par défaut ? (par exemple si vous passez à un nouvel écran)

@DanielNovak Flutter utilise son propre système de "routage" et s'intègre par défaut à Android via une seule vue dans une seule activité. Vous pouvez avoir une application hybride Android/Flutter et, en tant que telle, potentiellement plusieurs activités et vues Flutter, mais dans ce cas, vous pouvez facilement enregistrer/restaurer l'état de l'instance directement via Android.

Je voudrais simplement stocker/charger l'état redux sérialisé

@zoechi, vous ne voulez probablement pas faire cela car l'état de l'instance enregistré Bundle doit passer par IPC avec une limite stricte de 1 Mo pour l'ensemble de l'état de votre application Android. L'état de l'instance, par définition, ne devrait être que les éléments de données qui vous permettraient de recréer les mêmes conditions de toute activité en cours que l'utilisateur exécute, donc : la saisie de texte, la position de défilement, la page actuelle, etc. être persisté sur le disque ou dérivé d'un autre état.

Flutter expose l' événement de cycle de vie Android onPause . L' événement de cycle de vie Android onDestroy() n'est pas exposé. Je suppose que la bonne approche serait de se connecter à l'événement onPause () pour stocker l'état lié à l'instance.
Pour être compatible avec onSaveInstanceState() et onRestoreInstanceState() d'Android, il est peut-être logique d'ajouter des méthodes similaires aux widgets Flutter.
@sethladd @LouisCAD Quelqu'un a-t-il créé une proposition de conception ?

@raju-bitter onPause() n'est pas optimal, il est préférable de se connecter à onSaveInstanceState(). Voici la javadoc de onSaveInstanceState qui mentionne que onPause() est appelé plus régulièrement que onSaveInstanceState (vous déclencheriez la sauvegarde d'état plus souvent que nécessaire ou quand ce n'est pas du tout nécessaire):

Ne confondez pas cette méthode avec les rappels de cycle de vie d'activité tels que onPause(), qui est toujours appelé lorsqu'une activité est placée en arrière-plan ou en voie de destruction, ou onStop() qui est appelée avant la destruction. Un exemple d'appel de onPause() et onStop() et non de cette méthode est lorsqu'un utilisateur revient de l'activité B à l'activité A : il n'est pas nécessaire d'appeler onSaveInstanceState(Bundle) sur B car cette instance particulière ne sera jamais restaurée , le système évite donc de l'appeler. Un exemple lorsque onPause() est appelé et non onSaveInstanceState(Bundle) est lorsque l'activité B est lancée avant l'activité A : le système peut éviter d'appeler onSaveInstanceState(Bundle) sur l'activité A s'il n'est pas tué pendant la durée de vie de B depuis l'état de l'interface utilisateur de A restera intact.

@Takhion

L'état de l'instance Bundle doit passer par IPC avec une limite stricte de 1 Mo pour l'ensemble de l'état de votre application Android

Ce n'est pas mon problème.
J'ai juste besoin de savoir quand persister/restaurer, me persister sur le disque me convient.

La propagation du onSaveInstanceState / onRestoreInstanceState est la partie la plus simple : Flutter est contenu dans une vue et a un accès approprié à ces rappels. Ce que j'essaie de faire, c'est de créer une proposition de conception et un prototype pour sérialiser automatiquement les objets contenus dans un certain "référentiel", de sorte que son utilisation devienne indolore (par opposition à tout faire manuellement).

@Takhion tu es le vent sous mes ailes. Hâte de voir la proposition !

OK, je construis un PR pour cela et je rencontre un barrage routier : [ View.onSaveInstanceState ] (ou [ Activity.onSaveInstanceState ], d'ailleurs) nécessite de fournir l'état de l'instance sauvegardée de manière synchrone , et à partir de mon expérience (et ce [commentaire]) il semble que les messages hôte/plate-forme ne puissent

@sethladd @Hixie @eseidelGoogle @jason-simmons s'il vous plaît dites-moi qu'il existe un moyen simple de faire un seul appel synchrone/bloquant d'Android vers Flutter qui n'implique pas de code C/C++ ad-hoc, puisque [ dart:jni a été retraité]?

Cc @mravn-google

La nature asynchrone des messages de plate-forme vient du fait que l'interface utilisateur Dart s'exécute sur un thread différent de celui utilisé pour les rappels de plate-forme Android. Mais vous pouvez toujours transformer un appel d'API asynchrone en un appel synchrone en utilisant un verrou :

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  <strong i="6">@Override</strong>
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  <strong i="7">@Override</strong>
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  <strong i="8">@Override</strong>
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

Vous pouvez déclarer une variable final AtomicReference<SomeType> (ou similaire) à côté du verrou, si vous devez transmettre des valeurs des emplacements // handle xxx au code qui s'exécute après le retour de latch.await() .

merci @mravn-google !

@mravn-google c'est exactement la première approche que j'ai essayée, mais le fil principal sur Android se bloque pour toujours sur l'appel await() :(

@Takhion D'accord , bien sûr.

vous pouvez toujours transformer un appel API asynchrone en un appel synchrone en utilisant un verrou

... à moins que la réponse async ne soit livrée au thread sur lequel vous êtes déjà :-/

Nous semblons donc avoir besoin d'une sorte de poignée de main de synchronisation pour onSaveInstanceState . Connaissons-nous d'autres rappels similaires qui nécessitent une réponse de synchronisation impliquant des calculs côté Dart ? Cela nécessiterait un échange de messages de plate-forme synchrone à usage général. Sinon, nous pourrions faire quelque chose spécifiquement pour onSaveInstanceState .

@mravn-google malheureusement Android est _plein_ de ces méchantes "fenêtres d'opportunité synchrones", où tout doit se passer avant de revenir de l'appel de méthode ! Sur le dessus de ma tête : de nombreux événements de cycle Activity vie de Broadcast Receiver , SyncAdapter , etc. Je suis honnêtement surpris que cela n'ait pas été un problème plus important auparavant !

À mon avis, si nous voulons que Flutter atteigne vraiment la parité des fonctionnalités avec ce qui est possible grâce aux API "natives", alors avoir une communication de message synchrone est un must.

@mit-mit Je voulais dire SystemChannels.lifecycle et tout ce qui l'utilise, oui.

@sethladd Je pense que oui.

@mravn-google merci ! J'ai fermé le #7560 en faveur de celui-ci, juste pour dédupliquer et centraliser la conversation.

Je penche pour ajouter une contrepartie synchrone à MethodChannel pour ce type de communication de plate-forme à Dart.

@mravn-google comment ça va ? Puis-je aider?

@Takhion Merci ! Il devrait y en avoir bientôt. Je travaille sur un canal de méthode synchrone et j'espère avoir un PR d'ici un jour ou deux. Une fois là-bas, je vous demanderai votre avis. Je veux connecter le nouveau canal à onSaveInstanceState possible pour tester que tout fonctionne comme prévu dans ce scénario. Si vous avez déjà un exemple d'application auquel vous pourriez contribuer, ce serait génial.

@mravn-google génial ! Je n'ai pas d'application car je modifie directement le framework, mais je peux certainement fournir un exemple. D'ailleurs, j'utilise directement BinaryMessages car il n'y a pas besoin de codec côté Android (ce ne sont que des octets transmis), est-ce que ça va?

Vous êtes top les gars.

@Takhion L'utilisation directe de BinaryMessages est très bien, si vous n'avez pas besoin d'un codec. je vais ajouter un

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

à cette classe.

@Takhion Frappe ça. Nous ne pouvons pas simplement faire des appels synchrones dans la machine virtuelle Dart pour invoquer le handler . Donc BinaryMessages ne sera pas modifié. Au lieu de cela, tout sera asynchrone du point de vue du code Dart, comme d'habitude. Le changement portera sur BinaryMessenger et ses implémentations côté plateforme. je vais ajouter

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

Le code de vue de plate-forme prenant en charge FlutterView implémentera cela en envoyant un message de plate-forme asynchrone, puis attendra un verrou. L'objet de réponse inclus avec ce message de plate-forme ne publiera pas de fermeture de gestion de réponse sur le thread de plate-forme (qui est maintenant bloqué), mais stockera à la place la réponse et libérera le verrou.

@mravn-google fonctionne pour moi, car cela ne nous empêche pas de renvoyer des données du côté Dart, même s'il s'agit d'un appel asynchrone :+1 :
Je suppose qu'il n'y aura pas d'optimisations si l'on utilisait Future.value(...) dans la réponse asynchrone Dart ?

@Takhion Veuillez consulter https://github.com/flutter/engine/pull/4358

Je vois que vous parlez principalement de la plate-forme Android ici. Mais, en fait, iOS dispose également d'un mécanisme quelque peu similaire pour enregistrer/restaurer l'état de l'interface utilisateur lorsque l'application a été supprimée pour une raison quelconque. Par exemple, lorsque l'utilisateur passe à une autre application, l'application actuelle est suspendue et peut même être supprimée en raison de contraintes de mémoire. Vous pouvez trouver plus d'informations sur ce processus dans les documents officiels.

Les développeurs iOS s'attendent à ce que cette procédure de préservation/restauration soit presque automatique. Je suppose que si votre objectif est de créer un framework véritablement multiplateforme, vous devez absolument en tenir compte.

On pourrait penser que quelque chose qui vise à cibler Android en tant que plate-forme tente au moins d'obéir aux bases du contrat d'activité....

@Zhuinden ne nous plaignons pas des choses qui sont encore en version bêta ? :)

Les autres API synchrones sont onLowMemory et onTrimMemory et onConfigurationChanged je ne sais pas si IOS a également des API ou des mécanismes similaires

Nous ne voulons peut-être pas reproduire le mécanisme de sauvegarde et de restauration Android défectueux sur Flutter : vous ne pouvez pas enregistrer l'intégralité de l'état dans le bundle en raison de la limite de taille de la transaction.

Sur Android, vous finissez par avoir plusieurs logiques de sauvegarde/restauration d'état :

  • saveInstanceState du développeur, mais vous ne pouvez pas stocker de gros objets à l'intérieur, comme une liste d'éléments
  • base de données, où vous pouvez stocker de gros objets
  • la plupart des widgets gèrent également leur propre état
  • activité/pile de fragments

Il est vraiment facile pour les développeurs d'implémenter une logique désordonnée et boguée. D'après ma propre expérience, il est vraiment rare de voir une sauvegarde et une restauration de l'état correctement effectuées.

Sur flutter, l'état du développeur est la seule source de vérité.
Les développeurs doivent conserver cet état sur le disque lorsqu'il change, sans passer par les mécanismes limités de la plate-forme native.

Bien sûr, vous ne pourrez pas facilement enregistrer la position de défilement, mais qu'importe.
La plupart du temps, une bonne UX pour la restauration signifie que l'utilisateur devrait voir le même écran que celui sur lequel il se trouvait auparavant. Vous pouvez déjà le faire avec Flutter aujourd'hui.

Point de fond : il y a place à l'amélioration du côté de Flutter afin de rendre la sauvegarde/restauration d'état moins passe-partout (pour des choses comme Navigator). Mais je ne pense pas que cela devrait fournir un crochet à l'état de sauvegarde/restauration de la plate-forme.

Bien sûr, vous ne pourrez pas facilement enregistrer la position de défilement, mais qu'importe.

Toutes les personnes? Je serais vraiment frustré si l'application réinitialise sa liste chaque fois que je déclenche accidentellement un changement d'orientation. :p

@Saketme FWIW, Flutter ne réinitialise pas les positions de défilement lors du changement d'orientation.

Utiliser une application Flutter avec un peu de multitâche devrait être amusant sur un appareil Android Go ! Des progrès perdus à chaque fois que 1 Go de RAM ou moins est sous pression

@Saketme : comme l'a dit @mravn-google, Flutter n'est pas lié aux changements de configuration comme les activités Android.
La seule gestion d'état nécessaire est lorsque l'application est tuée par le système d'exploitation tout en étant en arrière-plan.

@LouisCAD La sauvegarde et la restauration de l'état sûr sont importantes.
Les développeurs de Flutter peuvent déjà implémenter la plupart de cette logique eux-mêmes, et Flutter devrait probablement fournir des moyens de la rendre moins passe-partout. Cependant, le mécanisme proposé par Android est défectueux et nous pourrions en profiter avec Flutter pour concevoir quelque chose de mieux, plutôt que de pirater Activity onSaveInstanceState / onRestoreInstanceState .

Quel est l'état actuel de ce problème ?

Prenons le cas d'une application de chat, où je tape un long message dans le TextField .
Soudain, je reçois un appel téléphonique et après un certain temps, le système d'exploitation tue l'application de chat en raison d'une mémoire insuffisante.
Après l'appel téléphonique, je reviens à l'application de chat. Flutter persistera-t-il automatiquement et recréera-t-il le message que j'étais en train de taper ? ou L'application doit persister et la recréer manuellement ?

À part la position de défilement et l'itinéraire, que sont les « états » qui ne sont généralement pas gérés par les développeurs ?

@nsreenath Toute application de discussion doit conserver les messages

En fait, j'ai dormi là-dessus et je me suis rendu compte qu'il est en fait assez facile pour les développeurs de Flutter de détecter si l'application est revenue de la mort du processus ou est une instance complètement nouvelle.

Très simple, tant qu'ils vérifient savedInstanceState == null (en supposant qu'ils mettent au moins 1 élément dans le onSaveInstanceState(bundle) ). Si un indicateur booléen statique isFirstInit == false et savedInstanceState != null , c'est après la mort du processus.

Ensuite, ils peuvent créer n'importe quel type de rappel de sérialisation/désérialisation pour un état complexe qui pourrait être effacé automatiquement lorsqu'ils détectent savedInstanceState == null . De cette façon, ils n'ont besoin que de conserver certains booléens aléatoires, mais peuvent gérer leur propre système de persistance.

Très simple, tant qu'ils vérifient saveInstanceState == null (en supposant qu'ils mettent au moins 1 élément dans le onSaveInstanceState (bundle)).

Il n'y a pas de savedInstanceState et de onSaveInstanceState sur iOS. Mais l'application peut toujours être tuée par le système d'exploitation.

Considérez un flux de travail typique :

  • Un utilisateur accède à un écran spécifique dans l'application
  • Ensuite, l'utilisateur passe à une autre application
  • Soudain, le sage OS décide de tuer l'application précédente pour préserver la mémoire
  • L'utilisateur revient à l'application précédente mais elle voit l'écran d'accueil. Pas l'écran qui était ouvert lorsqu'elle a décidé de changer d'application

Alors, quelle est actuellement la méthode recommandée pour préserver l'historique de l'écran dans Flutter ?

PS Bien sûr, ce n'est pas important si votre application n'a qu'un seul écran :smirk:

@Zhuinden, je ne vois pas de nombreux scénarios où cela est nécessaire de toute façon.

a) Exemple de chat :
Enregistrez et restaurez toujours le contenu précédent écrit dans le champ de texte qui n'a pas été envoyé.

b) Exemple de forme longue :

  1. Enregistrez toujours le contenu
  2. Lorsque l'utilisateur revient à l'écran du formulaire, informez l'utilisateur qu'une version brouillon a été enregistrée mais n'a pas été soumise, en lui demandant s'il souhaite la restaurer. Cela ne fait aucune différence si l'utilisateur a quitté le formulaire manuellement (peut-être demander si l'utilisateur veut enregistrer le brouillon) ou si l'application a été tuée en arrière-plan.

Il existe probablement des cas où une connaissance spécifique du "nouvel écran frais" par rapport au "nouvel écran après la mort du processus" est utile, mais cela peut ne pas être si courant lorsque vous concevez le contenu de votre application pour qu'il soit toujours soutenu par une base de données.

@storix Vous devez conserver manuellement l'itinéraire actuel et le restaurer au redémarrage de l'application. Trop de passe-partout pour un cas d'utilisation courant, je suis d'accord. Mais toujours faisable.
Aussi: De nombreux développeurs Android seraient surpris de voir à quel point peu d'applications/dev iOS se soucient de la restauration de l'état. La majorité des développeurs que je connais ne savent même pas que c'est un problème. Je suppose que c'est le luxe de travailler uniquement sur des appareils haut de gamme.

@Zhuinden les rappels de cycle de vie de https://github.com/flutter/flutter/issues/6827#issuecomment -335160555 ont fonctionné pour mon application de chat. J'enregistre juste un état à chaque fois que l'application passe en arrière-plan. Voir aussi https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html

Je voudrais exhorter l'équipe Flutter à ne pas exposer les méthodes de cycle de vie Android/iOS comme moyen canonique de persister/restaurer l'état de l'interface utilisateur. Même dans les applications natives, il était difficile de les gérer et de les coder correctement, et la frontière entre l'interface utilisateur et l'état de l'application était souvent floue, avec des mécanismes différents fournis pour chacun. Il serait dommage de promulguer toutes ces API disparates dans Flutter (surtout si des plates-formes supplémentaires sont ciblées à l'avenir).

Les widgets sans état de Flutter ont _déjà_ migré la responsabilité de l'état de l'interface utilisateur des widgets vers l'application, de sorte que le problème de la persistance/restauration de l'état de l'interface utilisateur est de plus en plus couvert par la persistance/restauration de l'état _app_ à la place. Je considère cela comme une bonne chose, par opposition à un traitement différent de l'état de l'interface utilisateur et de l'application, comme dans iOS et Android.

Je remarque que certains widgets Flutter, comme ScrollView et TextField, fournissent des classes "Controller" qui encapsulent l'état dynamique du widget. D'une certaine manière, ils agissent comme des objets "souvenirs" -- un instantané de l'état du widget -- qui peuvent être fournis ultérieurement au widget pour restaurer leur état à une configuration précédente. Sonne familier. Si ces « souvenirs » étaient rendus faciles à conserver/restaurer, une application pourrait alors en assumer la responsabilité dans le cadre de la gestion de son propre état. Cela mettrait fin à la dichotomie entre l'état de l'application et de l'interface utilisateur, serait activé et éviterait au framework Flutter de fournir des crochets de préservation de l'état de l'interface utilisateur (moins c'est plus !)

En d'autres termes, faites-en la responsabilité du développeur, mais essayez de le rendre aussi simple que possible. Si un développeur pense qu'un formulaire à moitié rempli est suffisamment important pour que Flutter persiste à travers les invocations, alors on pourrait argumenter qu'il est probablement suffisamment important pour être considéré comme un état d'application. Je me rends compte que la persistance de l'état des objets introduit des problèmes de version, mais je pense que c'est un problème plus gérable.

Y a-t-il eu des progrès sur ce sujet? Je pense qu'il y a un réel besoin de faire la distinction entre l'état de processus « première exécution » et « restauré ». Sinon, comment pouvez-vous programmer pour afficher le bon écran lorsque l'utilisateur revient à un processus tué ?

@lukaspili vous avez écrit : "La seule gestion d'état nécessaire est lorsque l'application est tuée par le système d'exploitation tout en étant en arrière-plan."

Ok, alors qu'en est-il de la pile de routeurs, enregistrez-vous également sur le disque ? De plus, je n'utilise pas t find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I don Redux ?

Je pense que dans un premier temps, nous devrions donner aux plugins la possibilité de sauvegarder et de restaurer l'état. Voici ma proposition : #22328

Y a-t-il eu une mise à jour sur ce problème?

Je suis confronté au même problème. J'ai une solution pour enregistrer les entrées de texte ici via la base de données en temps réel Firebase. Mais la méthode est assez grossière et manque souvent d'option d'évolutivité car chaque utilisateur sera redirigé vers la même base de données. Par conséquent, mes données sont disponibles pour tout le monde avec un accès à un compte gmail. J'ai essayé d'utiliser Redis, mais bien que je puisse créer des listes distinctes dans la même base de données pour d'autres utilisateurs, cela se fait toujours via une méthode POST. Cela signifie que la vue doit être actualisée chaque fois qu'une nouvelle donnée est stockée. Ce qui signifie pas de visuels en temps réel. Un gros bug en effet.

Il y a un plus gros problème ici. J'ai eu des échanges avec @matthew-carroll il y a deux mois et je suis assez confiant que c'est en cours.

J'ai commencé à créer une bibliothèque qui traite de cela . En fait, je ne recommanderais pas de l'utiliser, mais c'est une idée d'une implémentation Dart uniquement. #6225 a rendu les choses un peu délicates.

J'ai remarqué que l'application Google Ads, qui aurait été conçue avec Flutter, préserve l'état de la navigation après un redémarrage de l'activité. Ce serait une bonne référence pour savoir comment il est implémenté, même si c'est une solution de contournement.

@aletorrado L'application Google Ads préserve-t-elle l'état de navigation au cas où l'utilisateur aurait supprimé l'application du « gestionnaire de tâches » Android/IOS et l'aurait ensuite lancée à nouveau ?

@gitspeak Non, il ne conserve l'état que si l'activité a été automatiquement tuée par le système d'exploitation (peut-être en utilisant le hook onSaveInstanceState).

@aletorrado Alors oui, c'est probablement le seul moyen...

J'ai en fait essayé d'entrer dans l'application Google Ads, mais vous devez avoir un compte actif pour accéder à n'importe quel écran au-delà du tout premier : DI ne peut pas tester si cela fonctionne vraiment ou non

Cela fait. Garanti. Il préserve également d'autres états comme la position de défilement.

Pour clarifier une certaine confusion :

  • Oui, l'application Google Ads est écrite avec Flutter.
  • Étant l'un des premiers à adopter, ils ont dû faire quelque chose d'inhabituel pour l'exécution en arrière-plan. Ils créent et maintiennent leur isolat principal lorsque l' application démarre (pas l'activité). Ensuite, ils transmettent cet isolat à la vue Flutter lors de sa création.
  • Ce que vous observez comme « maintien de l'État » est un effet secondaire de la méthodologie décrite ci-dessus. Lorsque vous tuez l'activité dans Google Ads, l'"UI" de Flutter ne meurt pas. Il est conservé en mémoire lié au contexte de l'application. Lorsque l'activité est redémarrée, elle charge le même isolat et le même alto, vous récupérez votre interface utilisateur.

Nous ne recommandons pas cette approche pour résoudre ce problème car elle ne résout pas du tout le problème. Si le processus de demande meurt, l'état entier est toujours perdu. Vous pouvez essayer cela avec Google Ads en arrêtant le processus depuis Paramètres> Applications. L'interface utilisateur sera perdue.

Il existe peut-être un moyen de sauvegarder l'état d'isolement localement, mais cela nécessite une enquête plus approfondie car nous ne savons pas si c'est faisable/sûr.

Pour l'instant, les solutions décrites ci-dessus sont les meilleures : connectez-vous à des méthodes natives et enregistrez de manière sélective l'état de votre application afin de pouvoir la restaurer plus tard. Une solution automatique prise en charge par le framework sur iOS et Android est encore loin.

Vous pouvez essayer cela avec Google Ads en arrêtant le processus depuis Paramètres> Applications. L'interface utilisateur sera perdue.

non c'est tout à fait ok. Forcer l'arrêt d'une application devrait perdre l'état.

Être terminé par une condition de mémoire faible ne devrait pas.

Quoi qu'il en soit, s'ils conservent l'état dans l'application en tant que singleton en mémoire sans rien conserver sur le disque/le paquet, alors il sera perdu.

C'est dommage que je ne puisse pas tester l'application sans compte, car je ne créerai pas de compte Google Ads juste pour bricoler l'application elle-même. :thinking_face:

Accrochez-vous aux méthodes natives et enregistrez de manière sélective l'état de votre application afin de pouvoir la restaurer plus tard.

En effet, la meilleure solution serait de conserver l'état actif sur le disque et de détecter la mort du processus du côté natif d'Android ; si vous détectez la mort du processus, rechargez à partir du disque, sinon effacez-le et redémarrez à partir de zéro.

Je ne connais pas assez les navigateurs de Flutter pour vous dire si vous pouvez reconstruire l'historique de navigation avec une sorte de méthode setHistory ou setBackstack comme vous le feriez avec un backstack ou Conductor ou quelque chose comme ça.

Les activités/fragments gèrent ces éléments en interne avec ActivityRecord/FragmentRecord, ils ne sont donc pas mentionnés au-delà de cela.

Merci pour toutes ces idées @mehmetf. Il est triste d'apprendre que la prise en charge de la sauvegarde/restauration de l'état est loin d'être mise en œuvre.

Peut-être que flutter_redux pourrait aider à sérialiser une partie de l'état de l'application.

Eh bien, si vous persistez le blob du magasin Redux sur le disque. Méfiez-vous simplement du problème de "boîte de dialogue de chargement infini après la mort du processus". :thinking_face:

Pour clarifier une certaine confusion :

En fait, je suis un peu plus confus..

Étant l'un des premiers à adopter, ils ont dû faire quelque chose d'inhabituel pour l'exécution en arrière-plan. Ils créent et maintiennent leur isolat principal lorsque l'application démarre (pas l'activité). Ensuite, ils transmettent cet isolat à la vue Flutter lors de sa création.

Quel est le rapport avec le problème en question ? Les isolats Dart s'exécutent-ils dans un processus distinct ?

Ce que vous observez comme « maintien de l'État » est un effet secondaire de la méthodologie décrite ci-dessus. Lorsque vous tuez l'activité dans Google Ads, l'"UI" de Flutter ne meurt pas. Il est conservé en mémoire lié au contexte de l'application. Lorsque l'activité est redémarrée, elle charge le même isolat et le même alto, vous récupérez votre interface utilisateur.

D'après ce que je comprends, @aletorrado a observé une « conservation de l'état » après la fin du processus, peut-être lorsque l'application a été déplacée en arrière-plan (en appuyant sur le bouton d'accueil).

Pour info : lorsqu'une « activité meurt » au moment de l'exécution, elle est TOUJOURS suivie de la fin du processus.

Nous ne recommandons pas cette approche pour résoudre ce problème car elle ne résout pas du tout le problème. Si le processus de demande meurt, l'état entier est toujours perdu. Vous pouvez essayer cela avec Google Ads en arrêtant le processus depuis Paramètres> Applications. L'interface utilisateur sera perdue.

Comme @Zhuinden l'a mentionné, il ne s'agit pas d'un scénario de « conservation d'état » valide, donc de supprimer l'application du « gestionnaire de tâches »

Il existe peut-être un moyen de sauvegarder l'état d'isolement localement, mais cela nécessite une enquête plus approfondie car nous ne savons pas si c'est faisable/sûr.

Ce n'est pas la façon de procéder. Pour Android, vous devez vous connecter au mécanisme onSaveInstanceState car il a été explicitement créé pour enregistrer l'état de l'activité pour les scénarios où la "conservation de l'état" est requise après la fin du processus. Je ne connais pas le cycle de vie de l'application IOS.

Pour l'instant, les solutions décrites ci-dessus sont les meilleures : connectez-vous à des méthodes natives et enregistrez de manière sélective l'état de votre application afin de pouvoir la restaurer plus tard. Une solution automatique prise en charge par le framework sur iOS et Android est encore loin.

Aucune solution n'a été décrite plutôt que de reconnaître la fonction Android appropriée pour conserver l'état dans les cas où l'environnement d'exécution décide de mettre fin au processus.

Aucune solution n'a été décrite plutôt que de reconnaître la fonction Android appropriée pour conserver l'état dans les cas où l'environnement d'exécution décide de mettre fin au processus.

J'ai un peu décrit ce que nous pouvons faire mais je ne connais pas assez Flutter pour vous dire s'il prend en charge ce qui est nécessaire pour cela (la possibilité de configurer un historique de navigation arbitraire à un moment donné 😄 )

@Zhuinden, il existe un "zillion" de cas marginaux couverts par onSaveInstanceState concernant les activités/contrôles... croyez-moi à ce sujet... mais vous êtes les bienvenus pour enquêter en tant qu'exercice éducatif.

... et juste pour réitérer ma position : les tâches/activités Android, les services, le cycle de vie de l'application (y compris onSaveInstanceState) font tous partie du contrat de programmation Android « incidemment », ils sont d'abord réalisés en Java mais l'essentiel est qu'ils sont moyen d'imposer des contraintes au niveau du système sur le processus d'application et étant donné que le code Flutter vit dans un processus d'application Android, il serait imprudent de la part de Flutter de contourner ces contraintes.

Chers développeurs Android,

merci pour votre passion à offrir la meilleure expérience utilisateur à vos utilisateurs en sauvegardant l'état non seulement lors des modifications de configuration, mais également lors de la mort du processus. Vous avez peut-être lu dans les 200 commentaires ci-dessus que Flutter a du mal à sauvegarder l'état de l'application. Je voudrais vous dire pourquoi et comment le résoudre .

Pourquoi ne puis-je pas utiliser onSavedInstanceState ?

onSavedInstanceState oblige les développeurs à enregistrer de manière synchrone l'état de l'application. Vous devez revenir immédiatement. Mais la communication avec Flutter est asynchrone par défaut ( Future ). Par conséquent, vous ne pouvez pas demander l'état enregistré à partir de votre application Flutter lorsqu'Android le demande.
_Mais vous pourriez retourner l'état enregistré si vous l'aviez créé à l'avance, n'est-ce pas ?_

Le problème n'est pas que onSavedInstanceState est inaccessible, le problème est le timing. Lorsque Android demande d'enregistrer l'état, vous n'avez pas besoin de données.

Solution de récupération A : votre propre fichier d'état d'instance enregistré

Vous pouvez écouter onSavedInstanceState et informer votre application flutter de cet événement. Enregistrez ensuite l'état de votre application et écrivez-le dans un file . Lorsque votre application est récupérée de la mort du processus (vous pouvez le détecter via savedInstanceState du côté Android), chargez le fichier à partir du système de fichiers, analysez-le et récupérez votre état. (Astuce : utilisez les canaux de la plate-forme, ils sont plus faciles que vous ne le pensez).

Vous pouvez même récupérer l'état lorsque les utilisateurs ont supprimé l'application, ou ignorer l'état enregistré s'il est trop ancien !

Solution de récupération B : Enregistrer l'état en continu

Nous pourrions exécuter une minuterie et enregistrer en permanence l'état de l'application côté plate-forme en mémoire. Disons toutes les 3 secondes. Lorsque Android appelle ensuite onSavedInstanceState nous pouvons retourner l'état précédemment enregistré. (Oui, nous pourrions perdre des changements survenus au cours des 3 dernières secondes)

Solution de récupération C : Enregistrer l'état aux points clés

Comme pour la solution B, l'état sauvegardé sera conservé en mémoire côté plate-forme. Mais au lieu d'enregistrer l'état toutes les quelques secondes, vous demandez manuellement d'enregistrer l'état après que les utilisateurs ont effectué certaines actions. (a ouvert un tiroir, saisi du texte dans un TextField , nommez-le).


Les 3 solutions sont combinables, utilisez ce qui convient le mieux à votre application.

Mais comment puis-je enregistrer l'état de mon application ?

Contrairement aux View Android, les Widget n'ont pas onSaveInstanceState rappel

Pour enregistrer l'état, vous devez parcourir chaque écran de votre application et enregistrer toutes les informations nécessaires pour les restaurer. J'aime le faire en json mais vous pouvez choisir protobuf pour de meilleures performances (mesurez avant d'optimiser !).

En regardant notre application de compteur d'échantillons bien-aimée, cela peut être très facile :

// complete app state of sample application
{
  "counter": 24,
}

Si vous avez plusieurs écrans ou boîtes de dialogue, vous pourriez vous retrouver avec l'état d'application json suivant :

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

La mise en œuvre réelle dépend fortement de l'architecture de votre application.

  • Si vous utilisez Redux, vous pouvez utiliser un middleware pour conserver l'état global de l'application à la volée.
  • Si vous utilisez scoped_model cela peut suffire pour enregistrer les modèles. Les recréer est fondamentalement différent de recréer un état Redux.

Les meilleurs conseils que je peux vous donner :

  • Gardez une séparation stricte entre votre interface utilisateur et vos modèles.
  • Regardez chaque écran et demandez-vous ce dont vous avez vraiment besoin pour récupérer l'état. La plupart du temps, ce n'est vraiment pas grand-chose.
  • Parlez à votre iOS colleagues et il vous dira qu'il n'a pas implémenté la restauration de l'état via encodeRestorableState depuis des années. Android 1M+ contre iOS 974

Pascal

Activez le paramètre « Ne pas conserver les activités ».

Non, cela ne teste pas correctement la mort du processus. Cela ne se produit JAMAIS à moins que vous n'activiez explicitement ce paramètre. ??

Si vous avez plusieurs écrans ou boîtes de dialogue, vous pourriez vous retrouver avec l'état d'application json suivant :

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

Oh mon Dieu, nous atteignons "l'état de navigation fait partie de l'état de l'application en tant qu'état d'une machine à états finis" pour que cela fonctionne, mais j'aime son apparence 😄

Oh mon Dieu, nous atteignons le "l'état de navigation fait partie de l'état d'application en tant qu'état d'une machine à états finis" pour que cela fonctionne

ne me lancez pas.

Le reste de l'état de l'application doit être enregistré dans une base de données/sur le serveur. Le seul état transitoire qui mérite d'être enregistré dans onSaveInstanceState est la pile de navigation et certaines entrées utilisateur. C'est aussi ce que la plupart des gens demandent dans ce fil.

Le reste de l'état de l'application doit être enregistré dans une base de données/sur le serveur. Le seul état transitoire qui mérite d'être enregistré dans onSaveInstanceState est la pile de navigation et certaines entrées utilisateur. C'est aussi ce que la plupart des gens demandent dans ce fil.

et c'est la CLÉ ! (ainsi que la prise en charge du « Service Android »)

@gitspeak , le dernier échange que nous avons eu n'a rien à voir avec ce problème. Je vais cacher les deux. Si vous souhaitez continuer la discussion, veuillez me contacter par MP en utilisant mon identifiant github @ gmail.com. Il y a clairement un malentendu ici qui dépasse le cadre de cette question.

En tant que l'un des développeurs travaillant sur l'interface entre Flutter et Android, je tiens à remercier @passsy pour avoir fourni un excellent aperçu des problèmes liés à l'enregistrement de l'état.

Je voudrais également ajouter que je suis intéressé à comprendre les cas d'utilisation spécifiques auxquels les développeurs de ce fil sont confrontés. L'état d'épargne est un concept qui couvre probablement de nombreux objectifs différents. Ce qu'une application appelle l'état, une autre non. Une application doit absolument avoir un état enregistré garanti, tandis qu'une autre préférerait simplement enregistrer un état mais ne s'arrêtera pas sans lui. Certaines applications sont entièrement conçues avec Flutter, d'autres doivent synchroniser les informations entre l'interface utilisateur Android/iOS traditionnelle et Flutter.

Si l'un d'entre vous souhaite décrire le cas d'utilisation particulier que votre application doit prendre en charge en ce qui concerne l'enregistrement de l'état, n'hésitez pas à m'envoyer un e-mail à [email protected] -

@passsy Veuillez considérer mes commentaires ci-dessous comme une tentative sincère de fournir des commentaires constructifs

Solution de récupération A : votre propre fichier d'état d'instance enregistré
Vous pouvez écouter onSavedInstanceState et informer votre application flutter de cet événement. Enregistrez ensuite l'état de votre application et écrivez-le dans un fichier. Lorsque votre application est récupérée de la mort du processus (vous pouvez le détecter via saveInstanceState du côté Android), chargez le fichier à partir du système de fichiers, analysez-le et récupérez votre état. (Astuce : utilisez les canaux de la plate-forme, ils sont plus faciles que vous ne le pensez).
Vous pouvez même récupérer l'état lorsque les utilisateurs ont supprimé l'application, ou ignorer l'état enregistré s'il est trop ancien !

Plusieurs problèmes :

1) Il y a une condition de course inhérente à cette approche. Le thread "à état persistant" peut très bien survivre au thread principal de l'application Android qui appelle les événements du cycle de vie, auquel cas, le processus peut être terminé pendant l'écriture du fichier. Vous pouvez essayer de coordonner les deux threads en bloquant une méthode de cycle de vie sur un signal d'achèvement du thread de « sauvegarde d'état », mais cela, en plus d'introduire une complexité de synchronisation supplémentaire, ne peut pas garantir l'exactitude car un délai suffisamment long peut introduire l'ANR. (https://developer.android.com/topic/performance/vitals/anr)
Vous pourriez faire valoir que "l'enregistrement de l'état" dans un fichier est si rapide que cela ne se produirait probablement pas, mais considérez les retards réels dans la planification des threads et la latence de stockage pendant que d'autres processus sont en cours d'exécution, tels que les téléchargements en arrière-plan, les mises à jour du Play Store, le lecteur multimédia, etc. pas à en débattre, mais mon expérience personnelle (partagée par d'autres) est qu'apparemment les appareils Android ne sont pas à l'abri de la même "grippe" qui infecte les PC Windows - ils deviennent également plus lents avec le temps. J'ai récemment nettoyé mon Nexus 7 et non seulement il a fallu des HEURES pour télécharger et installer toutes les applications via Wifi, mais la réactivité de l'appareil est maintenant une blague complète au point où je doute d'avoir la patience de l'utiliser même pour rechercher des recettes.

2) Vous ne pouvez pas avoir une définition fiable de "beaucoup trop vieux". Si je supprime l'application du gestionnaire de tâches, je m'attends à ce que tous les états disparaissent immédiatement, tandis que si je passe à une autre application, je m'attends à ce que l'état soit préservé indéfiniment tant que le téléphone est allumé. En fait, Android ne vous permet pas de faire la distinction entre ces deux états.

3) Tous les contrôles d'interface utilisateur n'exposent pas leur état via une API qui permet à l'utilisateur de configurer le contrôle pour un état particulier. Une telle API entraînerait sans aucun doute un effort de développement supplémentaire de la part du développeur de contrôle. Sur Android, les contrôles d'interface utilisateur bien comportés devraient éliminer les problèmes de "préservation de l'état" du développeur en gérant les notifications onSave/Restore InstanceState en interne, contrôlant ainsi l'état, le cas échéant, préservé. Si je me souviens bien, le contrôle de saisie semi-automatique conserve le terme de requête mais pas la liste de résultats.

Solution de récupération B : Enregistrer l'état en continu
Nous pourrions exécuter une minuterie et enregistrer en permanence l'état de l'application côté plate-forme en mémoire. Disons toutes les 3 secondes. Lorsque Android appelle ensuite onSavedInstanceState, nous pouvons retourner l'état précédemment enregistré. (Oui, nous pourrions perdre des changements survenus au cours des 3 dernières secondes)

Très en désaccord, en plus d'avoir les mêmes problèmes autour de la "Solution A", cela ajoute un nouveau risque d'ouvrir une boîte de vers de duplication de données.

Solution de récupération C : Enregistrer l'état aux points clés
Comme pour la solution B, l'état sauvegardé sera conservé en mémoire côté plate-forme. Mais au lieu d'enregistrer l'état toutes les quelques secondes, vous demandez manuellement d'enregistrer l'état après que les utilisateurs ont effectué certaines actions. (a ouvert un tiroir, saisi du texte dans un TextField, nommez-le).

Avoir les mêmes problèmes concernant la "Solution A".

L'arborescence des widgets n'est pas enregistrée automatiquement. Et c'est une bonne chose ! L'interface utilisateur et l'état sont enfin séparés et il n'y a pas de magie de réflexion lors de la restauration de l'état d'affichage ou de l'instanciation d'activités et de fragments.

Être en désaccord. La rétention automatique de l'arbre des widgets par Android dans les cas où son état est susceptible d'être jeté est très utile ! Pourquoi diable voudriez-vous transférer cette responsabilité dans son intégralité au développeur de l'application ?  Pourquoi cela contraste-t-il avec la séparation UI-État ?? En fait, cette façon automatique de sauvegarder/restaurer l'Arbre des Widgets semble être un parfait exemple de séparation UI/État.

La "Solution A" me semble parfaitement plausible lors de l'utilisation de serrures appropriées, et aucune ANR ne doit être déclenchée si la quantité de travail est raisonnable. De plus, en adoptant le mécanisme de notification de la plate-forme, cela évite de faire un travail de sérialisation inutile tout le temps, ce qui peut être coûteux à faire pour chaque frappe.

Concernant la possibilité d'automatiser ce comportement, il me semble clair que n'avoir aucun mécanisme de réflexion dans le runtime Dart, et n'avoir aucun moyen structuré de stocker l'état en mémoire dans Flutter, ce n'est en aucun cas possible. Pour le moment, l'approche la plus simple que je puisse imaginer consisterait à choisir de conserver l'état dans un seul objet dynamique (tout comme this.props et this.state sur React).

@aletorrado

La "Solution A" me semble parfaitement plausible lors de l'utilisation de serrures appropriées, et aucune ANR ne doit être déclenchée si la quantité de travail est raisonnable.

Je suis d'accord. quand ... Là où le modèle Android est beaucoup plus simple et robuste puisqu'il ne nécessite pas que le développeur gère les verrous, il ne persiste pas l'état transitoire sur le disque et il ne conserve l'état que pour les cas où il est pertinent quelque chose que vous (flutter) ne peut pas se passer de se connecter aux événements du cycle de vie Android et à la fonction onSaveInstance - pour commencer, alors à quoi cela sert-il ?

De plus, en adoptant le mécanisme de notification de la plate-forme, cela évite de faire un travail de sérialisation inutile tout le temps, ce qui peut être coûteux à faire pour chaque frappe.

Il n'est pas nécessaire de conserver chaque frappe de touche, mais plutôt un instantané composé d'états sélectionnés

Les données doivent simplement être stockées dans le Bundle en tant que ByteArray pour éviter
déclenchant tout ANR ou retard causé par les E/S sur le thread principal.

Le mer. 19 décembre 2018, 18:08 Alejandro Torrado [email protected]
a écrit:

La "Solution A" me semble parfaitement plausible lors de l'utilisation appropriée
verrous, et aucune ANR ne doit être déclenchée si la quantité de travail est raisonnable.
De plus, en adoptant le mécanisme de notification de la plate-forme, cela évite de faire
travail de sérialisation inutile tout le temps, ce qui peut être coûteux à faire
pour chaque frappe.

Concernant la possibilité d'automatiser ce comportement, il me semble clair que
n'ayant aucun mécanisme de réflexion dans l'environnement d'exécution de Dart et n'ayant aucun
manière structurée de stocker l'état en mémoire dans Flutter, ce n'est pas possible
n'importe comment. Pour le moment, l'approche la plus simple que je puisse penser à faire
ce serait choisir de conserver l'état dans un seul objet dynamique (tout comme
this.props et this.state sur React).

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

La vraie supercherie est de construire ce ByteArray dont vous parlez :slightly_smiling_face:

Je dirais plutôt que la vraie supercherie c'est de pouvoir restituer ça
ByteArray correctement.

Le mercredi 19 décembre 2018, à 18 h 41, Gabor Varadi [email protected] a écrit :

La vraie supercherie est de construire ce ByteArray dont vous parlez 🙂

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

Non. Une fois que vous avez le tableau d'octets, c'est très simple. Affichez simplement un indicateur de chargement pendant que vous le chargez (en supposant que vous vous soyez dit avec les chaînes dont vous avez besoin pour le charger).

Le vrai problème est l'intégration de Flutter, ce que j'appelle la partie restauration.
La navigation doit être enregistrée automatiquement, ainsi que la saisie de l'utilisateur, comme sur
applications Android natives.

En raison de ce problème, les applications Flutter ne peuvent pas s'intégrer correctement dans Android car
ils ne se comportent pas correctement lorsqu'il y a une pression de RAM et que l'utilisateur change
apps dans ces conditions avant de revenir.

Le jeudi 20 décembre 2018, à 02h42, Gabor Varadi [email protected] a écrit :

Non. Une fois que vous avez le tableau d'octets, c'est très simple. Montrez juste un chargement
indicateur pendant que vous le chargez (en supposant que vous vous êtes dit avec
canaux dont vous avez besoin pour le charger).

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

Un groupe d'entre nous en a discuté un peu plus ici : https://github.com/mehmetf/flutter/issues/1. Points pertinents à ce problème :

  • Pour corriger (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177) : Google Ads a été testé avec l'option "Ne pas conserver les activités" activée. Le processus de l'application ne meurt pas dans ce cas, donc mon explication sur (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562) tient. Comme @gitspeak et @Zhuinden l' ont souligné, ce n'est pas un bon moyen de tester les scénarios contre protégeons car cela ne fonctionne pas de cette façon dans la pratique.

  • Chaque fois que l'application est en arrière-plan, View.onSaveInstanceState est appelé sur le thread d'interface utilisateur pour chaque vue de la hiérarchie pour laquelle la sauvegarde est activée. Ainsi, une bonne solution du point de vue du développeur serait que FlutterView implémente cela. Pour éviter les conditions de concurrence avec le thread d'interface utilisateur (que vous ne pouvez pas/ne devriez pas bloquer), cette méthode ne doit pas atteindre l'application Flutter car il n'y a pas de moyen synchrone de le faire.

  • Tout ce que onSaveInstanceState enregistre ne peut pas dépasser 1 Mo, il est donc hors de question de sauvegarder l'intégralité de l'état d'isolement. Au lieu de cela, nous pouvons éventuellement trouver un moyen de diviser le code, les actifs et les données contenus dans la mémoire isolée et de ne vider que la partie des données sérialisée dans un parcelable.

  • Lorsque la vue est restaurée ultérieurement, FlutterView crée un isolat et décompresse la partie de données précédemment enregistrée, le cas échéant.

  • Lorsque l'application est en arrière-plan, l'état devrait probablement cesser de changer du côté de Flutter. Il y aura toujours des cas difficiles, donc il serait bon de proposer une liste des meilleures pratiques pour les applications Flutter pour accompagner cette solution (compte tenu à la fois du cycle de vie et de la pression de la mémoire ).

  • J'imagine que ce serait un problème beaucoup plus urgent en ce qui concerne les marchés émergents tels que l'Inde, où Flutter devrait fonctionner sur des téléphones bas de gamme. Android Go aurait également une stratégie de gestion de la mémoire beaucoup plus agressive.

  • Ils créent et maintiennent leur isolat principal lorsque l'_application_ démarre (pas l'activité). Ensuite, ils transmettent cet isolat à la vue Flutter lors de sa création.

pouvez-vous fournir un exemple ce code?

Ils créent et maintiennent leur isolat principal lorsque l'application démarre (pas l'activité). Ensuite, ils transmettent cet isolat à la vue Flutter lors de sa création.

pouvez-vous fournir un exemple ce code?

Je ne peux pas fournir d'exemple de type copier/coller, mais voici ce qui se passe :

Application

  • Dans votre Application.onCreate , appelez les fonctions d'initialisation pour FlutterMain . ( startInitialization et ensureInitializationComplete ). Vous pouvez trouver des références à ceux-ci dans le repo flutter/moteur.
  • Créez un nouvel objet FlutterNativeView, transmettez l'instance d'application en tant que contexte.
  • Exécutez votre bundle dans cette vue native (recherchez des exemples de runFromBundle ).
  • Implémentez une interface dans cette application qui vous permet de renvoyer cette vue native (quelque chose comme FlutterNativeViewProvider ).

Activité

  • Étendez FlutterFragmentActivity et remplacez ces deux méthodes :
  <strong i="26">@Override</strong>
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  <strong i="27">@Override</strong>
  public boolean retainFlutterNativeView() {
    return true;
  }

Quelque chose de ce genre devrait vous faire avancer. Notez que votre application Flutter commencera à s'exécuter avant qu'une activité (et donc une fenêtre) ne soit disponible. Par conséquent, vous ne devriez pas appeler runApp() dans votre fonction principale jusqu'à ce que vous receviez un signal d'Activity indiquant qu'elle est prête. Vous pouvez le faire via PlatformChannels et un endroit pour signaler ce qui serait dans createFlutterNativeView().

Avis de non-responsabilité

  1. Comme je l'ai mentionné ci-dessus, nous ne tolérons pas tout à fait ce modèle car il ne résout pas vraiment ce problème particulier et il est assez désordonné.

  2. Notez que bon nombre de ces API sont appelées à changer. Étant donné que les cas d'utilisation d'add2app évoluent encore, les API d'intégration Android et iOS ne sont pas encore tout à fait stables. Nous annoncerons les changements de rupture sur flutter-dev@ comme d'habitude.

@passsy , @gitspeak

Solution de récupération A : votre propre fichier d'état d'instance enregistré

Pour être sûr que vous disposez d'un fichier d'état valide, vous pouvez écrire dans un fichier .tmp, puis le renommer lorsque l'écriture est terminée. Si vous conservez également le fichier d'état précédent, vous obtiendrez toujours un état valide. Également lorsque le processus est tué lors de la sauvegarde de l'état.

@jsroest

vous vous retrouverez toujours avec un état valide.

Mais pas nécessairement le bon état, ce qui est un peu le problème.

Il n'y a pas non plus d'exigence de persistance de l'état dans la mémoire non volatile pour commencer ..

@gitspeak

vous vous retrouverez toujours avec un état valide.

Mais pas nécessairement le bon état, ce qui est un peu le problème.

Il n'y a pas non plus d'exigence de persistance de l'état dans la mémoire non volatile pour commencer ..

Dans de nombreuses situations, il est plus important d'avoir un état cohérent que d'avoir « la plupart des dernières valeurs ». Les bases de données relationnelles utilisées avec les transactions en sont un exemple. Ce qui, à mon avis, est aussi un excellent endroit pour stocker l'état.

La persistance dans la mémoire non volatile présente certains avantages ; Le processus est également tué en cas de "coupures de téléphone", de changements de batterie, de pannes matérielles, de mises à jour du système d'exploitation, de redémarrages en général, etc., de sorte que l'état persiste lorsqu'il est enregistré dans une mémoire non volatile.

@jsroest Vos arguments sont valides mais à mon l'état transitoire de l'interface utilisateur (par exemple, position de défilement, sélection(s) active(s), saisie de texte temporaire, etc...). Gardez à l'esprit que la restauration de ce type de données n'est requise que dans les situations où l'utilisateur n'a pas explicitement supprimé « l'écran », mais la saisie de l'utilisateur peut toujours être perdue, comme lorsqu'un utilisateur passe à une autre application. Il n'y a aucune attente de l'utilisateur pour récupérer l'état transitoire en cas de perte de puissance, tout comme l'utilisateur ne s'attend pas à ce que le navigateur fasse défiler la dernière page Web affichée jusqu'au décalage exact avant une perte de puissance du système. En effet, la nature des données considérées comme « état transitoire » est importante et la discussion ici (installation Android onSaveInsatnceState en particulier) concerne la gestion de ce type de données.

@gitspeak
J'écris des programmes pour l'entreprise. Par exemple, les programmes utilisés dans les entrepôts par les préparateurs de commandes, ou d'autres exemples dans le commerce de détail, où le propriétaire du magasin numérise les articles à commander auprès de son fournisseur. Mes clients s'attendent à ce que l'application démarre là où ils l'ont laissée, quoi qu'il arrive. Cela inclut l'état transitoire, mais aussi la pile de navigation. Pourriez-vous expliquer pourquoi cela ne convient pas parfaitement à l'installation OnSaveInstanceState, comme indiqué ici ? Peut-être que les attentes des utilisateurs sur le marché des consommateurs sont différentes, mais la même logique pour enregistrer et restaurer l'état peut être utilisée à mon humble avis.

@jsroest

Mes clients s'attendent à ce que l'application démarre là où ils l'ont laissée, quoi qu'il arrive.

Désolé, je ne sais pas comment créer des applications pour de tels clients, peut-être que quelqu'un d'autre pourra m'aider...

@jsroest C'est une excellente question mais qui @gitspeak , le onSaveInstanceState Android consiste à sauvegarder l'état transitoire de l'interface utilisateur où le cycle de vie des données est différent de ce que vous voulez. Le stockage local ou le stockage à distance (si vous voulez que l'état survive à travers les désinstallations et les périphériques) vous donnerait ce que vous voulez, mais il y a des mises en garde, qui sont à nouveau en dehors de la portée de ce problème.

Autres lectures recommandées :

https://developer.android.com/topic/libraries/architecture/saving-states [Notez par exemple comment cet article sépare le stockage local de onSaveInstanceState]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

@jsroest si vos clients s'attendent à ce que l'application revienne là où elle se trouvait même si elle a été arrêtée de force, effacée des tâches ou si le système d'exploitation a été redémarré , alors c'est quelque chose de complètement personnalisé, et ce n'est pas ce dont les gens parlent ici.

onSaveInstanceState enregistrait l'état de la tâche pour une tâche donnée, mais il a été supprimé lors de l'effacement de la tâche ou de l'arrêt forcé ou du redémarrage du système d'exploitation.

@mehmetf Je pense que ce problème a en effet atteint un niveau de verbosité auquel il peut être assez difficile de grok. Avec la bénédiction de @LouisCAD , je vous suggère de fermer ce problème et d'en ouvrir un nouveau qui commence par votre résumé le plus récent (peut-être quelque peu adapté pour mettre l'accent sur l'énoncé du problème et la portée) avec des références aux discussions précédentes.

Je suis d'accord mais je laisserai cela à @matthew-carroll; il sera probablement propriétaire de cette question.

Merci pour les commentaires.

Je ne veux pas pousser les choses, mais je pense que je n'ai pas été assez clair en décrivant ce que je recherche. Je ne suis pas à la hauteur de toute la terminologie utilisée ici sur ce fil. Je vois beaucoup de similitudes entre ce que j'aimerais voir dans Flutter et ce que @LouisCAD et @passsy décrivent.

Mes programmes se composent généralement des parties suivantes :

  • Backend pour pousser les données de l'état stable lorsque l'utilisateur a terminé un travail.
  • Base de données sqlite locale pour l'état stable
  • Structure de données en mémoire appelée « variables » pour l'état transitoire qui peut être sérialisé dans une mémoire non volatile afin qu'elle puisse survivre au processus.

La structure de données 'variables' n'a que des classes simples avec des propriétés sérialisables. Une partie importante de cette structure de données « variables » est la pile d'écrans. (Simplement la liste). L'écran qui est en haut est le dernier écran avec lequel l'utilisateur a interagi. Celui ci-dessous est celui vers lequel l'utilisateur navigue lorsqu'il appuie sur 'retour'.

Je peux imaginer que le OnSaveInstanceState peut être utilisé sur la plate-forme Android pour déclencher une sérialisation de cette structure de données. Quelque chose de similaire doit être trouvé sur les autres plates-formes (iOS, future: win, mac, web), mais comme @passy le suggère, d'autres points clés peuvent également déclencher cela et cela peut suffire.

Lorsque le programme démarre, il vérifie si une structure de données sérialisée est disponible et si la version actuelle du programme est la même que la version qui l'a sérialisée. Cela peut être fait simplement en comparant les numéros de version. Nous ne voulons pas charger une structure de données qui n'est pas compatible.

Si tout cela s'additionne, la structure de données est désérialisée et disponible en mémoire. Le programme charge l'écran qui se trouve au-dessus de la pile. L'écran lui-même charge son état transitoire à partir de la structure de données. Maintenant, nous avons un programme qui survit aux décès de processus. (Ou est-ce que j'ai raté quelque chose ? Cela fonctionne certainement sur mes projets passés sur Windows Mobile, les formulaires Xamarin et asp.net. Je pense que cela devrait également fonctionner dans Flutter).

Exemple d'application, où SXXX représente un écran avec un numéro. Le numéro est uniquement utilisé pour rappeler au programmeur quels écrans appartiennent plus ou moins ensemble.

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

Exemple de structure de données de variables

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

Tout ce que j'aimerais voir dans Flutter est probablement déjà là, à l'exception de la recréation de la screenstack aka navigationstack. Je n'ai aucune idée de comment recréer cet arbre d'objets en mémoire. Je ne sais pas non plus si je devrais vouloir le recréer. Je peux également créer ma propre pile de navigation et l'implémenter dans Flutter en tant qu'application d'une seule page. La même chose que j'ai fait dans les projets précédents. Mais alors je perdrais beaucoup de goodies intégrés des classes MaterialApp et Scaffold, où la flèche/le bouton de retour est le plus évident.

Je me rends compte que cela n'enregistre pas automatiquement tous les états transitoires. Par exemple, textes sélectionnés, position de listes, position d'une animation spécifique. Mais en tant que programmeur, vous pouvez décider par écran ce qui est nécessaire pour sauvegarder. Bien que ce soit exactement ce que @LouisCAD essaie d'empêcher, car tout le code passe-partout était nécessaire.

Dois-je créer un nouveau problème ou est-ce que cela rentre dans ce fil?

Merci encore pour le retour, je l'apprécie vraiment.

@jsroest Merci pour l'explication détaillée. Veuillez publier ceci sur StackOverflow avec la balise flutter. Vous demandez essentiellement une recommandation sur la façon de structurer et de construire vos itinéraires et sur la façon d'enregistrer le dernier itinéraire visité dans le stockage persistant.

Vous pourriez penser que la résolution de ce problème particulier résout également votre problème, mais ce n'est pas vrai. Vous pouvez me contacter par MP sur mon compte github @ gmail.com si vous ne comprenez pas pourquoi.

Des avancées sur cette question ?

@vanlooverenkoen théoriquement, si vous persistez la liste des routes du navigateur ET la hiérarchie des clés dans un fichier (et l'état des widgets avec état), et que vous l'effacez du côté natif d'Android, vous obtenez savedInstanceState == null , alors vous pourriez potentiellement le faire persistant et restaurable, ce n'est tout simplement pas si facile.

@Zhuinden C'est en fait beaucoup plus complexe que de simplement enregistrer les routes et les clés. La communication entre les écrans se fait soit à l'aide de rappels, soit en attente d'un résultat. Vous devrez également restaurer ces états. Sinon, vous obtenez quelque chose qui semble correct mais qui n'agit pas comme tel. Vous pouvez contourner la restauration des rappels et autres en utilisant une forme de communication utilisant des ensembles de données comme vous le faites avec Android natif (avec intentions), mais cela enlèverait beaucoup de la facilité d'utilisation de Flutter. Tant qu'il n'y a pas de vraie solution à ce problème, Flutter n'est pas utilisable pour de nombreuses applications, principalement celles qui sont utilisées pour collecter des données volumineuses et complexes basées sur des formulaires. Je me demande pourquoi ce problème n'est pas abordé ouvertement par l'équipe Flutter, car ils doivent sûrement en être conscients. Peut-être que cela révélerait une grave faille dans l'architecture de Flutter et montrerait qu'au-delà des présentations tape-à-l'œil pour impressionner la direction, le système n'est pas une bonne option pour les applications réelles.

@jsroest @mehmetf @claudiofelber , la réponse à cette question StackOverflow résout-elle ces problèmes ? https://stackoverflow.com/questions/54015775/what-is-the-best-way-to-pass-data-or-arguments-between-flutter-app-screens/54015932

J'ai suivi cela depuis le début, mais j'ai dû relire un tas de messages car j'ai perdu le contexte.

Si l'équipe Flutter pense qu'il devrait y avoir un nouveau problème, avec moins d'inondations et des plans plus clairs, cela ne me pose aucun problème et celui-ci est donc fermé.

J'ai créé le plugin native_state pour tirer parti des mécanismes Android et iOS pour enregistrer l'état de l'application lorsque le processus est arrêté.

Le Navigator peut déjà restaurer les routes de l'application via la propriété initialRoute en fin de compte, _si_ vous suivez certaines règles spécifiques, pour lesquelles j'ai documenté et ajouté des exemples, et vous voudrez probablement toujours utiliser le plugin en combinaison avec la restauration de la route (soit en utilisant le SavedStateRouteObserver fourni ou un autre mécanisme).

Tao vient de me faire remarquer qu'il s'agissait du 4e numéro le plus demandé dans notre sondage de l'automne dernier. Je pense qu'il y a un mélange de désirs des utilisateurs exprimés ici (dans les plus de 100 commentaires !)

@eseidelGoogle Je ne suis pas d'accord avec votre évaluation. À mon humble avis, il n'y a pas de "mélange de désirs des utilisateurs exprimés", mais plutôt un manque de prise en charge de la plate-forme Android qui fait apparaître différents défis de programmation résultant du même problème central.

Les applications Android ont deux thèmes centraux :

1) Machine d'état de processus (aka événements de cycle de vie).
2) Boîte à outils de l'interface utilisateur.

Flutter fait (2) mais pas (1). Il doit aborder les deux.

3 années...

Mise à jour rapide : je travaille actuellement activement à l'exploration de solutions à ce problème.

@goderbauer Juste pour s'assurer qu'il est clair qu'il ne s'agit pas seulement d'un problème Android, iOS a les mêmes mécanismes en place, bien que l'impact sur les applications iOS soit probablement moindre. J'ai créé un plugin qui permet de sauvegarder l'état via les mécanismes du système d'exploitation, ce qui pourrait également être quelque chose à regarder : https://github.com/littlerobots/flutter-native-state

@hvisser comment recommandez-vous d'enregistrer l'état de navigation ? Le navigateur de Flutter n'a pas getRoutes méthode

@hvisser Oui, je regarde cela pour les deux plates-formes (et il devrait également être suffisamment flexible pour se connecter à d'autres plates-formes). J'ai supprimé l'étiquette platform-android pour indiquer clairement qu'il ne s'agit pas d'un problème spécifique à Android.

@hvisser comment recommandez-vous d'enregistrer l'état de navigation ? Le navigateur de Flutter n'a pas getRoutes méthode

@Zhuinden Le navigateur a déjà un support pour cela (bien que je ne pense pas que ce soit bien connu). J'ai un exemple de comment le configurer dans le projet d'état natif, mais TL;DR

  • Vous devez utiliser des routes nommées
  • Les routes doivent être hiérarchiques, par exemple /screen1/screen2 suit sur /screen1 suit sur /
  • Les itinéraires sont poussés vers la pile de navigation lorsque vous définissez la propriété initialRoutes et vos itinéraires sont organisés de cette façon
  • Je _pense_ que vous avez également besoin d'un GlobalKey pour le navigatorKey sur le MaterialApp mais cela pourrait être spécifique à mon cas.

Donc, si vous définissez initialRoutes sur /screen1/screen2 et que vos itinéraires sont correctement configurés, la restauration de la navigation fonctionnera également.

Un cas de plus sur les anciens appareils avec 2 cartes SIM lorsque vous activez et désactivez le mode avion, les applications natives youtube et flutter redémarreront # 49057
La vidéo est jointe à mon problème

Essayez de désactiver le mode de débogage flottant. si le mode de débogage est activé, l'application redémarre toujours.

debugShowCheckedModeBanner : false,

Je travaille dans le flutter depuis 2018, peut-être en juin/juillet et j'ai appris que c'était un problème après 2-3 mois. Je ne savais pas que c'était devenu un si gros problème. Je créais une application de lecteur de musique et si j'appuyais sur le bouton de retour, le flottement détruisait tous les états pendant que la musique continuait à jouer en arrière-plan. Pour résoudre ce problème à l'époque, j'ai créé cette bibliothèque system_shortcuts .
Ajoutez le plugin à votre projet dans pubspec.yaml _[ Pour ceux qui sont débutants :) ]_

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

J'ai toujours utilisé une solution de contournement qui fonctionne très bien pour moi, mais il y a quelques problèmes, mentionnés ci-dessous dans ce commentaire, mais ils ne m'affectent pas beaucoup.

Utilisation -

Quel que soit le widget sur lequel vous vous trouvez et appuyez sur le bouton de retour, vous savez que l'application se fermera (c'est-à-dire que l'utilisateur quitte l'application), ajoutez WidgetsBindingObserver Mixin dans la classe d'état comme celle-ci

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

Après cela, remplacez la méthode didPopRoute et ajoutez le code suivant dans la méthode

<strong i="21">@override</strong>
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_Permet de comprendre comment cela fonctionne_ :
L'application est en cours d'exécution >> L' utilisateur appuie sur le bouton de retour >> Le bouton d'accueil est enfoncé et le bouton de retour ne l'est pas >> L'application passe en arrière-plan et l' état n'est pas détruit

Échantillon de travail -

_ MODIFIER _
_Cette application est écrite à l'aide de la gestion de l'état du fournisseur, mais aucun code n'est utilisé pour gérer l'état après la fermeture de l'application et sa réouverture à partir du récent. C'est juste le code que nous avons ajouté qui fonctionne ici._

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

Problèmes -

Si vous êtes venu à votre application depuis une autre application, chaque fois que vous revenez de votre widget d'accueil, vous irez toujours à l'écran d'accueil et non à cette application précédente d'où vous venez.
Exemple :-
L'utilisateur est dans WhatsApp (ou toute autre application) >> Ils reçoivent une notification de votre application >> Ils ouvrent votre application à partir de la notification >> Maintenant, ils appuient sur le bouton de retour >> Ils reviendront à l'écran d'accueil du téléphone et non WhatsApp (qui devrait être le scénario par défaut)
_Ce problème ne m'a pas posé de gros problème jusqu'à présent et je pense que tous les utilisateurs et développeurs normaux seront d'accord car c'est dans de rares cas que vous accédez à votre application à partir d'une autre application. Je ne néglige pas le fait que cela peut arriver plusieurs fois, mais cela fonctionne pour moi et ce n'est qu'une solution de contournement jusqu'à ce qu'il reçoive une aide réelle de l'équipe de base de Flutter._

Le fait que ce problème ait presque 3 ans et demi et que rien ne soit si bouleversant.

Apparemment, cela devrait être travaillé d'ici mai 2020 à en juger par le jalon actuellement fixé… à temps pour Google I/O ?

hvisser ci-dessus a résolu le problème, maintenant les gens ont juste besoin d'écrire le code qui le fait fonctionner

Je progresse dans la recherche d'une solution à ce problème. Pour un premier aperçu de la façon dont une application pourra restaurer l'état de l'instance une fois cela fait, consultez ce PR : https://github.com/flutter/samples/pull/433.

Malheureusement, cela prendra un peu plus de temps jusqu'à ce que tout soit terminé.

Le document de conception avec des détails sur la façon de faire la restauration d'état dans Flutter est disponible ici : http://flutter.dev/go/state-restoration-design

Le PR avec le cadre général de restauration de l'état tel que décrit dans la conception ci-dessus a été publié : https://github.com/flutter/flutter/pull/60375. Je travaille toujours sur l'ajout d'une couverture de test supplémentaire.

Le cadre général de restauration de l'état (https://github.com/flutter/flutter/pull/60375) a été soumis. Je travaille maintenant à l'intégrer dans nos widgets. Commençons par les défilements, les champs de texte et le navigateur.

Vous pouvez l'implémenter vous-même en stockant les données que vous souhaitez restaurer dans une base de données sqlite . C'est une technique qui est utilisée lors de l'écriture de serveurs.

Cela a l'avantage de restaurer une application même si la batterie du téléphone meurt ou s'il y a une panique du noyau ou autre. Je recommande de créer un wrapper OO autour de vos appels SQLite. Dart n'a pas quelque chose comme SQLite.Net qui le fait automatiquement pour vous malheureusement.

edit: C'est aussi quelque chose que nous avons fait au début du développement mobile parce que la mémoire était si limitée et sqlite est un excellent moyen d'échanger des choses entre la mémoire et le disque.

Je ferme ce problème puisque le cadre général de restauration de l'état (et l'intégration Android) a atterri. Le travail restant est suivi dans des numéros distincts :

Bien fait!!!!! Merci flutter !!!

Cette page vous a été utile?
0 / 5 - 0 notes