Obtenir une activité à partir du contexte dans Android

184

Celui-ci m'a perplexe.

J'ai besoin d'appeler une méthode d'activité à partir d'une classe de mise en page personnalisée. Le problème avec cela est que je ne sais pas comment accéder à l'activité à partir de la mise en page.

ProfileView

public class ProfileView extends LinearLayout
{
    TextView profileTitleTextView;
    ImageView profileScreenImageButton;
    boolean isEmpty;
    ProfileData data;
    String name;

    public ProfileView(Context context, AttributeSet attrs, String name, final ProfileData profileData)
    {
        super(context, attrs);
        ......
        ......
    }

    //Heres where things get complicated
    public void onClick(View v)
    {
        //Need to get the parent activity and call its method.
        ProfileActivity x = (ProfileActivity) context;
        x.activityMethod();
    }
}

ProfilActivité

public class ProfileActivityActivity extends Activity
{
    //In here I am creating multiple ProfileViews and adding them to the activity dynamically.

    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.profile_activity_main);
    }

    public void addProfilesToThisView()
    {
        ProfileData tempPd = new tempPd(.....)
        Context actvitiyContext = this.getApplicationContext();
        //Profile view needs context, null, name and a profileData
        ProfileView pv = new ProfileView(actvitiyContext, null, temp, tempPd);
        profileLayout.addView(pv);
    }
}

Comme vous pouvez le voir ci-dessus, j'instancie le profileView par programme et je lui passe le activityContext. 2 questions:

  1. Suis-je en train de passer le bon contexte dans Profileview?
  2. Comment obtenir l'activité contenante du contexte?
HARMONIQUE
la source

Réponses:

473

À partir de votre Activity, passez simplement thiscomme Contextpour votre mise en page:

ProfileView pv = new ProfileView(this, null, temp, tempPd);

Ensuite, vous aurez un Contextdans la mise en page, mais vous saurez que c'est en fait le vôtre Activityet vous pouvez le lancer pour avoir ce dont vous avez besoin:

Activity activity = (Activity) context;
Boris Strandjev
la source
53
Vous ne pouvez pas être assuré que le contexte avec lequel vous travaillez est un contexte d'activité ou un contexte d'application. Essayez de transmettre un contexte d'application à un DialogView, observez-le se bloquer et vous verrez la différence.
Sky Kelsey
6
Boris, la question demande s'il existe un moyen d'obtenir une activité à partir d'un contexte. Ce n'est pas possible. Bien sûr, vous pouvez lancer, mais c'est un dernier recours. Si vous souhaitez traiter le contexte comme une activité, ne vous abaissez pas en activité. Cela simplifie le code et est moins sujet aux bogues par la suite lorsqu'une autre personne gère votre code.
Sky Kelsey
6
Notez que «getApplicationContext ()» au lieu de «this» ne fonctionnera pas.
dwbrito
1
@BorisStrandjev Je n'ai pas bien compris votre commentaire. Quoi qu'il en soit, j'ai dit qu'après avoir essayé votre exemple, mais au lieu de `` this '', j'ai utilisé getApplicationContext () et l'application a essayé de lancer l'application elle-même, donnant ainsi une erreur de conversion au lieu de l'activité. Après être passé à «ceci», comme vous avez répondu, cela a fonctionné.
dwbrito
1
Les réponses les plus élevées sur votre lien suggèrent toutes deux de contester la question si elle sent mauvais. Cette question est certainement malodorante. L'OP a d'abord déclaré: "Je dois appeler une méthode d'activité à partir d'une classe de mise en page personnalisée." ce qui est tout à fait réalisable avec une utilisation appropriée des interfaces. Puis il dit: "Le problème avec ceci est que je ne sais pas comment accéder à l'activité depuis la mise en page." ce qui est un signe significatif vers un malentendu. Les gens essaient tout le temps de faire la mauvaise chose dans la programmation et nous ne devons pas fermer les yeux.
Sam
39

C'est quelque chose que j'ai utilisé avec succès pour convertir Contextà Activitylors de l' utilisation dans l'interface utilisateur dans des fragments ou des vues personnalisées. Il décompressera ContextWrapper de manière récursive ou retournera null en cas d'échec.

