Android Smack with MVP fragment - android

I am using smack (XMPP library) and Mosby's MvpFagment to show the roster of a user in a listview (his/her connections).
I got the following code which works in a different fragment just doing a network call using the Retrofit library:
public void loadListData(final List<NeighbourListItemModel> itemsList) {
Log.e("list", itemsList.size() + "");
listViewAdapter.clear();
listViewAdapter.addThemAll(itemsList);
listViewAdapter.notifyDataSetChanged();
}
Which gets called by a RosterListener using roster.addRosterListener(new RosterListener() { .. });
The NeighbourListItemModel is just a simple POJO class having some getters and setts.
However, this gives a AbstractXMPPConnection﹕ Exception in async packet listener
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. error, probably because the XMP connection runs in its own thread.
Now if I change the code to the following:
#Override
public void loadListData(final List<NeighbourListItemModel> itemsList) {
Log.e("list", itemsList.size() + "");
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
listViewAdapter.clear();
listViewAdapter.addThemAll(itemsList);
listViewAdapter.notifyDataSetChanged();
}
});
}
I get a java.lang.NullPointerException: Attempt to invoke virtual method 'int getLayout()' on a null object reference error. Where getLayout is defined in:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolderAbstractClass viewHolder;
if (null == convertView || null == convertView.getTag()) {
viewHolder = getItem(position).getDefaultViewHolder(mItemType);
convertView = LayoutInflater.from(getContext()).inflate(viewHolder.getLayout(), null);
viewHolder.findViewsById(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolderAbstractClass) convertView.getTag();
}
BaseModel previousItem = position > 0 ? getItem(position - 1) : null;
viewHolder.setup(getItem(position), previousItem, mCaller.getContext(), position);
return convertView;
}
The view holder abstract class is nothing special:
public abstract class ViewHolderAbstractClass {
abstract public int getLayout();
abstract public void findViewsById(View view);
abstract public void initializeComponentBehavior(BaseModel item, Context context, int position);
public void setup(BaseModel item, BaseModel previous_item, Context context, int position) {
initializeComponentBehavior(item, context, position);
}
}
So obviously the viewHolder variable is null, but I have no idea why. My adapter is defined as new BaseListAdapter(this, R.layout.neighbours_list_item, new ArrayList<ChatItemModel>(), Constants.DATA_TYPE.CHAT);
Like I said, the former code is working when I make a call using Retrofit, but I suspect XMPP running in its own thread is giving me headaches.

The issue was that the Constants.DATA_TYPE itemType was referring to another model instead of a NeighbourListItemModel which caused a null return. The stacktrace did not show this clearly, but at least it is solved now.

1) I think this code viewHolder = getItem(position).getDefaultViewHolder(mItemType) is a suspect. It seems the code is caching the ViewHolder class and the problem is you cannot cache or save the UI IDs. I am sure you notice other developers simply create an instance of ViewHolder.
Besides that, please post code for getDefaultViewHolder.
2) Another, I suspect findViewsById() method could not work because it seems it is trying to cache UI IDs to find the correct View object.
** This issue of caching UI IDs is more noticeable now (!) because you are getting IDs on a separate thread.
You may however update the UI views on a separate UI thread. And you can get the UI IDs on a separate thread, but don't cache it, use it right away.

Related

Android Glide - Cannot recycle a resource that has already been recycled

