213

This one has me stumped.

I need to call an activity method from within a custom layout class. The problem with this is that I don't know how to access the activity from within the layout.

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();
    }
}

ProfileActivity

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);
    }
}

As you can see above, I am instantiating the profileView programatically and passing in the activityContext with it. 2 questions:

  1. Am i passing the correct context into the Profileview?
  2. How do I get the containing activity from the context?
Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
OVERTONE
  • 11,797
  • 20
  • 71
  • 87

15 Answers15

506

From your Activity, just pass in this as the Context for your layout:

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

Afterwards you will have a Context in the layout, but you will know it is actually your Activity and you can cast it so that you have what you need:

Activity activity = (Activity) context;
bartonstanley
  • 1,167
  • 12
  • 25
Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • 70
    You can't be guaranteed that the context you are working with is an Activity Context or an Application Context. Try passing an Application Context to a DialogView, watch it crash, and you will see the difference. – Sky Kelsey Aug 01 '12 at 23:10
  • 9
    Boris, the question asks if there is a way to get an Activity from a Context. This is not possible. Of course you can cast, but that is a last resort. If you want to treat the Context as an Activity, then don't downcast to an Activity. It makes for simpler code, and is less prone to bugs later when another person is maintaining your code. – Sky Kelsey Aug 02 '12 at 18:42
  • 6
    Note that 'getApplicationContext()' instead of 'this' will not work. – dwbrito Jan 21 '13 at 18:13
  • @alaxid Nope, it needs to be `Activity` context in this case. – Boris Strandjev Jan 22 '13 at 10:30
  • 1
    @BorisStrandjev I haven't quite understand your comment. Anyway, I said that after trying your example but instead of 'this' I used getApplicationContext() and the application tried to cast the App itself, hence giving a cast error, instead of the activity. After switching to 'this', as you answered, it worked. – dwbrito Jan 22 '13 at 23:06
  • Of course it is dangerous to cast just because. But sometimes you are completely sure that the context is indeed an Activity, and things are much simpler... But I agree... use with care ! – rupps May 21 '13 at 16:52
  • It's worth mentioning type casting is NOT always safe, so this check should be used, before you cast the context into an activity: if(context instanceof MyActivity) – Sipty Mar 13 '15 at 12:27
  • @Sipty `instanceof Activity` would be sufficient. – Boris Strandjev Mar 13 '15 at 13:44
  • @Sam: Hey, any particular reason for that, or you just felt like it? – Boris Strandjev Jun 03 '15 at 04:29
  • for the reasons listed by others. a high profile answer like this is definitely going to encourage bad habits. Nepster has a good solution to this problem – Sam Jun 03 '15 at 09:29
  • @Sam I think this is the A car with square wheels dilemma: http://meta.stackoverflow.com/questions/254341/a-car-with-square-wheels . I just asnwer OP's questions. On the other hand, I agree that better solutions do exist. However, I remind you that downvote's hover reads "This answer was not useful" – Boris Strandjev Jun 03 '15 at 11:08
  • 1
    The highest upvoted answers on your link both suggest challenging the question if it is smelly. This question certainly is smelly. The OP first stated: "I need to call an activity method from within a custom layout class." which is completely achievable with appropriate use of interfaces. Then he says "The problem with this is that I don't know how to access the activity from within the layout." which is a significant hint toward a misunderstanding. People try to to do the wrong thing all the time in programming and we shouldn't turn a blind eye to it. – Sam Jun 03 '15 at 14:16
  • 1
    This answer was not useful to me. It wasn't even useless, it was actively harmful as we now have code committed with this pattern by another developer that likely won't be undone any time soon due to time constraints. So... down with this answer :P – Sam Jun 03 '15 at 14:18
  • 1
    java.lang.ClassCastException: android.app.ReceiverRestrictedContext cannot be cast to android.app.Activity – UmAnusorn Aug 20 '15 at 08:33
  • 1
    This is bad, and not answering the question. I can't believe this got so many upvotes... – Primoz990 Jan 13 '16 at 14:08
  • @Primoz990 as for the downvote, i do not mind it, but you listed two claims that are both utterly incorrect. If you claim something is bad, mention also the good otherwise you act just as any modern media would do - of no social benefit, piling up negative feelings. As for answering the question - i believe accept and the amount of upvotes prove you wrong, but feel free to post your interpretation of the OP's query, so we can try to improve the answer – Boris Strandjev Jan 14 '16 at 06:41
  • @Boris Strandjev When i read my comment again, i see it is really like modern media... i know what you mean. But many other people in comments and other questions have already explained why this cast is a bad idea... this works only if you pass a Activity as context... for simple use cases this works, this is why the up votes, but later you can have trouble with such code. The other answers and comments prove this. – Primoz990 Jan 14 '16 at 07:34
  • This method seems useless for a CustomView which is instantiated in XML. There is no way to use the contructor – Niklas Rosencrantz Jun 17 '18 at 16:26
  • Can we say that activity is subclass of context, because it can be cast into activity? I didnot understand how this can be possible can you elaborate your answer? – Jay Dangar Sep 08 '18 at 16:06
  • 1
    @JayDangar It is a subclass, you can check it yourself: https://developer.android.com/reference/android/app/Activity java.lang. Object ↳ android.content.Context ↳ android.content.ContextWrapper ↳ android.view.ContextThemeWrapper ↳ android.app.Activity – Boris Strandjev Sep 09 '18 at 07:41
  • While this has some dubious safety, I am currently working on a very poorly maintained project and need some proof of concept that it can even be salvaged. As a non-Android dev, I didn't know Activity was a type of Context. This answer was a blessing, thank you Boris. – Aaron Aug 09 '22 at 06:25
