ListView is leaking activity context - android

The following code is leaking the activity context:
This is actually inside an asyncTask in the onPostExecute
ChatCustomAdapter customAdapter = new ChatCustomAdapter(mContext, chatData, Typeface.createFromAsset(getAssets(), "font/Helvetica-Bold.ttf"));
mChatList.setAdapter(customAdapter);
inside the adapter the context is used for
inflater = LayoutInflater.from(mContext);
Am I holding a reference to the context? if so how do I release it?
LeakCanary is telling me that the ListView (mChatList) is leaking the context and if i remove setAdapter the leak has gone.

EDIT:
You can try to wrap your mChatList with WeakReference, for exp:
class ChatTask extends AsyncTask {
private WeakReference<ListView> mListRef;
public ChatTask(ListView chatList) {
mListRef = new WeakReference<ListView>(chatList);
}
public void onPostExecute() {
ListView chatList = mListRef.get();
if (chatList != null) {
Context context = chatList.getContext();
ChatCustomAdapter customAdapter = new ChatCustomAdapter(context, chatData,
Typeface.createFromAsset(context.getAssets(), "font/Helvetica-Bold.ttf"));
chatList.setAdapter(customAdapter);
}
}
}
If it still not work, you could try to follow this post
I assume that you create the inflater inside the ChatCustomAdapter constructor and keep that inflater as global variable to use later in getView method?
If that true, I think you should try to remove the variable inflater and inside getView method, create a local inflater by LayoutInflater.from(parentView.getContext);
Hope that helps.

ok so i have found the problem but i do not understand it so maybe some can comment and explain or answer with why it does this. the leak was in the Chat activity. to get to the Chat activity i have a button in the previous activity:
ChatButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View arg0)
{
Intent intent = new Intent(mContext, Chat.class);
mContext.startActivity(intent);
}
});
when i start the activity i was starting it from
mContext.startActivity(intent)
if i change this to just
startActivity(intent);
it doesn't leak.
Edit
it does still leak

Related

Best approach to access an Activity on the inside of a (custom) view/dialog?

I have only one activity in my app. Before I just stored my views and dialogs static in the activity, so I could access them from anywhere. But I know that this is bad practice because it leads to memory leaks.
So I made them non-static, but now I need to have a reference to my activity deep down in my view hierarchy, to access the views and dialogs stored in the activity.
Example:
My MainActivity has a dialog called a and a custom view called b. How can the onClick method of b show the dialog a?
or in code:
public class MainActivity extends Activity {
private CustomDialog a;
private CustomView b;
#Override
protected void onCreate(Bundle savedInstanceState) {
a = new CustomDialog(this);
b = new CustomView(this);
}
}
public class CustomView extends Button implements OnClickListener {
public CustomView(Context context) {
super(context);
setOnClickListener(this);
}
#Override
public void onClick(View view) {
//wants to show dialog a
MainActivity.a.show(); //Not possible -> a is not static
mainActivity.a.show(); //<-- needs a reference of the activity
// but where from?
}
}
MainActivity mainActivity = (MainActivity) getContext(); won't work because getContext() is not always an activity context.
UPDATE:
I posted an answer below!
For some reasons StackOverflow only lets me accept my own answer in two days
I do not know what exactly your view hierarchy looks like.
I picture your problem for example as:
Activity A has a recyclerview R, now every viewholder H in R should be able to trigger some method in A.
In such a scenario it would be feasable to pass a reference of your activity to your recyclerview adapter and then the adapter passes it to the ViewHolder.
Which then uses it in the onClick method of your (viewholder's) view.
Here, you could use the "callback" pattern. There are many posts about this on stackoverflow, e.g. here.
So the implementation steps would be:
define interface
let your activity implement that interface
let your adapter take the interface as a constructor parameter and pass your activity. (in this example: you have to repeat the step with your viewHolder, pass the interface from the adapter)
use this interfaces method in the onClick method -> this will then trigger your activities method
The implementation depends on the actual hierarchy. If your other view is in a fragment, then you could also use a (shared) ViewModel.
According to your picture I was thinking of the callback-pattern approach first.
You could override onClick in MainActivity; there is probably no need to do it in the class definition itself.
public class MainActivity extends Activity {
private CustomDialog a;
private CustomView b;
#Override
protected void onCreate(Bundle savedInstanceState) {
a = new CustomDialog(this);
b = new CustomView(this);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
a.show();
}
});
}
}
This is a very common pattern in android and I don't know what your view hierarchy looks like but it should work in most cases.
I am having trouble understanding why any class extending Button would need to implement View.OnClickListener. It makes much more sense to create listeners in activities or have MainActivity implement OnClickListener.
A few minutes a go there was an answer here that turned out to be correct.
I don't know why the author deleted it, but it had a link to this answer:
private static Activity unwrap(Context context) {
while (!(context instanceof Activity) && context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
}
return (Activity) context;
}
So everytime you need the activity you just can call Activity activity = unwrap(getContext());.
I don't know if it is really intended to do it that way or if it is a workaround, but it does its job (atleast in my case).

