Searchable Spinner not working in android - android

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

Related

How to fetch data from another classes via Adapter in Android

Here I'm trying to make a quiz application without using databases (requirement). Each question has 4 options.
I had made a class for Questions. Now, in the activity in which I want to show my data, I'm unable to get method to fetch the data from the QuestionModelClass.
I had made 2D Array but it gets more complicated to get it. Is there any way to bind 3 of the classes (QuestionModelClass, Adapter class, and Activity class)?
public class QuestionsModelClass {
private String sQuestion;
private String sRightAnswer;
private List<String> sOptions;
QuestionsModelClass(){
sQuestion = null;
sRightAnswer = null;
sOptions = null;
}
public QuestionsModelClass(String sQuestion, String sRightAnswer, List<String> sOptions) {
this.sQuestion = sQuestion;
this.sRightAnswer = sRightAnswer;
this.sOptions = sOptions;
}
public String getsQuestion() {
return sQuestion;
}
public void setsQuestion(String sQuestion) {
this.sQuestion = sQuestion;
}
public String getsRightAnswer() {
return sRightAnswer;
}
public void setsRightAnswer(String sRightAnswer) {
this.sRightAnswer = sRightAnswer;
}
public List<String> getsOptions() {
return sOptions;
}
public void setsOptions(List<String> sOptions) {
this.sOptions = sOptions;
}
}
And my Adapter Class
public class QuizAdapter extends BaseAdapter {
private Context context;
private List<QuestionsModelClass> questionClassList;
private String[][] options;
private LayoutInflater inflater;
private QuizAdapter(Context c, List<QuestionsModelClass> l){
this.context= c;
this.questionClassList = l;
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return questionClassList.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) {
convertView = inflater.inflate(R.layout.questionpattern, parent,false);
QuestionsModelClass questions = questionClassList.get(position);
TextView quesText= convertView.findViewById(R.id.questionTextView);
RadioButton radioButtonA = convertView.findViewById(R.id.optionA);
RadioButton radioButtonB = convertView.findViewById(R.id.optionB);
RadioButton radioButtonC = convertView.findViewById(R.id.optionC);
RadioButton radioButtonD = convertView.findViewById(R.id.optionD);
return convertView;
}
And this is the Activity class in which I am trying to implement all the functions
public class QuizActivity extends Activity {
final Context context= this;
private List<QuestionsModelClass> classObject;
Button okayButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
String[] question= new String[]{"Q1. ABDE", "Q2. ADDASD"};
String[][] op;
String[] right = new String[]{"abc","def"};
classObject = new ArrayList<>();
op= new String[][]{
{"a1", "2", "3", "4"},
{"b1","b2","b3","b4"}};
final Dialog dialog = new Dialog(context);
dialog.setContentView(R.layout.customdialoguebox);
dialog.show();
okayButton = (Button) dialog.findViewById(R.id.okayButton);
okayButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(QuizActivity.this,"Good Luck!", Toast.LENGTH_SHORT).show();
dialog.cancel();
}
});
}

How to use SearchView.OnQueryTextListener() in searchable spinner?