56

This is something that I have used successfully to convert Context to Activity when operating within the UI in fragments or custom views. It will unpack ContextWrapper recursively or return null if it fails.

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;
}
Theo
  • 5,963
  • 3
  • 38
  • 56
34
  1. No
  2. You can't

There are two different contexts in Android. One for your application (Let's call it the BIG one) and one for each view (let's call it the activity context).

A linearLayout is a view, so you have to call the activity context. To call it from an activity, simply call "this". So easy isn't it?

When you use

this.getApplicationContext();

You call the BIG context, the one that describes your application and cannot manage your view.

A big problem with Android is that a context cannot call your activity. That's a big deal to avoid this when someone begins with the Android development. You have to find a better way to code your class (or replace "Context context" by "Activity activity" and cast it to "Context" when needed).

Regards.


Just to update my answer. The easiest way to get your Activity context is to define a static instance in your Activity. For example

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;
    }
}

And then, in your Task, Dialog, View, you could use that kind of code to get your Activity context:

if (DummyActivity.instance != null)
{
    // Do your operations with DummyActivity.instance
}
Manitoba
  • 8,522
  • 11
  • 60
  • 122
13

And in Kotlin:

tailrec fun Context.activity(): Activity? = when {
  this is Activity -> this
  else -> (this as? ContextWrapper)?.baseContext?.activity()
}
rjrjr
  • 3,892
  • 1
  • 22
  • 18
9

If you like to call an activity method from within a custom layout class(non-Activity Class).You should create a delegate using interface.

It is untested and i coded it right . but i am conveying a way to achieve what you want.

First of all create and 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");
    }
}

And implement this to any Activity.

and call it like

ProfileView pv = new ProfileView(actvitiyContext, null, temp, tempPd);
pv.setCallBack(new TaskCompleteListener
               {
                   public void onProfileClicked(String resultStringFromProfileView){}
               });
Zar E Ahmer
  • 33,936
  • 20
  • 234
  • 300
  • 1
    This is the correct answer and should be marked as the correct answer. I know the answer marked as the correct one actually answers OP's question, but it shouldn't be answering the question like that. The fact is that it's not good practice to pass in the Activity like that inside a view. The child should never know about their parent in any case, except through the `Context`. As Nepster states, the best practice is to pass in a callback, so whenever something happens of interest to the parent, the callback will be fired with the relevant data. – Darwind Jun 27 '18 at 13:52
7

Context may be an Application, a Service, an Activity, and more.

Normally the context of Views in an Activity is the Activity itself so you may think you can just cast this Context to Activity but actually you can't always do it, because the context can also be a ContextThemeWrapper in this case.

ContextThemeWrapper is used heavily in the recent versions of AppCompat and Android (thanks to the android:theme attribute in layouts) so I would personally never perform this cast.

So short answer is: you can't reliably retrieve an Activity from a Context in a View. Pass the Activity to the view by calling a method on it which takes the Activity as parameter.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
4

Never ever use getApplicationContext() with views.

It should always be activity's context, as the view is attached to activity. Also, you may have a custom theme set, and when using application's context, all theming will be lost. Read more about different versions of contexts here.

lomza
  • 9,412
  • 15
  • 70
  • 85
2

I used convert Activity

Activity activity = (Activity) context;
Samuel Seda
  • 2,814
  • 1
  • 24
  • 13
  • 2
    There are different kind of contexts. Activities and Applications can have contexts. This will only work when the context is of a an activity. – cylov Nov 15 '17 at 14:42
2

For kotlin user -

val activity = context as Activity
hasan
  • 29
  • 2
1

an Activity is a specialization of Context so, if you have a Context you already know which activity you intend to use and can simply cast a into c; where a is an Activity and c is a Context.

Activity a = (Activity) c;
aclima
  • 630
  • 1
  • 11
  • 20
1

I use this:

fun Context.findActivity(): Activity? = when (this) {
    is Activity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}

or this:

fun Context.findActivity(): Activity {
    var context = this
    while (context is ContextWrapper) {
        if (context is Activity) return context
        context = context.baseContext
    }
    throw IllegalStateException("no activity")
}
yazan sayed
  • 777
  • 7
  • 24
0

This method should be helpful..!

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;

    }

I hope this helps.. Merry coding!

Taslim Oseni
  • 6,086
  • 10
  • 44
  • 69
0

how about some live data callback,

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

class ProfileActivity{

  override fun onCreateView(...){

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

}

Abhinav Atul
  • 601
  • 6
  • 14
0

Create an extension function. And call this extension function with your context like context.getActivity().

fun Context.getActivity(): AppCompatActivity? {
      var currentContext = this
      while (currentContext is ContextWrapper) {
           if (currentContext is AppCompatActivity) {
                return currentContext
           }
           currentContext = currentContext.baseContext
      }
      return null
}
Rajeev Shetty
  • 1,534
  • 1
  • 17
  • 27
0

Kotlin android shorthand extension version of Theo's solution

private fun Context?.getParentActivity() : AppCompatActivity? = when {
    this is ContextWrapper -> if (this is AppCompatActivity) this else this.baseContext.getParentActivity()
    else -> null
}

Usage of above explained here

MDT
  • 1,535
  • 3
  • 16
  • 29