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);
}
}
}
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!
Related
Android Studio 3.2 Canary 18
kotlin_version = 1.2.50
I have a simple app that uses a recyclerview and adapter. When the app starts is load all the data.
However, when I click the back button and start the app again. It won't display the data (blank).
If I clear the app from memory and start the app. The data will load as normal.
I am loading the data from sqlite and the data is loaded each time. as it populates the insectDataModelList.
After going into the RecyclerView.java source code the reason is the mAdapter is null. However, I have
checked that the adapter is correct when I set it to the recyclerview.
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
...
}
My MainActivity.java is Java
public class MainActivity extends AppCompatActivity {
private RecyclerView rvInsects;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
rvInsects = (RecyclerView)findViewById(R.id.recycler_view);
DatabaseManager databaseManager = DatabaseManager.getInstance(this);
databaseManager.queryAllInsects("friendlyName");
}
private void setupAdapter(List<InsectDataModel> insectDataModelList) {
final LayoutManager layoutManager = new LinearLayoutManager(
this, LinearLayoutManager.VERTICAL, false);
rvInsects.setLayoutManager(layoutManager);
rvInsects.setHasFixedSize(true);
final InsectAdapter insectAdapter = new InsectAdapter(insectDataModelList);
rvInsects.setAdapter(insectAdapter);
insectAdapter.notifyDataSetChanged();
}
/* Callback from database */
public void loadAllInsects(final Cursor cursor) {
InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
final List<InsectDataModel> insectDataModelList = insectInteractorMapper.map(cursor);
/* data loaded with 24 items */
setupAdapter(insectDataModelList);
}
}
InsectAdapter.kt is Kotlin.
class InsectAdapter(private val insectList: MutableList<InsectDataModel>)
: RecyclerView.Adapter<InsectAdapter.CustomInsectHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomInsectHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.insect_row_item, parent, false)
return CustomInsectHolder(view)
}
override fun onBindViewHolder(holder: CustomInsectHolder, position: Int) {
holder.tvFriendlyName.text = insectList[position].friendlyName
holder.tvScientificName.text = insectList[position].scientificName
}
override fun getItemCount(): Int {
return insectList.size
}
class CustomInsectHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val ivDangerLevel: DangerLevelView = itemView.findViewById(R.id.ivDangerLevel)
val tvFriendlyName: TextView = itemView.findViewById(R.id.tvFriendlyName)
val tvScientificName: TextView = itemView.findViewById(R.id.tvScientificName)
}
}
The database I use rxjava2 to do the query
public class DatabaseManager {
private static DatabaseManager sInstance;
private MainActivity mainActivity;
private BugsDbHelper mBugsDbHelper;
public static synchronized DatabaseManager getInstance(MainActivity context) {
if (sInstance == null) {
sInstance = new DatabaseManager(context);
}
return sInstance;
}
private DatabaseManager(MainActivity context) {
mBugsDbHelper = new BugsDbHelper(context);
mainActivity = context;
}
#SuppressLint("CheckResult")
public void queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));
insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Cursor>() {
Disposable disposable;
#Override
public void onSubscribe(Disposable d) {
disposable = d;
}
#Override
public void onSuccess(Cursor cursor) {
mainActivity.loadAllInsects(cursor);
disposable.dispose();
}
#Override
public void onError(Throwable e) {
disposable.dispose();
}
});
}
}
Everything works as expected when the apps installs for the first time. And if you clear it out of memory.
However, its only when you click the back button, and then try and start the app it will not load any data
because of the mAdapter being null in the RecyclerView class.
When I click the back button and then start the app again. All I get is a blank screen i.e.
Updated DatabaseManager class that removes the singleton and used a weakreference to ensure that the MainActivity instance is garbage collected.
public class DatabaseManager {
private WeakReference<MainActivity> mainActivity;
private BugsDbHelper mBugsDbHelper;
public DatabaseManager(MainActivity context) {
mBugsDbHelper = new BugsDbHelper(context);
mainActivity = new WeakReference<>(context);
}
#SuppressLint("CheckResult")
public void queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));
insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Cursor>() {
Disposable disposable;
#Override
public void onSubscribe(Disposable d) {
disposable = d;
}
#Override
public void onSuccess(Cursor cursor) {
mainActivity.loadAllInsects(cursor);
disposable.dispose();
}
#Override
public void onError(Throwable e) {
disposable.dispose();
}
});
}
}
Many thanks for any suggestions,
When you click the back button and relaunch the app, a new instance of MainActivity is started.
At the same time, your DatabaseManager is a singleton. Its reference is stored as a static variable. It survives the activity recreation. It will live until the process is killed.
So, when you run queryAllInsects for the second time, the callback is sent to the old instance of MainActivity, which is not visible anymore.
You should not keep a reference to MainActivity in DatabaseManager. It's a memory leak, because it cannot be garbage collected.
The issue is most likely that you are loading the data in your onCreate() and not in onResume(). When you press back to "close the app" you are not necessarily clearing the UI stack from memory. That's why when you go back into the app, it doesn't invoke onCreate() again, and doesn't load your data again.
Keep everything the same, just move your data loading from onCreate() to onResume(). That way, whenever the screen is shown to the user, the data will load.
Few observations:
You are still passing the MainActivity to the BugsDbHelper class, take care of the reference there.
It's probably a good idea to include a "cleaning method" in Singleton classes, which should be called in onStop() or onDestroy() of an activity. onStop() is preferred since onDestroy() is not guaranteed to be called immediately.
The "cleaning method" in Singleton class should do the following:
a) Nullify any references to the parameters, objects, context or callbacks you have asked as a dependency in the constructor or otherwise.
b) If the Singleton class has created "new" objects with context dependencies, make sure to include similar cleaning methods in these classes too.
To avoid crashes and memory leakage in fragment/activities, make sure you are cleaning up your recycler view/adapter in onStop(). The callbacks can be received anytime, and if that happens while your activity is in the background, you are bound to get a "force close" fortune cookie.
Keep an eye on the activity/fragment lifecycle. A lot of issues are just because of ignoring the lifecycle callbacks. These are there for a reason, utilize them.
Put this 2 lines in onResume() and remove from onCreate() and try it.
DatabaseManager databaseManager = DatabaseManager.getInstance(this);
databaseManager.queryAllInsects("friendlyName");
I suggest the following changes:
MainActivity, the less code you write in the activity the better, move all the data retrieval part to the DatabaseManager. Also setup the RecyclerView once and only update the dataset when appropriate:
public class MainActivity extends AppCompatActivity {
private List<InsectDataModel> insectDataModelList = new ArrayList<>();
private Disposable disposable;
private RecyclerView rvInsects;
private InsectAdapter insectAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setupAdapter();
//Request Data, take advantage of RxJava to load data asynchronously
DatabaseManager.getInstance(this)
.queryAllInsects("friendlyName")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<List<InsectDataModel>>() {
#Override
public void onSubscribe(Disposable d) {
disposable = d;
}
#Override
public void onSuccess(List<InsectDataModel> response) {
insectDataModelList.clear();
insectDataModelList.addAll(response);
insectAdapter.notifyDatasetChanged();
}
#Override
public void onError(Throwable e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});;
}
private void setupAdapter() {
//Setup RecyclerView Only need to be called once
rvInsects = (RecyclerView)findViewById(R.id.recycler_view);
LayoutManager layoutManager = new LinearLayoutManager(this); // LinearLayoutManager is Vertical by default
rvInsects.setLayoutManager(layoutManager); // You don't event have to define it as RecyclerView use LinearLayoutManager.Vertical by default
rvInsects.setHasFixedSize(true);
insectAdapter = new InsectAdapter(insectDataModelList);
rvInsects.setAdapter(insectAdapter);
}
#Override
protected void onDestroy() {
//Dispose observer if activity is destroyed to prevent memory leak
if(disposable != null && !disposable.isDisposed())
disposable.dispose();
super.onDestroy();
}
}
And in DatabaseManager, instead of observing the data source(Cursor) and notify the requester(Activity) via callback, we get the data stream and pass it the caller to observe:
public class DatabaseManager {
private static DatabaseManager sInstance;
private BugsDbHelper mBugsDbHelper;
public static synchronized DatabaseManager getInstance() {
if (sInstance == null) {
sInstance = new DatabaseManager();
}
return sInstance;
}
private DatabaseManager() {
// Move the actualy database initiation to application class or singleton
mBugsDbHelper = BugsDbHelper.getInstance(); // or ApplicationController.getDbHelper();
}
#SuppressLint("CheckResult")
public SingleObserver<List<InsectDataModel>> queryAllInsects(String sortOrder) {
final InsectStorageInteractorImp insectStorageInteractorImp
= new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));
insectStorageInteractorImp.getAllSortedInsects(sortOrder)
.map(new Function<Cursor, List<Object>>() {
#Override
public List<Object> apply(Cursor cursor) throws Exception {
InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
return insectInteractorMapper.map(cursor);
}
});
}
}
Now the solution here is to rely on the RxJava to change the callback pattern to the observer pattern. So instead of passing the activity (callback) and waiting to be called, we get the data steram (observable) and observe it for the response. This eliminate the leak problem all together and enhance the readability and maintainability.
Also don't forget to move the Database initialization to the Application class or a Singleton instance to prevent multiple instantiation. The easier solution would be like:
public class ApplicationController extends Application {
private BugsDbHelper mBugsDbHelper;
#Override
public void onCreate() {
super.onCreate();
mBugsDbHelper = new BugsDbHelper(this);
}
public BugsDbHelper getDbHelper(){
return mBugsDbHelper ;
}
}
So according to android developers: "Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI. ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance."
In the code below there is an asynchronous class that gets called in deleteItem function. My question is this: Does ViewModel also handles the asynchronous calls made inside it or will cause memory leaks?
Thank you
public class BorrowedListViewModel extends AndroidViewModel {
private final LiveData<List<BorrowModel>> itemAndPersonList;
private AppDatabase appDatabase;
public BorrowedListViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
itemAndPersonList = appDatabase.itemAndPersonModel().getAllBorrowedItems();
}
public LiveData<List<BorrowModel>> getItemAndPersonList() {
return itemAndPersonList;
}
public void deleteItem(BorrowModel borrowModel) {
new deleteAsyncTask(appDatabase).execute(borrowModel);
}
private static class deleteAsyncTask extends AsyncTask<BorrowModel, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
#Override
protected Void doInBackground(final BorrowModel... params) {
db.itemAndPersonModel().deleteBorrow(params[0]);
return null;
}
}
}
I would provide an example, probably you need to modify the code.
First you need a live data change and subscribe to that in your view. Then in the controller you post the value telling the subscriber that something appends. This way asynchronously the view would get alerted.
private MutableLiveData<String> databaseLiveData = new MutableLiveData<>();
...
And in the deleteAsyncTask class you can add:
protected void onPostExecute(Void result) {
databaseLiveData.postValue("some data deleted");
}
And in the BorrowedListViewModel class this method to access from the view add this method:
public LiveData<String> getChanger() {
return databaseLiveData;
}
In the view e.g.Activity add this:
private BorrowedListViewModel mBorrowedListViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
BorrowedListViewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);
subscribe();
}
private void subscribe() {
final Observer<String> liveDataChange = new Observer<String>() {
#Override
public void onChanged(#Nullable final String message) {
Log.d("Activity", message);
}
};
liveDataChange.getChanger().observe(this, liveDataChange);
}
Hope this help.
I have implemented class which extends ItemKeyedDataSource and provides paging data from room database's data access object (DAO). My DAO's query methods pass lists of data objects (not wrapped by LiveData) to DataSource callbacks.
What is the recommended way to invalidate DataSource after changes occur in it's wrapped database table, for example if changes come from background Service? How automatic data invalidation is implemented in DataSource.Factory<Integer, T> return parameter that DAOs can generate?
Automatic DataSource invalidation can be implemented by hooking InvalidationTracker.Observer to InvalidationTracker.
You can get InvalidationTracker instance from getInvalidationTracker().
I implemented my InvalidationTracker.Observer like this:
public class DataSourceTableObserver extends InvalidationTracker.Observer {
private DataSource dataSource;
public DataSourceTableObserver(#NonNull String tableName) {
super(tableName);
}
#Override
public void onInvalidated(#NonNull Set<String> tables) {
if (dataSource != null) dataSource.invalidate();
}
public void setCurrentDataSource(DataSource source) {
dataSource = source;
}
}
And I'm using it in my inner DataSource.Factory class like this:
public static class Factory implements DataSource.Factory<TvProgram, TvProgram> {
private Context appContext;
private DataSourceTableObserver observer;
private InvalidationTracker tracker;
private int channelId;
public Factory(Context context, int channelId) {
appContext = context.getApplicationContext();
observer = new DataSourceTableObserver(AppDatabase.PROGRAMS_TABLE);
tracker = AppDatabase.getInstance(appContext).getInvalidationTracker();
tracker.addObserver(observer);
this.channelId = channelId;
}
#Override
public DataSource<TvProgram, TvProgram> create() {
EpgDataSource epgDataSource = new EpgDataSource(appContext, channelId);
observer.setCurrentDataSource(epgDataSource);
return epgDataSource;
}
public void cleanUp() {
tracker.removeObserver(observer);
observer = null;
}
}
When DataSourceTableObserver invalidates DataSource, it's Factory inner class creates new DataSource instance with newest data.
Java POJO Object
public class Section {
#ColumnInfo(name="section_id")
public int mSectionId;
#ColumnInfo(name="section_name")
public String mSectionName;
public int getSectionId() {
return mSectionId;
}
public void setSectionId(int mSectionId) {
this.mSectionId = mSectionId;
}
public String getSectionName() {
return mSectionName;
}
public void setSectionName(String mSectionName) {
this.mSectionName = mSectionName;
}
}
My Query method
#Query("SELECT * FROM section")
LiveData<List<Section>> getAllSections();
Accessing DB
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
But when I omit LiveData from the query I am getting the data as expected.
Query Method:
#Query("SELECT * FROM section")
List<Section> getAllSections();
Accessing DB:
final List<Section> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
This is normal behavior, because queries that return LiveData, are working asynchronously. The value is null at that moment.
So calling this method
LiveData<List<Section>> getAllSections();
you will get the result later here
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
from documentation:
Room does not allow accessing the database on the main thread unless you called allowMainThreadQueries() on the builder because it might potentially lock the UI for long periods of time. Asynchronous queries (queries that return LiveData or RxJava Flowable) are exempt from this rule since they asynchronously run the query on a background thread when needed.
I solve this problem through this approach
private MediatorLiveData<List<Section>> mSectionLive = new MediatorLiveData<>();
.
.
.
#Override
public LiveData<List<Section>> getAllSections() {
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
mSectionLive.addSource(sections, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sectionList) {
if(sectionList == null || sectionList.isEmpty()) {
// Fetch data from API
}else{
mSectionLive.removeSource(sections);
mSectionLive.setValue(sectionList);
}
}
});
return mSectionLive;
}
LiveData is an asynchronous query, you get the LiveData object but it might contain no data. You could use an extra method to wait for the data to be filled and then extract the data.
public static <T> T getValue(LiveData<T> liveData) throws InterruptedException {
final Object[] objects = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer observer = new Observer() {
#Override
public void onChanged(#Nullable Object o) {
objects[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
return (T) objects[0];
}
I resolved the similar issue as follows
Inside your ViewModel class
private LiveData<List<Section>> mSections;
#Override
public LiveData<List<Section>> getAllSections() {
if (mSections == null) {
mSections = mDb.sectionDAO().getAllSections();
}
return mSections;
}
This is all required. Never change the LiveData's instance.
I would suggest creating another query without LiveData if you need to synchronously fetch data from the database in your code.
DAO:
#Query("SELECT COUNT(*) FROM section")
int countAllSections();
ViewModel:
Integer countAllSections() {
return new CountAllSectionsTask().execute().get();
}
private static class CountAllSectionsTask extends AsyncTask<Void, Void, Integer> {
#Override
protected Integer doInBackground(Void... notes) {
return mDb.sectionDAO().countAllSections();
}
}
if sections.getValue() is null I have to call api for data and insert
in into the database
You can handle this at onChange method:
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
if(sections == null || sections.size() == 0) {
// No data in your database, call your api for data
} else {
// One or more items retrieved, no need to call your api for data.
}
}
});
But you should better put this Database/Table initialization logic to a repository class. Check out Google's sample. See DatabaseCreator class.
For anyone that comes across this. If you are calling LiveData.getValue() and you are consistently getting null. It is possible that you forgot to invoke LiveData.observe(). If you forget to do so getValue() will always return null specially with List<> datatypes.
I'm using GreenDAO in my current app, and want to have a LoaderManager with a connection to the DB in order to monitor changes and updates on the DB on the fly.
I've seen in the Android documentation that it's not recommended to use a ContentProvider when your app has only an internal SQLite DB (which is what I have) however, I really want to implement the Observer Pattern in order to change the UI in real time according to the updates in the DB.
I've noticed that in order to use the LoaderManager, I need to give a URI for the CursorLoader.
My question is, is there some sample code anywhere using this?
how can I create a LoaderManager for a Green-DAO?
You don't use ContentProvider and Loaders with greenDAO. At this time, those technologies do not intersect.
Yes, You can write a custom loader where you have to notify about the database changes manually whenever you save data in database.For that purpose you can use Broadcast Receivers,Green robot Event bus etc.See the code below
Custom message loader class to load data whenever it get notified by eventbus.
MessageListLoader.java
public class MessageListLoader extends AsyncTaskLoader<List<Message>> {
private List<Message> mMessages;
private long mGroupId;
private Context mContext;
public MessageListLoader(Context context, long groupId) {
super(context);
mGroupId = groupId;
}
private IMobileService getMobileService() {
return MobileServiceImpl.getInstance(mContext);
}
#Override
public List<Message> loadInBackground() {
return getMobileService().getMessagesByGroupId(mGroupId);
}
#Override
public void deliverResult(List<Message> newMessageList) {
if (isReset()) {
mMessages = null;
return;
}
List<Message> oldMessageList = mMessages;
mMessages = newMessageList;
if (isStarted()) {
super.deliverResult(newMessageList);
}
// Invalidate the old data as we don't need it any more.
if (oldMessageList != null && oldMessageList != newMessageList) {
oldMessageList = null;
}
}
/**
* The OnEvent method will called when new message is added to database.
*
* #param event
*/
#Subscribe
public void onEvent(NewMessageEvent event) {
// reload data from data base
forceLoad();
}
#Override
protected void onStartLoading() {
if (mMessages != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mMessages);
}
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
#Override
protected void onReset() {
mMessages = null;
EventBus.getDefault().unregister(this);
}
}
The mobile service class is used provide all database related services.
MobileServiceImpl.java
public class MobileServiceImpl implements IMobileService {
private static final String TAG = "MobileServiceImpl";
private static final String DATABASE_NAME = "demo.db";
private static IMobileService instance = null;
private DaoSession mDaoSession;
private MobileServiceImpl(Context context) {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, DATABASE_NAME, null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
}
public static IMobileService getInstance(Context context) {
if (instance == null) {
instance = new MobileServiceImpl(context);
}
return instance;
}
private MessageDao getMessageDao() {
return mDaoSession.getMessageDao();
}
/**
* The saveMessage() method is used to save given message into database.
*
* #param message Specifies the message object to be saved.
* #param notifyUi Specifies the boolean flag to notify the change in database to ui.
* #return Saved message id.
*/
#Override
public long saveMessage(Message message, boolean notifyUi) {
long id = getMessageDao().insert(message);
if (notifyUi)
EventBus.getDefault().post(new NewMessageEvent(id));
return id;
}
#Override
public List<Message> getMessagesByGroupId(long groupId) {
return getMessageDao()
.queryBuilder()
.where(MessageDao.Properties.GroupId.eq(groupId))
.orderDesc(MessageDao.Properties.Id).list();
}
#Override
public Message getMessageById(long messageId) {
return getMessageDao().load(messageId);
}
}
Download Sample Project from Here