I'm creating a searchable spinner using third party library. I have added library classes(SearchableListDialog, SearchableSpinner) in my app. Everything is working fine but still one problem I'm facing for example, In search-view widget if I search Abc, I'm not getting the result filtered as Abc but when clicking on the list-view items, results is showing item as Abc. It is like the position is change for the items but the list is not showing the searchable result. I'm not getting where is I'm wrong. I modified code many times but didn't get desirable result.
Searchable Spinner xml code:
<com.example.my.currencyconverterapp.activity.SearchableSpinner
android:id="#+id/spinner"
android:layout_below="#+id/rl_currency_converterted_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
This is my Fragment code where I'm setting a adapter to searchable spinner.
countriesCustomAdapterInr = new CountriesCustomAdapterInr(getActivity(), R.layout.custom_spinner_items, arrayList,res);
spinner.setAdapter(countriesCustomAdapterInr);
assert spinner != null;
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(getActivity(), ""+arrayList.get(i).getFull_name()+i, Toast.LENGTH_LONG).show();
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {}
});
This is third party SearchableSpinner class:
public class SearchableSpinner extends android.support.v7.widget.AppCompatSpinner implements View.OnTouchListener,
SearchableListDialog.SearchableItem {
public static final int NO_ITEM_SELECTED = -1;
private Context _context;
private List _items;
private SearchableListDialog _searchableListDialog;
private boolean _isDirty;
private ArrayAdapter _arrayAdapter;
private String _strHintText;
private boolean _isFromInit;
public SearchableSpinner(Context context) {
super(context);
this._context = context;
init();
}
public SearchableSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
this._context = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchableSpinner);
final int N = a.getIndexCount();
for (int i = 0; i < N; ++i) {
int attr = a.getIndex(i);
if (attr == R.styleable.SearchableSpinner_hintText) {
_strHintText = a.getString(attr);
}
}
a.recycle();
init();
}
public SearchableSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this._context = context;
init();
}
private void init() {
_items = new ArrayList();
_searchableListDialog = SearchableListDialog.newInstance
(_items);
_searchableListDialog.setOnSearchableItemClickListener(this);
setOnTouchListener(this);
_arrayAdapter = (ArrayAdapter) getAdapter();
if (!TextUtils.isEmpty(_strHintText)) {
ArrayAdapter arrayAdapter = new ArrayAdapter(_context, android.R.layout
.simple_list_item_1, new String[]{_strHintText});
_isFromInit = true;
setAdapter(arrayAdapter);
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (null != _arrayAdapter) {
// Refresh content #6
// Change Start
// Description: The items were only set initially, not reloading the data in the
// spinner every time it is loaded with items in the adapter.
_items.clear();
for (int i = 0; i < _arrayAdapter.getCount(); i++) {
_items.add(_arrayAdapter.getItem(i));
}
// Change end.
_searchableListDialog.show(scanForActivity(_context).getFragmentManager(), "TAG");
}
}
return true;
}
#Override
public void setAdapter(SpinnerAdapter adapter) {
if (!_isFromInit) {
_arrayAdapter = (ArrayAdapter) adapter;
if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
ArrayAdapter arrayAdapter = new ArrayAdapter(_context, android.R.layout
.simple_list_item_1, new String[]{_strHintText});
super.setAdapter(arrayAdapter);
} else {
super.setAdapter(adapter);
}
} else {
_isFromInit = false;
super.setAdapter(adapter);
}
}
#Override
public void onSearchableItemClicked(Object item, int position) {
setSelection(_items.indexOf(item));
if (!_isDirty) {
_isDirty = true;
setAdapter(_arrayAdapter);
setSelection(_items.indexOf(item));
}
}
public void setTitle(String strTitle) {
_searchableListDialog.setTitle(strTitle);
}
public void setPositiveButton(String strPositiveButtonText) {
_searchableListDialog.setPositiveButton(strPositiveButtonText);
}
public void setPositiveButton(String strPositiveButtonText, DialogInterface.OnClickListener onClickListener) {
_searchableListDialog.setPositiveButton(strPositiveButtonText, onClickListener);
}
public void setOnSearchTextChangedListener(SearchableListDialog.OnSearchTextChanged onSearchTextChanged) {
_searchableListDialog.setOnSearchTextChangedListener(onSearchTextChanged);
}
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;
}
#Override
public int getSelectedItemPosition() {
if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
return NO_ITEM_SELECTED;
} else {
return super.getSelectedItemPosition();
}
}
#Override
public Object getSelectedItem() {
if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
return null;
} else {
return super.getSelectedItem();
}
}
}
This is third party SearchableListDialog class:
public class SearchableListDialog extends DialogFragment implements
SearchView.OnQueryTextListener, SearchView.OnCloseListener {
private static final String ITEMS = "items";
private CountriesCustomAdapterInr listAdapter;
private ListView _listViewItems;
private SearchableItem _searchableItem;
private OnSearchTextChanged _onSearchTextChanged;
private SearchView _searchView;
private String _strTitle;
private String _strPositiveButtonText;
private DialogInterface.OnClickListener _onClickListener;
public SearchableListDialog() {
}
public static SearchableListDialog newInstance(List items) {
SearchableListDialog multiSelectExpandableFragment = new
SearchableListDialog();
Bundle args = new Bundle();
args.putSerializable(ITEMS, (Serializable) items);
multiSelectExpandableFragment.setArguments(args);
return multiSelectExpandableFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams
.SOFT_INPUT_STATE_HIDDEN);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Getting the layout inflater to inflate the view in an alert dialog.
LayoutInflater inflater = LayoutInflater.from(getActivity());
// Crash on orientation change #7
// Change Start
// Description: As the instance was re initializing to null on rotating the device,
// getting the instance from the saved instance
if (null != savedInstanceState) {
_searchableItem = (SearchableItem) savedInstanceState.getSerializable("item");
}
// Change End
View rootView = inflater.inflate(R.layout.searchable_list_dialog, null);
setData(rootView);
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
alertDialog.setView(rootView);
String strPositiveButton = _strPositiveButtonText == null ? "CLOSE" : _strPositiveButtonText;
alertDialog.setPositiveButton(strPositiveButton, _onClickListener);
// String strTitle = _strTitle == null ? "Select Country" : _strTitle;
// alertDialog.setTitle(strTitle);
final AlertDialog dialog = alertDialog.create();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams
.SOFT_INPUT_STATE_HIDDEN);
return dialog;
}
// Crash on orientation change #7
// Change Start
// Description: Saving the instance of searchable item instance.
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("item", _searchableItem);
super.onSaveInstanceState(outState);
}
// Change End
public void setTitle(String strTitle) {
_strTitle = strTitle;
}
public void setPositiveButton(String strPositiveButtonText) {
_strPositiveButtonText = strPositiveButtonText;
}
public void setPositiveButton(String strPositiveButtonText, DialogInterface.OnClickListener onClickListener) {
_strPositiveButtonText = strPositiveButtonText;
_onClickListener = onClickListener;
}
public void setOnSearchableItemClickListener(SearchableItem searchableItem) {
this._searchableItem = searchableItem;
}
public void setOnSearchTextChangedListener(OnSearchTextChanged onSearchTextChanged) {
this._onSearchTextChanged = onSearchTextChanged;
}
private void setData(View rootView) {
SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
_searchView = (SearchView) rootView.findViewById(R.id.search);
_searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName
()));
_searchView.setIconifiedByDefault(false);
_searchView.setOnQueryTextListener(this);
_searchView.setOnCloseListener(this);
_searchView.setQueryHint("Search Country");
_searchView.clearFocus();
InputMethodManager mgr = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.hideSoftInputFromWindow(_searchView.getWindowToken(), 0);
List items = (List) getArguments().getSerializable(ITEMS);
_listViewItems = (ListView) rootView.findViewById(R.id.listItems);
//create the adapter by passing your ArrayList data
// listAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, items);
listAdapter = new CountriesCustomAdapterInr(getActivity(), R.layout.custom_spinner_items, arrayList, getResources());
//
//attach the adapter to the list
_listViewItems.setAdapter(listAdapter);
_listViewItems.setTextFilterEnabled(true);
_listViewItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
_searchableItem.onSearchableItemClicked(listAdapter.getItem(position), position);
getDialog().dismiss();
}
});
}
#Override
public boolean onClose() {
return false;
}
#Override
public boolean onQueryTextSubmit(String s) {
_searchView.clearFocus();
return true;
}
#Override
public boolean onQueryTextChange(String s) {
// listAdapter.filterData(s);
if (TextUtils.isEmpty(s)) {
// _listViewItems.clearTextFilter();
((ArrayAdapter) _listViewItems.getAdapter()).getFilter().filter(null);
} else {
((ArrayAdapter) _listViewItems.getAdapter()).getFilter().filter(s);
}
if (null != _onSearchTextChanged) {
_onSearchTextChanged.onSearchTextChanged(s);
}
return true;
}
public interface SearchableItem<T> extends Serializable {
void onSearchableItemClicked(T item, int position);
}
public interface OnSearchTextChanged {
void onSearchTextChanged(String strText);
}
}
Here OnQueryTextListener() not working fine. Please help me. I tried but didn't any solution. Can anyone please help me. Above, I have mentioned my query. Thanks
Instead of using a Spinner with SearchView, I would suggest you to achieve this with ListView and SearchView, I tried and it works very well.
Put a button on your activity. Now clicking on this button will open a custom dialog.
Your custom_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.SearchView
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="#+id/listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
Then set your button onClick event and do the following.
#Override
public void onClick(View view) {
Dialog dialog = new Dialog(SearchText.this);
LayoutInflater inflater = LayoutInflater.from(SearchText.this);
View view1 = inflater.inflate(R.layout.custom_search_layout, null);
ListView listView = view1.findViewById(R.id.listView);
SearchView searchView = view1.findViewById(R.id.searchView);
final ArrayAdapter<String> stringArrayAdapter = new ArrayAdapter<String>(SearchText.this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.my_currency));
listView.setAdapter(stringArrayAdapter);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String newText) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
stringArrayAdapter.getFilter().filter(newText);
return false;
}
});
dialog.setContentView(view1);
dialog.show();
}
Now do your search, and add OnItemClickListener on your listview, and do whatever you want after selecting your choice.
you can search in your adapter... i didn't see your adapter
you can look my adapter
https://github.com/kenmeidearu/SearchableSpinner/blob/master/searchablespinnerlibrary/src/main/java/com/kenmeidearu/searchablespinnerlibrary/ListAdapterSpinner.java
i give you example

