如何观察数据库中的更改以更新LiveData

时间:2018-10-12 19:06:05

标签: android viewmodel contentobserver mutablelivedata

我正在将应用程序从LoaderManagerCallbacks迁移到使用ViewModelLiveData的实现。我想继续使用现有的SQLiteDatabase

主要实现正常。 Activity实例化ViewModel并创建一个Observer,如果它观察到View中存在的MutableLiveData中的变化,则更新ViewModelViewModel使用SQLiteDatabase通过查询从ContentProvider获取数据(光标)。

但是我有其他活动可以更改数据库,而MainActivity已停止但未被破坏。还有一个后台服务,可以在MainActivity处于前台时对数据库进行更改。

其他活动和后台服务可以更改数据库中的值,因此可以影响MutableLiveData中的ViewModel

我的问题是:如何观察SQLiteDatabase中的变化以更新LiveData

这是MainActivity的简化版本:

public class MainActivity extends AppCompatActivity {
    private DrawerAdapter mDrawerAdapter;
    HomeActivityViewModel homeActivityViewModel;

    private Observer<Cursor> leftDrawerLiveDataObserver = new Observer<Cursor>() {
        @Override
        public void onChanged(@Nullable Cursor cursor) {
            if (cursor != null && cursor.moveToFirst()) { // Do we have a non-empty cursor?
                mDrawerAdapter.setCursor(cursor);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        homeActivityViewModel = ViewModelProviders.of(this).get(HomeActivityViewModel.class);
        homeActivityViewModel.getLiveData().observe(this, leftDrawerLiveDataObserver);
        homeActivityViewModel.updateLiveData(); //,LEFT_DRAWER_LIVEDATA_ID);
    }

    @Override
    protected void onResume(){  // update the LiveData on Resume
        super.onResume();
        homeActivityViewModel.updateLiveData();
    }
}

这是我的ViewModel

public class HomeActivityViewModel extends AndroidViewModel {

    public HomeActivityViewModel(Application application) {
        super(application);
    }

    @NonNull
    private final MutableLiveData<Integer> updateCookie = new MutableLiveData<>();

    @NonNull
    private final LiveData<Cursor> cursorLeftDrawer =
            Transformations.switchMap(updateCookie,
                    new Function<Integer, LiveData<Cursor>>() {
                        private QueryHandler mQueryHandler;

                        @Override
                        public LiveData<Cursor> apply(Integer input) {
                            mQueryHandler = new QueryHandler(getApplication().getContentResolver());
                            MutableLiveData<Cursor> cursorMutableLiveData = new MutableLiveData<>();
                            mQueryHandler.startQuery(ID, cursorMutableLiveData, URI,
                                new String[]{FeedData.ID, FeedData.URL},
                                null,null,null
                            );
                            return cursorMutableLiveData;
                        }
                    }
            );

    // By changing the value of the updateCookie, LiveData gets refreshed through the Observer.
    void updateLiveData() {
        Integer x = updateCookie.getValue();
        int y = (x != null) ? Math.abs(x -1) : 1 ;
        updateCookie.setValue(y);
    }

    @NonNull
    LiveData<Cursor> getLiveData() {
        return cursorLeftDrawer;
    }

    /**
     * Inner class to perform a query on a background thread.
     * When the query is completed, the result is handled in onQueryComplete
     */
    private static class QueryHandler extends AsyncQueryHandler {
        QueryHandler(ContentResolver cr) {
            super(cr);
        }
        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
            MutableLiveData<Cursor> cursorMutableLiveData = (MutableLiveData<Cursor>) cookie;
                             cursorMutableLiveData.setValue(cursor);

        }
    }

}

2 个答案:

答案 0 :(得分:1)

也许您应该看看Room。 Room数据库在后台使用SQLite,并且在数据库中进行了任何更改后会自动通知您的LiveData对象。因此,您无需担心查询和游标等。 看看这个tutorial

答案 1 :(得分:1)

  1. 您不应将游标与LiveData一起使用,而应将其转换为您的数据类型列表。
  2. 您不应在ViewModel中使用数据库查询。创建一个单例存储库,并在存储库内执行所有与数据库相关的查询/插入/删除/更新。存储库应返回LiveData 或LiveData >。插入不仅应更改数据库,还应更改存储库中的LiveData。您应该始终将存储库用于数据的任何操作。

作为单例,这样的存储库将成为数据的“单一真相”。

public class MyRepository {
    private static MyRepository sInstance;

    private final Application mApp;
    private MutableLiveData<MyDataType> mData; 
    // or MutableLiveData<List<MyDataType>> in case your cursor can have several rows

    @NonNull
    public static MyRepository getInstance(@NonNull Application app)
    {
        if(sInstance == null)
            sInstance = new MyRepository(app);
        return sInstance;
    }

    private MyRepository(@NonNull Application app)
    {
        mApp = app; // you will need it for getContentResolver()
    }

    public LiveData<MyDataType> getData() // or LiveData<List<MyDataType>>
    {
        if(mSourcesInfo != null)
            return mSourcesInfo;

        mData = new MutableLiveData<>();
        // 1) use your AsyncQueryHandler to get your data as cursor
        // 2) in onQueryComplete() convert the result cursor
        // to MyDataType or List<MyDataType>
        // and use setValue or postValue for to change your LiveData

        // we immidiately return empty mData, its not a problems
        // because all your Activities has observers that will be fired
        // after you setValue/postValue in the onQueryComplete 
        return mData; 
     }

     public void setData(...)
     {
        // insert data to your database AND also change the mData with 
        // setValue and postValue, so every activity/service that listen on 
        // the changes will be able to use it
     }
}