Cant pass context inside fragment class

I have following code in which I want to pass the context as a first parameter but I cant , tried all possible answers from net such as : getActivity(), getContext(), this , getActivity().getApplicationContext()
Nothing works and my app crashes because of exception :| My Class is extended from fragment
public void onClick(View v) {
HashMap postData = new HashMap();
PostResponseAsyncTask task = new PostResponseAsyncTask(this,postData);
task.execute("http://ashna.netau.net/A_location.php");
}
Your function onClick() is probably inside an object implementing the interface View.OnClickListener which contains no functions like getActivity() or getContext().
Try the following:
final Context context = getActivity();
View.OnCLickListner mListener = new View.OnClickListener() {
public void onClick(View v) {
HashMap postData = new HashMap();
PostResponseAsyncTask task = new PostResponseAsyncTask(context,postData);
task.execute("http://ashna.netau.net/A_location.php");
}
}
you can create application class and create a global instance of the application variable inside in it. So you can have the access to the application context from anywhere in the app.
Try this:
PostResponseAsyncTask task = new PostResponseAsyncTask(v.getContext(),postData);

Access parent activity's method from dialog

My project have a activity named MainActivity and a BrowserActivity extend dialog service.
MainActivity will intent BrowserActivity on application started.
I would like to BrowserActivity can access MainActivity's public method.
something like that:
Method on MainActivity:
public void chooseShare(Intent intent)
{
try
{
startActivityForResult( intent , PICK_SHARE);
} catch (android.content.ActivityNotFoundException ex)
{
Log.e("Share" , ex.getMessage());
}
}
And i want to do on BrowserActivity :
(Pseudocode)
((MainActivity)BrowserActivity.this.getOwnerActivity()).chooseShare(intent);
I try to do that:
MainActivity ma = new MainActivity();
ma.chooseShare(i);
However, it not work, it throw NULLPointerException.
Because i need startActivityForResult() instead of startActivity() for callback result.
And i digg on SOF, i found startActivityForResult() should be start on Activity, but not Dialog.
thanks you.
You should be able to use getParent() if it's within the same project.
Activity parent = getParent();
if (parent instanceof MainActivity)
((MainActivity)parent).chooseShare(i);
Another option would be to bind it with an ibinder and use a service or implement interfaces.
Services | Android Developers
you can access all classes method like this:
Context context;
public ProceedDialog(#NonNull Context context) {
super(context);
this.context = context;
//do something
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//do something
}
#Override
public void onClick(View view) {
ParentActivity activity = (ParentActivity)context;
activity.method();
}
I had the same question. And I found a partial solution.
The key is that Activity is a subclass of Context.
You pass the Context paraneter to the constructor of your dialog, right?
And most people pass it by using this of MainActivity.
So, I used the following codes to get MainActivity reference.
private MainActivity getMainActivity()
{
Context c= getContext();
if( c instanceof MainActivity)
{
return (MainActivity)c;
}
return null;
}
Then you can call the desired method by
this.getMainActivity().chooseShare(intent);
In the dialog.
I tested this and it works!
Hope it helped you or forecomers.
(I saw the last modification date just now)

overridePendingTransition on gridview adapter