public Activity getActivity(Context context)
{
    if (context == null)
    {
        return null;
    }
    else if (context instanceof ContextWrapper)
    {
        if (context instanceof Activity)
        {
            return (Activity) context;
        }
        else
        {
            return getActivity(((ContextWrapper) context).getBaseContext());
        }
    }

    return null;
}
Théo
la source
C'est la bonne réponse. Les autres ne prennent pas en compte la hiérarchie ContentWrapper.
Snicolas du
C'est la vraie réponse :)
lygstate
1
@lygstate: quel niveau d'API cible utilisez-vous dans votre application? Quelle est l'erreur? Cela ne fonctionne que dans l'interface utilisateur (activités, fragments, etc.), pas dans les services.
Theo
31
  1. Non
  2. Tu ne peux pas

Il existe deux contextes différents dans Android. Un pour votre application (appelons-le le GRAND) et un pour chaque vue (appelons-le le contexte d'activité).

Un linearLayout est une vue, vous devez donc appeler le contexte d'activité. Pour l'appeler à partir d'une activité, appelez simplement «ceci». Si facile n'est-ce pas?

Lorsque vous utilisez

this.getApplicationContext();

Vous appelez le contexte BIG, celui qui décrit votre application et ne peut pas gérer votre vue.

Un gros problème avec Android est qu'un contexte ne peut pas appeler votre activité. C'est un gros problème pour éviter cela lorsque quelqu'un commence avec le développement Android. Vous devez trouver une meilleure façon de coder votre classe (ou remplacer «Contexte contextuel» par «Activité d'activité» et le convertir en «Contexte» si nécessaire).

Cordialement.


Juste pour mettre à jour ma réponse. Le moyen le plus simple d'obtenir votre Activity contextest de définir une staticinstance dans votre fichier Activity. Par exemple

public class DummyActivity extends Activity
{
    public static DummyActivity instance = null;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // Do some operations here
    }

    @Override
    public void onResume()
    {
        super.onResume();
        instance = this;
    }

    @Override
    public void onPause()
    {
        super.onPause();
        instance = null;
    }
}

Et puis, dans votre Task, Dialog, View, vous pouvez utiliser ce genre de code pour obtenir votre Activity context:

if (DummyActivity.instance != null)
{
    // Do your operations with DummyActivity.instance
}
Manitoba
la source
4
+1 pour expliquer une zone de confusion très courante entre les 2 différents types de contextes (tout comme il y en a 2 différents R). Les gens de Google ont besoin d'enrichir leur vocabulaire.
an00b
3
BTW, @BorisStrandjev a raison: 2. Oui, vous pouvez . (ne peut pas discuter avec le code de travail)
an00b
2
2. Pas vraiment. Si le contexte était le contexte de l'application, votre application se planterait.
StackOverflowed le
instance statique?! @Nepster a la meilleure solution à cet imo
Sam
14
La création d'une référence statique à une activité est le meilleur moyen de créer des fuites de mémoire.
BladeCoder
8

