35

I have a Simple DAO including CRUD function

FeedEntryDAO.java

@Dao
public interface FeedEntryDAO {

  @Query("SELECT * FROM feedEntrys")
  LiveData<List<FeedEntry>> getAll();

  @Query("SELECT * FROM feedEntrys WHERE uid = :uid LIMIT 1")
  LiveData<FeedEntry> findByUid(int uid);

  @Insert
  void insertAll(FeedEntry... feedEntries);

  @Delete
  void delete(FeedEntry feedEntry);

  @Update
  int update(FeedEntry feedEntry);

}

For the select , it is okay to return the LiveData type.

Inside the Activity the code is pretty for the selection

viewModel.getFeedEntrys().observe(this,entries -> {...});

However, when I try to insert, update , delete the data. The code seems a little bit ugly and also create a asynctask every time.

new AsyncTask<FeedEntry, Void, Void>() {
                @Override
                protected Void doInBackground(FeedEntry... feedEntries) {
                  viewModel.update(feedEntries[0]);
                  return null;
                }
}.execute(feedEntry);

I have 2 question about it:

  1. Can I use LiveData to wrap the delete, insert , update function ?
  2. Better way to maintain such asynctask class for delete, insert , update?

Appreciate any suggestions and advices. Thank you.

Sagar
  • 23,903
  • 4
  • 62
  • 62
Long Ranger
  • 5,888
  • 8
  • 43
  • 72

7 Answers7

9
  1. Can i use LiveData to wrap Delete, Insert, Update calls?

No, you can't. I wrote an answer to the issue. The reason is, that LiveData is used to notify for changes. Insert, Update, Delete won't trigger a change. It will return the deleted rows, the inserted ID or the affected rows. Even if it looks horrible it makes sense not having LiveData wrapped around your stuff. Anyway, it would make sense to have something like Single around the calls to let the operation triggered and operated on a RX-Java operation.

If you want to trigger those calls, you observe on a selection query which notify your LiveData onec you have updated, inserted or deleted some/any data.

  1. Better way to maintain such asynctask class for delete, insert , update?

After looking at your example it looks like that you misuse the (Model/View/)ViewModel-Pattern. You should never access your repository in your view. I'm not sure if you'r doing this because its not visible in your sample. Anyway, after observing your LiveData and getting a result, there's no need to wrap the updating of data inside your viewModel in an AsyncTask. That means, that you should alway take care of

a) view <-> viewmodel <-> repository and not view <-> repository and view <-> viewmodel

and

b) don't try to use threads which are not needed. You observe LiveData on a Background Thread (@WorkerThread) by default (if not annotated with @MainThread) and get the value in the ui-thread (@MainThread).

Emanuel
  • 8,027
  • 2
  • 37
  • 56
  • In fact, my activity does not call the repository but the view model instead. My sample does not show it clearly. It's my failure. Using Rxjava to handle this issue will be a better idea. Thanks for your comment, I will try to modify the code and post the better one here. – Long Ranger Sep 30 '17 at 14:09
  • 1
    Alright, will follow this question and modify my answer as soon as you have updated your code – Emanuel Sep 30 '17 at 14:11
  • 7
    Anyway.. I think that the framework should offer more easy-to-use utilities for scheduling short jobs in the background. I mean.. if you don't use external libraries like RxJava, you are forced to use AsyncTask or IntentService or stuff like this.. but in order to be managed correctly they require a lot of boilerplate code. – andrea.rinaldi Nov 06 '17 at 08:59
  • 1
    Thats how they Catch you to use kotlin :-) – Emanuel Nov 06 '17 at 09:01
  • @EmanuelS i not understood how to make an insert inside viewmodel without asnyctask – Alessandro Scarozza Jun 24 '19 at 16:18
  • @EmanuelS How can we make sure to observe(fetch) the data only after the insert operation is complete? I mean suppose if I have 4 different tables in the database and all of them are being inserted in different `AsyncTask`(s), so they will be doing that in 4 background threads, so how can I make a mechanism to wait before all insert operations are complete before fetching? – Aman Grover Apr 18 '20 at 09:10
5

Concerning question 2:

For Kotlin users there is now a really nice way to achieve this, because since Room 2.1 there is direct support for coroutines. A neat example is given here.

You can use a "suspend function" directly in the DAO, which takes care that nothing is executed on the main thread:

@Dao
 interface BarDao {

   @Query("SELECT * FROM bar WHERE groupId = 2")
   fun getAllBars(): LiveData<MutableList<Bar>>

   @Query( "SELECT * FROM bar WHERE groupId = 0 LIMIT 1")
   fun getRecentBar(): LiveData<Bar>

   @Insert
   suspend fun insert(bar: Bar)

   @Update
   suspend fun update(bar: Bar)

   @Delete
   suspend fun delete(bar: Bar)

}

then in your viewModel you would just:

    fun insert(bar: Bar) = viewModelScope.launch {
        barDao.insert(bar)
    }

    fun update(bar: Bar) = viewModelScope.launch {
        barDao.update(bar)
    }

    fun delete(bar: Bar)= viewModelScope.launch {
        barDao.delete(bar)
    }
nulldroid
  • 1,150
  • 8
  • 15
4

For the second question, there is another neater alternative to AsyncTask; which is using java Executor, the good news is that you can use a single instance of Executor instead of multiple instances of the AsyncTask for all CRUD operations.

Demo Example

public class Repository {

    private static Repository instance;
    private MyDatabase mDatabase;
    private Executor mExecutor = Executors.newSingleThreadExecutor();

    private Repository(Application application) {
        mDatabase = MyDatabase.getInstance(application.getApplicationContext());
    }

    public static Repository getInstance(Application application) {
        if (instance == null) {
            instance = new Repository(application);
        }
        return instance;
    }


