Spinner setOnItemSelectedListener does not work properly with Room DB calls












0















The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.



This issue can be summed up as follows:




  1. the Spinner is somehow retaining the previous value from itself and
    setting it

  2. the Spinner's OnItemClicked function is called whenever it's bound
    to the Spinner, which has to be a bug within Android's Spinner
    object, because this should not happen and is not how the Spinner is
    explained in the Android docs
    (https://developer.android.com/guide/topics/ui/controls/spinner)


For example, I have this list of types:




  • Type "X"

  • Type "Y"

  • Type "Z"


Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.



The issue (I think) here is setOnItemSelectedListener is called whenever the user scrolls, and makes changes according to the currently-selected item.



I tried many different solutions; most of them are included here.



But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.



Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.



    public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {

private static final String TAG = "TypeFragment";

private TypeAdapter adapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_type_list, container, false);

SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
refreshLayout.setOnRefreshListener(() -> {
if(refreshLayout.isRefreshing()){
refreshLayout.setRefreshing(false);
}
});

return view;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();

ArrayList<String> typeOptions = new ArrayList<>();
typeOptions.add(getString(R.string.type_x));
typeOptions.add(getString(R.string.type_y));
typeOptions.add(getString(R.string.type_z));

ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
R.layout.support_simple_spinner_dropdown_item, typeOptions);

adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
getListView().setOnItemClickListener(this);
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}

class TypeAdapter extends BaseAdapter {

List<dbType> typesList;
ArrayList<String> typeOptions;
ArrayAdapter<String> arrayAdapter;

public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
this.typesList = typesList;
this.typeOptions = typeOptions;
this.arrayAdapter = arrayAdapter;
}

public int getCount() {
if (this.typesList != null) {
return this.typesList.size();
}
return 0;
}

public Object getItem(int position) {
return this.typesList.get(position);
}

public long getItemId(int position) {
return this.typesList.get(position).id;
}

public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
}

TextView typeTV = convertView.findViewById(R.id.type_tv_label);
Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);

typeSpinner.setAdapter(this.arrayAdapter);

dbType currentType = this.typesList.get(position);

// here I ideally would set the Type to the one currently in the DB
//typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));

// most popular solution from StackOverflow questions:
typeSpinner.setSelected(false);
typeSpinner.setSelection(Adapter.NO_SELECTION, true);

// set a spinner with its' listener on each row in the list
typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
int item = typeSpinner.getSelectedItemPosition();
Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
}
@Override
public void onNothingSelected(AdapterView<?> parent1) {}
}));

return convertView;
}
}
}


The database files:



dbType:



@Entity(tableName="types")
public class dbType {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
public int id;

@ColumnInfo(name = "type")
public String type;


TypeDao:



@Dao
public interface TypeDao {
@Query("SELECT * FROM types;")
List<dbType> getTypes();

@Query("SELECT type FROM types WHERE id = :typeID;")
String getType(int typeID);

@Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
void updateType(String typeStr, int typeID);


ExampleDatabase excluding Migrations for brevity:



@Database(entities = {dbType.class})
@TypeConverters({dbTypeConverters.class})
public abstract class ExampleDatabase extends RoomDatabase {
public abstract TypeDao getTypeDao()

private static ExampleDatabase INSTANCE;

public static CoriolisDatabase getCoriolisDatabase() {
return INSTANCE;
}


types_row_layout.xml:



<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/type_tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_gravity="center_vertical"
android:text="@string/runs_list_runvolerror_column"
app:layout_constraintStart_toEndOf="@id/netvolume_column"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>

<android.support.constraint.Barrier
android:id="@+id/barrier_run_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>

<Spinner
android:id="@+id/type_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/run_info_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />


</android.support.constraint.ConstraintLayout>


Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:



// before update check:
Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
// update happens
Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);


This logging revealed that the type was/is changed when scrolling, and the database update stuck.



Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):



DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type X


Again, after tapping and changing the value to Type Y:



DB type before updating: type ID is 57, type is Type X
DB type after updating: type ID is 57, type is Type Y


Looks like it updated just fine...



Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):



DB type before updating: type ID is 57, type is Type Y
DB type after updating: type ID is 57, type is Type X









