英文:
Instantiating View in MVVM model without redundant observable calls?
问题
我正在使用Android的Room数据库来处理后端,并因此定义了一个`Repository`类来创建数据库。`Database`类管理`DAO`,初始化内容等等。
当我的应用程序加载时,`主Activity`观察视图模型的`getAllData()`函数的更改,该函数返回其Live变量的副本。当数据库正在初始化时,`视图模型`在其完成初始化之前请求数据。因此,我必须添加一个observable来在初始化之后捕获该数据。
然而,通常需要大约2个`onChange()`调用才能确保所有UI数据都存在并且视图正确初始化。(在第一个`onChange()`调用时数据为空)。
我对此的问题在于我无法正常地观察它,因为那实质上会复制视图的更新。(视图发送更新 -> 数据库更新 -> observable改变 -> 视图再次被“初始化”)。另外,我希获取掉当数据尚不可用时主Activity尝试初始化视图的混乱初始化代码。
还要注意的是:当数据被清除(通过安卓设置),并且应用程序启动时,按钮显示为空,因为此时从ViewModel获得的数据仍为空。(因此需要额外的observe)。我认为这很可能是由于第一个onChange在数据库最初创建时触发,然后在数据插入到数据库后第二次触发。
对于如何解决这些问题的任何建议将不胜感激!
以下是上述代码的简要概述:
视图模型:
```java
public class ViewModel extends AndroidViewModel {
private Repository repo;
private LiveData<List<Data>> allData;
public ViewModel(Application application) {
super(application);
repo = new Repository(application); // 仓库获取DB实例 -> DB构建带有初始化数据的数据库回调(如果文件未找到)
allData = repo.getAllData();
}
public LiveData<List<Data>> getAllData() { return allData; }
public void update(Data data) { repo.update(data); }
}
主Activity:
protected void onCreate(...) {
// ...
//ViewModel.getAllTempos().observe(this, this::initializeData); <-- 希望不要这样做
Observer<List<Data>> observer = this::initializeData;
observeOnce(ViewModel.getAllData(), observer); // <-- 调用ObserveForever,然后立即删除观察者
// 设置视图文本 - 此时并不会立即发生,此时数据数组为空
if (data.size() > 0) {
for (int i = 0; i < 5; i++) {
buttons[i].setText(Objects.requireNonNull(data.get(i)).number);
}
}
// 设置监听器...(监听器更新模型,模型更新数据库中的条目)
}
// ...
public void initializeData(List<Data> data) {
this.data.clear();
this.data.addAll(data);
// 对数据进行排序...
for (int i = 0; i < data.size(); i++) {
buttons[i].setText(String.format(Locale.getDefault(), "%d", data.get(i).number));
}
}
我一直好奇Transformations是否可以在observable方面对我有所帮助,但对于清理onCreate()
部分中的初始化代码不太确定。
<details>
<summary>英文:</summary>
I am using Android's Room database to handle my backend, and thus have defined a `Repository` class, that creates the database. The `Database` class manages the `DAO`, initializes content, etc..
When my app loads, the `Main activity` observes changes in the View Model's `getAllData()` function, which returns it's copy of the Live variable. While the database is getting initialized, the `View Model` requests for the data before it is done initializing. Thus I have to add an observable to capture that data after-the-fact.
However, it takes about 2 `onChange()` calls for all the UI data to be present and the view to be initialized correctly. (Data is empty on first `onChange()` call).
My issue with this is I can't observe it normally because that would essentially duplicate updates to the view. (View sends updates -> database updates -> observable changes -> view gets "initialized" again). Also, I wish to get rid of the smelly initialization code where the main activity tries to initialize view when the data isn't available yet.
Also to note here: using the observe once, when the data is cleared (through android settings), and the app boots up, the buttons appear blank, as the data obtained from the ViewModel is still empty at this point. (Thus the extra observe needed). I believe this is most likely due to the first onChange firing when the database is initially created, then secondly when data is inserted into the DB.
Any recommendations on how to get around these issues would be appreciated!
Here is a simple outline of the above in code:
View Model:
```java
Public class ViewModel extends AndroidViewMOdel {
private Repository repo;
private LiveData<List<Data>> allData;
public ViewModel (Application application) {
super(application);
repo = new Repository(application); // the repo gets the DB instance -> DB builds database with callback to initialize data (if file not found)
allData = repo.getAllData();
}
public LiveData<List<Data>> getAllData() { return allData; }
public void update(Data data) { repo.update(data); }
}
Main Activity:
protected void onCreate(...) {
// ...
//ViewModel.getAllTempos().observe(this, this::initializeData); <-- rather not do this
Observer<List<Data>> observer = this::initializeData;
observeOnce(ViewModel.getAllData(), observer); // <-- calls ObserveForever, then immediately removes the observer
// Set View text -- this doesn't happen right away, and the data array is null at this point
if (data.size() > 0) {
for (int i = 0; i < 5; i++) {
buttons[i].setText(Objects.requireNonNull(data.get(i)).number);
}
}
// set listeners ... (Listener updates the model, which updates the entry in the db
}
// ...
public void initializeData(List<Data> data) {
this.data.clear();
this.data.addAll(data);
// sort the data ...
for (int i = 0; i < data.size(); i++) {
buttons[i].setText(String.format(Locale.getDefault(), "%d", data.get(i).number));
}
}
I have been curious if Transformations could help me here on the observable side, but not sure about cleaning up the initializing code in the onCreate()
section.
答案1
得分: 0
在最后,我决定使用`MediatorLiveData`类来使观察者无效化,以便不会发生冗余更新。这意味着这个观察者实际上只在应用程序启动的前几秒钟内使用,但至少它不应影响任何与性能相关的内容。
**onCreate()**:
```java
viewModel = new ViewModelProvider(this).get(ViewModel.class);
viewModel.getAllData().observe(this, this::setData);
setData():
public void setData(List<Data> data) {
this.data.clear();
this.data.addAll(data);
Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));
for (int i = 0; i < data.size(); i++) {
buttons[i].setText(String.format(Locale.getDefault(), "%d", data.get(i).tempo));
}
}
ViewModel:
public class ViewModel extends AndroidViewModel {
private Repository repository;
private LiveData<List<Data>> dbAllData;
private MediatorLiveData<List<Data>> allData;
public ViewModel (Application application) {
super(application);
repository = new Repository(application);
dbAllData = repository.getAllData();
allData = new MediatorLiveData<>();
allData.addSource(dbAllData, observedList -> {
allData.setValue(observedList);
if (Objects.requireNonNull(observedList).size() == 5) {
allData.removeSource(dbAllData);
}
});
}
public MediatorLiveData<List<Data>> getAllData() { return allData; }
public void update(Data data) { repository.update(data); }
}
英文:
In the end I decided with using the MediatorLiveData
Class to invalidate the observer so that redundant updating would not happen. This means this observer is only really used for a couple (micro)-seconds on app start-up, but at least it shouldn't impact anything performance related.
onCreate():
viewModel = new ViewModelProvider(this).get(ViewModel.class);
viewModel.getAllData().observe(this, this::setData);
setData():
public void setData(List<Data> data) {
this.data.clear();
this.data.addAll(data);
Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));
for (int i = 0; i < data.size(); i++) {
buttons[i].setText(String.format(Locale.getDefault(),"%d", data.get(i).tempo));
}
}
ViewModel:
public class ViewModel extends AndroidViewModel {
private Repository repository;
private LiveData<List<Data>> dbAllData;
private MediatorLiveData<List<Data>> allData;
public ViewModel (Application application) {
super(application);
repository = new Repository(application);
dbAllData = repository.getAllData();
allData = new MediatorLiveData<>();
allData.addSource(dbAllData, observedList -> {
allData.setValue(observedList);
if (Objects.requireNonNull(observedList).size() == 5) {
allData.removeSource(dbAllData);
}
});
}
public MediatorLiveData<List<Data>> getAllData() { return allData; }
public void update(Data data) { repository.update(data); }
}
专注分享java语言的经验与见解,让所有开发者获益!
评论