如何在同一个 Fragment 中使用 4 个简单的 ViewModel?
How to use 4 simple ViewModels with the same Fragment?
我有一个应用程序,它在片段中显示 4 个单词列表(重复使用相同的 class!):
- 2 个字母的单词
- 3 个字母的单词
- 包含俄语的单词 eh 字母[=71=]
- 包含俄文硬符号字母的单词
这里是抽屉导航的截图(非英文请见谅):
目前我的 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 个列表不是最佳选择,可能会导致 UI 延迟。
所以我尝试将视图模型拆分为基础 class 然后 4 个继承 classes -
WordsViewModel(现在作为基础class):
public class WordsViewModel extends AndroidViewModel {
protected LiveData<List<Word>> mWords;
public WordsViewModel (Application app) {
super(app);
}
public LiveData<List<Word>> getWords() {
return mWords;
}
}
而继承classes仅在被调用的DAO方法上有所不同-
TwoViewModel(继承class):
public class TwoViewModel extends WordsViewModel {
public TwoViewModel(Application app) {
super(app);
mWords = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(2);
}
}
ThreeViewModel(继承class):
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);
}
});
不幸的是,这会破坏我的应用程序,因为它始终显示 2 个字母的单词列表。
我想知道,为什么会发生这种情况(因为继承?)以及如何解决这个问题?
更新:
这里是我使用 MaterialDrawer 和 withTag()
从主 activity 打开片段的代码,我已经在调试器和日志中验证(以及 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);
}
您需要在每个 case 块的末尾放置一个 'break',以便在找到 case 匹配表达式时转义出 switch。如果没有 break 语句,控制流将 'fall through' 在找到第一个匹配 case 后的不同 case 语句。在您的代码中,将始终执行默认情况,即加载 TwoViewModel。
我有一个应用程序,它在片段中显示 4 个单词列表(重复使用相同的 class!):
- 2 个字母的单词
- 3 个字母的单词
- 包含俄语的单词 eh 字母[=71=]
- 包含俄文硬符号字母的单词
这里是抽屉导航的截图(非英文请见谅):
目前我的 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 个列表不是最佳选择,可能会导致 UI 延迟。
所以我尝试将视图模型拆分为基础 class 然后 4 个继承 classes -
WordsViewModel(现在作为基础class):
public class WordsViewModel extends AndroidViewModel {
protected LiveData<List<Word>> mWords;
public WordsViewModel (Application app) {
super(app);
}
public LiveData<List<Word>> getWords() {
return mWords;
}
}
而继承classes仅在被调用的DAO方法上有所不同-
TwoViewModel(继承class):
public class TwoViewModel extends WordsViewModel {
public TwoViewModel(Application app) {
super(app);
mWords = WordsDatabase.getInstance(app).wordsDao().fetchWordsLength(2);
}
}
ThreeViewModel(继承class):
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);
}
});
不幸的是,这会破坏我的应用程序,因为它始终显示 2 个字母的单词列表。
我想知道,为什么会发生这种情况(因为继承?)以及如何解决这个问题?
更新:
这里是我使用 MaterialDrawer 和 withTag()
从主 activity 打开片段的代码,我已经在调试器和日志中验证(以及 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);
}
您需要在每个 case 块的末尾放置一个 'break',以便在找到 case 匹配表达式时转义出 switch。如果没有 break 语句,控制流将 'fall through' 在找到第一个匹配 case 后的不同 case 语句。在您的代码中,将始终执行默认情况,即加载 TwoViewModel。