I have a FragmentActivity with a FragmentMediaOverview containing a list of MediaItemViews (each with a imageview and some text) and a click on one of the items opening a detail-Fragment.
Now when I go back (via back button) and forth (click on listitem) several times from list to detail fragment I eventually run into OOM-Errors. I use SoftReferences for the bitmaps in the listitems as well as in the detail fragment.
According to MAT there is an incresing number of MediaItemViews as well as FragmentMediaOverview instances, but I just cannot figure out why.
I read this Android: AlertDialog causes a memory leak , but couldn't solve it nulling out listeners.
Here is my code:
FragmentMediaOverview.java
(This is not a ListFragment because for a tablet-layout the MediaAdapter needs to connect to a gridview)
public class FragmentMediaOverview extends Fragment {
private static String TAG = FragmentMediaOverview.class.getSimpleName();
private MediaAdapter adapter;
private OnMediaSelectedListener selListener;
private ArrayList<BOObject> mediaItems;
private ViewGroup layoutContainer;
private AdapterView itemContainer; // list or gridview
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
layoutContainer = (ViewGroup) inflater.inflate(R.layout.fragment_media_overview, null);
return layoutContainer;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
selListener = (OnMediaSelectedListener) activity;
}
#Override
public void onDestroy() {
super.onDestroy();
itemContainer.setOnItemClickListener(null);
selListener = null;
adapter = null;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initUi(layoutContainer);
displayMedia();
}
private void initUi(ViewGroup layoutContainer) {
itemContainer = (AdapterView) layoutContainer.findViewById(android.R.id.list);
itemContainer.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BOMedia mediaItem = ((BOMedia) mediaItems.get(position));
//the FragmentActivity is coordinating the FragmentTransactions
selListener.onMediaSelected(mediaItem);
}
});
}
private void displayMedia() {
Log.d(TAG, "Displaying List");
if (mediaItems == null) {
loadMedia();
return;
}
Log.d(TAG, "List: " + mediaItems.size() + ", adapter: " + itemContainer.getAdapter());
if (adapter == null) {
Log.d(TAG, "Create Adapter with " + mediaItems.size());
adapter = new MediaAdapter(getActivity(), mediaItems);
}
if (itemContainer.getAdapter() == null) {
itemContainer.setAdapter(adapter);
} else {
adapter.setItems(mediaItems);
adapter.notifyDataSetChanged();
}
}
private void loadMedia() {
FragmentHelper.showProgressSpinner(layoutContainer, android.R.id.list);
DbHelper.getInstance().getMedia(mediaType, new DbQueryFinishListener() {
#Override
public void onDbCallFinish(ArrayList<BOObject> objects) {
if (!getActivity().isFinishing()) {
mediaItems = objects;
Collections.sort(mediaItems, new Comparator<BOObject>() {
final Collator c = Collator.getInstance(Locale.GERMAN);
#Override
public int compare(BOObject s1, BOObject s2) {
if (s2 != null && ((BOMedia) s2).getTitle() != null && s1 != null
&& ((BOMedia) s1).getTitle() != null) {
return c.compare(((BOMedia) s1).getTitle(),((BOMedia) s2).getTitle());
} else {
return 0;
}
}
});
displayMedia();
FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
}
}
#Override
public void onDbCallException(Exception exception) {
if (!getActivity().isFinishing()) {
FragmentHelper.hideProgressSpinner(layoutContainer, android.R.id.list);
}
}
});
}
}
MediaAdapter.java
public class MediaAdapter extends BaseAdapter {
private static final String TAG = MediaAdapter.class.getSimpleName();
private Context context;
private ArrayList<BOObject> mediaItems;
public MediaAdapter(Context c, ArrayList<BOObject> mediaItems) {
super();
context = c;
this.mediaItems = mediaItems;
}
#Override
public int getCount() {
return mediaItems.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new MediaItemView(context);
}
((MediaItemView)convertView).initialize((BOMedia) mediaItems.get(position));
return convertView;
}
public void setItems(ArrayList<BOObject> mediaItems) {
this.mediaItems = mediaItems;
}
}
MediaItemView.java
public class MediaItemView extends LinearLayout {
private static final String TAG = MediaItemView.class.getSimpleName();
private BOMedia item;
private SoftReference<Bitmap> bm;
private ImageView iv;
private Context ctx;
public MediaItemView(Context context) {
super(context);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.view_media_item, this);
this.ctx = context;
}
/** Init the view with a new BOMedia object
* #param mediaItem
*/
public void initialize(BOMedia mediaItem) {
this.item = mediaItem;
initUI();
}
private void initUI() {
TextView title = (TextView) findViewById(R.id.itemText);
iv = (ImageView) findViewById(R.id.itemImage);
title.setText(Html.fromHtml(item.getTitle()));
iv.setImageBitmap(null);
bm = null;
System.gc();
iv.invalidate();
if (item.getFilepathThumb() != null && !item.getFilepathThumb().equals("")) {
ExpansionPackManager.getInstance().getBitmapResource(item.getFilepathThumb(), false,
new BitmapReadListener() {
#Override
public void onFileRead(BitmapResponseMessage message) {
Log.d(TAG, "Bitmap read: " + message.getFilepath());
Bitmap image = message.getBitmap();
if (image != null && message.getFilepath().equals(item.getFilepathThumb())) {
bm = new SoftReference<Bitmap>(image);
iv.setImageBitmap(bm.get());
Log.d(TAG, "image set");
} else {
Log.d(TAG, "image too late: " + image);
}
}
#Override
public void onFileException(Throwable exception) {
Log.d(TAG, "image exception");
}
});
}
}
}
In MediaItemView the size of your bitmap must be too big. If the bitmap is 600x600 and you want to display a image with a size of 50x50 you can use Bitmap.createScaledBitmap. You should also use bitmap cache while loading your bitmap.
This is because the View for rach child in the ListView is recreated as you scroll through. This is very heavy on resources. To avoid this use a holder class in adapters getView() to hold and reuse the views. This is called an Efficient Adapter. For example see Efficient List Adapter in API demos. http://developer.android.com/tools/samples/index.html
You can also use:
android:hardwareAccelerated = true
Beginning in Android 3.0 (API level 11), the Android 2D rendering pipeline is designed to better support hardware acceleration. Hardware acceleration carries out all drawing operations that are performed on a View's canvas using the GPU.
For more info http://developer.android.com/guide/topics/graphics/hardware-accel.html
Related
The problem is that in my tablayout when im switching between tabs my list duplicating. So i need to remove list on onStop() to recreate it then. Or might be other better solution.
I have tried the following solutions
https://code-examples.net/en/q/1c97047
How to reset recyclerView position item views to original state after refreshing adapter
Remove all items from RecyclerView
My code of adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(Context context) {
this.onlineUsers = new ArrayList<>();
this.context = context;
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
Log.d("testList", "rating " + user.getRating() + " uid " + user.getUid());
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
int initSize = userList.size();
onlineUsers.addAll(userList);
// notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public String getLastItemId() {
return onlineUsers.get(onlineUsers.size() - 1).getUid();
}
public void clearData() {
List<OnlineUser> data = new ArrayList<>();
addUsers(data);
notifyDataSetChanged();
}
My code in fragment
#Override
public void onStop() {
super.onStop();
firstUid = "";
stopDownloadList = false;
List<OnlineUser> list = new ArrayList<>();
mAdapter.addUsers(list);
mAdapter.notifyDataSetChanged();
}
`users are added after callback
#Override
public void addUsers(List<OnlineUser> onlineUsers) {
if (firstUid.equals("")){
firstUid = onlineUsers.get(0).getUid();
}
if (!firstUid.equals("") && onlineUsers.contains(firstUid)){
stopDownloadList = true;
}
if (!stopDownloadList){
mAdapter.addUsers(onlineUsers);
}
setRefreshProgress(false);
isLoading = false;
isMaxData = true;
}
The line mAdapter.addUsers(onlineUsers); from addUsers method gets called twice. Looks like your asynchronous operation gets triggered twice (e. g. from repeating lifecycle methods like onCreate/onCreateView/onViewCreated).
Solution #1: request users a single time
Move your user requesting machinery to onCreate or onAttach. This will save network traffic but could lead to showing outdated data.
Solution #2: replaceUsers
Your clearData calls mAdapter.addUsers(new ArrayList<>()); (btw, take a look at Collections.emptyList()). Looks like you're trying to replace adapter data but appending instead. Replacement method could look like
public void replaceUsers(List<OnlineUser> userList) {
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size);
}
This version still requeses users every time your fragment gets focused but shows fresher data.
I had a simple spinner which i working perfect. Now I wanted to change it that a user can able to search the items in it. By following the below code I have done changes.
Sample code
//Gradle
compile 'com.toptoche.searchablespinner:searchablespinnerlibrary:1.3.1'
//activity_main.xml
<com.toptoche.searchablespinnerlibrary.SearchableSpinner
android:id="#+id/searchable_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
// In main activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SearchableSpinner searchableSpinner = (SearchableSpinner) findViewById(R.id.searchable_spinner);
String[] names = new String[]{"India","CHINA","UK","US","MALYSIA"};
ArrayAdapter arrayAdapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_spinner_dropdown_item,names);
searchableSpinner.setAdapter(arrayAdapter);
searchableSpinner.setTitle("Select Item");
searchableSpinner.setPositiveButton("OK");
}
Output
On click of the dropdown
What i have done?
//added the library in gradle
compile 'com.toptoche.searchablespinner:searchablespinnerlibrary:1.3.1'
//new_form_layout (i have created this)
<com.toptoche.searchablespinnerlibrary.SearchableSpinner
android:id="#+id/smart_msn_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:gravity="right" />
**In My Fragment**
#BindView(R.id.smart_msn_spinner)
SearchableSpinner smartMsnSpinner;
Now I have created a bindListners() function in which I am binding all the values and I am calling it in my onCreateView function
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.new_form_layout, container, false);
ButterKnife.bind(this, view);
bindListners(); // here i am calling it
imsiNo.setVisibility(View.GONE);
setupUI(mScrollView);
}
Bundle arguments = getArguments();
if (arguments != null && arguments.containsKey("install_id")) {
isNewInstall = false;
editInstallId = arguments.getString("install_id");
getActivity().setTitle(getString(R.string.title_fragment_edit_form));
setEditData();
imsiNo.setVisibility(View.GONE);
resetFormButton.setVisibility(View.GONE);
} else {
getActivity().setTitle(getString(R.string.title_fragment_new_form));
}
/*mCoordinatesReceiver = new CoordinatesReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Common.GetCoordinatesAction());
getActivity().registerReceiver(mCoordinatesReceiver, intentFilter);*/
return view;
}
bindListeners(){
.......
//Searchable smartMsnSpinner spinner and adapter
meterSrArrayList = new ArrayList<String>();
meterSrNumAdapter = new ArrayAdapter<String>(getActivity(), R.layout.custom_spinner_layout, meterSrArrayList);
smartMsnSpinner.setAdapter(meterSrNumAdapter);
smartMsnSpinner.setTitle("Select Item");
smartMsnSpinner.setPositiveButton("Ok");
smartMsnSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedMeterNo = meterSrArrayList.get(position);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
On running my app i am just getting simple drop down list as before.
I don't know what is the problem and what I am missing as i have done everything that is in the sample.
I have run the sample code in my device and it's working fine. I don't know why it's not working on my app
Update
After watching the logcatthe error i am seeing is
Parcelable encountered IOException writing serializable object (name = com.toptoche.searchablespinnerlibrary.SearchableSpinner)
Any help would be highly appreciated.
I'm using same library but I didn't have this problem.
Anyway I had others problem both on visualization, Parcelization and Re-Initializing the same view (the scroll down view) with this library.
Personally I extended the "SearchableSpinner" and made this changes:
public class BaseSearchableSpinner extends SearchableSpinner
implements SearchView.OnAttachStateChangeListener {
private static final String TAG = BaseSearchableSpinner.class.getSimpleName();
private static final String TAG_DIALOG = TAG.concat(".dialog");
// Dialogs Tags
private static final String TAG_DIALOG_SEARCHABLE_LIST = TAG_DIALOG.concat(".searchableList");
// SearchableSpinner Fields
private static final String FIELD_SEARCHABLE_LIST_DIALOG = "_searchableListDialog";
private static final String FIELD_SEARCH_VIEW = "_searchView";
private static final String FIELD_SEARCHABLE_ITEM = "_searchableItem";
private static final String FIELD_ARRAY_ADAPTER = "_arrayAdapter";
private static final String FIELD_ITEMS = "_items";
private boolean mIsListDialogAdded;
private boolean mIsListenerAdded;
public BaseSearchableSpinner(Context context) {
super(context);
initListDialog();
}
public BaseSearchableSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
initListDialog();
}
public BaseSearchableSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initListDialog();
}
/** Override SearchableSpinner Methods **/
#Override
public boolean onTouch(View v, MotionEvent event) {
try{
SearchableListDialog sld = (SearchableListDialog) FieldUtils.readField(this, FIELD_SEARCHABLE_LIST_DIALOG, true);
ArrayAdapter adapter = (ArrayAdapter) FieldUtils.readField(this, FIELD_ARRAY_ADAPTER, true);
List items = (List) FieldUtils.readField(this, FIELD_ITEMS, true);
if (sld != null && adapter != null && items != null && event.getAction() == MotionEvent.ACTION_UP && checkIfListDialogNotAdded()) {
if(mIsListenerAdded){
mIsListDialogAdded = true;
}
items.clear();
for (int i = 0x0; i < adapter.getCount(); i++) {
items.add(adapter.getItem(i));
}
sld.show(scanForActivity(getContext()).getFragmentManager(), TAG_DIALOG_SEARCHABLE_LIST);
}
} catch (IllegalAccessException iaE){
EMaxLogger.onException(TAG, iaE);
}
return true;
}
/** Override SearchView.OnAttachStateChangeListener Methods **/
#Override
public void onViewAttachedToWindow(View view) {
mIsListDialogAdded = true;
}
#Override
public void onViewDetachedFromWindow(View view) {
mIsListDialogAdded = false;
}
/** Private Methods **/
private void initListDialog(){
try{
SearchableListDialog oldD = (SearchableListDialog) FieldUtils.readField(this, FIELD_SEARCHABLE_LIST_DIALOG, true);
if(oldD != null) {
BaseSearchableListDialog newD = new BaseSearchableListDialog(this);
newD.setArguments(oldD.getArguments());
newD.setOnSearchableItemClickListener(this);
FieldUtils.writeField(this, FIELD_SEARCHABLE_LIST_DIALOG, newD, true);
}
} catch (IllegalAccessException iaE){
EMaxLogger.onException(TAG, iaE);
}
}
private void initListenerOnCloseSearchView(SearchableListDialog instance) {
try{
SearchView sv = (SearchView) FieldUtils.readField(instance, FIELD_SEARCH_VIEW, true);
if(sv != null){
sv.addOnAttachStateChangeListener(this);
mIsListenerAdded = true;
}
} catch (IllegalAccessException iaE){
EMaxLogger.onException(TAG, iaE);
}
}
private boolean checkIfListDialogNotAdded(){
return !mIsListDialogAdded && scanForActivity(getContext()).getFragmentManager().findFragmentByTag(TAG_DIALOG_SEARCHABLE_LIST) == null;
}
private Activity scanForActivity(Context cont) {
if (cont == null)
return null;
else if (cont instanceof Activity)
return (Activity) cont;
else if (cont instanceof ContextWrapper)
return scanForActivity(((ContextWrapper) cont).getBaseContext());
return null;
}
/** Private Classes **/
#SuppressLint("ValidFragment")
public static class BaseSearchableListDialog extends SearchableListDialog {
private BaseSearchableSpinner mOuter;
private BaseSearchableListDialog(BaseSearchableSpinner bss){
super();
mOuter = bss;
}
/** Override SearchableListDialog Methods **/
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog dialog = (AlertDialog) super.onCreateDialog(savedInstanceState);
mOuter.initListenerOnCloseSearchView(this);
return dialog;
}
}
}
Try using this and see if it works.
Also I changed the adapter to have custom texts and don't always display the "toString" of an object :/ I normally use the toString only for debugging purposes, so to show info about an object I make specific methods.
So this is the class for the Adapter:
public abstract class BaseSearchableSpinnerAdapter<T> extends ArrayAdapter<CharSequence> {
// Empty Item Label
protected static final String LABEL_EMPTY_ITEM = " ";
// Label Length
protected static final int LABEL_LENGTH = 50;
// Spinner Adapter Positions
public static final int POS_ITEM_NOT_FOUND = -0x1;
public static final int POS_EMPTY_ITEM = 0x0; // Not always true, depends if implemented
protected List<T> mItems;
private int mResLayout;
public BaseSearchableSpinnerAdapter(#NonNull Context context, #LayoutRes int resource) {
super(context, resource);
mItems = new ArrayList<>();
mResLayout = resource;
}
/** Abstract Methods **/
public abstract <T extends CharSequence> T getLabelView(int pos);
/** Override ArrayAdapter Methods **/
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(mResLayout, parent, false);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.spinner_default, parent, false);
}
}
TextView tv = convertView.findViewById(R.id.text1);
if(tv != null){
tv.setText(getLabelView(position));
}
return convertView;
}
#Override
public void clear(){
mItems.clear();
super.clear();
}
/** Public Methods **/
public void addAll(List<T> objs){
clear();
ArrayList<CharSequence> labels = new ArrayList<>();
if(objs != null && objs.size() > 0x0){
mItems.addAll(objs);
for(int i = 0x0; i < objs.size(); i++){
labels.add(getLabelView(i));
}
}
super.addAll(labels);
}
public T getMyItem(int pos){
if(mItems != null && mItems.size() > pos && pos != -0x1){
return mItems.get(pos);
}
return null;
}
public List<T> getMyItems(){
return mItems;
}
}
Extend this class and use the object you want.
LABEL_EMTPY_ITEM is a long series of spaces because in some app when the text don't take all the line in the list view it will be clickable only on the text and not on all the line.. So when you have no item the clikable part of the line is a little small piece on the left (in my case, I had this problem).
Example to extend this base Adapter class:
public class MyObjectSearchableSpinnerAdapter extends BaseSearchableSpinnerAdapter<MyObject> {
private #StringRes int mIdFstr;
public MyObjectSearchableSpinnerAdapter(#NonNull Context context, #LayoutRes int resource){
this(context, resource, R.string.fstr_two_fields_dash);
}
public MyObjectSearchableSpinnerAdapter(#NonNull Context context, #LayoutRes int resource, int idFstr){
super(context, resource);
mIdFstr = idFstr;
}
/** Override BaseSearchableSpinnerAdapter Methods **/
#Override
public <T extends CharSequence> T getLabelView(int pos) {
MyObject item = mItems.get(pos);
if(item != null){
return (T) (!TextUtils.isEmpty(item.getName2()) ?
getContext().getString(mIdFstr, item.getName1(), item.getName2()) :
item.getName1());
}
return (T) LABEL_EMPTY_ITEM;
}
/** Public Methods **/
public int getItemPosition(int idMyObject){
return getItemPosition(String.valueOf(idMyObject));
}
public int getItemPosition(String idMyObject){
if(mItems != null && mItems.size() > 0x0){
for(int i = 0x0; i < mItems.size(); i++){
MyObject item = mItems.get(i);
if(item != null && idMyObject.equals(item.getId())){
return i;
}
}
}
return POS_ITEM_NOT_FOUND;
}
}
Example Init BaseSearchableSpinner:
private void initBaseSearchableSpinnerMyObjects(){
MyObjectSearchableSpinnerAdapter adapter = new MyObjectSearchableSpinnerAdapter(getContext(), R.layout.spinner_default);
adapter.setDropDownViewResource(R.layout.spinner_default);
mBaseSearchableSpinnerMyObjects.setAdapter(adapter);
}
Example Add your list of MyObject to the adapter:
((MyObjectSearchableSpinnerAdapter)mBaseSearchableSpinnerMyObjects.getAdapter()).addAll(items);
Example Get back an object from a BaseSearchableSpinner with an extensions of BaseSearchableAdapter with a list of MyObject :
MyObject obj = ((MyObjectSearchableSpinnerAdapter) mBaseSearchableSpinnerMyObjects.getAdapter()).getMyItem(mBaseSearchableSpinnerMyObjects.getSelectedItemPosition());
Have a nice coding and day!
bb
I'm developing android App. My app is showing many of pictures from server by image_url with ImageLoader Library such as Picasso, Glide or AUIL.
There are five fragments in main activity. and each of fragments have many image list view.
My question is that after I click specific image view, new activity is created, and then fragment is created that have many images list view.
I clicked different images, and new activities are created. When this gesture occurred , heap memory is not fetched ...
Among of three libraries, AUIL is the best of them.
I tried recursing method at base adapter and drawable callback to null etc...
But This situation was not solved.
Here is my example Adapter sourse:
public class MagazineRelBrandItemAdapter extends BaseAdapter {
ArrayList<String> receivedList;
ArrayList<View> brandThumList = new ArrayList<>();
PrintLog printLog = new PrintLog(Application.isDEBUG(), "MagazineRelBrandItemAdap ", "created");
Activity mActivity;
public MagazineRelBrandItemAdapter(ArrayList<String> dataList, Activity activity) {
receivedList = dataList;
mActivity = activity;
}
#Override
public int getCount() {
return receivedList.size();
}
#Override
public String getItem(int position) {
return receivedList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MagazineRelBrandView itemView;
if (convertView == null) {
itemView = new MagazineRelBrandView(Application.getContext(),mActivity);
} else {
itemView = (MagazineRelBrandView) convertView;
}
try {
itemView.setContent(receivedList.get(position));
brandThumList.add(itemView);
} catch (OutOfMemoryError e) {
if (mRecycleList.size() <= parent.getChildCount()) {
printLog.cutsomPrintLog(Application.isDEBUG(), "img recycle size comment>>", mRecycleList.size() + "");
throw e;
}
recycleHalf();
System.gc();
return getView(position, itemView, parent);
}
mRecycleList.add(new WeakReference<View>(itemView));
return itemView;
}
private List<WeakReference<View>> mRecycleList = new ArrayList<WeakReference<View>>();
public void recycleHalf() {
int halfSize = mRecycleList.size() / 2;
List<WeakReference<View>> recycleHalfList = mRecycleList.subList(0, halfSize);
RecycleUtils.recursiveRecycle(recycleHalfList);
for (int i = 0; i < halfSize; i++)
mRecycleList.remove(0);
}
public void recycle() {
RecycleUtils.recursiveRecycle(mRecycleList);
}
}
And Here is my widget :
public class MagazineRelBrandView extends LinearLayout {
Activity mActivity;
public MagazineRelBrandView(Context context,Activity activity) {
super(context);
mActivity = activity;
init();
}
CircleImageView brandLogo;
TextView brandName;
ImageView brandBg;
View clickV;
private void init() {
inflate(getContext(), R.layout.magazine_rel_brand_view, this);
brandBg = (ImageView) findViewById(R.id.magazine_rel_brand_bg);
brandName = (TextView) findViewById(R.id.magazine_rel_brand_name);
brandLogo = (CircleImageView) findViewById(R.id.magazine_rel_brand_icon);
clickV = findViewById(R.id.content_click_view);
brandName.setTypeface(Typeface.createFromAsset(getContext().getAssets(), CommonKeys.FuturaFont));
clickV.setOnClickListener(goBrandDetailPageListener);
}
public void setContent(String brandName) {
this.brandName.setText(brandName);
try {
String brandTitleUrl;
brandTitleUrl = URLEncoder.encode(brandName, "UTF-8");
totalThumbUrl = CommonKeys.brandThumbUrl + brandTitleUrl;
totalBackUrl = CommonKeys.brandBackgroundUrl + brandTitleUrl;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
showBG(brandName);
showLogo(brandName);
}
String totalThumbUrl;
String totalBackUrl;
public void showLogo(final String brandName){
Glide.with(mActivity).load(totalThumbUrl).override(DpToPxUtil.dp2px(38),DpToPxUtil.dp2px(38)).into(brandLogo);
}public void showBG(final String brandName){
Glide.with(mActivity).load(totalBackUrl).override(DpToPxUtil.dp2px(120),DpToPxUtil.dp2px(144)).into(brandBg);
}
public String getBrandName() {
return brandName.getText().toString();
}
Handler brandHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
MyToast.show("" + msg.obj);
break;
case 1:
BrandInfoZip brandInfoZip = (BrandInfoZip) ((BrandInfoParentZip) msg.obj).getData();
BrandListZip getBrandZip = new BrandListZip();
getBrandZip.setIs_following(brandInfoZip.is_following());
getBrandZip.setBrand_name(getBrandName());
Intent goBrandPage = new Intent(Application.getContext(), BrandDetailActivity.class);
goBrandPage.putExtra("brandZip", getBrandZip);
goBrandPage.putExtra("title", getBrandName());
goBrandPage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Application.getContext().startActivity(goBrandPage);
break;
}
}
};
OnClickListener goBrandDetailPageListener = new OnClickListener() {
#Override
public void onClick(View v) {
BrandDescriptionZip brandDescriptionZip = new BrandDescriptionZip();
brandDescriptionZip.setBrand_name(getBrandName());
new GetBrandInfoFromServer(brandHandler).execute(brandDescriptionZip);
}
};
}
And fragment onDestroyView sourse :
#Override
public void onStop() {
super.onStop();
if (upperBgImgV.getDrawable() != null) {
upperBgImgV.getDrawable().setCallback(null);
}
if (writerThumb.getDrawable() != null) {
writerThumb.getDrawable().setCallback(null);
}
if (otherContentV.getDrawable() != null) {
otherContentV.getDrawable().setCallback(null);
}
}
#Override
public void onDestroyView() {
Glide.clear(writerThumb);
magazineRelBrandItemAdapter.recycle();
unbindDrawables(getView());
//unbindDrawables(writerThumb);
//unbindDrawables(otherContentV);
System.gc();
super.onDestroyView();
Log.i(TAG, "onDestroyView-");
}
private void unbindDrawables(View view) {
if (view == null)
return;
if (view instanceof ImageView) {
((ImageView) view).setImageDrawable(null);
}
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
view.setBackgroundResource(0);
view.setBackgroundDrawable(null);
}
}
and Here is activity onDestroy() course :
#Override
protected void onDestroy() {
RecycleUtils.recursiveRecycle(getWindow().getDecorView());
System.gc();
Glide.get(this).clearMemory();
FlurryAgent.onEndSession(Application.getContext());
printLog.cutsomPrintLog(Application.isDEBUG(),"Magazine Detail Activity onDestroy-","onDestroy-");
super.onDestroy();
finish();
}
These course is used to clear memory heap. But Memory leak is continue....
Please help me.
I am attempting to enact the Parcelable pattern to save and restore my ListView data on orientation changes.
The problem is, the ListView will only update once I do an orientation change. It will not update otherwise. It used to work just fine, until I started implementing Parcelable.
All help is greatly appreciated!
My fragment class is copied below, but can also be accessed here on GitHub:
public class TrackActivityFragment extends Fragment {
private IconicAdapter trackResultListViewAdapter = null;
private String artistId = null;
private final String LOG_TAG = TrackActivityFragment.class.getSimpleName();
public TrackActivityFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayList<TrackParcelable> trackParcelables = null;
View rootView = inflater.inflate(R.layout.fragment_track, container, false);
Intent intent = getActivity().getIntent();
if(intent != null && intent.hasExtra(Intent.EXTRA_SHORTCUT_NAME)) {
artistId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
}
if(savedInstanceState == null || !savedInstanceState.containsKey("tracks_key")) {
trackParcelables = new ArrayList<TrackParcelable>();
performSearch(artistId);
}
else {
trackParcelables = savedInstanceState.getParcelableArrayList("tracks_key");
}
ListView listView = (ListView) rootView.findViewById(R.id.listViewOfTopTracks);
trackResultListViewAdapter = new IconicAdapter(trackParcelables,
getTrackNamesFromParcelables(trackParcelables));
listView.setAdapter(trackResultListViewAdapter);
return rootView;
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList("tracks_key",
trackResultListViewAdapter.getTrackParcelables());
super.onSaveInstanceState(outState);
}
private ArrayList<String> getTrackNamesFromParcelables(ArrayList<TrackParcelable>
trackParcelables){
ArrayList<String> trackNames = new ArrayList<>();
for(TrackParcelable element : trackParcelables){
trackNames.add(element.name);
}
return trackNames;
}
private void performSearch(String artistId) {
SpotifyApi api = new SpotifyApi();
SpotifyService spotify = api.getService();
Map<String, Object> options = new HashMap<>();
options.put("country", "US");
spotify.getArtistTopTrack(artistId, options, new Callback<Tracks>() {
#Override
public void success(Tracks tracks, Response response) {
final ArrayList<TrackParcelable> trackParcelables =
new ArrayList<TrackParcelable>();
for (Track track : tracks.tracks) {
trackParcelables.add(new TrackParcelable(track.name,track.album.name,
track.album.images.get(0).url,track.preview_url));
}
trackResultListViewAdapter.swapItems(trackParcelables);
Log.d(LOG_TAG,trackParcelables.toString());
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if(trackParcelables.size()==0){
Toast.makeText(getActivity(),
getString(R.string.no_tracks_found_toast),
Toast.LENGTH_SHORT).show();
}
trackResultListViewAdapter.notifyDataSetChanged();
}
});
}
#Override
public void failure(RetrofitError error) {
}
});
}
//This ViewHolder Pattern is from Busy Android Coder's Guide page 274 of book version 6.7
class ViewHolder {
ImageView icon=null;
TextView trackName=null;
TextView trackAlbum=null;
ViewHolder(View row) {
this.icon = (ImageView)row.findViewById(R.id.imageViewAlbum);
this.trackName = (TextView)row.findViewById(R.id.textViewTrackTitle);
this.trackAlbum = (TextView)row.findViewById(R.id.textViewTrackAlbum);
}
}
//This IconicAdapter Pattern is from Busy Android Coder's Guide page 272 of book version 6.7
class IconicAdapter extends ArrayAdapter<String> {
private ArrayList<TrackParcelable> trackParcelables;
public IconicAdapter(ArrayList<TrackParcelable> trackParcelables,
ArrayList<String> trackNames) {
super(getActivity(), R.layout.list_item_top_tracks, R.id.textViewTrackTitle
, trackNames);
this.trackParcelables = trackParcelables;
}
public void swapItems(ArrayList<TrackParcelable> trackParcelables) {
this.trackParcelables = trackParcelables;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
ViewHolder holder = (ViewHolder)row.getTag();
if (holder==null) {
holder=new ViewHolder(row);
row.setTag(holder);
}
Picasso.with(getActivity()).load(trackParcelables.get(position).albumImageUrl)
.into(holder.icon);
TextView trackAlbumTextView = (TextView)row.findViewById(R.id.textViewTrackAlbum);
trackAlbumTextView.setText(trackParcelables.get(position).albumName);
return row;
}
public ArrayList<TrackParcelable> getTrackParcelables(){
return trackParcelables;
}
}
The issue is that IconicAdapter is only tracking changes in trackNames, not in ArrayList <TrackParcelable> trackParcelables.
To fix this, I added a a member variable in my IconicAdapter for the trackNames and set this in the constructor like so:
private ArrayList<TrackParcelable> trackParcelables;
private ArrayList <String> trackNames;
public IconicAdapter(ArrayList<TrackParcelable> trackParcelables,
ArrayList<String> trackNames) {
super(getActivity(), R.layout.list_item_top_tracks, R.id.textViewTrackTitle
, trackNames);
this.trackNames = trackNames;
this.trackParcelables = trackParcelables;
}
Then, I edited my swapItems to update my ListView data like so:
public void swapItems(ArrayList<TrackParcelable> trackParcelables) {
this.trackParcelables = trackParcelables;
trackNames.clear();
trackNames.addAll(getTrackNamesFromParcelables(trackParcelables));
}
I have a FragmentActivity that has 5 Fragments
On my 2nd fragment is a gridview that displays many images.
That GridFragment is starting an AsyncTask with callback to get the arraylist of images.
It then sets an adapter using the following as arguments (listener, context, arraylist) context is getActivity()
when adapter starts it tries to do LayoutInflater.from(Context);
That is where im getting my null pointer. If the async task is complete it will not crash. but it i rotate while async task is working it crashes.
Is there any way around this?
Fragment
public class IconsFrag extends GridFragmentIcons implements AdapterIcons.AdapterListener {
AsyncTaskIconsAll aTask;
Button button;
final String TAG = "IconsFrag";
private ArrayList<Integer> mThumbs;
private final String KEY_LIST_DATA = "icons_cache";
private final String KEY_LIST_POSITION = "icons_position";
private int mPosition = -1;
private AdapterIcons mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.i(TAG, "onActivityCreated");
super.onCreate(savedInstanceState);
if (savedInstanceState == null){
Log.i(TAG, "savedInstanceState null");
aTask = new AsyncTaskIconsAll();
aTask.updateActivity(this, getActivity(), new AsyncTaskIconsAll.Callback() {
#Override
public void onData(ArrayList<Integer> data) {
mThumbs = data;
mAdapter = new AdapterIcons(IconsFrag.this, getActivity(), mThumbs);
getGridView().setNumColumns(getResources().getInteger(R.integer.column_count_icon));
setGridAdapter(mAdapter);
getGridView().setOnItemClickListener(null);
}
});
aTask.execute();
}
AsyncTask
public class AsyncTaskIconsAll extends AsyncTask<Void, Integer, ArrayList<Integer>> {
private Activity mContext;
private Fragment mFragment;
private ArrayList<Integer> mThumbs;
final String TAG = "AsyncTaskIconsAll";
Callback mCallback;
public static interface Callback{
public void onData(ArrayList<Integer> data);
}
public void updateActivity(Fragment f, Activity a, final Callback c) {
Log.i(TAG, "updateActivity");
mContext = a;
mFragment = f;
mCallback = c;
if(mThumbs != null)
Log.i(TAG, "Callback not null");
mCallback.onData(mThumbs);
}
#Override
protected void onPreExecute() {
}
#Override
protected ArrayList<Integer> doInBackground(Void... unused){
Log.i(TAG, "doInBackground");
mThumbs = new ArrayList<Integer>();
final String[] extras = mContext.getResources().getStringArray(R.array.icon_pack);
for (String extra : extras) {
String uri = "drawable/" + extra;
int res = mContext.getResources().getIdentifier(uri, null, mContext.getPackageName());
if (res != 0) {
mThumbs.add(res);
}
}
return mThumbs;
}
protected void onProgressUpdate(Integer... progress) {
}
#Override
protected void onPostExecute(ArrayList<Integer> icons) {
Log.i(TAG, "onPostExecute");
mThumbs = icons;
mCallback.onData(mThumbs);
ProgressBar mProgess = (ProgressBar) mFragment.getView().findViewById(R.id.pending);
mProgess.setVisibility(mFragment.getView().GONE);
}
}
Adapter
public class AdapterIcons extends BaseAdapter implements SpinnerAdapter {
private final String TAG = "AdapterIcons";
private AdapterListener mListener;
private ArrayList<?> mData;
private final LayoutInflater mInflater;
public AdapterIcons(AdapterListener listener, Activity activity) {
this.mData = new ArrayList<Object>();
this.mInflater = LayoutInflater.from(activity);
this.mListener = listener;
}
public AdapterIcons(AdapterListener listener, Context Context, ArrayList<?> data) {
this.mData = (data == null) ? new ArrayList<Object>() : data;
this.mInflater = LayoutInflater.from(Context);
this.mListener = listener;
}
public ArrayList<?> getData () {
return this.mData;
}
public void setData (ArrayList<?> data) {
this.mData = data;
}
public void clearData () {
this.mData.clear();
}
public static abstract interface AdapterListener
{
public abstract View getView(int paramInt, View paramView, ViewGroup paramViewGroup);
}
public Intent.ShortcutIconResource getResource(int position){
Icons icons= new Icons();
ArrayList<Integer> list = (ArrayList<Integer>) mData;
return Intent.ShortcutIconResource.fromContext(icons.getBaseContext(), list.get(position));
}
#Override
public int getCount () {
if (mData == null)
Log.d(TAG, "getCount() Data Set Is Null");
return (mData != null) ? mData.size() : 0;
}
#Override
public Object getItem (int position) {
if (mData == null)
Log.d(TAG, "getItem(int position) Data Set Is Null");
return (mData != null) ? mData.get(position) : null;
}
#Override
public long getItemId (int position) {
if (mData == null)
Log.d(TAG, "getItemId(int position) Data Set Is Null");
return (mData != null) ? position : 0;
}
#Override
public View getView (int position, View convertView, ViewGroup parent) {
return (mListener == null) ? new LinearLayout(mInflater.getContext()) : this.mListener.getView(position, convertView, parent);
}
#Override
public View getDropDownView (int position, View convertView, ViewGroup parent) {
return (mListener == null) ? new LinearLayout(mInflater.getContext()) : this.mListener.getView(position, convertView, parent);
}
}
Take a look on this answer. It's pretty much the same problem. You need to handle orientation changes which changes the activity state in default(if you don't override).
https://stackoverflow.com/a/7618739/1080954
so in your onPostExecute() you try to add items to an activity which is (temporarily) destroyed. Check if your getActivity() == null before doing stuff with the context. Something like:
public void onPostExecute(){
if(getActivity() == null){
// activity is destroyed... skip
return;
}
// proceed like normal
}
This is the best I can do without anymore code. Good luck
Did you notice that you should use brackets {} in here, otherwise mCallback.onData(mThumbs) will always be called:
if(mThumbs != null){
Log.i(TAG, "Callback not null");
mCallback.onData(mThumbs);
}
Also you're passing the fragment and the activity in asyncTask updateActivity() method when you create the asyncTask but they will be null when you rotate your device, the activity is going to be recreated, so when you use them in asyncTask doInBackground() and onPostExecute() you have to check first if they are not null, otherwise you could end up with a NullPointerException.