RecyclerView reload same data when refresh

I have a problem, when i swipe to refresh the data, the first swipe is ok but after that every swipe reload and add the same data over and over again, by the end i have a list with same items over and over... I'm using a loader.
I tried to clear before but i don't understand what's wrong with my code, if someone could explain it to me. Thank You.
Here my code :
public abstract class NewsFragment extends Fragment implements LoaderManager.LoaderCallbacks<ArrayList<Articles>> {
protected ItemAdapter mArticleAdapter;
protected RecyclerView mRecyclerView;
protected NewsFragment.OnNewSelectedInterface mListener;
protected RecyclerView.LayoutManager mManager;
protected SwipeRefreshLayout mSwipeRefreshLayout;
protected LoaderManager mLoaderManager;
private boolean mStateSaved;
private static final int NEWS_LOAD_ID = 1;
public static final String KEY_LIST = "key_list";
public interface OnNewSelectedInterface {
void onListNewSelected(int index, ArrayList<Articles> articles);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.list_present_news, container, false);
mListener = (NewsFragment.OnNewSelectedInterface) getActivity();
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeContainer);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
mManager = new LinearLayoutManager(getActivity());
mArticleAdapter = new ItemAdapter(getActivity(), new ArrayList<Articles>(), mListener);
mLoaderManager = getLoaderManager();
mStateSaved = mArticleAdapter.isStateSaved();
mRecyclerView.setAdapter(mArticleAdapter);
mRecyclerView.setLayoutManager(mManager);
getData();
refreshData();
if(!isNetworkAvailable())alertUserAboutError();
setDivider();
return view;
}
private void setDivider() {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView
.getContext(), DividerItemDecoration.VERTICAL);
mRecyclerView.addItemDecoration(dividerItemDecoration);
}
private void getData() {
getLoaderManager().initLoader(NEWS_LOAD_ID, null, this).forceLoad();
}
private void alertUserAboutError() {
AlertDialogFragment alertDialogFragment = new AlertDialogFragment();
alertDialogFragment.show(getActivity().getFragmentManager(), "error_dialog");
}
protected abstract String[] getUrl();
private boolean isNetworkAvailable() {
ConnectivityManager manager = (ConnectivityManager)
getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
boolean isAvailable = false;
if (networkInfo != null && networkInfo.isConnected()) {
isAvailable = true;
}
return isAvailable;
}
private void refreshData() {
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mArticleAdapter.clear();
mSwipeRefreshLayout.setRefreshing(false);
}
});
mSwipeRefreshLayout.setColorSchemeResources(
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
#Override
public Loader<ArrayList<Articles>> onCreateLoader(int id, Bundle args) {
return new NewsLoader(getActivity(), getUrl());
}
#Override
public void onLoadFinished(Loader<ArrayList<Articles>> loader, ArrayList<Articles> data) {
if (data != null && !data.isEmpty()) {
mArticleAdapter.addAll(data);
}
}
#Override
public void onLoaderReset(Loader<ArrayList<Articles>> loader) {
mArticleAdapter.clear();
}
}
My loader class :
public class NewsLoader extends AsyncTaskLoader<ArrayList<Articles>>{
private ArrayList<Articles> mArticlesArrayList;
private String[] mUrl;
public NewsLoader(Context context, String[] url) {
super(context);
mUrl = url;
}
#Override
public ArrayList<Articles> loadInBackground() {
OkHttpClient mClient = new OkHttpClient();
for (String aMUrl : mUrl) {
Request mRequest = new Request.Builder().url(aMUrl).build();
try {
Response response = mClient.newCall(mRequest).execute();
try {
if (response.isSuccessful()) {
String json = response.body().string();
getMultipleUrls(json);
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return mArticlesArrayList;
}
private void getMultipleUrls(String jsonData) throws JSONException {
if (mArticlesArrayList == null) {
mArticlesArrayList = getArticleForecast(jsonData);
} else {
mArticlesArrayList.addAll(getArticleForecast(jsonData));
}
}
private ArrayList<Articles> getArticleForecast(String jsonData) throws JSONException {
JSONObject forecast = new JSONObject(jsonData);
JSONArray articles = forecast.getJSONArray("articles");
ArrayList<Articles> listArticles = new ArrayList<>(articles.length());
for (int i = 0; i < articles.length(); i++) {
JSONObject jsonArticle = articles.getJSONObject(i);
Articles article = new Articles();
String urlImage = jsonArticle.getString("urlToImage");
article.setTitle(jsonArticle.getString("title"));
article.setDescription(jsonArticle.getString("description"));
article.setImageView(urlImage);
article.setArticleUrl(jsonArticle.getString("url"));
listArticles.add(i, article);
}
return listArticles;
}
}
My Adapter class :
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ArticleViewHolder> {
private static final String TAGO = ItemAdapter.class.getSimpleName();
private final NewsFragment.OnNewSelectedInterface mListener;
private ArrayList<Articles> mArticlesList;
private Context mContext;
private int lastPosition = -1;
private boolean mStateSaved = false;
public boolean isStateSaved() {
return mStateSaved;
}
public void setStateSaved(boolean stateSaved) {
mStateSaved = stateSaved;
}
public ItemAdapter(Context context, ArrayList<Articles> articles, NewsFragment.OnNewSelectedInterface listener){
mContext = context;
mArticlesList = articles;
mListener = listener;
}
#Override
public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);
ArticleViewHolder articleViewHolder = new ArticleViewHolder(view);
articleViewHolder.setIsRecyclable(false);
return articleViewHolder;
}
#Override
public void onBindViewHolder(ArticleViewHolder holder, int position) {
holder.bindArticle(mArticlesList.get(holder.getAdapterPosition()));
setAnimation(holder.itemView, holder.getAdapterPosition());
}
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(viewToAnimate.getContext(), android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
#Override
public int getItemCount() {
return mArticlesList.size();
}
public void clear() {
mArticlesList.clear();
notifyDataSetChanged();
}
public void addAll(ArrayList<Articles> articles) {
mArticlesList.addAll(articles);
notifyDataSetChanged();
}
protected class ArticleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private ImageView mImageView;
private TextView mTitleTextView, mDescriptionTextView;
private FloatingActionButton mSaveButton;
private ArticleViewHolder(View itemView) {
super(itemView);
mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);
itemView.setOnClickListener(this);
}
private void bindArticle(final Articles article) {
Glide.with(mContext).load(article.getImageView()).into(mImageView);
mTitleTextView.setText(article.getTitle());
mDescriptionTextView.setText(article.getDescription());
if(mDescriptionTextView.getText().equals("")){
mDescriptionTextView.setVisibility(View.GONE);
}
mSaveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
insertArticle(article);
article.setStateSaved(true);
}
});
Log.v(TAGO, "Item id : " + getItemId()
+ "Item count : " + getItemCount()
+ "Item position : " + getAdapterPosition()
+ String.valueOf(article.isStateSaved()));
}
private void insertArticle(Articles articles) {
String title = articles.getTitle();
String description = articles.getDescription();
String url = articles.getArticleUrl();
ContentValues contentValues = new ContentValues();
contentValues.put(ArticleContract.ArticleEntry.COLUMN_TITLE_ARTICLE, title);
contentValues.put(ArticleContract.ArticleEntry.COLUMN_DESCRIPTION_ARTICLE, description);
contentValues.put(ArticleContract.ArticleEntry.COLUMN_URL_ARTICLE, url);
Uri uri = mContext.getContentResolver().insert(ArticleContract.ArticleEntry.CONTENT_URI, contentValues);
if(uri == null) {
Log.v(TAGO, "Error");
} else Toast.makeText(mContext, "Article Saved", Toast.LENGTH_SHORT).show();
}
#Override
public void onClick(View view) {
mListener.onListNewSelected(getLayoutPosition(), mArticlesList);
}
}
}
You are using ViewHolder#setIsRecyclable incorrectly; this method is meant to be used to prevent a ViewHolder from being recycled only while changes are being made to it. According to the documentation:
Calls to setIsRecyclable() should always be paired (one call to
setIsRecyclabe(false) should always be matched with a later call to
setIsRecyclable(true)).
This means none of your ViewHolder objects will be recycled, effectively making the use of a RecyclerView worthless, and preventing it from reusing the views when you attempt to bind new objects to your RecyclerView.
So, in short, remove that line of code.
I noticed a few other small issues with your adapter code as well, which can cause a multitude headaches in the future; so I took the liberty of highlighting some of the changes I would make.
Just for my own sanity, I will refer to your Articles class as Article.
It is usually not a good idea to pass around your Context all over the place. The View passed to your ViewHolder already has a reference to a Context, so you can use that instead.
As for the insertArticle() code, the Activity should be handling this anyway. So you can pass the Article back to the Activity by passing a listener to your Adapter (and subsequently, each ViewHolder) instead of the Context.
You should also consider using the DiffUtil class instead of just calling notifyDataSetChanged(); it is much more efficient. Just make sure your Article class is implementing equals() and hashCode() or it will not work.
I didn't include the animation code (that can easily be added back in) or the saved state code (mostly because I don't know what you were trying to do).
public class ArticleAdapter extends RecyclerView.Adapter<Article> {
private List<Article> mData;
private ArticleViewHolder.OnSelectedListener mOnSelectedListener;
private ArticleViewHolder.OnSaveListener mOnSaveListener;
public ArticleAdapter(ArticleViewHolder.OnSelectedListener onSelectedListener, ArticleViewHolder.OnSaveListener onSaveListener) {
mOnSelectedListener = onSelectedListener;
mOnSaveListener = onSaveListener;
mData = new ArrayList<>();
}
public void replaceData(final List<Article> data) {
final List<Article> oldData = new ArrayList<>(mData);
mData.clear();
if (data != null) {
mData.addAll(data);
}
DiffUtil.calculateDiff(new DiffUtil.Callback() {
#Override
public int getOldListSize() {
return oldData.size();
}
#Override
public int getNewListSize() {
return mData.size();
}
#Override
public int areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).equals(mData.get(newItemPosition));
}
}).dispatchUpdatesTo(this);
}
#Override
public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view, parent, false);
return new SelectLocationViewHolder(view, mOnSelectedListener, mOnSaveListener);
}
#Override
public void onBindViewHolder(ArticleViewHolder holder, int position) {
holder.bind(mData.get(position));
}
#Override
public int getItemCount() {
return mData.size();
}
}
public class ArticleViewHolder extends RecyclerView.ViewHolder {
public interface OnSelectedListener {
void onSelected(Article article);
}
public interface OnSaveListener {
void onSave(Article article);
}
private View mView;
private Article mArticle;
private OnSelectedListener mOnSelectedListener;
private OnSaveListener mOnSaveListener;
private ImageView mImageView;
private TextView mTitleTextView, mDescriptionTextView;
private FloatingActionButton mSaveButton;
public ArticleViewHolder(View itemView, final OnSelectedListener onSelectedListener, final OnSaveListener onSaveListener) {
super(itemView);
mImageView = (ImageView) itemView.findViewById(R.id.photoImageView);
mTitleTextView = (TextView) itemView.findViewById(R.id.titleWithoutImage);
mDescriptionTextView = (TextView) itemView.findViewById(R.id.descriptionTextView);
mSaveButton = (FloatingActionButton) itemView.findViewById(R.id.floatingActionButton);
mView = itemView;
mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onSelectedListener.onSelected(mArticle);
}
});
mSaveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onSaveListener.onSave(mArticle);
}
});
}
public void bind(Article article) {
mArticle = article;
mTitleTextView.setText(article.getTitle());
mDescriptionTextView.setText(article.getDescription());
if(TextUtils.isEmpty(article.getDescription())) {
mDescriptionTextView.setVisibility(View.GONE);
}
Glide.with(mView.getContext()).load(article.getImage()).into(mImageView);
}
}
Edit
The actual issue is that your loader uses the same ArrayList every time, and keeps adding the new results to it.
public class NewsLoader extends AsyncTaskLoader<List<Article>> {
private final String[] mUrls;
private final OkHttpClient mClient;
public NewsLoader(Context context, OkHttpClient client, String... urls) {
super(context);
mClient = client;
mUrls = urls;
}
#Override
public List<Article> loadInBackground() {
List<Article> articles = new ArrayList<>();
for (String url : mUrls) {
Request request = new Request.Builder().url(url).build();
try {
Response response = mClient.newCall(request).execute();
if (response.isSuccessful()) {
parseData(response.body().string(), articles);
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
}
return articles;
}
private void parseData(List<Article> articles, String data) throws JSONException {
JSONObject forecast = new JSONObject(data);
JSONArray a = forecast.getJSONArray("articles");
for (int i = 0; i < a.length(); i++) {
JSONObject o = a.getJSONObject(i);
Article article = new Article(
o.getString("title"),
o.getString("description"),
o.getString("url"),
o.getString("urlToImage"));
articles.add(article);
}
}
}
Also, you may have noticed, I made a small change to your Article constructor. You should consider making the Article class immutable, as this will prevent you from making mistakes when dealing with multithreading. It should look something like this:
public class Article {
private final String mTitle;
private final String mDescription;
private final String mUrl;
private final String mImageUrl;
public Article(String title, String description, String url, String imageUrl) {
mTitle = title;
mDescription = description;
mUrl = url;
mImageUrl = imageUrl;
}
public String title() {
return mTitle;
}
public String description() {
return mDescription;
}
public String url() {
return mUrl;
}
public String imageUrl() {
return mImageUrl;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Article other = (Article) o;
return mTitle != null && mTitle.equals(other.mTitle) &&
mDescription != null && mDescription.equals(other.mDescription) &&
mUrl != null && mUrl.equals(other.mUrl) &&
mImageUrl != null && mImageUrl.equals(other.mImageUrl);
}
#Override
public int hashCode() {
int result = mTitle != null ? mTitle.hashCode() : 0;
result = 31 * result + (mDescription != null ? mDescription.hashCode() : 0);
result = 31 * result + (mUrl != null ? mUrl.hashCode() : 0);
result = 31 * result + (mImageUrl != null ? mImageUrl.hashCode() : 0);
return result;
}
}
#Override
public void onBindViewHolder(ArticleViewHolder holder, int position) {
holder.bindArticle(mArticlesList.get(position));
setAnimation(holder.itemView, position);
}
public void addAll(ArrayList<Articles> articles) {
mArticlesList.clear();
mArticlesList.addAll(articles);
notifyDataSetChanged();
}
If this doesn't wrok then I think your api is giving you redundant data.
Why you are using articleViewHolder.setIsRecyclable(false);
One another place which might cause the problem is
private void getMultipleUrls(String jsonData) throws JSONException {
if (mArticlesArrayList == null) {
mArticlesArrayList = getArticleForecast(jsonData);
} else {
mArticlesArrayList.addAll(getArticleForecast(jsonData));
}
}
You are calling it from a loop add adding data to your arraylist. There somehow multiple data can be inserted in your ArrayList

Running an AsynTask in a GridFragment is causing a null pointer on rotation

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.

Listview in Fragment is causing Memory Leak

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

Categories

Resources