4

I am migrating an app from a LoaderManager with Callbacks to an implementation using ViewModel and LiveData. I would like to keep using the existing SQLiteDatabase.

The main implementation works OK. The Activity instantiates the ViewModel and creates an Observer which updates the View if it observes changes in the MutableLiveData that lives in the ViewModel. The ViewModel gets it data (cursor) from the SQLiteDatabase through a query using a ContentProvider.

But I have other activities that can make changes to the database, while MainActivity is stopped but not destroyed. There is also a background service that can make changes to the database while the MainActivity is on the foreground.

Other activities and the background service can change values in the database and therefore can have an effect to the MutableLiveData in the ViewModel.

My question is: How to observe changes in the SQLiteDatabase in order to update LiveData?

This is a simplified version of 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();
    }
}

This is my 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);

        }
    }

}
Paul Naveda
  • 724
  • 8
  • 16
  • probably using https://developer.android.com/reference/android/database/ContentObserver (and notifying in the contentresolver! https://stackoverflow.com/questions/24465999/content-resolver-notifychange-not-working) – zapl Oct 12 '18 at 19:11
  • That maybe a good way to go ahead. I will look into that a bit further. Thanks! – Paul Naveda Oct 12 '18 at 20:03
  • I implemented a simple `ContentObserver` and it works like a charm. Thanks for your help! @zapl – Paul Naveda Oct 13 '18 at 13:32

1 Answers1

0

Maybe you should take a look Room. A Room database uses SQLite in the background and will automatically notify your LiveData objects when any changes have been made in the database. Thus you never need to worry about queries and cursors and so on. Take a look at this tutorial!

  • Thanks. I will definitely look into `Room`. Unfortunately, this is an existing app with a rather complex contentprovider and database. Android deprecated the `CursorLoaders` (which worked perfectly fine btw), so I was thinking to migrate to ViewModels first and keep the app up-and-running. After that, I can take steps to implement `Room`. – Paul Naveda Oct 13 '18 at 13:29
  • Alright, in that case ContentObserver might work better :) – Criticalstone Oct 15 '18 at 14:32