Harshal Kalavadiya
Harshal Kalavadiya

Reputation: 95

Issue in searchable spinner adapter android

I'm integrating searchable spinner in my app. Below is my code

gradle file

    compile 'com.toptoche.searchablespinner:searchablespinnerlibrary:1.3.1'

Xml file

 <com.toptoche.searchablespinnerlibrary.SearchableSpinner
    android:id="@+id/spinner"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:hintText="Select Country"/>

Man.java

public class MainActivity extends AppCompatActivity {

SearchableSpinner mSearchableSpinner;
ArrayList<GetCountry> mGetCountries;
PriorityAdapter mPriorityAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mSearchableSpinner = (SearchableSpinner) findViewById(R.id.spinner);

    mGetCountries = new ArrayList<>();
    GetCountry mGetCountry = new GetCountry();
    mGetCountry.setId("1");
    mGetCountry.setName("India");
    mGetCountries.add(mGetCountry);

    GetCountry mGetCountry2 = new GetCountry();
    mGetCountry2.setId("2");
    mGetCountry2.setName("USA");
    mGetCountries.add(mGetCountry2);


    GetCountry mGetCountry3 = new GetCountry();
    mGetCountry3.setId("3");
    mGetCountry3.setName("UK");
    mGetCountries.add(mGetCountry3);

    GetCountry mGetCountry4 = new GetCountry();
    mGetCountry4.setId("4");
    mGetCountry4.setName("CHINE");
    mGetCountries.add(mGetCountry4);

    GetCountry mGetCountry5 = new GetCountry();
    mGetCountry5.setId("5");
    mGetCountry5.setName("MALASIYA");
    mGetCountries.add(mGetCountry5);

    mPriorityAdapter=new PriorityAdapter(mGetCountries);
    mSearchableSpinner.setAdapter(mPriorityAdapter);
}


public class PriorityAdapter extends ArrayAdapter<GetCountry> {

    ArrayList<GetCountry> list;

    public PriorityAdapter(ArrayList<GetCountry> list) {
        super(MainActivity.this, R.layout.spin_layout, list);
        this.list = list;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) { // Ordinary


        return initView(position, convertView, parent);
    }

    @Override
    public View getDropDownView(int position, View convertView,
                                ViewGroup parent) { // This view starts when we click the
        // spinner.
        return initView(position, convertView, parent);
    }

    public View initView(int position, View convertView, ViewGroup parent) {

        LayoutInflater inflator = getLayoutInflater();
        convertView = inflator.inflate(R.layout.spin_layout, null);
        TextView mTextView = (TextView) convertView
                .findViewById(android.R.id.text1);
        mTextView.setText(list.get(position).getName());
        return convertView;
    }

}

}

When i run above code i m getting output like below image. Custom arraylist data not display it is print garbage value of java every item Spinner Output

Any idea how can i solve this?

EDIT

    public class PriorityAdapter extends ArrayAdapter<GetCountry> {

    ArrayList<GetCountry> list;

    public PriorityAdapter(ArrayList<GetCountry> list) {
        super(MainActivity.this, R.layout.spin_layout, list);
        this.list = list;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) { // Ordinary


        return initView(position, convertView, parent);
    }

    @Nullable
    @Override
    public GetCountry getItem(int position) {
        return super.getItem(position);
    }



    @Override
    public View getDropDownView(int position, View convertView,
                                ViewGroup parent) { // This view starts when we click the

        View view = convertView;
        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
        view = layoutInflater.inflate(R.layout.spin_layout_1, parent, false);

        TextView tv= (TextView) view.findViewById(R.id.text1);
        tv.setText(list.get(position).getName());
        return view;

    }

    public View initView(int position, View convertView, ViewGroup parent) {


        View view = convertView;
        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
        view = layoutInflater.inflate(R.layout.spin_layout_1, parent, false);

        TextView tv= (TextView) view.findViewById(R.id.text1);
        tv.setText(list.get(position).getName());
        return view;
    }

}

GetCountry.java

public class GetCountry {
String name;
String id;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}
}

Upvotes: 4

Views: 5186

Answers (4)

I_4m_Z3r0
I_4m_Z3r0

Reputation: 1080

There is a solution, but it is bad LoL. If you look to the searchableSpinner Library it calls the ArrayAdapter base class and not the ArrayAdapter class you setted. So..

Or you re-implement the library and then you use a "generic Object which extends ArrayAdapter class" so it will use your getDropDownView.

Or, if you look to ArrayAdapter base class you can find this code which is the one it is used to display to "toString" of your items in the adapter:

private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position,
            @Nullable View convertView, @NonNull ViewGroup parent, int resource) {
        final View view;
        final TextView text;

        if (convertView == null) {
            view = inflater.inflate(resource, parent, false);
        } else {
            view = convertView;
        }

        try {
            if (mFieldId == 0) {
                //  If no custom field is assigned, assume the whole resource is a TextView
                text = (TextView) view;
            } else {
                //  Otherwise, find the TextView field within the layout
                text = view.findViewById(mFieldId);

                if (text == null) {
                    throw new RuntimeException("Failed to find view with ID "
                            + mContext.getResources().getResourceName(mFieldId)
                            + " in item layout");
                }
            }
        } catch (ClassCastException e) {
            Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
            throw new IllegalStateException(
                    "ArrayAdapter requires the resource ID to be a TextView", e);
        }

        final T item = getItem(position);
        if (item instanceof CharSequence) {
            text.setText((CharSequence) item);
        } else {
            text.setText(item.toString());
        }

        return view;
    }

