12

J'ai une activité avec des onglets de balayage en utilisant les onglets ActionBar, basé sur the android developer example.Le chargeur délivre le résultat à un mauvais fragment

Chaque onglet affiche un fragment, et chaque fragment (en fait, un SherlockFragment) charge un type différent de demande d'API distante via un AsyncTaskLoader personnalisé. Le problème est que si vous appuyez sur un onglet pour déplacer 2 onglets/pages alors que le fragment pour l'onglet que vous quittez (l'ancien fragment) est en train de charger un résultat, ce résultat est livré au fragment pour l'onglet que vous déplacer vers (le nouveau fragment). Dans mon cas, cela conduit à une exception ClassCastException, car les résultats attendus sont de types incompatibles.

Dans le code, l'essentiel de la situation est:

Manutention:

public class FooLoader extends AsyncTaskLoader<Foo> 
public class BarLoader extends AsyncTaskLoader<Bar> 

Fragments:

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> { 
... 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); } 
... 
} 
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> { 
    ... 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); } 
    ... 
} 

Le code de gestion de l'onglet est comme dans le the aforementioned example. Il y a un troisième onglet entre les onglets Foo et Bar (appelez-le Baz). Lorsque nous sautons dans l'onglet Foo à l'onglet Barre en appuyant sur l'onglet Barre après FooFragment a appelé sur son initLoader LoaderManager mais avant FooFragment.onLoadFinished est appelé, nous nous retrouvons avec un ClassCastException sur un appel à BarFragment.onLoadFinished:

java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar 
at com.example.BarFragment.onLoadFinished(BarFragment.java:1) 
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427) 
at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:562) 
at com.example.BarFragment.onCreate(BarFragment.java:36) 
at android.support.v4.app.Fragment.performCreate(Fragment.java:1437) 
... 

Pourquoi cela se produit-il et comment peut-on le prévenir? Il semble à partir des journaux de débogage que le même LoaderManager est réutilisé dans le fragment Barre (bien que le fragment Baz ait le sien), mais je ne sais pas pourquoi cela devrait se produire. L'utilisation de différents ID de chargeur dans chaque fragment élimine le crash (ou semble - je ne sais pas vraiment pourquoi) mais je préférerais ne pas le faire. Dans l'un des fragments, je crée des ID dynamiquement et je ne veux pas supposer qu'il n'y aura pas de collision. Aussi, cette solution est bizarre pour moi - les ID du chargeur doivent être locaux pour chaque fragment (sinon, pourquoi est-ce que je peux avoir des chargeurs avec les mêmes ID dans différents fragments dans des circonstances normales?)

en appelant le setOffscreenPageLimit(2) sur mon ViewPager, afin que la vue Foo ne soit pas supprimée lorsque nous passons à la vue Barre. Mais c'est une solution de contournement, pas une solution générale.

Code complet: J'ai créé un example application demonstrating the error. Il inclut un script monkeyrunner pour forcer l'erreur (bien que cela puisse ne pas fonctionner pour toutes les tailles d'écran).

+0

Pourriez-vous poster le code complet pour l'un des chargeurs s'il vous plaît. – yarian

+1

Essayez de déplacer votre appel à 'initLoader' dans' onActivityCreated' au lieu de 'onCreate'. –

+0

Cela ressemble à résoudre le problème. Merci! – jgiles

Répondre

2

Vous pouvez éviter ce problème en appelant initLoader dans onActivityCreated plutôt que dans onCreate - comme l'a noté dans les commentaires Alex Lockwood question. Code modifié ci-dessous.

Fragments: Correction

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> { 
... 
    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); } 
... 
} 
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> { 
    ... 
    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); } 
    ... 
} 
+2

Je ne pense pas que cela fonctionnera dans le cas où vous avez un viewpager avec des fragments du même type. Dans ce cas, le viewpager détruira le fragment et le recréera une fois que vous l'aurez fait glisser. À ce stade, le fragment ne recevra jamais d'événement onActivityCreated, mais seulement l'objet onAttach(). À mon humble avis vous avez probablement besoin de s'assurer que les id de chargeur sont différents. Si vous utilisez le même fragment plusieurs fois dans la même activité, vous devez passer des arguments pour vous assurer que les identifiants du chargeur ne se chevauchent pas. – RaB

10

N'utilisez pas 0 comme ID. Dans la mesure où le LoaderManager sait qu'ils sont destinés à être le même chargeur.

Vous pouvez définir des identifiants uniques dans un fichier de ressources XML

<item type="id" name="loader_foo" /> 
<item type="id" name="loader_bar" /> 

et d'y accéder de R.

loaderManager.initLoader(R.id.loader_foo, null, new LoaderCallbacks(){}); 

La documentation LoaderManager dit "Identifiants sont étendus à une instance LoaderManager particulière". Et les instances de LoaderMananger sont liées à Activities.

Pour générer des identifiants uniques, vous pouvez leur attribuer manuellement des ID comportant de grands espaces entre eux.

private static final int LOADER_FOO = 1; 
private static final int LOADER_BAR = 100; 

for(int i = 0; i < 10; ++i){ 
    loaderManager.initLoader(LOADER_FOO + i, null, new LoaderCallbacks(){}); 
} 
+0

Je peux éliminer le crash en utilisant des ID différents dans chaque fragment, mais je ne devrais pas le faire et je ne veux pas le faire. (voir mise à jour de la question originale). – jgiles

+0

Voir édition. À votre santé. – alex

+0

Les identifiants que je génère n'ont pas de limite particulière, donc il n'y a pas une taille d'écart que je puisse supposer être assez grande. De plus, cela ressemble toujours à une solution de contournement. – jgiles

0

récemment, je travaillais avec SimpleCursorAdapter et chargeurs pour afficher des données à partir des tables SQL Lite. J'ai passé un long moment à déboguer une erreur "pas une telle colonne '_id'." qui s'est produit dans mon second fragment onLoadFinished(). Ma première table avait la colonne '_id' mais ma deuxième table ne l'a pas fait, ce qui m'a amené à croire que le Loader ne livre pas le bon fragment. Donc, juste au cas où vous avez fait quelque chose de similaire, assurez-vous que vos tables sql ont la colonne '_id' en premier. J'espère que cela aide quelqu'un d'autre qui a eu le même problème que moi.

Questions connexes