【问题标题】:how to retrieve list of objects from dao via LiveData<> in View Model when there is no change to trigger onChange method in View Model in android当没有更改触发Android视图模型中的onChange方法时,如何通过视图模型中的LiveData <>从dao检索对象列表
【发布时间】:2019-08-13 19:37:27
【问题描述】:

我正在使用 android studios 来构建一个字典应用程序。在 SO 社区的帮助下,一切进展顺利,现在我有另一个问题。

我已经设置了几个自定义词典,以便用户可以存储和查看不同词典中的单词。 (例如文学、编程、一般等)

这些将是单词实体中的 FK id,并在用户添加新单词时填充。

在 MainActivity 中,我有一个“更改字典”的选项菜单项。这会打开一个 AlertDialog,用户可以在其中更改字典并从理论上看到一组新单词。

这就是问题所在。当用户选择特定字典时,数据库不会发生任何更改,因此附加到 WordViewModel 的 getWordsByDictionaryId() 方法的 onChange 不会触发。因此,LiveData> 不会重新填充。

onCreate() 方法中的代码:

    // get all words from WordDao
    mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
    mWordViewModel.getWordByDictionaryId(dictionaryId).observe(MainActivity.this, new Observer<List<Word>>() {
        @Override
        public void onChanged(@Nullable List<Word> words) {
            mainAdapter.setWords(words);
            mAllWords = words;
            mainAdapter.notifyDataSetChanged();
        }
    });

OnOptionsItemSelected() 方法中的代码:

        AlertDialog.Builder changeDictionaryDialog = new AlertDialog.Builder(MainActivity.this);
        changeDictionaryDialog.setTitle("Select Dictionary:");
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.alert_dialog_spinner_view, null);

        mDictionaryStringList = new ArrayList<>();
        mDictionaryStringList = convertToList(mDictionaryList);

        final Spinner dictionarySpinner = (Spinner) view.findViewById(R.id.alert_dialog_spinner);

        ArrayAdapter<String> spinnerAdapter = new ArrayAdapter(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, mDictionaryStringList);
        spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        dictionarySpinner.setAdapter(spinnerAdapter);

        dictionarySpinner.setSelection(0);

        changeDictionaryDialog.setView(view);
        changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

                for (Dictionary dictionary : mDictionaryList) {
                    if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                        dictionaryId = dictionary.getDid();
                       LiveData<List<Word>> myNewList = mWordViewModel.getWordByDictionaryId(dictionaryId);
                        List<Word> testList = myNewList.
                    }
                }
            }
        });

        changeDictionaryDialog.setNegativeButton("Cancel", null);
        changeDictionaryDialog.create();
        changeDictionaryDialog.show();

WordViewModel 中的代码:

public LiveData<List<Word>> getWordByDictionaryId(long dictionaryId) {
    return mWordRepository.getWordByDictionaryId(dictionaryId);
}

WordDao 中的代码:

@Query("SELECT * FROM word_table WHERE dictionary_id =:dictionaryId")
public LiveData<List<Word>> getWordsByDictionaryID(long dictionaryId);

最后是Word实体:

@Entity(tableName = "word_table",
indices = { @Index(value = "word", unique = true),
@Index("dictionary_id") })
public class Word {
@PrimaryKey(autoGenerate = true)
private long id;
private String word;
@ColumnInfo(name = "dictionary_id")
private long dictionaryId;

@Ignore
public Word(String word, long dictionaryId) {
    this.word = word;
    this.dictionaryId = dictionaryId;
}

public Word(long id, String word, long dictionaryId) {
    this.id = id;
    this.word = word;
    this.dictionaryId = dictionaryId;
}

public long getId() {
    return id;
}

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

public String getWord() {
    return word;
}

public void setWord(String word) {
    this.word = word;
}

public long getDictionaryId() {
    return dictionaryId;
}

public void setDictionaryId(long dictionaryId) {
    this.dictionaryId = dictionaryId;
}
}

我曾尝试设置一个后台线程 (AsyncTask),但有一次我这样做了,结果是在我需要它们之后出现的,因此返回值为 null。一些 SO 开发人员说不要使用 Asynctask,但我认为问题仍然是时间......

我还阅读了一些关于生成回调的内容。这在这种情况下真的有效吗?在我看来,它可能会让我与后台线程处于同一条船上。

当涉及到通过房间持久性通过视图模型通过 LiveData 通过 ???whatever 从 SQLite 数据库中获取数据时,我一直在努力学习如何从这种“非常简单的创建数据库的方法”中获取数据接下来呢???有没有“谷歌”的方式来做到这一点?或者这就是 RxJava 和其他库发挥作用的地方?