As you can see in last lines:

final T item = getItem(position);
            if (item instanceof CharSequence) {
                text.setText((CharSequence) item);
            } else {
                text.setText(item.toString());
            }

So it will call the onString method if it isn't a CharSequence. Then: or you override the onString method of your object (I don't like this solution because I often use the onString for debugging purposes on the debugger) or you implement 2 arrays in your custom adapter:

1 array of CharSequences with the values to display

1 array with your objects

You ArrayAdapter will be a simple "ArrayAdapter<String>" and not "ArrayAdapter<YourObject>" (this is why it isn't the best solution: you will lose all "ArrayAdapter" generic objects structure benefits.).

Your "getItem" method will return the object in the String List, then you will use the position to retrieve your item from the other array stored in the adapter.

This will require another step and public method inside of you adapter. So when you use "getSelectedItem" it will return you the label instead of the object, so to get the real "selectedObject" you should call "getSelectedItemPosition" and pass it to the method which returns the real object using the position.

You can pass your custom items list in the addAll method overrided (not in the constructor...) of your custom adapter. Inside that you will create your list of CharSequences (or Strings) which will be only used for visualization purpose. I mean something like this:

public void addAll(List<YourObject> myObjects){
   mItems.clear()     // mItems => List<YourObject>
   mLabels.clear()    // mLabels => List<String> (so labels visualized)
   if(myObjects != null && myObjects.size > 0x0{
      mItems.addAll(myObjects);
      for(YourObject obj : myObjects){
          mLabels.add(obj.getMyLabel());
      }
   }
   notifyDataSetChanged()
}

(I wrote all the code on the fly here in stackoverflow so sorry if there are any errors)

This is the faster solution I found without changing the "onString" methods of my objects, the better one is to re-implement the searchable spinner library, if you have time, to use custom ArrayAdapter and not only the base class ArrayAdapter.

Summarizing:

  • Faster Solution: override the "toString" method as @AJS said
  • Fast Solution if you don't wanna override the "toString" -> solution above
  • Best Solution -> re-implent the library to use generic custom Adapter which extends ArrayAdapter, because the SearchableSpinnerLibrary only use the base ArrayAdapter class (I don't know why)

gg, have an happy coding

I did a BaseSearchableSpinnerAdapter class using the logic above, here the code:

public abstract class BaseSearchableSpinnerAdapter<T extends Object> extends ArrayAdapter<String> {

    protected Context mContext;
    protected List<String> mLabels;
    protected List<T> mItems;

    public BaseSearchableSpinnerAdapter(@NonNull Context context, int resource) {
        super(context, resource);
        mContext = context;
        mItems = new ArrayList<>();
        mLabels = new ArrayList<>();
    }

    /** Abstract Methods **/
    public abstract String getLabel(int pos); //used to get the label to view

    /** Override ArrayAdapter Methods **/
    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        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(getLabel(position));
        }
        return convertView;
    }

    @Override
    public int getCount(){
        return mLabels.size();
    }

    @Override
    public String getItem(int position){
        return mLabels.get(position);
    }

    @Override
    public long getItemId(int position){
        return position;
    }

    /** Public Methods **/
    public void clear(){
        mLabels.clear();
        mItems.clear();
    }

    public void addAll(List<T> objs){
        mLabels.clear();
        mItems.clear();
        if(objs != null && objs.size() > 0x0){
            mItems.addAll(objs);
            for(int i=0x0; i<objs.size(); i++){
                mLabels.add(getLabel(i));
            }
        }
        notifyDataSetChanged();
    }

    public T getMyItem(int pos){
        return mItems.get(pos);
    }

}

I used generic object (the ) because it doesn't require the cast when you retrieve your object. You can also use base class like "Object" or may "" (Didn't check last one), but in this case you need to cast it.

Extends by doing:

public class MyClassSearchableSpinnerAdapter extends BaseSearchableSpinnerAdapter<MyObject>;

Instead of do:

YourObject myobj = mSpinner.getSelectedItem();

You must to do:

YourObject myObj = ((MyClassSearchableSpinnerAdapter) mSpinner.getAdapter()).getMyItem(mSpinner.getSelectedItemPosition());

Hope this is helpful, bb have a nice coding. In the searchable spinners views (dropdown and selected) you will see what you returns in the "getLabel()" method.

Upvotes: 1

AJS
AJS

Reputation: 57

You can override toString() function of your model (GetCountry) like so:

GetCountry.java

public class GetCountry {
    String name;
    String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return name;
    }
}

Upvotes: 4

B.shruti
B.shruti

Reputation: 1630

You are getting this list because your getView and any other method declared in the PriorityAdapter is not calling at all. So why to use a custom Adapter,instead just go for the simple array adapter as below -

 String[] names = new String[]{"India","CHINA","UK","US","MALYSIA"};
        ArrayAdapter arrayAdapter = new ArrayAdapter(SearchActivity.this,android.R.layout.simple_spinner_dropdown_item,names);
        mSearchableSpinner.setAdapter(arrayAdapter);

Add these lines in onCreate Method of activity and you will get your proper dropdown.

Upvotes: 0

onur
onur

Reputation: 98

you can try to change getView and getDropDownView return value

like this;

View view = convertView;
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
view = layoutInflater.inflate(R.layout.spinneradapter, parent, false);

TextView tv= (TextView) view.findViewById(R.id.tvEkipCantasiEkipman);
tvAdet.setText(arrayListItem);
return view;

Upvotes: 1

Related Questions