【问题标题】:Can I add Observer inside Observer? I actually tried but inner observers not working properly. Sometimes one of them work, sometimes no one work我可以在观察者中添加观察者吗?我实际上尝试过,但内部观察者无法正常工作。有时其中一个工作,有时没有一个工作
【发布时间】:2021-06-26 01:54:46
【问题描述】:

我使用 Android 架构组件(ViewModel、LiveData)并使用 Room 通过 Repository 从本地 Sqlite 数据库中获取数据。最外层的观察者就好了,问题只出在内部的观察者身上。

查看代码:

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        BranchDetailsViewModel branchDetailsViewModel = new BranchDetailsViewModel(application);

        Observer<Bank> bankObserver = new Observer<Bank>() {
            @Override
            public void onChanged(Bank bank) {
                if(bank!= null)
                    binding.tvBank.setText(bank.getBank());
            }
        };


        branchDetailsViewModel.getBranchById(branchIid).observe(this, new Observer<Branch>() {
            @Override
            public void onChanged(Branch branch) {

                branchDetailsViewModel.getBankById(branch.getBankId()).observe(getViewLifecycleOwner(), bankObserver);

                branchDetailsViewModel.getCityById(branch.getCityId()).observe(requireActivity(), new Observer<City>() {
                    @Override
                    public void onChanged(City city) {
                        if(city != null)
                        binding.tvCity.setText(city.getCity() + " cty");
                    }
                });

                branchDetailsViewModel.getDistrictById(branch.getDistrictId()).observe(requireActivity(), new Observer<District>() {
                    @Override
                    public void onChanged(District district) {
                        if(district != null)
                        binding.tvDistrict.setText(district.getDistrict());
                    }
                });

                branchDetailsViewModel.getStateById(branch.getStateId()).observe(requireActivity(), new Observer<State>() {
                    @Override
                    public void onChanged(State state) {
                        if(state!= null)
                        binding.tvState.setText(state.getState());
                    }
                });
            }
        });
    }

我使用命名和匿名观察者都试过了。我不明白这里有什么问题。

有时它会抛出 NullPointerExceptions。有时无法更改语言环境。

04-09 15:48:45.182 2096-2113/com.appsbharti.bharatbankdetails E/SQLiteDatabase: Failed to open database '/data/data/com.appsbharti.bharatbankdetails/databases/bank_details.db'.
    android.database.sqlite.SQLiteException: Failed to change locale for db '/data/data/com.appsbharti.bharatbankdetails/databases/bank_details.db' to 'en_IN'.
        at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:386)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:218)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
        at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:804)
        at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:224)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:92)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:53)
        at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:90)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at com.appsbharti.bharatbankdetails.Daos.HistoryDao_Impl.insert(HistoryDao_Impl.java:45)
        at com.appsbharti.bharatbankdetails.Repositories.HistoryRepository$1.run(HistoryRepository.java:37)
        at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:45)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
        at java.lang.Thread.run(Thread.java:856)
     Caused by: android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 1802)
        at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
        at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:548)
        at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:364)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:218) 
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193) 
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463) 
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185) 
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177) 
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804) 
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789) 
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694) 
        at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:804) 
        at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221) 
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:224) 
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164) 
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:92) 
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:53) 
        at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:90) 
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476) 
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281) 
        at com.appsbharti.bharatbankdetails.Daos.HistoryDao_Impl.insert(HistoryDao_Impl.java:45) 
        at com.appsbharti.bharatbankdetails.Repositories.HistoryRepository$1.run(HistoryRepository.java:37) 
        at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:45) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569) 
        at java.lang.Thread.run(Thread.java:856) 

还有一个问题是我不确定我应该在 Fragment 中使用哪个作为 Observer 的所有者,requireActivity()getActivity()(我收到警告说这可能返回 null,所以我没有使用它)或 @987654326 @(获取片段的生命周期所有者对象)。

【问题讨论】:

  • 真正的问题是什么?如果你在每个“onChanged()”方法上放置断点,它会被触发吗?或者您仅通过查看您的用户界面(binding.tvCity、binding.tvState 等...)就发现某些东西无法正常工作?
  • 实际上我通过创建自定义 POJO 并在 sql 中运行长查询以一次发送所有数据来找出解决方案。这个问题的主要问题仍然是我如何确保所有这些内部观察者在父观察者不返回某些东西之前不会运行。 (我相信你知道他们第一次总是返回 null )。我们如何链接观察者,以便一个在另一个获取值之后运行。这个问题对我来说还是很重要的。
  • 它没有回答我的问题;)我多次使用嵌套的“observe()”调用,没有任何问题。在您的情况下,有 LiveData、LifecycleOwner 和 ViewModel 可能是问题所在。因此,如果您将断点放在每个“onChanged()”级别,那么您就可以理解真正的工作流程。另外:我们看不到任何“LiveData.postValue()”,因此更难为您提供帮助。
  • 我遵循 Android 架构。我还听说 RxJava 相当不错。实际上这个应用程序很简单,我不想添加第三方库。我使用了从资产创建的预填充数据库。几乎有 0 个插入。我还搜索了是否可以使用 Room 进行任何只读数据库访问,但一无所获。我个人认为 SqliteHelper 比 ROOM 更好。
  • 如果你了解 Android 架构组件和 ROOM 库,我请求你看看我的另一个问题,如果你有足够的时间。感谢 sql joins,我幸运地解决了这个问题,但解决这个问题很棘手。由于未知原因,它给了我 FOREIGN KEY CONSTRAINT 错误。它也是同一个项目的一部分。 stackoverflow.com/questions/66965010/…

