2017-10-14 5 views
0

J'essaie d'implémenter un fragment abstrait avec un rappel typé pour l'utiliser dans plusieurs sous-classes.Comment vérifier le type de rappel typé dans Fragment.onAttach()

Comment puis-je vérifier si Context est l'instance de la classe appropriée?

Mon code de abstact CallbackFragment:

public abstract class CallbackFragment<C> extends Fragment { 

    protected C mCallback; 

    public CallbackFragment() { 
    } 

    @Override 
    public void onAttach(Context context) { 
     super.onAttach(context); 

     //just in case 
     if(context == null) 
      throw new NullPointerException(); 

     try { 
      mCallback = (C) context; //this line not seems to throw any exception 
     } catch (ClassCastException exception) { 
      throw new RuntimeException(context.toString() + " must implement Callbacks"); 
     } 
    } 

    @Override 
    public void onDetach() { 
     super.onDetach(); 
     mCallback = null; 
    } 
} 

fragment de la liste des véhicules:

public abstract class VehicleListFragment<T extends Vehicle> 
     extends CallbackFragment<VehicleListFragment.Callback<T>> { 

    //callback for any list of any vehicle 
    public interface Callback<T extends Vehicle> { 
     void onListItemSelected(T selectedItem); 
    } 

    //common code for list of any vehicle 
    public VehicleListFragment() { 
    } 
} 

Bus, camion, bateau, vélo, tout fragment liste:

public class BusListFragment 
    extends VehicleListFragment<Bus> { 

    //code specific for list of bus 
    public BusListFragment() { 
    } 
} 

Détails du véhicule fragment:

public abstract class VehicleDetailsFragment<T extends Vehicle, C extends VehicleDetailsFragment.Callback<T>> 
     extends CallbackFragment<C> { 

    //common methods of callback for any vehicle 
    public interface Callback<T> { 
     void onVehicleEdited(T editeItem); 
    } 

    //common code for any vehicle 
    public VehicleDetailsFragment() { 
    } 
} 

Bus, camion, bateau, vélo, tout détails fragment:

public class BusDetailsFragment 
     extends VehicleDetailsFragment<Bus, BusDetailsFragment.Callback> { 

    //specific for Bus methods 
    public interface Callback 
      extends VehicleDetailsFragment.Callback<Bus> { 
     void onSomethingSpecificForBusHappened(Bus bus); 
    } 

    //code specific for Bus 
    public BusDetailsFragment() { 
    } 
} 

J'ai essayé d'ajouter une méthode abstraite pour CallbackFragment pour obtenir la classe de rappel:

public abstract class CallbackFragment<C> extends Fragment { 

    ... 

    @NonNull 
    protected abstract Class<C> getCallbackClass(); 

    @Override 
    public void onAttach(Context context) { 
     super.onAttach(context); 
     ... 

     //now checking instanceof like this 
     if(!getCallbackClass().isAssignableFrom(context.getClass())){ 
      throw new RuntimeException(context.toString() + " must implement Callbacks"); 
     } 
    } 
} 

Avec BusDetailsFragment tout semble OK:

public class BusDetailsFragment 
     extends VehicleDetailsFragment<Bus, BusDetailsFragment.Callback> { 

    @NonNull 
    @Override 
    protected Class<Callback> getCallbackClass() { 
     return Callback.class; 
    } 

    ... 
} 

Mais pas avec BusListFragment:

public class BusListFragment 
     extends VehicleListFragment<Bus> { 

    @NonNull 
    @Override 
    protected Class<Callback<Bus>> getCallbackClass() { 
     /** 
     * I'm not seeing any option here 
     * 
     * mCallback - is null yet. So, there is no way to use mCallback.getClass() 
     * 
     * Callback<Bus>.class - Cannot select from parameterized type 
     */ 
     //return mCallback.getClass(); 
     //return Callback<Bus>.class; 
    } 

    ... 
} 

Bien sûr, je pourrais créer une propre interface pour chaque sous-classe de VehicleListFragment qui s'étend VehicleListFragment.Callback (comme dans les sous-classes de VehicleDetailsFragment) mais il regardera toujours comme ça :

public interface Callback 
     extends VehicleListFragment.Callback<Bus> { 
    //nothing more here 
} 

Cela ne semble pas être la meilleure option pour moi. Peut-être qu'il existe une autre solution? S'il vous plaît partagez vos pensées. Toute aide serait appréciée.

Répondre

2
mCallback = (C) context; //this line not seems to throw any exception 

cet appel ne lèvera jamais une exception. Pendant l'exécution, votre C est remplacé par Object (c'est ce qu'on appelle le type-effacement) - et tout est un Object. Par conséquent, vous pouvez affecter n'importe quoi à ce stade.

Pour avoir l'exception (ou au moins une erreur-détermination) au point où vous en avez besoin, vous pouvez utiliser:

public abstract class CallbackFragment<C> extends Fragment { 

    protected C mCallback; 
    protected Class<C> callbackClass; 

    public CallbackFragment(Class<C> clazz) { 
     this.callbackClass = clazz; 
    } 

    @Override 
    public void onAttach(Context context) { 
     super.onAttach(context); 

     //just in case 
     if(context == null) 
      throw new NullPointerException(); 

     if (clazz.isAssignableFrom(context.getClass()){ 
      mCallback = (C) context; 
     }else{ 
      //oops 
     } 
    } 
} 

ofc.alors votre FragmentCreation changerait de

CallbackFragment<Something> fragment = new CallbackFragment<Something>(); 

à

CallbackFragment<Something> fragment = new CallbackFragment<Something>(Something.class); 

Il est un peu différent, mais vous permet de garder trace du type réel à tout moment, sans passer par le type Erasure.

ps .: Pour Héritée classes, vous pouvez le faire plus générique:

public abstract class CallbackFragment<C> extends Fragment { 
    protected Class<C> callbackClass; 

    public CallbackFragment() { 
      this.callbackClass = (Class<C>) ((ParameterizedType) getClass() 
         .getGenericSuperclass()).getActualTypeArguments()[0];; 
    } 
} 

public class CallbackFragmentOfSomething extends <CallbackFragment<Something>>{ 

} 

Cela échoue seulement, si votre classe réelle n'est pas définie en raison de l'héritage, mais « à la volée »:

CallbackFragment<Something> fragment = new CallbackFragment<Something>(); 

(Tout non testé/non copier coller, mais devrait être un peu précis)

+0

Mise à jour le 'admissible PS - était peu de mal :) – dognose

+0

Wow! Merci. Je vais essayer ça. Je ne suis pas sûr que j'ai complètement compris votre réponse en ce moment. Avez-vous remarqué que Callback for BusListFragment est VehicleListFragment.Callback et que je ne peux pas obtenir .class de type paramétré? – user2661298

+1

@ user2661298 Il est assez difficile de garder une trace des génériques utilisant des génériques comme types ayant leurs propres types tout en héritant d'autres génériques avec d'autres types - sans être capable de déboguer étape par étape. Règle du pouce: Gardez une trace de votre classe réelle chaque fois que possible et vous pouvez déterminer les types de classe réels et "lancer la sauvegarde" à tout moment. – dognose