所以我的问题就是“如果没有通过实时数据的 onChange 事件,我究竟如何从数据库中获取实体对象列表?”在没有 LiveData 包装器的情况下调用查询是否有最佳实践或正确方法?还是一种直接在 LiveData 包装器中访问查询而不需要更改数据来完成任务的方法?

【问题讨论】:

  • 当你传递一个新的 dictionaryId 时,它会使用该参数构造一个新的 Query 并且你的观察者只监听 那个 查询,所以当你的字典 id 改变时,你将拥有再次从 viewmodel 调用 get 函数以构造一个新查询并将观察者添加到该查询返回的 Livedata
  • 您要做的是在构造查询后更改查询参数,我检查了文档和资源,一旦构造查询就无法更改参数,所以我害怕没有其他选择
  • 我不太明白你在说什么......我理解的内容刚好可以做我所做的事情,但是对经验的理解还没有。您能否提供我提供给您的代码示例,说明如何再次从视图模型中调用 get 函数?您是否将 getWordByDictionaryId() 方法称为“get”方法?如果是这样,当我在 AlertDialog 中调用 GetWordByDictionaryId() 方法时,我无法查看或访问数据。我以为是因为它是“LiveData”。
  • 这就是我的意思,当用户在对话框中选择一个选项时,您发送告诉视图模型或活动以更新 dictionaryId,然后使用您从 AlertDialog 获得的这个新 id 调用 GetWordByDictionaryId()
  • 好的,我正在查看 LiveData> myNewList 对象,但看不到其中的数据。它似乎已经运行,但我如何提取数据?

标签: android android-studio mvvm android-room android-livedata


【解决方案1】:

LiveData 解决方案

@Zohaib Amir 的评论是正确的。只要您记得清除观察者,您就可以在任何您想要的地方拨打LiveData#observe()。这种方法的一个缺点是您必须保留对这些观察者的引用,而LiveData 已经拥有Transformations.switchMap(),这样您就可以避免这种情况。

视图模型

// Instance variable that stores the current dictionaryId that user wants to see.
private final MutableLiveData<Long> currentDictionaryId = new MutableLiveData<>();

// Instance variable that stores the current list of words. This will automatically change when currentDictionaryId value changes.
private final LiveData<List<Word>> words = Transformations.switchMap(currentDictionaryId, dictionaryId -> 
    mWordRepository.getWordByDictionaryId(dictionaryId));



// Set new dictionaryId
public void setDictionaryId(long dictionaryId) {
    currentDictionaryId.postValue(dictionaryId);
}

// Get LiveData to listen to
public LiveData<List<Word>> getWords() {
    return words;
}

活动

// onCreate()
    // get all words from WordDao
    mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
    mWordViewModel.setDictionaryId(dictionaryId);
    mWordViewModel.getWords().observe(MainActivity.this, new Observer<List<Word>>() {
        @Override
        public void onChanged(@Nullable List<Word> words) {
            mainAdapter.setWords(words);
            mAllWords = words;
            mainAdapter.notifyDataSetChanged();
        }
    });

    // note that the order of calling setDictionaryId and getWords doesn't matter. setDictionaryId is supposed to call at anytime.
// OnOptionsItemSelected
    ...
    changeDictionaryDialog.setView(view);
        changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

                for (Dictionary dictionary : mDictionaryList) {
                    if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                        dictionaryId = dictionary.getDid();
                        mWordViewModel.setDictionaryId(dictionaryId);
                        break;
                    }
                }
            }
        });
    ...

这次我准备了其他解决方案,以便您进行比较。其他解决方案(几乎)做同样的事情。


非反应性解决方案

您可以基于回调实现上述功能,这意味着您必须手动处理生命周期更改、通知和线程。还有一个功能上的区别是LiveData会在db发生变化时自动通知观察者,而这种回调设计只是一次查找而已。

在本例中,我们将使用Executor在后台线程中执行任务。

WordDao

@Query("SELECT * FROM word_table WHERE dictionary_id =:dictionaryId")
public List<Word> getWordsByDictionaryID(long dictionaryId); // notice that livedata is gone.

WordRepository 数据库访问必须在后台线程中完成。所以当我们访问db时,我们需要在某个时候切换到后台线程。在这个例子中,我们将切换到后台 线程在这一层。

// This executor is needed to run Runnables on a background thread. In real application, you may
// create this executor outside of this repository and later inject it to this repository.
private final Executor ioExecutor = Executors.newSingleThreadExecutor();


private void getWordByDictionaryId(long dictionaryId, Consumer<List<Word>> callback) {
    ioExecutor.execute(() -> {
        List<Word> wordList = wordDao.getWordsByDictionaryId(dictionaryId);
        callback.accept(wordList);
    });
}

WordViewModel 在这个例子中,ViewModel 没有做太多的事情。只需将参数传递给存储库。

public void getWordByDictionaryId(long dictionaryId, Consumer<List<Word>> callback) {
    mWordRepository.getWordByDictionaryId(dictionaryId, callback);
}

活动 请注意,Consumer#accept 将在后台线程上运行。因此,在对 ui 进行任何操作之前,您需要切换回 ui 线程。

// onCreate
mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
mWordViewModel.getWordByDictionaryId(dictionaryId, words -> {
    runOnUiThread(() -> {
        if (isFinishing() || isDestroyed()) return; // if the activity is finishing, don't do anything.

        mainAdapter.setWords(words);
        mAllWords = words;
        mainAdapter.notifyDataSetChanged();
    });
});


// onOptionsItemSelected
changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

        for (Dictionary dictionary : mDictionaryList) {
            if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                dictionaryId = dictionary.getDid();
                mWordViewModel.getWordByDictionaryId(dictionaryId, words -> {
                    runOnUiThread(() -> {
                        if (isFinishing() || isDestroyed()) return; // if the activity is finishing, don't do anything.

                        mainAdapter.setWords(words);
                        mAllWords = words;
                        mainAdapter.notifyDataSetChanged();
                    });
                });
                break;
            }
        }
    }
});


RxJava

RxJava 解决方案看起来很像LiveData 解决方案。与LiveData 相比,它有很多优点:它有很多可观察对象和操作符可供使用。这些优势在更复杂的应用程序中更加明显,您需要执行条件或延迟异步任务、周期性任务、一个接一个地链接的多个请求、错误处理等。

WordDao

@Query("SELECT * FROM word_table WHERE dictionary_id =:dictionaryId")
public Flowable<List<Word>> getWordsByDictionaryID(long dictionaryId);

视图模型

private final BehaviorProcessor<Long> currentDictionaryId = BehaviorProcessor.create();

// Set new dictionaryId
public void setDictionaryId(long dictionaryId) {
    currentDictionaryId.onNext(dictionaryId);
}

// Get Flowable to listen to
public Flowable<List<Word>> getWords() {
    return currentDictionaryId.switchMap(dictionaryId -> mWordRepository
        .getWordByDictionaryId(dictionaryId)
        // add ".take(1)" if you need one-time look up.
        .subscribeOn(Schedulers.io())); 
}

活动

private fianl CompositeDisposable disposables = new CompositeDisposable();

// onCreate()
mWordViewModel = ViewModelProviders.of(MainActivity.this).get(WordViewModel.class);
mWordViewModel.setDictionaryId(dictionaryId);
disposables.add(mWordViewModel.getWords()
    .observeOn(AndroidSchedulers.mainThread()))
    .subscribe(words -> {
        mainAdapter.setWords(words);
        mAllWords = words;
        mainAdapter.notifyDataSetChanged();
    });


// onOptionsItemSelected()
 changeDictionaryDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        final String dictionaryValue = dictionarySpinner.getSelectedItem().toString();

        for (Dictionary dictionary : mDictionaryList) {
            if (dictionary.getDictionaryName().trim().equals(dictionaryValue)) {
                dictionaryId = dictionary.getDid();
                mWordViewModel.setDictionaryId(dictionaryId);
                break;
            }
        }
    }
});

// onDestroy()
disposables.clear();

【讨论】:

  • 感谢您的耐心和愿意帮助我学习。这是我一直试图在我迄今为止阅读的所有文件中找到的内容。我读过的许多文件都没有得到这个答案,也不会深入到这个程度。再次感谢!!
  • 刚刚尝试了上面的第一个示例,它完全符合我的要求。它根据用户对他们请求的特定字典的输入从 Word 表中返回数据子集。
  • 所以,使用 LiveData 方法,任何时候我想从 LiveData 包装的查询中获取信息,我是否需要使用上面的代码来强制“更改”实际上没有发生变化?还是有其他方法?我遇到了不同的情况,我想从 DAO 查询中获取 List 或只是一个 Word,但我似乎无法找到解决方案,但在我的脑海中,我似乎是向那个方向倾斜。这是一个有效的解决方案吗?还是有另一种直接调用查询的方式?
  • @MSMartens,您不需要强制执行任何操作。如果LiveData 包含任何值并且没有变化,您应该可以调用.getValue() 来查看LiveData
  • 但是,如果我想从接受 WHERE 的查询中获取值,那么我使用您上面提到的代码。正确的?就像我们在谈论字典 id 改变一样?假设我想使用 word_id 获得不同的单词,然后我会在 ViewModel 中添加使用 Transformations.SwitchMap() 的代码,对吗?只是想看看我目前的理解是否正确......谢谢
猜你喜欢
  • 1970-01-01
  • 2021-12-27
  • 2018-05-23
  • 2019-05-16
  • 2013-04-24
  • 1970-01-01
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多