J'essaie de stocker un ensemble de chaînes à l'aide de l'API SharedPreferences
.
Set<String> s = sharedPrefs.getStringSet("key", new HashSet<String>());
s.add(new_element);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putStringSet(s);
edit.commit()
La première fois que j'exécute le code ci-dessus, s
est réglé sur la valeur par défaut (la fin juste créée HashSet
) et il est stocké sans problème.
La deuxième fois et la prochaine fois que j'exécute ce code, un objet s
est renvoyé avec le premier élément ajouté. Je peux ajouter l'élément, et pendant l'exécution du programme, il est apparemment stocké dans le SharedPreferences
, mais lorsque le programme est tué, le SharedPreferences
relit à partir de son stockage persistant et les nouvelles valeurs sont perdu.
Comment le second et les éléments suivants peuvent-ils être stockés pour ne pas se perdre?
Ce "problème" est documenté sur SharedPreferences.getStringSet
.
Le SharedPreferences.getStringSet
renvoie une référence de l'objet HashSet stocké à l'intérieur de SharedPreferences
. Lorsque vous ajoutez des éléments à cet objet, ils sont en fait ajoutés à l'intérieur du SharedPreferences
.
C'est correct, mais le problème survient lorsque vous essayez de le stocker: Android compare le HashSet modifié que vous essayez d'enregistrer en utilisant SharedPreferences.Editor.putStringSet
avec celui actuellement stocké sur le SharedPreference
, et les deux sont le même objet !!!
Une solution possible consiste à faire une copie du Set<String>
retourné par l'objet SharedPreferences
:
Set<String> s = new HashSet<String>(sharedPrefs.getStringSet("key", new HashSet<String>()));
Cela fait de s
un objet différent, et les chaînes ajoutées à s
ne seront pas ajoutées à l'ensemble stocké dans SharedPreferences
.
Une autre solution de contournement qui fonctionnera consiste à utiliser le même SharedPreferences.Editor
transaction pour stocker une autre préférence plus simple (comme un entier ou un booléen), la seule chose dont vous avez besoin est de forcer la valeur stockée à chaque transaction (par exemple, vous pouvez stocker la taille de l'ensemble de chaînes).
Ce comportement est documenté, il est donc par conception:
de getStringSet:
"Notez que vous ne devez pas modifier l'instance d'ensemble renvoyée par cet appel. La cohérence des données stockées n'est pas garantie si vous le faites, ni votre capacité à modifier l'instance du tout."
Et cela semble tout à fait raisonnable, surtout s'il est documenté dans l'API, sinon cette API devrait faire une copie à chaque accès. La raison de cette conception était donc probablement la performance. Je suppose qu'ils devraient faire en sorte que cette fonction renvoie le résultat enveloppé dans une instance de classe non modifiable, mais cela nécessite une fois de plus une allocation.
Était à la recherche d'une solution pour le même problème, l'a résolu par:
1) Récupérez l'ensemble existant des préférences partagées
2) Faites-en une copie
3) Mettre à jour la copie
4) Enregistrez la copie
SharedPreferences.Editor editor = sharedPrefs.edit();
Set<String> oldSet = sharedPrefs.getStringSet("key", new HashSet<String>());
//make a copy, update it and save it
Set<String> newStrSet = new HashSet<String>();
newStrSet.add(new_element);
newStrSet.addAll(oldSet);
editor.putStringSet("key",newStrSet); edit.commit();
J'ai essayé toutes les réponses ci-dessus, aucune n'a fonctionné pour moi. J'ai donc fait les étapes suivantes
ajoutez les valeurs présentes dans la copie à la préférence partagée effacée, il la traitera comme nouvelle.
public static void addCalcsToSharedPrefSet(Context ctx,Set<String> favoriteCalcList) {
ctx.getSharedPreferences(FAV_PREFERENCES, 0).edit().clear().commit();
SharedPreferences sharedpreferences = ctx.getSharedPreferences(FAV_PREFERENCES, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putStringSet(FAV_CALC_NAME, favoriteCalcList);
editor.apply(); }
J'étais confronté à un problème avec les valeurs non persistantes, si je rouvrais l'application après avoir nettoyé l'application de l'arrière-plan, seul le premier élément ajouté à la liste était affiché.
Alors que les autres bonnes réponses ici ont correctement souligné que ce problème potentiel est documenté dans SharedPreferences.getStringSet () , essentiellement "Ne modifiez pas l'ensemble renvoyé car le comportement n'est pas garanti", je Je voudrais réellement contribuer au code source qui cause ce problème/comportement pour quiconque souhaite approfondir.
En jetant un œil à SharedPreferencesImpl (code source à partir de Android Pie), nous pouvons voir que dans SharedPreferencesImpl.commitToMemory()
il y a une comparaison qui se produit entre l'original (un Set<String>
dans notre cas) et la valeur nouvellement modifiée:
private MemoryCommitResult commitToMemory() {
// ... other code
// mModified is a Map of all the key/values added through the various put*() methods.
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// ... other code
// mapToWriteToDisk is a copy of the in-memory Map of our SharedPreference file's
// key/value pairs.
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
Donc, fondamentalement, ce qui se passe ici, c'est que lorsque vous essayez d'écrire vos modifications dans le fichier, ce code passera en revue vos paires clé/valeur modifiées/ajoutées et vérifiera qu'elles existent déjà, et ne les écrira dans le fichier que si elles ne le sont pas ou ne le sont pas différente de la valeur existante qui a été lue en mémoire.
La ligne clé à suivre ici est if (existingValue != null && existingValue.equals(v))
. Votre nouvelle valeur ne sera écrite sur le disque que si existingValue
est null
(n'existe pas déjà) ou si le contenu de existingValue
est différent du contenu de la nouvelle valeur.
Ceci est le nœud du problème. existingValue
est lu depuis la mémoire. Le fichier SharedPreferences que vous essayez de modifier est lu en mémoire et stocké sous Map<String, Object> mMap;
(Plus tard copié dans mapToWriteToDisk
chaque fois que vous essayez d'écrire dans un fichier). Lorsque vous appelez getStringSet()
, vous obtenez un Set
à partir de cette carte en mémoire. Si vous ajoutez ensuite une valeur à cette même instance Set
, vous modifiez la carte en mémoire . Ensuite, lorsque vous appelez editor.putStringSet()
et essayez de valider, commitToMemory()
est exécuté et la ligne de comparaison essaie de comparer votre valeur nouvellement modifiée, v
, à existingValue
qui est essentiellement le même _ en mémoire Set
que celui que vous venez de modifier. Les instances d'objet sont différentes, car les Set
ont été copiés à divers endroits, mais le contenu est identique.
Donc vous essayez de comparer vos nouvelles données à vos anciennes données, mais vous avez déjà involontairement mis à jour vos anciennes données en modifiant directement ce Set
instance. Ainsi, vos nouvelles données ne seront pas écrites dans un fichier.
Comme l'OP l'a indiqué, il semble que les valeurs soient stockées pendant que vous testez l'application, mais les nouvelles valeurs disparaissent après avoir tué le processus d'application et redémarré. En effet, pendant que l'application est en cours d'exécution et que vous ajoutez des valeurs, vous ajoutez toujours les valeurs à la mémoire en mémoire Set
structure, et lorsque vous appelez getStringSet()
vous récupérez cette même mémoire Set
. Toutes vos valeurs sont là et il semble que ça marche. Mais après avoir tué l'application, cette structure en mémoire est détruite avec toutes les nouvelles valeurs car elles n'ont jamais été écrites dans un fichier.
Comme d'autres l'ont dit, évitez simplement de modifier la structure en mémoire, car vous causez essentiellement un effet secondaire. Ainsi, lorsque vous appelez getStringSet()
et que vous souhaitez réutiliser le contenu comme point de départ, copiez simplement le contenu dans une autre instance Set
au lieu de le modifier directement: new HashSet<>(getPrefs().getStringSet())
. Maintenant, lorsque la comparaison se produit, le existingValue
en mémoire sera en fait différent de votre valeur modifiée v
.