Si vous souhaitez appeler une méthode d'activité à partir d'une classe de mise en page personnalisée (non-classe d'activité), vous devez créer un délégué à l'aide de l'interface.

Il n'a pas été testé et je l'ai bien codé. mais je transmets un moyen de réaliser ce que vous voulez.

Tout d'abord créer et interface

interface TaskCompleteListener<T> {
   public void onProfileClicked(T result);
}



public class ProfileView extends LinearLayout
{
    private TaskCompleteListener<String> callback;
    TextView profileTitleTextView;
    ImageView profileScreenImageButton;
    boolean isEmpty;
    ProfileData data;
    String name;

    public ProfileView(Context context, AttributeSet attrs, String name, final ProfileData profileData)
    {
        super(context, attrs);
        ......
        ......
    }
    public setCallBack( TaskCompleteListener<String> cb) 
    {
      this.callback = cb;
    }
    //Heres where things get complicated
    public void onClick(View v)
    {
        callback.onProfileClicked("Pass your result or any type");
    }
}

Et implémentez ceci dans n'importe quelle activité.

et appelle ça comme

ProfileView pv = new ProfileView(actvitiyContext, null, temp, tempPd);
pv.setCallBack(new TaskCompleteListener
               {
                   public void onProfileClicked(String resultStringFromProfileView){}
               });
Zar E Ahmer
la source
1
C'est la bonne réponse et doit être marquée comme la bonne réponse. Je sais que la réponse marquée comme la bonne répond en fait à la question de OP, mais elle ne devrait pas répondre à la question comme ça. Le fait est que ce n'est pas une bonne pratique de passer l'activité comme ça dans une vue. L'enfant ne doit en aucun cas connaître son parent, sauf par le biais du Context. Comme le déclare Nepster, la meilleure pratique consiste à transmettre un rappel, donc chaque fois qu'un événement intéressant le parent se produit, le rappel sera déclenché avec les données pertinentes.
Darwind le
6

Le contexte peut être une application, un service, une activité, etc.

Normalement, le contexte des vues dans une activité est l'activité elle-même, vous pouvez donc penser que vous pouvez simplement convertir ce contexte en activité, mais en réalité vous ne pouvez pas toujours le faire, car le contexte peut également être un ContextThemeWrapper dans ce cas.

ContextThemeWrapper est fortement utilisé dans les versions récentes d'AppCompat et d'Android (grâce à l'attribut android: theme dans les mises en page), donc je n'effectuerais personnellement jamais ce casting.

La réponse est donc courte: vous ne pouvez pas récupérer de manière fiable une activité à partir d'un contexte dans une vue. Passez l'activité à la vue en appelant une méthode sur celle-ci qui prend l'activité comme paramètre.

BladeCoder
la source
3

N'utilisez jamais getApplicationContext () avec des vues.

Cela doit toujours être le contexte de l'activité, car la vue est attachée à l'activité. En outre, vous pouvez avoir un ensemble de thèmes personnalisé et lors de l'utilisation du contexte de l'application, tous les thèmes seront perdus. En savoir plus sur les différentes versions de contextes ici .

Lomza
la source
3

Et à Kotlin:

tailrec fun Context.activity(): Activity? = when {
  this is Activity -> this
  else -> (this as? ContextWrapper)?.baseContext?.activity()
}
rjrjr
la source
0

une activité est une spécialisation du contexte donc, si vous avez un contexte, vous savez déjà quelle activité vous avez l'intention d'utiliser et pouvez simplement convertir a en c ; où a est une activité et c est un contexte.

Activity a = (Activity) c;
ACLima
la source
7
Ceci est dangereux car, comme mentionné dans un commentaire séparé, le contexte peut ne pas toujours être une activité.
4
typecast only if (context instanceof Activity) {// typecast}
Amit Yadav
0

J'ai utilisé l'activité de conversion

Activity activity = (Activity) context;
Samuel Ivan
la source
2
Il existe différents types de contextes. Les activités et applications peuvent avoir des contextes. Cela ne fonctionnera que lorsque le contexte est celui d'une activité.
cylov
0

Cette méthode devrait être utile ..!

public Activity getActivityByContext(Context context){

if(context == null){
    return null;
    }

else if((context instanceof ContextWrapper) && (context instanceof Activity)){
        return (Activity) context;
    }

else if(context instanceof ContextWrapper){
        return getActivity(((ContextWrapper) context).getBaseContext());
    }

return null;

    }

J'espère que cela aide .. Joyeux codage!

Taslim Oseni
la source
Vérifiez que le contexte que vous avez transmis n'est pas nul. C'est probablement le problème.
Taslim Oseni
0

que diriez-vous d'un rappel de données en direct,

class ProfileView{
    private val _profileViewClicked = MutableLiveData<ProfileView>()
    val profileViewClicked: LiveData<ProfileView> = _profileViewClicked
}

class ProfileActivity{

  override fun onCreateView(...){

    profileViewClicked.observe(viewLifecycleOwner, Observer { 
       activityMethod()
    })
  }

}
Abhinav Atul
la source