Как использовать 4 простых ViewModels с одним и тем же фрагментом?

У меня есть приложение, которое отображает 4 списка слов во фрагменте (повторное использование одного и того же класса!):

  • 2-буквенные слова
  • 3-буквенные слова
  • Слова, содержащие русскую букву а
  • Слова, содержащие русскую букву жесткий знак

Вот скриншот панели навигации (прошу прощения за неанглийский язык):

Как использовать 4 простых ViewModels с одним и тем же фрагментом?

В настоящее время мой ViewModel хранит все 4 списка как LiveData:

public class WordsViewModel extends AndroidViewModel {

    private LiveData<List<Word>> mWords2;
    private LiveData<List<Word>> mWords3;
    private LiveData<List<Word>> mWordsHard;
    private LiveData<List<Word>> mWordsEh;

    public WordsViewModel(Application app) {
        super(app);
        mWords2    = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(2);
        mWords3    = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(3);
        mWordsHard = WordsDatabase.getInstance(app).wordsDao().fetchWordsContaining("Ъ");
        mWordsEh   = WordsDatabase.getInstance(app).wordsDao().fetchWordsContaining("Э");
    }

    public LiveData<List<Word>> getWords(int condition) {
        switch (condition) {
            case R.id.navi_drawer_letters_2:
                return mWords2;
            case R.id.navi_drawer_letters_3:
                return mWords3;
            case R.id.navi_drawer_letter_hard:
                return mWordsHard;
            case R.id.navi_drawer_letter_eh:
                return mWordsEh;
        }

        return mWords2;
    }
}

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

Итак, я попытался разделить модель представления на базовый класс, а затем на 4 наследующих класса -

WordsViewModel (действует теперь как базовый класс):

public class WordsViewModel extends AndroidViewModel {

    protected LiveData<List<Word>> mWords;

    public WordsViewModel (Application app) {
        super(app);
    }

    public LiveData<List<Word>> getWords() {
        return mWords;
    }
}

И наследующие классы отличаются только вызываемым методом DAO -

TwoViewModel (наследующий класс):

public class TwoViewModel extends WordsViewModel {
    public TwoViewModel(Application app) {
        super(app);
        mWords = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(2);
    }
}

ThreeViewModel (наследующий класс):

public class ThreeViewModel extends WordsViewModel {
    public ThreeViewModel(Application app) {
        super(app);
        mWords = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(3);
    }
}

Наконец (и спасибо, что прочитали софар!) Вот мой фрагмент:

public class WordsFragment extends Fragment {
    private final ItemAdapter<WordItem> mItemAdapter = new ItemAdapter<>();
    private final FastAdapter<WordItem> mFastAdapter = FastAdapter.with(mItemAdapter);

    private WordsViewModel mViewModel;

