Android - Best way to access Room Database Asynchronously - android

So I have a Room database with some Users stored in it. This is the current method im using to access the database and get data.
User user;
userDAO = AppDatabase.getAppDatabase(getApplicationContext()).userDao();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
user = userDAO.getUser("testuser1");
runOnUiThread(new Runnable() {
#Override
public void run() {
String name = user.getName();
}
});
}
});
thread.start();
My question is there a better way to do this. I'm doing all this in my Activity code and I'd like to clean it up.
How would you go around implementing something like a static or abstract class which has callbacks to the main thread/activity so I can listen for responses. I'm still new to asynchronous tasks, so apologies if im being stupid.
I also need to be able to access the database from my Service which is always running. Also I sometimes need to save some data when the OnDestroy() method is called, and if I run an thread from there, it results in an crash. Maybe something like a IntentService? But I need to be able to call different methods to get and save different objects.

in AppDatabase class you have to use below code:
#Database(entities = {User.class},version = 1)
public abstract class AppDatabase extends RoomDatabase
{
public abstract userDAO userDao();
}
in UserDAo class you have to use below codes:
#Query("select * from User where user =:search")
User getUser(String search);
for use these methods you can use below codes:
User user;
public static AppDatabase database=null;
if (database==null)
{
database= Room.databaseBuilder(getApplicationContext(),AppDatabase.class,"nameOfDatabase")
.allowMainThreadQueries().build();
}
user=database.userDao.getUser("testuser1");

Related

What process is the best way to send on API and save SQLiteDatabase?

I'm new on Android and working an big app which has sending data to API and saving it on SQlite. All of this process is on one class file . But it leaves me on an error. Sometimes the device hanged. other scenario is the data is incomplete . I have read about Intent Service and Services and I want to learn about the two, but I'm wondering how to get all of my data from UI and put it on services. May I know How?
It depends on the nature of the application. If this should happen in response to a user input...you could well use an AsyncTask. Otherwise, a background service could also do the job.
What you should NEVER do is run a network operation and/or database access on the main UI thread.
Services can receive data via intents. The way to send these intents depend on the type of service (Started, Bound or both). There are plenty of resources out there you can read...here's one from Android documentation...
https://developer.android.com/guide/components/services
An Example of an AsyncTask
The example below shows an implementation of AsyncTask that fetches a user's details from a network resource...
public class FetchUserTask extends AsyncTask<String,Void, UserDTO> {
private FetchUserTaskListener listener;
#Override
protected UserDTO doInBackground(String...params){
if(params == null || params.length == 0)
return null;
String userID = params[0];
UserDataProvider provider = new UserDataProvider(userID);
try {
return provider.get(userID);
}
catch(Exception ex){
//log the error
return null;
}
}
#Override
protected void onPostExecute(UserDTO user){
if(listener != null)
listener.onCompleted(user);
}
public void setListener(FetchUserTaskListener listener){
this.listener = listener;
}
public interface FetchUserTaskListener{
void onCompleted(boolean success);
}
}
How'd you use this AsyncTask?
For example, in an Activity, you would use it as below...
public class UserDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//instantiate activity...
super.onCreate(savedInstanceState);
setContentView(R.layout.whatever_layout);
fetchUser(userId);
}
private void fetchUser(String userID){
FetchUserTask task = new FetchUserTask();
task.setListener(new FetchUserTaskListener<UserDTO>() {
#Override
public void onCompleted(UserDTO user) {
//CAUTION: make sure the activity hasn't been stopped before
//accessing any UI elements and/or context
}
}
task.execute(userID);
}
}
Note
You can (and will need to) make the example(s) above a bit more sophisticated. For example you can have the FetchUserTaskListener's onCompleted method return also an error message if an error occurred.
You will also need to check whether the activity has been paused or stopped before you access any context-bound data otherwise you might get an ILlegalStateException.
Make use of SQLiteOpenHelper class and it has methods to be overridden in your own class by extending SQLiteOpenHelper. Create Add, Update, Delete, Get methods as per your requirement in this class and keep this class as Singleton pattern. User Asynctasks to call those methids and you are done.
Hope that helps you visualise things in better way.

Returning value from inside Runnable

