【问题标题】:Can you use room and not use a LiveData object?你可以使用房间而不使用 LiveData 对象吗?
【发布时间】:2020-10-10 02:11:54
【问题描述】:

我目前的用法是将 5 个特定按钮的信息存储到数据库(房间)中,以便在重新启动时将其保留。我当前的使用不依赖于数据的更改,因为唯一更改数据的是长按按钮的用户(然后我更新数据库)。因此我不需要 LiveData 变量,这让我很难初始化我的 ViewModel。

本质上,由于 LiveData 对象仅在更改时更新,因此我的数据永远不会被初始化。 因此,应用程序在启动时总是会导致一个空指针。

我将在下面分享我的设置要点。我希望有一些方法可以让我不必观察任何 LiveData 对象,并且可以在实例化模型时获取数据。

实体:

@Entity(tableName = "myEntity")
public class MyEntity {
    @PrimaryKey
    public int buttonID;

    // other fields...
}

道:

@Dao
interface MyDAO {
    @Query("Select * from myDB")
    LiveData<List<MyEntity>> getEntityList();
    // I think this needs to change to just List<MyEntity>?

    // also insert and update here...
}

存储库:

class MyRepository {
    private MyDAO myDAO;
    private LiveData<List<MyEntity>> allEntities;

    MyRepository(Application application) {
        MyDatabase db = MyDatabase.getInstance(application);
        myDAO = db.myDAO();
        allEntities = myDAO.getAllEntities();
    }

    LiveData<List<MyEntity>> getAllEntities() { return allEntities; }

    // Update entity...
}

视图模型:

public class ViewModel extends AndroidViewModel {
    private MyRepository repository;
    private List<MyEntity> tempList;
    private HashMap<MyEntity> allEntities;

    public ViewModel (Application application) {
        super(application);
        repository = new MyRepository(application);

        Observer<List<MyEntity>> observer = data -> tempList = data;
        ObserveOnce(repository.getAllEntities(), observer); // ObserveOnce implementation found in this answer: https://stackoverflow.com/a/59845763/10013384
        allEntities = new HashMap<>();

        for (int i = 0; i < tempList.size(); i++) { // Nullpointer here, as tempList doesn't have any items yet.
            allEntities.put(tempList.get(i).buttonID, templist.get(i));
        }
    }

    // getter and update methods...
}

活动:

// ...
protected void onCreate(Bundle savedInstanceState) {
    // ...
    viewModel = new ViewModelProvider(this).get(ViewModel.class);

    // Initialize UI views with data from ViewModel
}

然后在各自的监听器中:

@Override
public boolean onLongClick(View v) {
    int index = (Integer) v.getTag();

    data.get(index).foo = fooNewUIData;
    ButtonArray[index].setText(fooNewUIData);
    ViewModel.update(data.get(index));  // if updated, update the ViewModel and the database
}

【问题讨论】:

  • 您的存储库实现看起来很奇怪。如果getAllEntities() 应该从 Room 返回数据,您需要在某处涉及您的 DAO。现在,您正在返回一个未初始化的 LiveData,这不太可能正常工作。此外,通常,视图模型使用数据将LiveData 暴露给活动/片段,而不是尝试使用LiveData 本身。
  • 哎呀,对不起,我会把我的 DAO 放在这里以供参考
  • “我会把我的 DAO 放在这里以供参考”——是的,但你没有使用它。
  • 用构造函数更新
  • 我什至没注意你的代码细节; 但这个问题本身就很好! 你可以在没有 LiveData 的情况下使用 Room 吗?即使对 SQL 了解很多,我也感觉自己像个使用 Room 的 n00b;有时,您只能通过 LiveData, due to the limitation LiveData 与数据库进行交互,这意味着 -> 您只能从 LifecycleOwners 观察它 -> AFAIK 只有活动才是。 PD:我仍然拒绝学习 Kotlin,我继续使用 Java:P

标签: java android android-room viewmodel android-livedata


【解决方案1】:

由于 LiveData 对象仅在更改时更新,因此我的数据永远不会被初始化

这不是你的问题。您的问题是您认为ObserveOnce() 是一个阻塞调用,并且当它返回时结果将立即准备好。实际上,来自 Room 的LiveData 确实在后台线程上工作。当数据在您的Observer 中可用时,您需要做出反应,而不是假设它会在下一条语句中可用。