I have a list of objects the user can create and delete on runtime and each object is assigned an icon. When I do multiple add/delete operations on these objects at some point I get the following exception.
java.lang.IllegalStateException: Cannot recycle a resource that has already been recycled
at com.bumptech.glide.load.engine.EngineResource.recycle(EngineResource.java:71)
at com.bumptech.glide.load.engine.ResourceRecycler$ResourceRecyclerCallback.handleMessage(ResourceRecycler.java:37)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:135)
I perform no recycle operations on my own and I do not make any calls to Glide bitmap pool. My custom Glide model is the following class
public class MyGlideInput {
private String packageName, apkFilePath, iconResName;
public MyGlideInput() {
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj == null){
return false;
}
if(!(obj instanceof MyGlideInput)){
return false;
}
MyGlideInput input = (MyGlideInput) obj;
return stringsEqual(packageName, input.packageName ) && stringsEqual(apkFilePath, input.apkFilePath)
&& stringsEqual(iconResName, input.iconResName);
}
#Override
public int hashCode() {
return (apkFilePath+packageName+iconResName+"").hashCode();
}
public boolean stringsEqual(String a, String b){
return a != null && a.equals(b);
}
}
on RecyclerView adapter class in bindViewHolder I do:
GlideInput glideInput = new GlideInput().setDrawableResName(iconResName);
GlideApp.with(img).load(glideInput).dontAnimate().error(R.drawable.warning).into(img);
Any suggestions?
From what I have seen until now, this error appears mostly because of transformations if you have any. I had some recycling in transform method even though it's stated that developers should not do it by themselves in their custom transformations. Also, you should be really careful in RecycleView.
Can you provide us with more of code, e.g. some transformations that you use or whatever you think it can have some bad impact?

What happens with view references in unfinished AsyncTask after orientation change?