I am using Android Room, I have tried to build a utility method where I retrieve values from the Database. The issue I am having is trying to pass the values back from inside the Runnable which is being executed asynchronously.
public static List<Genre> getAll(final Context context){
AsyncTask.execute(new Runnable() {
#Override
public void run() {
AppDatabase db = AppDatabase.getAppDatabase(context.getApplicationContext());
db.genreDao().getAll();
}
});
return null;
}
What methods do I have to pass this value back but still run in 'not' on the Main Thread?
As already mentioned, you can't return a value asynchronously. And I would advise against using static utility methods for working with the db. Also, AsyncTasks are cumbersome to use. Another option is Rx. For example:
#Dao
public interface GenreDao {
#Query("SELECT * FROM Genre")
List<Genre> getAll();
}
public class GenreRepository {
//...
#WorkerThread
public List<Genre> getAll() {
//Any other source management logic could be placed here, i.e. retrieving from cache or network
return genreDao.getAll();
}
}
public class GenreInteractor {
//...
public Single<List<Genre>> getAll() {
return Single
.fromCallable(genreRepository::getAll)
.subscribeOn(Schedulers.io());
}
}
Then you retrieve your genres list like this:
genreInteractor.getAll().subscribe(genres -> {});
Or if you want your callback to be on main thread:
genreInteractor.getAll().observeOn(AndroidSchedulers.mainThread()).subscribe(genres -> {});

How to call Application getDatabase in Room

I downloaded the Room BasicSample app from here:
https://github.com/googlesamples/android-architecture-components
This sample is a readonly database. There is no example to insert a single entity. I am modifying it, and struggling to figure out how to call the getDatabase so I can do a simple insert on the db on a button click -
getDatabase().wordDao().insert(...) ?
How do I get access to the singleton BasicApp and call getDatabase method, and where do I call it from?
Any help is appreciated.
single Insert
#Insert(onConflict = IGNORE)
void insert(WordEntity word);
AppDatabase.java (not sure if this insert method goes here)
private static void insert(final AppDatabase database, final WordEntity word) {
database.wordDao().insert(word);
}
BasicApp.java
public class BasicApp extends Application {
private AppExecutors mAppExecutors;
#Override
public void onCreate() {
super.onCreate();
mAppExecutors = new AppExecutors();
}
public AppDatabase getDatabase() {
return AppDatabase.getInstance(this, mAppExecutors);
} // ==> how do I get access to this?
public DataRepository getRepository() {
return DataRepository.getInstance(getDatabase());
}
}
In the case you are accessing BasicApp class from an activity or service you can just call ((BasicApp)getApplication()).getDatabase().
Depends a little on what class you are working in.
if it is an activity method (like onCreate):
BasicApp basicApp = (BasicApp) this.getApplicationContext();
AppDatabase appDatabase = basicApp.getDatabase();
//... do work here
If you only have a view (like in an onClickListener which passes a view as an arg):
BasicApp basicApp = (BasicApp) view.getContext().getApplicationContext();
AppDatabase appDatabase = basicApp.getDatabase();
//... do work here

Replacing global variable and synchronized block with realm

I rephrased the question in order not to confuse people on what I am really doing. The example used in the original question was way too simplified.
My project is client/server based. The android app is the client. To properly simply the situation, we can think about that I have two intents/activities and one background thread. Activity A is login . Activity B is where the rest of the app is. The background thread is a socket thread, and it needs to be kept up all the time until user exits the app, or network connection is interrupted.
A state machine is implemented to handle the app states, and this state machine is required by all activities, and the socket thread.
Singleton easily meets the requirement of my design, but there will be a lot of synchronized block and wait statement. I wonder is it possible to use realm to achieve the same goal
Original Question
I am new to Realm Java (Android) development. In my current code, I have a bunch of global variables and using them in a few AsyncTask or background threads.
I'll use some code here to demostrate my example
//Class holding global variable
public class GlobalInfo{
public static String info;
}
//Class changing the global variable
class A{
void doSomething(){
String info = GlobalInfo.info;
info = "start";
synchronized(info){
...... //do something
info = "done";
info.notifyAll();
}
}
}
//background thread waiting for info to be "done". Neglecting the class holding it
void doSomethingAfterDone(){
String info = GlobalInfo.info;
synchronized(info){
while(!info.Equals("done")){
info.wait();
}
//do something
}
}
Assume that when doSomethingAfterDone() is called, the method doSomething() is still running. Therefore, the doSomethingAfterDone() will be waiting for doSomething() to notify before exiting the while loop.
Is it possible to use Realm to replace them? For example, using a realm transaction + a listener waiting for change, rather than applying synchronized block on the global variable and wait for its notification?
I found Realm.waitForChange() will block the execution until there is a changed made to it. However, do all changes that apply to any objects registered or copied to realm returns true for this statement, regardless what get changed?
I know that I can pass String between intents, but I want to know if it is feasible that realm can do the job.
Well you generally don't need global variables at all. You can just write to the Realm on the background thread, and listen for change on the UI thread.
//Class holding global variable
//public class GlobalInfo{
//public static String info;
//}
//Class changing the global variable
class A{
void doSomething(){
//String info = GlobalInfo.info;
//info = "start";
//synchronized(info){
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() { // assuming background thread
#Override
public void execute(Realm realm) {
//!! do something with Realm
}
});
}
new Thread(new Runnable() { // could be merged to this background thread
#Override
public void run() {
doSomethingAfterDone();
}
}).start();
//info = "done";
//info.notifyAll();
//}
}
}
//background thread waiting for info to be "done". Neglecting the class holding it
void doSomethingAfterDone(){
//String info = GlobalInfo.info;
//synchronized(info){
//while(!info.Equals("done")){
// info.wait();
//}
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
//do something
}
});
}
//}
}