How can I insert overridePendingTransition on a GridView `Adapter? In this way don't work, without transition startactivity work perfectly
bt.setOnLongClickListener(new OnLongClickListener(){
#Override
public boolean onLongClick(View v) {
final String selectedPad = Drum.pads[position];
Intent modPad = new Intent(v.getContext(), ModifyPad.class);
modPad.putExtra("pad", selectedPad);
context.startActivity(modPad);
overridePendingTransition(R.anim.exit_slid_in, R.anim.exit_slid_out);
return false;
}
});
I've read this post:
android start Activity in adapter (transition animiation direction problem), and comments related, but I don't know how pass the Activity in the Adapter. Any help?
Context is the Base Object of Activity ( see: What is the difference between Activity and Context? ),
so I used following:
Activity activity = (Activity) mContext;
activity.startActivity(repinIntent);
activity.overridePendingTransition(R.anim.act_start_in_from_right, R.anim.act_start_out_to_left);
Refers to: Getting activity from context in android

AndroidRuntimeException "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag"

I create multiple layouts inside a listview, but when i click i get a AndroidRuntimeException "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?"
Im adding
Intent.FLAG_ACTIVITY_NEW_TASK
to my intent but i get the same message! =(
#Override
public View getView(int position, View convertView, ViewGroup parent) {
retval=LayoutInflater.from(getApplicationContext()).inflate(R.layout.layout_anuncio, null);
ImageView image=(ImageView) retval.findViewById(R.id.imageAD);
LoadAds loadAds= new CargaAnuncios();
clickUrl = LoadAds.cargaImagenAnuncio(image, mContext, GlobalInfo.ANUNCIO_CARRIL_PORTADA);
image.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view) {
Bundle bundle=new Bundle();
bundle.putString("url", clickUrl);
Intent intent =new Intent(mContext,CustomWebView.class);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
return retval;
}
Replace getApplicationContext() with this. Most likely, you should do that everywhere in your code that you have getApplicationContext() -- only use getApplicationContext() when you specifically need the Application object.
Old thread, but thought this easy solution might help someone. Instead of passing context around, just get it from view. For eg: view.getContext()
#Override
public void onClick(View view) {
Bundle bundle=new Bundle();
bundle.putString("url", clickUrl);
Intent intent =new Intent(view.getContext(),CustomWebView.class);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
I have added:
parent.getApplicationContext()
Instead of just:
getApplicationContext()
Whole line is:
retval=LayoutInflater.from(parent.getApplicationContext()).inflate(R.layout.layout_anuncio, null);
The solution of Warren Lankie Van Tonder is almost the good:
-You should avoid using Activity context and choose Application Context instead, in purpose to prevent memory leaks. This blog of an Android developer explains that http://android-developers.blogspot.be/2009/01/avoiding-memory-leaks.html
-But in the case of Activity context is the only solution for your code (maybe for calling another activity from outside an activity) and regarding the link above, you have to release the static reference in each onPause() with
AppGlobals.setAppContext(null);
In return, set the static field in onResume and not onCreate.
Thanks to CommonsWare i came up with this solution which worked best.
I added a new class
package com.test.test;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class AppGlobals extends Activity {
private static Context appContext;
public static Context getAppContext(){
return appContext;
}
public static void setAppContext(Context context){
appContext = context;
}
public static void StartActivity(Intent intent){
appContext.startActivity(intent);
}
}
All i needed to do then was add the below code on each activity onCreate that i navigated to.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
AppGlobals.setAppContext(this);
}
This allowed the new intent to start as if it working with normal application flow and this way i didn't have to set a Flag for the intent.
Calling the start activity method is as easy as:
Intent totalTimerIntent = new Intent(AppGlobals.getAppContext(), TotalTimer.class);
AppGlobals.StartActivity(totalTimerIntent);
Hope this helps someone as it has helped me.
Thank you CommonsWare.
CustomAdapter mAdapter = new CustomAdapter( getApplicationContext(), yourlist);
or
Context mContext = getAppliactionContext();
CustomAdapter mAdapter = new CustomAdapter( mContext, yourlist);
change to below
CustomAdapter mAdapter = new CustomAdapter( this, yourlist);

Categories

Resources