Note this question was inspired by a comment to https://stackoverflow.com/a/33370816/519334 .
I have a holder class that belongs to a GridView-item with a reference to an ImageView
public static class Holder {
ImageView mRowImage;
String mImageUrl;
// neccessary to cancel unfinished download
BitmapLoaderAsyncTask mDownloader;
}
The corresponding GridItemAdapter uses an AsyncTask to get the image for the GridItem
public class GridItemAdapter extends BaseAdapter {
#Override
public View getView(int position, ...) {
...
if (view == null) {
holder = ...
...
} else {
holder = (Holder) view.getTag();
}
...
// cancel unfinished mDownloader
if (holder.mDownloader != null) {
holder.mDownloader.cancel(false);
holder.mDownloader = null;
}
holder.mImageUrl = mImageUrls.get(position);
holder.mDownloader = new BitmapLoaderAsyncTask()
holder.mDownloader.execute(holder);
}
}
static class BitmapLoaderAsyncTask extends AsyncTask<Holder, Void, Bitmap> {
Holder mHolder;
protected Bitmap doInBackground(Holder... holders) {
mHolder = holders[0];
...
}
protected void onPostExecute(...) {
mHolder.mDownloader = null;
if (!isCancelled()) {
this.mHolder.mRowImage.setImageBitmap(image);
}
this.mHolder = null;
}
}
A comment suggested that there might be a problem with this code after orientation change.
Szenario
Grid is in Landscape - mode
GridItemAdapter starts BitmapLoaderAsyncTask#1 for loading Image1.jpg
async task has mHolder.mRowImage
Grid orientation changes from Landscape to Portrait mode
BitmapLoaderAsyncTask#1 finishes and calls onPostExecute(..)
(!!!) In onPostExecute(..) the image mHolder.mRowImage is updated. Since mHolder.mRowImage does not exist any more because orientation change so there should be a crash.
I have code similar to the described scenario
and up to now I havn-t had the (!!!) crash yet.
My Question
Is this just coincedence that there was no (!!!) crash yet?
Is there a simple solution to check in onPostExecute(..) that mHolder.mRowImage is not valid any more?
Or is there something in Android that protects the AsyncTask?
Since mHolder.mRowImage does not exist any more because orientation
change so there should be a crash.
That is not correct, all Threads are GC roots, and your AsyncTask is holding strong reference to View object (it's inside your Holder class) and your View has strong reference to Activity/Fragment/etc. So your Activity/Fragment/etc won't be properly garbage collected as long as your AsyncTask is running. It won't cause any crash (because View do exists) but memory leak will occur and result will be delivered to old Activity/Fragment/etc.
However if you make sure that AsyncTask is cancelled properly everything will be ok. But if you want to be 100% sure you should use WeakReference to hold you Holder class in BitmapLoaderAsyncTask
#edit
The way you do task cancelling now is incorrect. After orientation change all the view will be inflated once again (in new Activity/Fragment/etc), thus view.getTag will always result null.

Update TextView for different fragments from AsyncTask

I was looking over this answer and it seemed to only deal with a single textview.
Basically, I have an Android application with n fragments, each of which has a textview that is populated from a remote call to a database. Each time the fragment is selected, that remote call will fire and the textview should be repopulated.
Currently, I am using a central AsyncTask to accomplish this, however I am starting to wonder if it is the correct way to go about doing so (some textviews take too long to update for small amounts of data, some don't get updated at all, etc.).
Here is the code from my RetrieveData class. Essentially, it figures out which textview is to be updated, and then populates that textview.
public class RetrieveData extends AsyncTask<String, String, String[]> {
private int txtViewID = -1;
private Activity mainActivity;
public RetrieveData(Activity a) { mainActivity = a; }
protected String[] doInBackground(String... urls) {
String[] data;
// call web script to return JSON data
...
// figure out which fragment called which script
if (urls[0] == "get_A.php") {
data = parseJSONdata(); // parse out the JSON
txtViewID = R.id.txtViewA; // find INT-based ID
} else if (urls[0] == "get_B.php") {
data = parseOtherJSONdata(); // different type of call
txtViewID = R.id.txtViewB;
} else ... {
...
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
return data;
}
#Override
protected void onPostExecute(String[] op) {
if (txtViewID != -1) { // call was made
TextView tv = (TextView)mainActivity.findViewById(txtViewID);
tv.setText(op[0]);
}
and here is how I call this from a Fragment:
public class MainFragment extends Fragment {
Activity mainActivity;
public MainFragment(Activity a) { mainActivity = a; }
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.main_tab,container,false);
new RetrieveData(mainActivity).execute("get_A.php","1");
return v;
}
}
To me, its very kludgy and probably belies my newness to Android, so any suggestions for improvement are heartily appreciated.
You can do a couple of things to improve the robustness and performance and fix some issues which will creep in later:
Don't use findViewById() outside of init/setup type methods. It is an expensive call as it has to "search" your hierarchy for the ID you are requesting.
Don't use an overloaded constructor for your Fragment which takes the Activity. The Fragment default constructor should be empty. This allows the system to properly re-create your Fragment when configuration changes (screen rotates.) The Fragment will receive its attached Activity at the correct time when its onAttach() method is called, so there is no need to do this.
You shouldn't need the Activity at all for what you're trying to do. Instead, have your Fragment get the correct TextView from your layout in its onCreateView(). What you do from there is really up to you:
Pass the TextView instance to your RetrieveData class constructor as the one to be updated. This eliminates the hard coded IDs in your RetrieveData class, which gets rid of some explicit coupling and is a better approach. This is still very tightly coupled, though, since it depends on having a specific View so still not a great option IMHO.
Have the RetrieveData class define an inner Callback interface and have the Fragment implement it. The constructor for RetrieveData can then take an instance of the Callback interface (e.g. your Fragment instance) and when its onPostExecute() runs it just calls back the Fragment with the appropriate data. Now it is up to your Fragment implementation to make the right decision on what UI element it is hosting to update with the data. It may be a TextView now, but in the future you could make it something else, etc. Now you have decoupled the class from all explicit UI ties and put the responsibility on the thing hosting the UI elements: the Fragment.
Here's a brief example of the 2nd bullet:
public RetrieveData extends AsyncTask<String, String, String[]> {
// Define the interface used to provide results
public interface Callback {
public void onDataLoaded(String[] result);
}
private Callback mCb;
public RetrieveData(Callback cb) {
mCb = cb;
}
...
#Override
public void onPostExecute(String[] result) {
mCb.onDataLoaded(result);
}
}
public MyFragment extends Fragment implements RetrieveData.Callback {
TextView mResult;
RetrieveData mAsyncRetriever;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.main_tab,container,false);
// Get the TextView now where we want to show results.
// This avoids calling findViewById() constantly.
mResult = (TextView)root.findViewById(R.id.example_result);
...
}
#Override
public void onResume() {
// Keep a reference to the AsyncTask so we can properly
// cancel it when our lifecycle events dictate so.
mAsyncRetriever = new RetrieveData(this);
mAsyncRetriever.execute("get_A.php");
}
#Override
public void onPause() {
// If we have a pending data load going on, kill it.
if (mAsyncRetriever != null) {
mAsyncRetriever.cancel(true);
mAsyncRetriever = null;
}
}
#Override
public void onDataLoaded(String[] result) {
// Only pulling the first result provided
mResult.setText(result[0]);
// The RetrieveData is done, get rid of our ref
mAsyncRetriever = null;
}
}

Use of Target in Picasso on Adapter

Im having big troubles using a Target inside an adapter. Im confused about the documentation on the code
Objects implementing this class must have a working implementation of
{#link #equals(Object)} and {#link #hashCode()} for proper storage internally. Instances of this
interface will also be compared to determine if view recycling is occurring. It is recommended
that you add this interface directly on to a custom view type when using in an adapter to ensure
correct recycling behavior.
Im trying to use the Target in this way:
class CustomTarget implements Target {
private ImageView imageView;
public CustomTarget(ImageView imageView) {
this.imageView = imageView;
}
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
imageView.setImageDrawable(new RoundedAvatarDrawable(bitmap));
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
imageView.setImageDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
imageView.setImageDrawable(placeHolderDrawable);
}
#Override
public boolean equals(Object o) {
return imageView.equals(o);
}
#Override
public int hashCode() {
return imageView.hashCode();
}
}
#Override
public View getView(int position, View v, ViewGroup parent) {
....
RoundedAvatarDrawable r = new RoundedAvatarDrawable(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_avatar_seahorse));
ImageCacheController.with(mContext).getPicasso().load(member.getPicture_url()).resize(100, 100).centerCrop().placeholder(r).error(r).into(new CustomTarget(viewHolder.ivAvatar));
....
}
It's doesn't work and the images change between each others randomly
You don't show your whole getView function, so without knowing how you use the viewHandler, here's my take on what's going on:
Your problem is that you're creating a new CustomTarget every time getView gets called. You are going against the point of having a Target object. Let me elaborate.
When a new download request is made, previous requests to the same target get stopped or don't result in a call to the Target's callbacks. (so if the Target gets reused for a different row in a list it doesn't get both rows' images).
You are using a new object for each request, effectively hinting Picasso that each request is for a different row so to speak. The doc says "Instances of this interface will also be compared to determine if view recycling is occurring", so since each request has a newly created CustomTarget object, no two requests will have the same object and a row recycle won't be detected.
You're also using viewHolder. In this case I think the viewHolder should be extending the Target interface (if you only have 1 image per row). This way everytime you request a download you can use the same object and not create a new one.
You're also delegating the implementation of your CustomTarget to the ImageView's implementation. Make sure that ImageView's equals and hashCode functions fullfill the requirements Picasso asks for.
Some info on how to implement equals and hashCode: What issues should be considered when overriding equals and hashCode in Java?
It seems your equals method is broken. You are comparing an imageview to a custom target. This might fix it:
public boolean equals(Object o) {
if(o instanceof CustomTarget) {
return ((CustomTarget) o).imageView.equals(this.imageView);
}
return super.equals(o);
}