    public static WordsFragment newInstance(int condition) {
        WordsFragment f = new WordsFragment();

        Bundle args = new Bundle();
        args.putInt(KEY_CONDITION, condition);
        f.setArguments(args);

        return f;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {

        int condition = (getArguments() == null ? -1 : getArguments().getInt(KEY_CONDITION));
        switch (condition) {
            case R.id.navi_drawer_letter_eh:
                mViewModel = ViewModelProviders.of(this).get(EhViewModel.class);
            case R.id.navi_drawer_letter_hard:
                mViewModel = ViewModelProviders.of(this).get(HardViewModel.class);
            case R.id.navi_drawer_letters_3:
                mViewModel = ViewModelProviders.of(this).get(ThreeViewModel.class);
            default:
                mViewModel = ViewModelProviders.of(this).get(TwoViewModel.class);
        }

        mViewModel.getWords().observe(this, words -> {
            mItemAdapter.clear();
            for (Word word: words) {
                WordItem item = new WordItem();
                item.word = word.word;
                item.expl = word.expl;
                mItemAdapter.add(item);
            }
        });

К сожалению, это мешает моему приложению всегда отображать список двухбуквенных слов.

Интересно, почему это происходит (из-за наследования?) И как это решить?

Обновлено:

Вот мой код для открытия фрагмента из основного действия с использованием Материал и withTag(), и я проверил в отладчике и журналах (и в Toast, который можно увидеть на скриншоте выше), что переменная condition отличается:

private final Drawer.OnDrawerItemClickListener mFetchWordsListener = (view, position, drawerItem) -> {
    setTitle(drawerItem);
    WordsFragment f = WordsFragment.newInstance( (Integer)drawerItem.getTag() );
    getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.root, f)
            .commitAllowingStateLoss();
    return false;
};

mNavigationDrawer.addItems(
    ....
    new SectionDrawerItem().withName(R.string.item_dict),
    new PrimaryDrawerItem().withOnDrawerItemClickListener(mFindWordListener).withName(R.string.item_find_word).withIcon(R.drawable.magnify).withIconTintingEnabled(true).withIdentifier(R.id.navi_drawer_find_word),
    new PrimaryDrawerItem().withOnDrawerItemClickListener(mFetchWordsListener).withName(R.string.item_letters_2).withIcon(R.drawable.letters_2).withIconTintingEnabled(true).withIdentifier(R.id.navi_drawer_letters_2).withTag(R.id.navi_drawer_letters_2),
    new PrimaryDrawerItem().withOnDrawerItemClickListener(mFetchWordsListener).withName(R.string.item_letters_3).withIcon(R.drawable.letters_3).withIconTintingEnabled(true).withIdentifier(R.id.navi_drawer_letters_3).withTag(R.id.navi_drawer_letters_3),
    new PrimaryDrawerItem().withOnDrawerItemClickListener(mFetchWordsListener).withName(R.string.item_letters_2).withIcon(R.drawable.letters_hard).withIconTintingEnabled(true).withIdentifier(R.id.navi_drawer_letters_hard).withTag(R.id.navi_drawer_letters_hard),
    new PrimaryDrawerItem().withOnDrawerItemClickListener(mFetchWordsListener).withName(R.string.item_letters_eh).withIcon(R.drawable.letters_eh).withIconTintingEnabled(true).withIdentifier(R.id.navi_drawer_letters_eh).withTag(R.id.navi_drawer_letters_eh)
);

ОБНОВЛЕНИЕ 2:

Вот мой интерфейс DAO и, кстати, я заметил, что (mViewModel instanceof TwoViewModel) по какой-то причине всегда верен?

@Dao
public interface WordsDao {
    @Query("SELECT * FROM table_words WHERE LENGTH(word) = :length")
    LiveData<List<Word>> fetchWordsLength(int length);

    @Query("SELECT * FROM table_words WHERE word LIKE '%' || :letter || '%'")
    LiveData<List<Word>> fetchWordsContaining(String letter);
}

можешь показать, как открываешь фрагменты?

Jeel Vankhede 27.10.2018 11:44

Вы передаете тег в качестве аргумента вместо идентификатора.

Karol Kulbaka 27.10.2018 12:04

Спасибо, но нет - тег и идентификатор совпадают (например, R.id.navi_drawer_letters_3), и я убедился, что переменная condition верна в onCreateView.

Alexander Farber 27.10.2018 12:06

Правильно, я этого не заметил, извините.

Karol Kulbaka 27.10.2018 12:09

Канон вы показываете запрос fetchWordsLength?

Karol Kulbaka 27.10.2018 12:17
0
5
450
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам нужно поставить «разрыв» в конце каждого блока case, чтобы выйти из переключателя, когда будет найдено выражение соответствия регистру. Без оператора break поток управления будет «проваливаться» через различные операторы case после того, как будет найден первый подходящий вариант. В вашем коде всегда будет выполняться вариант по умолчанию, который загружает TwoViewModel.

Я не буду спать сегодня вечером :)

Karol Kulbaka 27.10.2018 12:29

Со мной такое случается постоянно :) Вам следует остерегаться этого, если вы когда-нибудь перейдете на Kotlin и привыкнете к выражению «когда» :)

Ryujin 27.10.2018 12:46

Спасибо! Я скопировал switch (...) { return ... } из верхнего фрагмента кода и забыл добавить break (я только что отредактировал свой комментарий, подумав, почему это случилось со мной)

Alexander Farber 27.10.2018 13:38

Другие вопросы по теме