【讨论】:

  • 我现在意识到这是我的问题。你有什么建议我可以解决这个异步问题吗?我可以使用allowMainThreadQueries,因为我的数据库只有几项大,但似乎普通民众认为这是一种反模式。如果您可以使用建议的解决方案更新您的答案,我们将不胜感激!
  • @MatthewStrom:正如我在 cmets 中提到的,典型的解决方案是让您的ViewModel 使您的活动/片段可以使用LiveData,并让它正常观察LiveData。当observe()(或lambda 表达式)被调用时,活动/片段使用它来显示它应该显示的任何内容。大多数(如果不是全部)涵盖 Jetpack 的书籍和课程(LiveData、ViewModel 等)都应该展示这种方法。
  • @MatthewStrom:我已经没有太多 Java 示例了。 This one 有点牵扯。 MainMotor 是我的ViewModel,它使用map() 转换将我的DAO 中的LiveData 转换为MainViewState 的实例。我的活动观察到 LiveData 并应用这些视图状态,特别是更新 RecyclerView 以显示已保存书签的列表。
【解决方案2】:

是的,当然可以。只需将DAO 中特定函数的返回类型从LiveData&lt;MyDataClass&gt; 更改为MyDataClass。

有关Room 的更多教程,请参阅此Codelab。

【讨论】:

  • 关于这种方法还有什么要了解的吗?如果是这样,那么值得编辑您的答案以进行一些总结。我这么说是因为我记得很久以前甚至在发现这个问题之前就尝试过这个,它只是对我不起作用。
  • 你到底有什么没去上班? Codelab 应包含有关如何开始使用 Room 的所有信息,当然包括 DAOs 和 LiveData。
  • Codelab 太冗长了;这就是为什么我建议在答案中添加更多相关信息;我刚刚尝试了您的回答;只需在 DAO 方法的返回类型中将 LiveData&lt;T&gt; 替换为 T。我尝试这样做的任何时候都没有获得预期的结果,所以每次我需要检索信息以在 UI 中显示某些内容时,我都不得不使用 LiveData。
  • 还有一个很好的理由将您的 Codelab 中的一些相关信息添加到答案中:您永远不知道有一天链接是否会因某种原因而断开。
  • 我同意。但是,我已经一年多没有使用 Room 了,我不记得我是如何让它工作的。我只知道它有效,也许他们在我提出问题后从 Room 中删除了该功能。
【解决方案3】:

当然可以,你可以简单地返回普通的对象类。 LiveData 只有在您想观察这些行的变化时才需要使用。

你也可以使用 kotlin 流来继续监听变化而不使用LiveData

没有 LiveData:

List<MyEntity> getAllEntities();

使用 Kotlin 流:

fun getAllEntities(): Flow<List<MyEntity>>

希望对你有帮助!!

【讨论】:

  • 感谢您的想法。但是,一旦我使用了这种方法,我实际上是在同步访问数据库,而 Room/android 不喜欢这种方式。 (我收到Cannot access database on the main thread since it may potentially lock the UI for a long period of time. 错误)。是否可以异步执行此操作?我感觉任何异步获取仍然会导致我的空指针。
  • 因此,当您这样做时,是的,您正在同步访问相同的内容,在遗留的 java 代码中,您将不得不创建一个 asyncTask 来执行此操作,在 Koltin 中更容易,因为您可以使用 @987654326 @ 功能。搬到 Kotlin,生活会更好。
  • 并且添加到执行代码是异步的不应该是使用LiveData的理由
  • 我认识到 Kotlin 或许能够克服 Android SDK 中的一些 Java 缺陷,但是,像我这样的一些固执的人仍然使用 Java 并拒绝学习 Kotlin。我们 Java 用户是否注定要依赖 LiveData 在用户界面中显示任何 SELECT 结果? ;-/
【解决方案4】:

好的,感谢所有提供的答案和反馈,非常感谢!

最终我同意@CommonsWare 的观点,处理这个问题的最简单方法可能是不让ViewModel 使用LiveData。我选择在我的 Activity 中正常设置 Observer,并在将数据保存到我的 Activity 类时将其转换为我想要的格式 (ArrayList)。

我最初担心的数据没有按照我的喜好排序的问题可以通过简单的排序来解决:

Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));

至于异步问题,我只是回退到允许观察者的回调在获取数据时更新数据(并相应地更新 UI)。如果这种情况涉及更多数据,那么当应用程序加载数据时,我可能需要某种启动画面。但幸运的是,我现在还不需要这样做。

活动:

// onCreate() {
    ViewModel = new ViewModelProvider(this).get(ViewModel.class);
            Observer<List<MyEntity>> observer = this::setData;
            observeOnce(ViewModel.getAllEntities(), observer);

    if (data.size() > 0) {
        // Initialize UI views
    }
// }
//...

public void setData(List<MyEntity> entities) {
    this.data = entities
    Collections.sort(this.data, (o1, o2) -> Integer.compare(o1.buttonID, o2.buttonID));

    // also update UI if need-be
}

剩下的唯一事情就是解决我的数据库在重新启动后不持久的问题,但这超出了这个问题的范围。

【讨论】:

    猜你喜欢
    • 2021-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-27
    • 1970-01-01
    相关资源
    最近更新 更多