Monodroid - passing data to ListView according to GREF limit

C#, Mono for Android. I need to output a large portion of combined data into ListView. To achieve this, I use the following obvious approach with Adapter:
class ItemInfo
{
public string Id;
public string Name;
public string Description;
public int Distance;
//Some more data
}
class ItemAdapter : ArrayAdapter<ItemInfo>
{
public WorldItemAdapter (Context context, int textViewResourceId, List<WorldItemInfo> items) :
base(context, textViewResourceId, items)
{
}
//...
public override View GetView (int position, View convertView, ViewGroup parent)
{
//Some stuff to format ListViewItem
}
}
public class OutputActivity : Activity
{
ListView _listView;
//...
void FillList (object SomeParameters)
{
var adaptedList = someDataSource.Where().Join().Union().//anything can be imagined
.Select ((item, item2, item3) =>
new ItemInfo (){
Id = item.Id,
Name = item.Name,
Description = String.Format(..., item2, item3),
Distance = ...,
//so on
}
).OrderBy ((arg) => arg.Name).ToList ();
_listView.Adapter = new ItemAdapter (this, Resource.Layout.ListItemFormat, adaptedList ());
}
}
This works very fine... until I start to refresh my ListView frequently. If I generate many ItemInfo's (by refreshing my view, for example), I reach GREF limit soon (described here, "Unexpected NullReferenceExceptions" section), and my application crashes. Looking into Android log, I can see thousands of Android.Runtime.IJavaObject objects, which overflow GREF limit.
According to concepts of Mono VM + Dalvik VM bridge I can understand, that my ItemInfo's need to be passed to Dalvik VM, wrapped to IJavaObject and to be formatted in ListView by native environment - this creates GREF's. As long as garbage collecting is a non-determined process, if I call FillList() many times, old, already used ItemInfo's stay into memory, leaking.
How can I avoid leaking? Or, possible, is there another way to output large portions of formatted data into ListView? I tried:
I can't reduce the number of ItemInfo's, as long as I need to place my data somehow.
I can't follow this advice, as long as my ItemInfo is not an IJavaObject.
As a temporarily solution, I call GC.Collect() every time I need to refresh list, but this looks not a clean way. Also, if I need to output more than 2к objects into list, this doesn't help.
In my project i had the same problem.
ItemInfo is managed object, so you don't need to do anything with it, GC collects when it will be necessary.
ListView does not load view for each list item at once, so you can control number of created views and dispose them.
Here is my solution
I have removed some unnecessary overrides from BaseAdapter so don't be afraid if it ask you to implement them.
class CustomViewAdapter : BaseAdapter<ItemInfo>
{
private readonly Context _context;
private IList<ItemInfo> _items;
private readonly IList<View> _views = new List<View>();
public CustomViewAdapter(IntPtr handle)
: base(handle)
{
}
public CustomViewAdapter (Context context, IList<ItemInfo> objects)
{
_context = context;
_items = objects;
}
private void ClearViews()
{
foreach (var view in _views)
{
view.Dispose();
}
_views.Clear();
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var inflater = LayoutInflater.From(_context);
var row = convertView ?? inflater.Inflate(Resource.Layout.ListItemView, parent, false);
/// set view data
if(!_views.Contains(row))
_views.Add(row);
return row;
}
public override int Count
{
get { return _items == null ? 0 : _items.Count; }
}
public override void Dispose()
{
ClearViews();
base.Dispose();
}
}
Usage example
[Activity]
public class MessagesActivity : Activity
{
private CustomViewAdapter _adapter;
protected override void OnCreate(Android.OS.Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.ListView);
_adapter=new CustomViewAdapter(this,Enumerable.Empty<ItemInfo>);
FindViewById<ListView>(Resource.Id.list).Adapter=_adapter;
}
public override void Finish()
{
base.Finish();
if(_adapter!=null)
_adapter.Dispose();
_adapter=null;
}
}
Answer found. I've inherited my ItemInfo class from Java.Lang.Object explicitly and now can call Dispose() when object is no longer needed. This kills leaking GREFs.

Categories

Resources