标签: android observable android-livedata observer-pattern


【解决方案1】:

好的,问题是我没有在 synchronized 块中获取 AppDatabase。

在 AppDatabse 类中,我将 newInsance 方法从:

public static AppDatabase getInstance(final Context context) {
    dbInstance = buildDatabaseInstance(context);
    return dbInstance;
}

public static AppDatabase getInstance(final Context context) {
    if (dbInstance == null)
        synchronized (AppDatabase.class) {
            if (dbInstance == null)
                dbInstance = buildDatabaseInstance(context);
         }
//  dbInstance = buildDatabaseInstance(context);
    return dbInstance;
}

现在所有观察者运行顺利。

【讨论】:

    【解决方案2】:
    1. 请使用YouFragmentClassName.thisviewLifecycleOwner
    2. 添加日志以检查您是否收到onChange 回调。
    3. 如果您仍然收到空值,请检查您的 viewModel 代码。

    【讨论】:

    • 观察者链对吗?如果是,他们我的外在观察者组合有什么问题?
    • 你的第一个子观察者正在工作吗(branch / bankObserver)?
    • 它们仅在调试中工作(因为那时应用程序运行速度很慢)。当我运行它时,它会崩溃。但这并不意味着外部观察者总是返回 null。当我们注释掉内部观察者时,设置分支对象详细信息的其余代码工作正常。我认为这是一些多线程问题。
    • 是因为空指针错误而崩溃吗?
    • 我发现这是因为我没有在synchronized 块内返回AppDatabse 实例添加synchronized 块后问题解决了。
    【解决方案3】:

    实际上,viewlifecycleOwnerthis 是可以接受的。使用内部你必须调用YourFragmnent.this

    我看到您没有调用任何函数来更新内部 LiveData 值。 而且你不应该包装嵌套的观察者,因为内部观察者将被调用多次,与调用外部观察者的次数相对应。

    只需要调用更新 LiveData 值的函数来触发内部观察者的 onChanged 回调

    请阅读这篇文章以避免多次消耗数据https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

    【讨论】:

    • 我只是从人口数据库中获取数据。我不会在数据库中插入任何东西。我搜索了只读数据库访问,就像我们使用 SqliteHelper 一样,但在 Room Libraray 中没有找到这样的东西。
    • @gpl,当我阅读您的代码时,我假设您想通过分行的 ID 获取银行、城市、地区和州,对吧?如果是这样,您可以创建查询以通过分支 ID 获取这些内容。您可以在此处查看如何创建查询link
    • 阅读我的 cmets,我通过创建自定义的 Branch POJO 以在单个查询中返回所有数据来找出解决方案。但我仍然不知道如何在 Room 中制作链式观察者。这就是我上面所做的。我正在使用包含 bankId、cityId、districtId 的 branchId 获取 Branch。我在内部观察者中使用这些 Id 来获取它们各自的数据并在屏幕上显示结果。因此我需要分层的观察者。只有当外部观察者返回分支对象时,内部观察者才需要运行。
    • 在调试时他们有时间返回 Branch 对象。但是在 Run 时,Branch 返回为 null 并且内部观察者出现异常。因此,我认为也存在一些线程问题。简单来说,我需要完整的证明解决方案来创建链式观察者。
    【解决方案4】:

    如果你在 Fragment 中,你不能通过 this 来观察,你应该通过 viewlifecycleOwner。在您的情况下,您正在设置观察以从房间获取分支的值,然后根据分支,您的其他观察者被调用。

    这意味着您的内部观察者将在您的 branch 观察者被调用后进行观察。

    当您将观察者绑定到片段的生命周期时,观察者会按照片段本身的生命周期工作。

    用生命周期所有者绑定它应该可以工作,主要是因为this在片段的情况下不起作用。

    【讨论】:

    • 好的,所以我把this改成了getViewLifecycleOwner();,结果还是一样。有趣的是,所有字符串结果都没有任何错误。但是,当我创建多个其他观察者来按 id 搜索城市/州/区时,它总是返回 null。矛盾的是,如果我只搜索其中一个结果总是正确的。我检查了很多次。我的sql是正确的。来自 CityDao 的片段:@Query("SELECT * FROM cities WHERE _id = :id") LiveData&lt;City&gt; getCityById(int id);
    • 我认为这里的多线程存在一些问题。但我正在关注 Android 架构组件。不手动做任何事情。
    • 无论我在 3 个内部观察者中设置一个,结果总是正确的。但是当我使用 2+ 内部观察者时,它们都返回 null
    猜你喜欢
    • 2014-03-24
    • 2011-04-14
    • 2013-01-30
    • 2017-12-04
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多