how to make sure that only one AsyncTask runs in the background

I have a class, DownloadAndSave that extends from AsyncTask. In its doInBackground method, it retrieves data from an http connection and saves the data using OrmLite, but also cleans the old entries from the database. So, something like this:
doInBackground()
{
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
}
I frequently get a DB exception:
attempt to re-open an already-closed object:SQLiteDatabase
in the clearDb() and saveToDb() functions.
And this is bad since old data from the previous call of DownloadAndSave is mixed with the new data from DownloadAndSave.
In my opinion, I need to make sure that when I start a thread, all of the other treads from the DownloadAndSave class have finished, or in other words I need to run at most one instance of DownloadAndSave at a time. So the question is: how do I make sure that only one instance of DownloadAndSave will run in any point of time?
Option 1. Move above:
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
in a separate class that synchronizes against the class object:
public class WorkerClass {
private WorkerListener workerListener;
public static interface WorkerListener {
public void publishWorkProgress(String data);
}
public WorkerClass(WorkerListener workerListener) {
this.workerListener = workerListener;
}
public void performWork() {
synchronized (WorkerClass.class) {
clearDb();
publish("Cleared DB");
dataList = fetchDataFromHttp();
publish("Got http data");
saveToDb(dataList);
publish("There! saved!");
}
}
private void publish(String message) {
if(workerListener != null) {
workerListener.publishWorkProgress(message);
}
}
}
While from your activity:
public class SampleActivity extends Activity {
public void doTheThing() {
new MyAsyncTask().execute();
}
private static class MyAsyncTask extends AsyncTask<Void, String, Void> implements WorkerListener {
#Override
protected Void doInBackground(Void... params) {
new WorkerClass(this).performWork();
return null;
}
#Override
public void publishWorkProgress(String data) {
publishProgress(data);
}
}
}
Option 2: Move above code to an IntentService:
public class WorkerIntentService extends IntentService {
public WorkerIntentService() {
super(null);
}
#Override
protected void onHandleIntent(Intent intent) {
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
}
}
Using an IntentService guarantees that tasks are executed serially.
Since API version 11 (HONEYCOMB) of the Android API, an AsyncTask can be executed on a given Executor. You can use the default SerialExecutor to execute tasks sequentially.
If you use db operation in doInBackground, you should be locked db for one thread.
public void insertToDb(){
SQliteDatabase db;
...
db.beginTransaction();
...//operation
db.yieldIfContendedSafely();
db.setTransactionSuccessful();
db.endTransaction();
}
I believe that the issue you are facing is that you are starting the AsyncTask from an activity. Your activity is extending ORMLiteBaseActivity which opens the helper (and with that the database) onCreate and closes it onDestroy. When you exit the activity and the background task still hasn't finished then when trying to do write to the database you end up with a closed DB.
ORMLite handles synchronizations internally and i have never needed to do synchronized blocks with it. I use it in all my projects that require a database.
Also for the other answers, the error is a closed database and not concurrent write operations, so synchronization doesn't make sense.

Categories

Resources