Почему мой пользовательский ArrayAdapter для Android не работает при использовании 2-сторонней привязки данных?

#android #android-room #android-databinding #autocompletetextview

#Android #android-комната #android-привязка данных #autocompletetextview

Вопрос:

Проект, над которым мы работаем, использует архитектуру MVVM, room и 2-стороннюю привязку данных. В моем layout.xml файл, который мы используем AutoCompleteTextView для выбора страны. Страны являются моделями в нашем проекте:

Ниже приведен код, но я, очевидно, не могу опубликовать весь проект, поэтому, если вам нужны дополнительные данные, дайте мне знать.

Country.java

 @Entity
public class Country
{
    @PrimaryKey
    @ColumnInfo(name = "ID")
    private Long id;

    @ColumnInfo(name = "Name")
    private String countryName;

    @ColumnInfo(name = "Code")
    private String countryCode;


    public Long getId()
    {
        return id;
    }

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

    public String getCountryName()
    {
        return countryName;
    }

    public void setCountryName(String countryName)
    {
        this.countryName = countryName;
    }

    public String getCountryCode()
    {
        return countryCode;
    }

    public void setCountryCode(String countryCode)
    {
        this.countryCode = countryCode;
    }
}
  

layout.xml

 <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <import type="android.view.View" />
            <variable name="myViewModel" type="ViewModel" />
        </data>
        <android.support.v4.widget.NestedScrollView>
            <AutoCompleteTextView
                android:id="@ id/countryAutoCompleteTextView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:completionThreshold="1"
                android:dropDownWidth="match_parent"
                android:imeOptions="actionNext"
                android:inputType="text"
                android:maxLines="1"
                android:text="@={myViewModel.countryObservable}"
                android:textIsSelectable="false"
                android:textColor="@color/edit_text_black_color_selector" />
        <android.support.v4.widget.NestedScrollView>
    </layout>
  

View.java

 public class View {
    onCreate(){
        ViewModel myViewModel = new ViewModel();
        ActivityViewBinding binding = DataBindingUtil();
        binding.setViewModel(myViewModel);
        AutoCompleteCountryAdapter countryAdapter = new AutoCompleteAdapter();
        binding.countryAutoCompleteTextView.setAdapter(countryAdapter);
    }
}
  

ViewModel.java

 public class ViewModel extends AndroidViewModel {
    private final ObservableField<String> countryObservable = new ObservableField<>();
}
  

AutoCompleteCountryAdapter.java
Я скопировал код отсюда

 public class AutoCompleteCountryAdapter extends ArrayAdapter<CountryItem> {
    private List<CountryItem> countryListFull;

    public AutoCompleteCountryAdapter(@NonNull Context context, @NonNull List<CountryItem> countryList) {
        super(context, 0, countryList);
        countryListFull = new ArrayList<>(countryList);
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return countryFilter;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
        }

        TextView textViewName = convertView.findViewById(R.id.text_view_name);

        CountryItem countryItem = getItem(position);

        if (countryItem != null) {
            textViewName.setText(country.getCountryName());
        }

        return convertView;
    }

    private Filter countryFilter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            List<CountryItem> suggestions = new ArrayList<>();

            if (constraint == null || constraint.length() == 0) {
                suggestions.addAll(countryListFull);
            } else {
                String filterPattern = constraint.toString().toLowerCase().trim();

                for (CountryItem item : countryListFull) {
                    if (item.getCountryName().toLowerCase().contains(filterPattern)) {
                        suggestions.add(item);
                    }
                }
            }

            results.values = suggestions;
            results.count = suggestions.size();

            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            clear();
            addAll((List) results.values);
            notifyDataSetChanged();
        }

        @Override
        public CharSequence convertResultToString(Object resultValue) {
            return ((CountryItem) resultValue).getCountryName();
        }
    };
}
  

Как вы можете видеть, мое наблюдаемое поле имеет тип String . Это работает… вроде того. Всякий раз, когда я выбираю страну из выпадающего списка AutoCompleteTextView, она заполняет AutoCompleteTextView android:text поле; однако мне нужен идентификатор базы данных из объекта Country, чтобы использовать его, это внешний ключ в другой нашей модели.

Итак, я подумал, что превращу наблюдаемое в тип Country , но это вообще не сработало. Коллега сказал, что мне нужны an @BindingAdapter и an @InverseBindingAdapter , чтобы заставить это работать, поэтому я их создал.

 @BindingAdapter(value = {"android:text", "android:onItemSelected"}, requireAll = false)
    public static void setText(AutoCompleteTextView autoCompleteTextView, Country newSelectedValue, final InverseBindingListener newTextAttrChanged)
    {
        autoCompleteTextView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
        {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View subView, int position, long id)
            {
                newTextAttrChanged.onChange();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView)
            {
            }
        });

        if (newSelectedValue != null)
        {
            int pos = autoCompleteTextView.getListSelection();
            autoCompleteTextView.setText(autoCompleteTextView.getAdapter().getItemViewType(pos));
        }
    }

    @InverseBindingAdapter(attribute = "android:text", event = "android:onItemSelected")
    public static Country getText(AutoCompleteTextView textView)
    {
        int pos = textView.getListSelection();
        Country country = (Country) textView.getAdapter().getItem(pos);
        return countryISO;
    }
  

Моя проблема сейчас в том, что my @BindingAdapter вызывается сразу после загрузки activity, но больше никогда… особенно когда я меняю значение, для чего мне это нужно.

Итак, что я делаю не так и что я могу сделать, чтобы это исправить?

Ответ №1:

 override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle? ): View? {


    val application = requireNotNull(this.activity).application

    val viewModelFactory = HomePageViewModelFactory(application)

    val homePageViewModel = activity?.run {
        ViewModelProviders.of(
            this, viewModelFactory
        ).get(HomePageViewModel::class.java)
    }

    val binding: FragmentReviewPageBinding = DataBindingUtil.inflate(
        inflater,
            R.layout.fragment_review_page,
        container,
        false
    )

    binding.homePageViewModel = homePageViewModel 
  

вот как вы связываете переменную xml с привязкой

     binding.homePageViewModel = homePageViewModel 
  

Заводской класс

 class HomePageViewModelFactory(
private val application: Application) : ViewModelProvider.Factory {

@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    if (modelClass.isAssignableFrom(HomePageViewModel::class.java)) {
        return HomePageViewModel(application) as T
    }

    throw IllegalArgumentException("Unknown ViewModel class")

}
}
  

Таким образом, вы можете использовать один и тот же экземпляр viewmodel для нескольких фрагментов.