Java Question Spinner change Adapter

Discussion in 'Libraries developers questions' started by warwound, May 2, 2014.

  1. warwound

    warwound Expert Licensed User

    I'm trying to take a Spinner created in the Designer and change it's Adaptor to an Adaptor i've created in a library.
    Look at a typical exception:
    I loaded the layout file containing the Spinner and then passed it to my library to have my Adapter passed to it's setAdapter() method.
    I added 64 strings as spinner items to my adapter, and clicked on item 6.

    My Spinner seems to have two Adapters set - the B4ASpinnerAdapter and my CustomSpinnerAdapter.
    The Spinner shows my 64 items so my CustomSpinnerAdapter has obviously been set as my Spinner's Adapter.
    But a click on an item causes the exception - the B4ASpinnerAdapter looks for item number 6 but has no items - so the original B4ASpinnerAdapter is still set.

    I can successfully set a custom Adapter as an Adapter for a B4A ListView using a similar technique, the B4A ListView's original Adapter is no longer set, my call to setAdapter() overwrites the previous Adapter with a ListView.
    BUT with a Spinner the call to setAdapter() seems to addAdapter.

    I've googled a bit but found no info for such a simple question - how can i change a Spinner's Adapter?

    Here's my code:

    Code:
    Sub Process_Globals
    End Sub

    Sub Globals
        
    Private ListView1 As ListView
        
    Private Spinner1 As Spinner
    End Sub

    Sub Activity_Create(FirstTime As Boolean)
        
    Activity.LoadLayout("Main")
        
        
    Dim CustomSpinnerAdapter1 As CustomSpinnerAdapter
        CustomSpinnerAdapter1.Initialize(
    "CustomSpinnerAdapter1")    '    raises no events yet
        CustomSpinnerAdapter1.SetSpinner(Spinner1)
        
    For i=0 To 63
            CustomSpinnerAdapter1.AddItem(
    "Item: "&i, Null)
        
    Next
    End Sub

    Sub Activity_Resume
    End Sub

    Sub Activity_Pause (UserClosed As Boolean)
    End Sub
    Code:
    public class CustomSpinnerAdapter extends BaseAdapter implements SpinnerAdapter {
        
        @ShortName(
    "CustomSpinnerAdapterItem")
        
    public static final class CustomSpinnerAdapterItem{
            protected final Object mTag;
            protected final 
    String mText;
            
            
    public CustomSpinnerAdapterItem(String pText, Object pTag){
                mTag=pTag;
                mText=pText;
            
    }
            
            /**
             * Returns the item Tag property.
             */
            public Object getTag(){
                return mTag;
            }
            
            /**
             * Returns the item Tag property.
             */
            public String getText(){
                return mText;
            }
        }
        
        private BA mBA;
        private String mEventName;
        private String mGetViewEventName;
        private LayoutInflater mLayoutInflater;
        private List<CustomSpinnerAdapterItem> mItems;
        
        public CustomSpinnerAdapter(BA pBA, String pEventName){
            mBA=pBA;
            mEventName=pEventName;
            mGetViewEventName=(pEventName+"_GetView").toLowerCase(BA.cul);
            if(!mBA.subExists(mGetViewEventName)){
                BA.LogError("CustomSpinnerAdapter callback not found "+pEventName+"_GetView");
            }
            mLayoutInflater=LayoutInflater.from(mBA.context);
            mItems=new ArrayList<CustomSpinnerAdapterItem>();
            
        }
        
        public synchronized void AddItem(String pText, Object pTag){
            mItems.add(new CustomSpinnerAdapterItem(pText, pTag));
            this.notifyDataSetChanged();
        }
        
        public synchronized void Clear(){
            mItems.clear();
            this.notifyDataSetChanged();
        }
        
        public BA getBA(){
            return mBA;
        }
        
        @Override
        public synchronized int getCount() {
            return mItems.size();
        }
        
        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            if(convertView==null){
                convertView=mLayoutInflater.inflate(R.layout.simple_spinner_dropdown_item, parent, false);
            }
            ((TextView) convertView).setText(mItems.get(position).getText());
            return convertView;
        }

        public String getEventName(){
            return mEventName;
        }
        
        @Override
        public synchronized Object getItem(int pPosition) {
            return mItems.get(pPosition);
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView==null){
                convertView=mLayoutInflater.inflate(R.layout.simple_spinner_item, parent, false);
            }
            ((TextView) convertView).setText("debug: "+mItems.get(position).getText());
            return convertView;
        }
    }
    Library and demo project attached.
    (Ignore the logged message 'CustomSpinnerAdapter callback not found CustomSpinnerAdapter1_GetView' it's not relevant).
    Thanks.

    Martin.
     

    Attached Files:

  2. thedesolatesoul

    thedesolatesoul Expert Licensed User

    I guess the B4A spinner always returns the item from its local adapter variable.
    Instead maybe it should return getObject.getAdapter.getItem.
     
  3. warwound

    warwound Expert Licensed User

    Another thing that had me stumped was that i couldn't see where in the b4a Spinner class (in Core.jar) the Spinner ItemClick event was being raised.
    I thought that'd lead me to some listener that was added in the b4a Spinner initialization but as i said, could find the raiseEvent code that raised that event.

    Martin.
     
  4. thedesolatesoul

    thedesolatesoul Expert Licensed User

    It is there. Line 211. In B4ASpinner.
     
  5. warwound

    warwound Expert Licensed User

    Searching the SpinnerWrapper.java source i only find this event being raised:

    Code:
    public void setSelection(int position)
        {
          super.setSelection(position);
          selectedItem = position;
          
    if ((ba != null) && (!disallowItemClick))
            ba.raiseEventFromUI(this, eventName + 
    "_itemclick", new Object[] { 
              Integer.valueOf(selectedItem), adapter.getItem(selectedItem) 
    });
        }
    Which seems to manually select an item and then manually raise the required 'itemclick' event.

    SpinnerWrapper extends ViewWrapper and ViewWrapper will try to set both OnClickListener and onLongClickListener if corresponding event subs exist.
    But i have no subs which are of the form mySpinnerEventName_Click or mySpinnerEventName_LongClick so am assuming that neither of these listeners have been added.

    I was expecting to find code such as:

    Code:
    setOnItemSelectedListener(new OnItemSelectedListener(){
        
                        @Override
                        
    public void onItemSelected(AdapterView<?> pParent, View pView, int pPosition, long pId) {
                            // raise an 
    event
                        
    }
        
                        @Override
                        public void onNothingSelected(AdapterView<?> pParent) {}
                    });
    Martin.
     
  6. thedesolatesoul

    thedesolatesoul Expert Licensed User

    I agree.
    But it seems like setSelected is being used to trigger onItemSelectedListener indirectly. It never happens though as there is no listener, but it is assumed that setSelected will be the only method to be able to raise that.

    But either way, your original problem is because of this being returned:
    Code:
    adapter.getItem(selectedItem)
     
  7. warwound

    warwound Expert Licensed User

    Yep:
    But why is the original adapter still executing it's methods after i have set a new adapter - that's the problem.

    Martin.
     
  8. thedesolatesoul

    thedesolatesoul Expert Licensed User

    Because that is how it is coded.
    Code:
    this.adapter.getItem(this.selectedItem)
    It should be coded like this:
    Code:
    this.getObject().getAdapter.getItem(this.selectedItem)
    This would have fetched whichever the current adapter was, especially if it was changed afterwards.

    EDIT:
    Also this is not a method of the original adapter, this is the method of the subclassed spinner B4ASpinner.
     
  9. warwound

    warwound Expert Licensed User

    Gotcha - didn't notice that!

    I seems to remember that the class's 'adaptor' member was public.
    If so i can set that member as well as calling setAdaptor().

    I'll be back...
     
  10. thedesolatesoul

    thedesolatesoul Expert Licensed User

    It is public but the type is: SpinnerWrapper.B4ASpinnerAdapter :(
     
  11. Erel

    Erel Administrator Staff Member Licensed User

  12. warwound

    warwound Expert Licensed User

    Success!

    I have to update a project with many layout files containing many Spinners.
    I didn't want to create a custom view as it'd involve too much modification of all those layout files.
    Instead i updated my library, this is from the BaseAdapter class:

    Code:
    /**
         * Set this adapter 
    as the adapter for AdapterView1.
         * AdapterView1 will be a 
    View such as a ListView or a Spinner.
         */
        
    public void SetAdapterView(BA pBA, AdapterView<android.widget.BaseAdapter> AdapterView1){
            
    if(B4ASpinner.class.isInstance(AdapterView1)){
                BA.LogInfo(
    "B4ASpinner being deactivated");
                
    View view=(View) AdapterView1;
                B4ASpinner b4aSpinner=(B4ASpinner) 
    view;
                b4aSpinner.ba=
    null;
                B4ASpinnerAdapter adapter=b4aSpinner.adapter;
                adapter.items=
    null;
                adapter=new SpinnerWrapper.B4ASpinnerAdapter(pBA.context);
                b4aSpinner.adapter=adapter;
            
    }
            // rest of method snipped
    My method accepts an android AdapterView - a ListView or a Spinner.
    If the AdapterView is an instance of B4ASpinner then i set it's 'ba' member to null.
    Now the B4ASpinner setSelection method doesn't execute anything that raises a NullPointerException:

    Code:
    public void setSelection(int position)
        {
          super.setSelection(position);
          selectedItem = position;
          
    if ((ba != null) && (!disallowItemClick))
            ba.raiseEventFromUI(this, eventName + 
    "_itemclick", new Object[] { 
              Integer.valueOf(selectedItem), adapter.getItem(selectedItem) 
    });
        }
    Setting it's 'items' member to null and then setting it's 'adapter' member to a new B4ASpinnerAdapter should hopefully release any references.

    Now i have an Adaptor object that can be used with a ListView and/or a Spinner, a single instance of the Adaptor can be simultaneously set as a ListView adaptor or Spinner adapter.

    Thanks.

    Martin.
     
  13. thedesolatesoul

    thedesolatesoul Expert Licensed User

    But then how do you return the items on the onSelect?
    Also, is this a new view you are releasing ;)
     
  14. warwound

    warwound Expert Licensed User

    The workflow is:
    • Initialize an instance of my BaseAdapter.
    • Set the instance as the adapter of a ListView or Spinner.
    • When setting this adapter, the type of object that it is being added to is established:
      • A ListView has OnItemClickListener and OnItemLongClickListener added.
      • A Spinner has an OnItemSelectedListener added.
    • An item in either ListView or Spinner is an instance of my AdaptorViewItem class.
    • AdaptorViewItem is inflated from xml layout files, it's not a native b4a View.
    • The BaseAdapter raises these events:
      • GetView(ItemIndex As Int, AdaptorViewItem1 As AdaptorViewItem) As AdaptorViewItem
        Return an AdapterViewItem.
        AdapterViewItem may or may not be initialized, if it is initialized then re-cycle it otherwise initialize it.
      • ItemClick(ItemIndex As Int, AdaptorViewItem1 As AdaptorViewItem)
        Not raised by a Spinner.
      • ItemLongClick(ItemIndex As Int, AdaptorViewItem1 As AdaptorViewItem)
        Not raised by a Spinner.
      • ItemSelected(ItemIndex As Int, AdaptorViewItem1 As AdaptorViewItem)
        Not raised by a ListView.
    • AdaptorViewItem is a type of View and therefore has a Tag property.

    So my customised Spinner/BaseAdapter will raise the event:

    ItemSelected(ItemIndex As Int, AdaptorViewItem1 As AdaptorViewItem)

    AdaptorViewItem1's Tag property will contain a value that i can use to establish the selected item.
    (Much as a standard b4a ListView item can pass an arbitrary object to the subs that handle it's item clicks and long clicks).
    The standard Spinner behaviour passes only the clicked or long clicked item's text property - my Spinner needs to contain localised text for different languages and so it's text property varies depending on language.
    Setting a unique integer Tag property on each AdapterViewItem makes it easier to identify the selected item.

    That's why i created the library.
    I extended the android BaseAdapter class here and a BaseAdaptor can be used for ListViews and Spinners - that wasn't important but is a nice feature.

    Might release it when the code has been polished, though if you're interested post again and i'll upload a demo.

    Martin.
     
  15. thedesolatesoul

    thedesolatesoul Expert Licensed User

    Sounds very intriguing. Didnt knkow the AdapterView itself will raise the events. Problem solved!
    Post whenever you have the time.
    Thanks
     
  16. warwound

    warwound Expert Licensed User

    @tds or indeed anyone that's interested.
    My CustomAdapters library and demo projects are attached.

    I expect to update the library's CustomSpinnerAdapter this week as i put it to use in a project.
    Rather than start a new library thread i'll just attach the files to this post for now.
    If there's any interest i'll start a new thread.

    I'm particularly pleased with the GeographBaseAdapterSpinner example - here we have a b4a Spinner which displays one of two different Views for each Spinner item.

    One View is the default single TextView - a plain and boring default!
    This View is displayed as the selected item - when the Spinner is collapsed.

    The other View is more complex - a RelativeLayout containing an ImageView and 4 TextViews all nicely aligned.
    This View is displayed when the Spinner is expanded - a list of these Views is selectable by the user.

    Seems like an occasional NullPointerException is thrown if an Activity is paused while the Adapter is requesting a View from the b4a code.
    I'll hopefully fix that next week.

    Martin.
     

    Attached Files:

    js1234, JordiCP and thedesolatesoul like this.
  17. thedesolatesoul

    thedesolatesoul Expert Licensed User

    Excellent. Works great for me.
    So this is another way to create a custom listview/spinner although it requires xml (sometimes xml is good).
    Thank you.
     
  18. bluedude

    bluedude Well-Known Member Licensed User

  19. warwound

    warwound Expert Licensed User

    Yes you'd set the gravity attribute of a TextView in the XML that defines your Spinner items layout - set it to 'right' i'd guess.
    Can you get my custom adapter example project to work?
    If so look at the resources located at 'res/layout/' - the simple_spinner_????.xml files.
    You want to add the attribute android:gravity="right" to the TextView and CheckedTextView elements in each layout file.

    Now recompile and see if that has worked...

    Martin.
     
  20. bluedude

    bluedude Well-Known Member Licensed User

    Hi warhound, I got something working. However, I would want to have a spinner which would inherit from the default spinner and only overwite the alignment. Is that actually possible? Could I still use stuff like color settings in B4A when I use this?

    I got the demo's working for the rest.
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice