android 中的视图模型中没有更改触发 onChange 方法时,如何通过视图模型中的 LiveData<> 从 dao 检索对象列表

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 工作室构建字典应用程序。在 SO 社区的帮助下,一切进展顺利,现在我有另一个问题。

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

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

在 MainActivity 中,我有一个 'Change Dictionary' 的选项菜单项。这会弹出一个 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),但有一次我这样做了,结果是在我需要它们之后出现的,所以 return 值为空。一些 SO 开发人员说不要使用 Asynctask,但我认为问题仍然是时间问题...

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

我一直在努力学习如何从这个 "wonderfully simple way to create databases" 中获取数据,当涉及到通过 Room persistence 通过 View Model 通过 LiveData 通过 ??? 无论发生什么下一个???。有 "Google" 的方法吗?或者这是 RxJava 和其他库发挥作用的地方?

所以我的问题就是这个 "exactly how can I get a List of entity objects out of the database if there is no onChange event through live data?" 是否有最佳实践或正确的方法来在没有 LiveData 包装器的情况下调用查询?或者直接在 LiveData 包装器中访问查询而不需要更改数据来完成事情的方法?

LiveData解决方案

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

ViewModel

// 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;
}

Activity

// 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);
}

Activity 请注意,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);

ViewModel

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())); 
}

Activity

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();