    public void insert(final MyModel model) {

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().insert(model);
            }
        });
    }

    public void update(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().update(model);
            }
        });
    }

    public void delete(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().delete(model);
            }
        });
    }

}
Zain
  • 37,492
  • 7
  • 60
  • 84
  • 1
    Nice answer from 5 years ago. I am using Room database with Android Architecture Components like LiveData and ViewModel. Is Executors.newSingleThreadExecutor(); still best method rather than AsyncTask? – AJW May 09 '19 at 02:00
  • Another question. If I do multiple inserts within my MainActivity won't the above code keep creating a new executor for every insert? Or does the Repository singleton instance only create a single executor that can be re-used if there are multiple inserts? – AJW May 09 '19 at 02:11
  • 1
    Hi @AJW for the 2nd "and good" question, only a single executor instance is created for all inserts, that bcz the reason you mentioned as Repository is singleton, the Repository class member variables are only created during class instantiation (same as constructors), so they're only called once. you can ensure that by using 'static' mExecutor ... – Zain May 09 '19 at 03:40
  • 1
    For first question, you're right Executors are obsolete but not deprecated; but official doc says AsyncTask is good for short operation; what if you're deleting or inserting bulk of data at once into database that can take a while?, also AsyncTask maybe the best solution that I still use when updating UI by onPostExecute, but here with database CRUD, architecture components do that for you, so maybe executors are more suitable. – Zain May 09 '19 at 03:52
  • When you use LiveData then the CRUD is done for you behind the scenes. For updates, inserts and deletes, you have to use something. So why do you say Executors are obsolete? Is there some other newer method to use besides AsyncTask that is better than Executors? – AJW May 09 '19 at 14:38
  • 1
    @AJW sorry if this misunderstood, I just wanted to illustrate your phrase "Nice answer from 5 years ago", that I know Excutors are old, but not deprecated when AsyncTask comes into life; I meant that if AsyncTask is new that doesn't mean it can be used in all cases. – Zain May 10 '19 at 11:24
  • @Zain does this have any advantage over simply doing: ``` AsyncTask.execute(Runnable) ``` It seems - unless I'm missing something - with this static method, and therefore no need to instantiate your own Executor, like this would be one tad bit easier. – Kevin Worth May 25 '19 at 23:59
  • @KevinWorth there're a couple of things that are mentioned in AsyncTask documentation: first is that it just lives for a few seconds; so if you just do an operation that takes a few seconds at the most, then both AsyncTask & Executors are equal, but Executors are typical in long operations such as deleting a bunch of rows in your database for instance. – Zain May 28 '19 at 17:52
  • the second thing is that AsyncTask is created to publish results on the UI thread when your background thread is done; and here in database may not be the case that you want to update UI for; you can just CRUD your database probably without having changes in UI, and so maybe Executors are enough though – Zain May 28 '19 at 17:53
3

To Address your 2nd Question:

Google has posted an Android Room Codelab here which has laid out a concise MVVM architecture for implementing Room in Android:

MVVM Architecture
(source: google.com)

Here the recommendation is to have database operations handled by a public static ExecutorService within the Database class. The location of the ExecutorService class can vary, just remember the idea is in MVVM your view does not care about how data are actually CURD'ed, that's the responsibility of the ViewModel, not View.

github repository for this code lab

In short, to apply a similar idea to your code, it would be something like this:

class YourRepository {
    private FeedEntryDAO mFeedEntryDAO;

    YourRepository(Application application) {
        YourDatabase db = YourDatabase.getDatabase(application);
        mFeedEntryDAO = db.feedEntryDAO();
        mAllWords = mWordDao.getAlphabetizedWords();
    }


    void update(FeedEntry feedEntry) {
        Database.databaseExecutor.execute(() - > {
            mFeedEntryDAO.update(feedEntry);
        });
    }
}

class YourViewModel extends ViewModel {
    private YourRepository mRepository;
    void update(FeedEntry feedEntry) {
        mRepository.update(feedEntry)
    }
}

By doing this, your View can directly call viewModel.update(feedEntries[0]).

One important thing to mention is the mFeedEntryDAO.update(feedEntry) will automatically trigger the onChanged callback of your observer on the getFeedEntrys LiveData.

This is quite handy in your case. You can read more about how the trigger happens here.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Wei
  • 51
  • 3
3

The supported return types for each of the supported libraries are listed here. I've included the table here for convenience.

Query type Kotlin language features RxJava Guava Jetpack Lifecycle
One-shot write Coroutines (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> N/A
One-shot read Coroutines (suspend) Single<T>, Maybe<T> ListenableFuture<T> N/A
Observable read Flow<T> Flowable<T>, Publisher<T>, Observable<T> N/A LiveData<T>

(Web archive link)

star4z
  • 377
  • 3
  • 10
1

You can use @Dao annotation in abstract classes too, so:

  1. Create an abstract @Dao BaseDao class with the abstract methods @Insert insert(entities) and with the concrete method insert(entities, callback) that do that ugly AsyncTask job, calling the abstract @Insert insert(entities) on onBackground and your callback on onPostExecute.
  2. Make your FeedEntryDAO also abstract extend BaseDao and the @Query methods abstract.

The result usage in Kotlin is quite pretty:

database.entityDao().insert(entities) { ids ->
    // success
}
Allan Veloso
  • 5,823
  • 1
  • 38
  • 36
0

To app's UI to update automatically when the data changes this, use a return value of type LiveData in your query method description. Room generates all necessary code to update the LiveData when the database is updated.

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}

Note: As of version 1.0, Room uses the list of tables accessed in the query to decide whether to update instances of LiveData.

Malwinder Singh
  • 6,644
  • 14
  • 65
  • 103