share|improve this question





























    0















    The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.



    This issue can be summed up as follows:




    1. the Spinner is somehow retaining the previous value from itself and
      setting it

    2. the Spinner's OnItemClicked function is called whenever it's bound
      to the Spinner, which has to be a bug within Android's Spinner
      object, because this should not happen and is not how the Spinner is
      explained in the Android docs
      (https://developer.android.com/guide/topics/ui/controls/spinner)


    For example, I have this list of types:




    • Type "X"

    • Type "Y"

    • Type "Z"


    Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.



    The issue (I think) here is setOnItemSelectedListener is called whenever the user scrolls, and makes changes according to the currently-selected item.



    I tried many different solutions; most of them are included here.



    But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.



    Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.



        public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {

    private static final String TAG = "TypeFragment";

    private TypeAdapter adapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState){
    View view = inflater.inflate(R.layout.fragment_type_list, container, false);

    SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
    refreshLayout.setOnRefreshListener(() -> {
    if(refreshLayout.isRefreshing()){
    refreshLayout.setRefreshing(false);
    }
    });

    return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();

    ArrayList<String> typeOptions = new ArrayList<>();
    typeOptions.add(getString(R.string.type_x));
    typeOptions.add(getString(R.string.type_y));
    typeOptions.add(getString(R.string.type_z));

    ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
    R.layout.support_simple_spinner_dropdown_item, typeOptions);

    adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
    setListAdapter(adapter);
    adapter.notifyDataSetChanged();
    getListView().setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}

    class TypeAdapter extends BaseAdapter {

    List<dbType> typesList;
    ArrayList<String> typeOptions;
    ArrayAdapter<String> arrayAdapter;

    public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
    this.typesList = typesList;
    this.typeOptions = typeOptions;
    this.arrayAdapter = arrayAdapter;
    }

    public int getCount() {
    if (this.typesList != null) {
    return this.typesList.size();
    }
    return 0;
    }

    public Object getItem(int position) {
    return this.typesList.get(position);
    }

    public long getItemId(int position) {
    return this.typesList.get(position).id;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
    convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
    }

    TextView typeTV = convertView.findViewById(R.id.type_tv_label);
    Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);

    typeSpinner.setAdapter(this.arrayAdapter);

    dbType currentType = this.typesList.get(position);

    // here I ideally would set the Type to the one currently in the DB
    //typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));

    // most popular solution from StackOverflow questions:
    typeSpinner.setSelected(false);
    typeSpinner.setSelection(Adapter.NO_SELECTION, true);

    // set a spinner with its' listener on each row in the list
    typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
    int item = typeSpinner.getSelectedItemPosition();
    Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
    ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
    typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
    }
    @Override
    public void onNothingSelected(AdapterView<?> parent1) {}
    }));

    return convertView;
    }
    }
    }


    The database files:



    dbType:



    @Entity(tableName="types")
    public class dbType {
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    public int id;

    @ColumnInfo(name = "type")
    public String type;


    TypeDao:



    @Dao
    public interface TypeDao {
    @Query("SELECT * FROM types;")
    List<dbType> getTypes();

    @Query("SELECT type FROM types WHERE id = :typeID;")
    String getType(int typeID);

    @Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
    void updateType(String typeStr, int typeID);


    ExampleDatabase excluding Migrations for brevity:



    @Database(entities = {dbType.class})
    @TypeConverters({dbTypeConverters.class})
    public abstract class ExampleDatabase extends RoomDatabase {
    public abstract TypeDao getTypeDao()

    private static ExampleDatabase INSTANCE;

    public static CoriolisDatabase getCoriolisDatabase() {
    return INSTANCE;
    }


    types_row_layout.xml:



    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
    android:id="@+id/type_tv_label"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="16sp"
    android:padding="2dp"
    android:layout_marginLeft="2dp"
    android:layout_marginRight="2dp"
    android:layout_marginBottom="2dp"
    android:layout_marginTop="2dp"
    android:layout_gravity="center_vertical"
    android:text="@string/runs_list_runvolerror_column"
    app:layout_constraintStart_toEndOf="@id/netvolume_column"
    app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>

    <android.support.constraint.Barrier
    android:id="@+id/barrier_run_row"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="bottom"
    app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>

    <Spinner
    android:id="@+id/type_spinner"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="start"
    android:layout_marginLeft="8dp"
    android:layout_marginStart="8dp"
    android:ellipsize="end"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@id/run_info_btn"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />


    </android.support.constraint.ConstraintLayout>


    Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:



    // before update check:
    Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
    // update happens
    Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);


    This logging revealed that the type was/is changed when scrolling, and the database update stuck.



    Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):



    DB type before updating: type ID is 57, type is Type X
    DB type after updating: type ID is 57, type is Type X


    Again, after tapping and changing the value to Type Y:



    DB type before updating: type ID is 57, type is Type X
    DB type after updating: type ID is 57, type is Type Y


    Looks like it updated just fine...



    Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):



    DB type before updating: type ID is 57, type is Type Y
    DB type after updating: type ID is 57, type is Type X









    share|improve this question



























      0












      0








      0








      The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.



      This issue can be summed up as follows:




      1. the Spinner is somehow retaining the previous value from itself and
        setting it

      2. the Spinner's OnItemClicked function is called whenever it's bound
        to the Spinner, which has to be a bug within Android's Spinner
        object, because this should not happen and is not how the Spinner is
        explained in the Android docs
        (https://developer.android.com/guide/topics/ui/controls/spinner)


      For example, I have this list of types:




      • Type "X"

      • Type "Y"

      • Type "Z"


      Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.



      The issue (I think) here is setOnItemSelectedListener is called whenever the user scrolls, and makes changes according to the currently-selected item.



      I tried many different solutions; most of them are included here.



      But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.



      Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.



          public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {

      private static final String TAG = "TypeFragment";

      private TypeAdapter adapter;

      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState){
      View view = inflater.inflate(R.layout.fragment_type_list, container, false);

      SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
      refreshLayout.setOnRefreshListener(() -> {
      if(refreshLayout.isRefreshing()){
      refreshLayout.setRefreshing(false);
      }
      });

      return view;
      }

      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);

      List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();

      ArrayList<String> typeOptions = new ArrayList<>();
      typeOptions.add(getString(R.string.type_x));
      typeOptions.add(getString(R.string.type_y));
      typeOptions.add(getString(R.string.type_z));

      ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
      R.layout.support_simple_spinner_dropdown_item, typeOptions);

      adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
      setListAdapter(adapter);
      adapter.notifyDataSetChanged();
      getListView().setOnItemClickListener(this);
      }

      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}

      class TypeAdapter extends BaseAdapter {

      List<dbType> typesList;
      ArrayList<String> typeOptions;
      ArrayAdapter<String> arrayAdapter;

      public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
      this.typesList = typesList;
      this.typeOptions = typeOptions;
      this.arrayAdapter = arrayAdapter;
      }

      public int getCount() {
      if (this.typesList != null) {
      return this.typesList.size();
      }
      return 0;
      }

      public Object getItem(int position) {
      return this.typesList.get(position);
      }

      public long getItemId(int position) {
      return this.typesList.get(position).id;
      }

      public View getView(int position, View convertView, ViewGroup parent) {
      if (convertView == null) {
      convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
      }

      TextView typeTV = convertView.findViewById(R.id.type_tv_label);
      Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);

      typeSpinner.setAdapter(this.arrayAdapter);

      dbType currentType = this.typesList.get(position);

      // here I ideally would set the Type to the one currently in the DB
      //typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));

      // most popular solution from StackOverflow questions:
      typeSpinner.setSelected(false);
      typeSpinner.setSelection(Adapter.NO_SELECTION, true);

      // set a spinner with its' listener on each row in the list
      typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
      @Override
      public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
      int item = typeSpinner.getSelectedItemPosition();
      Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
      ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
      typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
      }
      @Override
      public void onNothingSelected(AdapterView<?> parent1) {}
      }));

      return convertView;
      }
      }
      }


      The database files:



      dbType:



      @Entity(tableName="types")
      public class dbType {
      @PrimaryKey(autoGenerate = true)
      @ColumnInfo(name = "id")
      public int id;

      @ColumnInfo(name = "type")
      public String type;


      TypeDao:



      @Dao
      public interface TypeDao {
      @Query("SELECT * FROM types;")
      List<dbType> getTypes();

      @Query("SELECT type FROM types WHERE id = :typeID;")
      String getType(int typeID);

      @Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
      void updateType(String typeStr, int typeID);


      ExampleDatabase excluding Migrations for brevity:



      @Database(entities = {dbType.class})
      @TypeConverters({dbTypeConverters.class})
      public abstract class ExampleDatabase extends RoomDatabase {
      public abstract TypeDao getTypeDao()

      private static ExampleDatabase INSTANCE;

      public static CoriolisDatabase getCoriolisDatabase() {
      return INSTANCE;
      }


      types_row_layout.xml:



      <?xml version="1.0" encoding="utf-8"?>
      <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <TextView
      android:id="@+id/type_tv_label"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="16sp"
      android:padding="2dp"
      android:layout_marginLeft="2dp"
      android:layout_marginRight="2dp"
      android:layout_marginBottom="2dp"
      android:layout_marginTop="2dp"
      android:layout_gravity="center_vertical"
      android:text="@string/runs_list_runvolerror_column"
      app:layout_constraintStart_toEndOf="@id/netvolume_column"
      app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>

      <android.support.constraint.Barrier
      android:id="@+id/barrier_run_row"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:barrierDirection="bottom"
      app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>

      <Spinner
      android:id="@+id/type_spinner"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="start"
      android:layout_marginLeft="8dp"
      android:layout_marginStart="8dp"
      android:ellipsize="end"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toStartOf="@id/run_info_btn"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />


      </android.support.constraint.ConstraintLayout>


      Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:



      // before update check:
      Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
      // update happens
      Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);


      This logging revealed that the type was/is changed when scrolling, and the database update stuck.



      Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):



      DB type before updating: type ID is 57, type is Type X
      DB type after updating: type ID is 57, type is Type X


      Again, after tapping and changing the value to Type Y:



      DB type before updating: type ID is 57, type is Type X
      DB type after updating: type ID is 57, type is Type Y


      Looks like it updated just fine...



      Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):



      DB type before updating: type ID is 57, type is Type Y
      DB type after updating: type ID is 57, type is Type X









      share|improve this question
















      The problem here is after selecting a new type, the user scrolls down the list, then back up, and the type returns to the one it was before it was changed.



      This issue can be summed up as follows:




      1. the Spinner is somehow retaining the previous value from itself and
        setting it

      2. the Spinner's OnItemClicked function is called whenever it's bound
        to the Spinner, which has to be a bug within Android's Spinner
        object, because this should not happen and is not how the Spinner is
        explained in the Android docs
        (https://developer.android.com/guide/topics/ui/controls/spinner)


      For example, I have this list of types:




      • Type "X"

      • Type "Y"

      • Type "Z"


      Sorry for the length, but I probably should include my whole implementation. I'm looking for possible mistakes in my implementation before resorting to removing the Spinner and using something else.



      The issue (I think) here is setOnItemSelectedListener is called whenever the user scrolls, and makes changes according to the currently-selected item.



      I tried many different solutions; most of them are included here.



      But this should not cause the issue I'm seeing, because the code queries the DB for the value; so if the value was updated in the database when the user selected a new one, it should call that new value into the spinner, but it doesn't. It always calls the old one.



      Using logging, I verified that the new value was set in the DB correctly, logging before I call update and after.



          public class TypeFragment extends ListFragment implements AdapterView.OnItemClickListener {

      private static final String TAG = "TypeFragment";

      private TypeAdapter adapter;

      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState){
      View view = inflater.inflate(R.layout.fragment_type_list, container, false);

      SwipeRefreshLayout refreshLayout = view.findViewById(R.id.swiperefresh);
      refreshLayout.setOnRefreshListener(() -> {
      if(refreshLayout.isRefreshing()){
      refreshLayout.setRefreshing(false);
      }
      });

      return view;
      }

      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);

      List<dbRun> typesList = ExampleDatabase.getExampleDatabase().getTypeDao().getTypes();

      ArrayList<String> typeOptions = new ArrayList<>();
      typeOptions.add(getString(R.string.type_x));
      typeOptions.add(getString(R.string.type_y));
      typeOptions.add(getString(R.string.type_z));

      ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()),
      R.layout.support_simple_spinner_dropdown_item, typeOptions);

      adapter = new TypeAdapter(typesList, typeOptions, arrayAdapter);
      setListAdapter(adapter);
      adapter.notifyDataSetChanged();
      getListView().setOnItemClickListener(this);
      }

      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {}

      class TypeAdapter extends BaseAdapter {

      List<dbType> typesList;
      ArrayList<String> typeOptions;
      ArrayAdapter<String> arrayAdapter;

      public TypeAdapter(List<dbType> typesList, ArrayList<String> typeOptions, ArrayAdapter<String> arrayAdapter) {
      this.typesList = typesList;
      this.typeOptions = typeOptions;
      this.arrayAdapter = arrayAdapter;
      }

      public int getCount() {
      if (this.typesList != null) {
      return this.typesList.size();
      }
      return 0;
      }

      public Object getItem(int position) {
      return this.typesList.get(position);
      }

      public long getItemId(int position) {
      return this.typesList.get(position).id;
      }

      public View getView(int position, View convertView, ViewGroup parent) {
      if (convertView == null) {
      convertView = LayoutInflater.from(getContext()).inflate(R.layout.types_row_layout, parent, false);
      }

      TextView typeTV = convertView.findViewById(R.id.type_tv_label);
      Spinner typeSpinner = convertView.findViewById(R.id.type_spinner);

      typeSpinner.setAdapter(this.arrayAdapter);

      dbType currentType = this.typesList.get(position);

      // here I ideally would set the Type to the one currently in the DB
      //typeSpinner.setSelection(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id));

      // most popular solution from StackOverflow questions:
      typeSpinner.setSelected(false);
      typeSpinner.setSelection(Adapter.NO_SELECTION, true);

      // set a spinner with its' listener on each row in the list
      typeSpinner.post(() -> typeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
      @Override
      public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
      int item = typeSpinner.getSelectedItemPosition();
      Log.d(TAG, "called onItemSelected, position " + position1 + " item: " + item);
      ExampleDatabase.getExampleDatabase().getTypeDao().updateType(typeSpinner.get(item), currentType.id);
      typeSpinner.setSelection(arrayAdapter.getPosition(arrayAdapter.getPosition(ExampleDatabase.getExampleDatabase().getTypeDao().getType(currentType.id)));
      }
      @Override
      public void onNothingSelected(AdapterView<?> parent1) {}
      }));

      return convertView;
      }
      }
      }


      The database files:



      dbType:



      @Entity(tableName="types")
      public class dbType {
      @PrimaryKey(autoGenerate = true)
      @ColumnInfo(name = "id")
      public int id;

      @ColumnInfo(name = "type")
      public String type;


      TypeDao:



      @Dao
      public interface TypeDao {
      @Query("SELECT * FROM types;")
      List<dbType> getTypes();

      @Query("SELECT type FROM types WHERE id = :typeID;")
      String getType(int typeID);

      @Query("UPDATE types SET type = :typeStr WHERE id = :typeID;")
      void updateType(String typeStr, int typeID);


      ExampleDatabase excluding Migrations for brevity:



      @Database(entities = {dbType.class})
      @TypeConverters({dbTypeConverters.class})
      public abstract class ExampleDatabase extends RoomDatabase {
      public abstract TypeDao getTypeDao()

      private static ExampleDatabase INSTANCE;

      public static CoriolisDatabase getCoriolisDatabase() {
      return INSTANCE;
      }


      types_row_layout.xml:



      <?xml version="1.0" encoding="utf-8"?>
      <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <TextView
      android:id="@+id/type_tv_label"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textSize="16sp"
      android:padding="2dp"
      android:layout_marginLeft="2dp"
      android:layout_marginRight="2dp"
      android:layout_marginBottom="2dp"
      android:layout_marginTop="2dp"
      android:layout_gravity="center_vertical"
      android:text="@string/runs_list_runvolerror_column"
      app:layout_constraintStart_toEndOf="@id/netvolume_column"
      app:layout_constraintEnd_toStartOf="@id/run_info_btn"/>

      <android.support.constraint.Barrier
      android:id="@+id/barrier_run_row"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:barrierDirection="bottom"
      app:constraint_referenced_ids="netvolume_column, runvolerror_column"/>

      <Spinner
      android:id="@+id/type_spinner"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="start"
      android:layout_marginLeft="8dp"
      android:layout_marginStart="8dp"
      android:ellipsize="end"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toStartOf="@id/run_info_btn"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/barrier_run_row" />


      </android.support.constraint.ConstraintLayout>


      Edit -- here is some logging to verify the DB calls are working because adding code into the main body above seems to mess up the whole structure/formatting of the code:



      // before update check:
      Log.d(TAG, "DB type before updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);
      // update happens
      Log.d(TAG, "DB type after updating: type ID is " + currentType.id + ", type is " + ExampleDatabase.getExampleDatabase().getTypeDao().getTypeWithID(currentType.id).type);


      This logging revealed that the type was/is changed when scrolling, and the database update stuck.



      Output when scrolled initially (notice how it's called when it's not clicked, and updates with the same value):



      DB type before updating: type ID is 57, type is Type X
      DB type after updating: type ID is 57, type is Type X


      Again, after tapping and changing the value to Type Y:



      DB type before updating: type ID is 57, type is Type X
      DB type after updating: type ID is 57, type is Type Y


      Looks like it updated just fine...



      Then again, scrolling after actually updating it (notice how it reverts back to Type X from before):



      DB type before updating: type ID is 57, type is Type Y
      DB type after updating: type ID is 57, type is Type X






      android database android-spinner android-room






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jan 2 at 18:17









      Fantômas

      32.8k156490




      32.8k156490










      asked Jan 2 at 16:37









      star_tracstar_trac

      63114




      63114
























          0






          active

          oldest

          votes











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009963%2fspinner-setonitemselectedlistener-does-not-work-properly-with-room-db-calls%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54009963%2fspinner-setonitemselectedlistener-does-not-work-properly-with-room-db-calls%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Monofisismo

          Angular Downloading a file using contenturl with Basic Authentication

          Olmecas