Room database check for a condition before insert - android

I have to insert a value to table one in a hour for a specific id. I know it can be achieved by SQLite trigger but i read somewhere else that Room database currently doesn't support SQLite trigger function.
How to achieve above task? I have included my data model here.
#Entity(tableName = "device_table")
public class Device {
#PrimaryKey (autoGenerate = true)
private int id;
private String userId;
private long time;
public Device(String userId) {
this.userId = userId;
}
// and rest of the getter setter methods
}

I think you don't need SQLite trigger there (if update of your row doesn't depend on insert/delete another row|rows). You can divide your task to 2 subtasks:
How to create function, that updates row in Sqlite with some id.
How in Android invoke this function once in an hour.
For first subtask decision depends on how you want to update your data.
The simplest decision here - to use in your DAO interface method with #Insert annotation:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDevice(device: Device)
You should put "updated" instance of your data class Device to this method.
"OnConflictStrategy.REPLACE" means that if row with this "id" exists, then row will be overridden, else - it will be added.
For second subtask you can use one of the mechanisms - Handler, Executor, Timer, AlarmManager, WorkManager (last works even if your app is inactive). There are many answers/examples on using them here on SO.

Related

Is it a good practice to observeForever in Repository class? db+network paged list

Im building an app following architecture guidelines.Implemented room db caching + network.Need to get latest page number from separate entity.
My model:
#Entity(tableName = "top_rated_movie_page")
public class Top_Rated_Movies_Page {
#PrimaryKey(autoGenerate = true)
private int db_id;
private Integer page;
private Integer total_results;
private Integer total_pages;
#Ignore
private List<Result> results;
...
Result class contains data which i display in my paged list which observes changes from db.
Using PagedList.BoundaryCallback i need to fetch new data from network and insert it into db.
But i need to get page number somehow.
My dao:
#Insert
void insertAll(Top_Rated_Movies_Page page,List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM top_rated_movie_page WHERE page= (SELECT MAX(page) FROM top_rated_movie_page)")
LiveData<Top_Rated_Movies_Page> getMoviePage();
I was thinking to observe Top_Rated_Movies_Page from db in my repository class with observeForever() to get that page number.
Is that the best way to approach this?
Since the only time you'll read the next page key or update the backing DB is through BoundaryCallback, you can just read / write your next page key directly.
So in your onItemAtEndLoad() implementation you want something like:
String nextMoviePage = db.runInTransaction(() -> {
movieDao.nextRemoteKey().key;
});
// Make sure not to run on main thread
MovieNetworkResponse response = networkApi.fetchNextMoviePage(remoteKey);
db.runInTransaction(() -> {
movieDao.clearAll(); // Remove previous key
movieDao.insertKey(RemoteKey(response.nextPageKey)); // Insert new key
movieDao.insertAll(response.movies); // Update DataSource + invalidate()
});
Your DAO:
#Insert
void insertAll(List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM result_key LIMIT 1")
String nextRemoteKey();
#Insert
void insertKey(RemoteKey remoteKey);
And don't forget to clear out both the items and remoteKey whenever you expect to refresh the data!
In the case where you want to keep track of different keys for query, you can simply add that column to your RemoteKey entity.
FYI: Paging2 has been superseded by Paging3 (though just launched alpha01), here is the similar Paging3 sample which solves exactly your use-case: https://github.com/android/architecture-components-samples/blob/master/PagingWithNetworkSample/app/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/repository/inDb/PageKeyedRemoteMediator.kt

in Room, Why I could #insert a row in a table but unable to retrieve it with SQL request?

I have a problem with room (my fist time with room).
I can insert some data in my DB but not retrieve something. I'm stuck with this problem. If you can explain me.
I explain :
I use Android Architecture Components. So I have an DAO to make my SQL requests. I have a repository to not handle my DB directly.
And finally, I have a viewModel to be not worry by the data persistance.
My DAO uses the annotation #Insert to create a row .
I use Stetho to look through my DB and everything is oK.
My row is created.
So, When I want to get it with another SQL request (by the same way DAO => repository =>ViewModel),
my request returns always null.
// --- DAO ---
#Query("SELECT * FROM users WHERE id = :id")
LiveData<RealEstate> getRealEstate(long id);
// --- REPOSITORY ---
private final RealEstateDao realEstateDao;
public RealEstateRepository(RealEstateDao realEstateDao) { this.realEstateDao = realEstateDao; }
public LiveData<RealEstate> getRealEstate(long id) { return this.realEstateDao.getRealEstate(id); }
// --- VIEWMODEL ---
public LiveData<RealEstate> getUser(long userId) {
return userDataSource.getRealEstate(userId);
}
I think that you are using incorrectly LiveData, Room have bridge adapter for RXJava/Kotlin, Coroutines, etc.
First, change the return value to something like this
#Query("SELECT * FROM users WHERE id = :id")
RealEstate getRealEstate(long id);
Remember that you cannot make database operations in the Main Thread, you should be using a mechanism for that RX, Coroutines, threads, etc.
After this, you can use LiveData in your viewmodel wrapping the data retrieved from your database to notify their UI parent.

How to get the row count of Room database in android?

I'm following the practice of having a Repository and a Dao and so on. I was trying to get the row count in my database repository by having a function
int getNumFiles() {
List<AFile> lst = files.getValue(); // files is of type LiveData<List<AFile>> files;
if (lst == null) {
return 0;
} else {
return lst.size();
}
}
But lst always evaluates to null. I guess it has something to do with me not being allowed to query the DB from the UI thread or something? Should I implement it like one implements adding or deleting an element? In other words have a function in the Dao which is called via an AsyncTask in the Database repository? I'm confused about how to do this very simple thing.
There is this answer which shows what one would write in the Dao to find out the number of rows, but it does not explain how the repository should call this.
Room database Count Table Row
#Query("SELECT COUNT(column_name) FROM tableName")
LiveData<Integer> getRowCount(); //with LiveData
#Query("SELECT COUNT(column_name) FROM tableName")
int getRowCount();
I ended up doing it like this (using a new thread for the query).
In the Dao
#Query("SELECT COUNT(id) FROM table")
int getCount();
In the repository
int getNumFiles() {
return afileDao.getCount();
}
Where I need it
final AtomicInteger fcount = new AtomicInteger();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
int num = f_repo.getNumFiles();
fcount.set(num);
}
});
t.setPriority(10);
t.start();
t.join();
// use as fcount.get()
Let's see if this works. I may be off base, but I have struggled with this same issue trying to learn Room databases and most recently trying to get the row count of the table I was working with.
(This is my first post, so I apologize for the shortness of it and welcome constructive thought to make it better.)
Starting with the Dao, I declared the method with the #Query() annotation. This is the point where we will define the query we will be using to retrieve the desired information.
#Query("SELECT COUNT(*) FROM word_table")
LiveData<Integer> getCount();
Second, carry this through the Repository. The Repository will be calling our Dao class to retrieve information and essentially pass the query.
public LiveData<Integer> getCount() {
return mWordDao.getCount();
}
Third, bring it into the ViewModel. The ViewModel will be called by the (in this case) MainActivity and in turn will call the getCount() method from the Repository and back down the chain.
// count
public LiveData<Integer> getCount() { return mRepository.getCount(); }
Finally, create the observable in the MainActivity, seeing as I encased the value with a LiveData<> wrapper.
mWordViewModel.getCount().observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer integer) {
word_count.setText(String.valueOf(integer));
}
});
I know that this is simplistic, short and leaves out a lot of detail, but after going over the Room Database code a large number of times, this worked for me to be able to display the number of rows in the database table I was referencing. And it seems to be the way that the Room databases are intended to work.
(The code I was using as a base for branching out into retrieving the row count was grabbed from the codebase labs provided by Google for Room Databases part I.)
You can reach them with the following link and click on the one for Room Databases - Part 1:
Codelabs for Android Developers
Scott
I didn't need LiveData and I used a Coroutine:
// DAO
#Query("SELECT COUNT(*) FROM some_table")
suspend fun getCount(): Int
// REPOSITORY
fun getCount(): Int = runBlocking {
val count = async {
dao.getCount()
}
count.start()
count.await()
}
// VIEWMODEL
when (val count = repository.getCount()) {
// do stuff with count
}
I think a nicer way to do miniature things in the background thread is to create a Handler & HandlerThread and use them to perform one liner tasks.
//The handlers to perform tasks on the background threads
override lateinit var mHandler: Handler
override lateinit var mHandlerThread: HandlerThread
override fun start() {
//Instantiate the handlerThread
mHandlerThread = HandlerThread(MainPresenter::class.java.simpleName)
//A call to the start method has to be executed manually
mHandlerThread.start()
mHandler = Handler(mHandlerThread.looper)
}
And wherever you want to call something in the background thread, simply :
mHandler.post { getTableCountInBg() }
I was in the midst of typing what #Sameer Donga linked to, but refer that instead. Call it like above.
P.S. Ignore the override annotations. They're there because I enforce it on a presenter.
#Query("SELECT COUNT(column_name) FROM table)
LiveData getTotalNumberOfColumns();
or do this if you don't want multiple occurences of a value in the column
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData getTotalNumberOfColumns();
#Query("SELECT COUNT(DISTINCT column_name) FROM table)
LiveData<Integer> getTotalNumberOfRows();
Add DISTINCT as an argument to the COUNT function.

Reduce database requests in local db

I have a list view in android where i have to check every time do display the List item or not
to reduce the requests what i did is saved the id in a single row like
1,2,10,
everything was working fine to search i just had to use
String[] favs = fav.split(",");
for (int index = 0; index <(favs.length); index++) {
if(favs[index]==""){}else {
wishlist.add(Integer.parseInt(favs[index].trim()));
}
if(clicklist.contains((int)temp.getId())) //like this
and to remove from db like, this
temp2.replaceAll(""+m1.getId()+",", "") // and save in the db
now issue is i have two more data field associated with id like
10|data1|data2,100|apple|dog,150|data12|data24
Question 1 is this data model ok for small db
Question 2 how to perform search and delete in new data set?
please help!
Using a db is a proper choice here, i suggest you to take a look to the recently released Room, an Android component made by Google developers to support data persistence more easily.
You should of course know the basis of sql language.
In your case you should annotate your data class with #Entity annotation:
#Entity
public class DataModel {
#PrimaryKey
private int uid;
#ColumnInfo(name = "animal")
private String animal;
#ColumnInfo(name = "fruit")
private String fruit;
// Getters and setters are ignored for brevity,
// but they're required for Room to work.
}
And then, to answer your question about CRUD operations, define a Dao:
#Dao
public interface UserDao {
#Query("SELECT * FROM DataModel")
List<DataModel> getAll();
#Insert
void insertAll(DataModel... dataModels);
#Delete
void delete(DataModel dataModel);
}

Android -room persistent library - DAO calls are async, therefore how to get callback?

From what i have read Room doesn’t allow you to issue database queries on the main thread (as can cause delays on the main thread)). so imagine i am trying to update a textview on the UI main thread which some data how would i get a call back. Let me show you an example. Imagine i want to store my business model data into a object called Events. We would therefore have a EventDao object:
imagine we have this DAO object below:
#Dao
public interface EventDao {
#Query("SELECT * FROM " + Event.TABLE_NAME + " WHERE " + Event.DATE_FIELD + " > :minDate" limit 1)
LiveData<List<Event>> getEvent(LocalDateTime minDate);
#Insert(onConflict = REPLACE)
void addEvent(Event event);
#Delete
void deleteEvent(Event event);
#Update(onConflict = REPLACE)
void updateEvent(Event event);
}
and now in some activity i have a textview and i'd like to update its value so i do this:
myTextView.setText(EventDao.getEvent(someDate));/*i think this is illegal as im trying to call room dao on mainthread, therefore how is this done correctly ? would i need to show a spinner while it updates ?*/
since the fetching is occuring off of the main thread i dont think i can call it like this and expect a smooth update. Whats the best approach here ?
Some more information: i wanted to use the room database as mechanism for retrieving model information instead of keeping it statically in memory. so the model would be available to me locally through the db after i download it through a rest service.
UPDATE: so since i am returning a livedata then i can do this:
eventDao = eventDatabase.eventDao();
eventDao.getEvent().observe(this, event -> {
myTextView.setText(event.get(0));
});
and that works for something very small. but imagine my database has a million items. then when i do this call, there will be a delay retrieving the data. The very first time this gets called it will be visible to the user that there is a delay. How to avoid this ? So to be clear , there are times i do not want live data, i just need to update once the view. I need to know how to do this ? even if its not with liveData.
If you want to do your query synchronously and not receive notifications of updates on the dataset, just don't wrap you return value in a LiveData object. Check out the sample code from Google.
Take a look at loadProductSync() here
There is a way to turn off async and allow synchronous access.
when building the database you can use :allowMainThreadQueries()
and for in memory use: Room.inMemoryDatabaseBuilder()
Although its not recommended. So in the end i can use a in memory database and main thread access if i wanted super fast access. i guess it depends how big my data is and in this case is very small.
but if you did want to use a callback.... using rxJava here is one i made for a list of countries i wanted to store in a database:
public Observable<CountryModel> queryCountryInfoFor(final String isoCode) {
return Observable.fromCallable(new Callable<CountryModel>() {
#Override
public CountryModel call() throws Exception {
return db.countriesDao().getCountry(isoCode);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
you can then easily add a subscriber to this function to get the callback with Rxjava.
As Bohsen suggested use livedata for query synchronously. But in some special case, we want to do some asynchronous operation based on logic.
In below example case, I need to fetch some child comments for the parent comments. It is already available in DB, but need to fetch based on its parent_id in recyclerview adapter. To do this I used return concept of AsyncTask to get back the result. (Return in Kotlin)
Repositor Class
fun getChildDiscussions(parentId: Int): List<DiscussionEntity>? {
return GetChildDiscussionAsyncTask(discussionDao).execute(parentId).get()
}
private class GetChildDiscussionAsyncTask constructor(private val discussionDao: DiscussionDao?): AsyncTask<Int, Void, List<DiscussionEntity>?>() {
override fun doInBackground(vararg params: Int?): List<DiscussionEntity>? {
return discussionDao?.getChildDiscussionList(params[0]!!)
}
}
Dao Class
#Query("SELECT * FROM discussion_table WHERE parent_id = :parentId")
fun getChildDiscussionList(parentId: Int): List<DiscussionEntity>?
Well, the right answer is to use ListenableFuture or Observable depending if you need one shot query or a new value emitted after database change and the framework you want to use.
From the doc "To prevent queries from blocking the UI, Room does not allow database access on the main thread. This restriction means that you must make your DAO queries asynchronous. The Room library includes integrations with several different frameworks to provide asynchronous query execution."
Exemple with a one shot query. You just have to add this in your gradle file.
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
Then your SQL query in your DAO become.
#Query("SELECT * FROM " + Event.TABLE_NAME)
ListenableFuture<List<Event>> getEventList();
Last step is the future call itself.
ListenableFuture<List<Event>> future = dao.getEventList();
future.addListener(new Runnable() {
#Override
public void run() {
try {
List<Event>> result = future.get();
} catch (ExecutionException | InterruptedException e) {
}
}
}, Executors.newSingleThreadExecutor());
Source : https://developer.android.com/training/data-storage/room/async-queries#guava-livedata

Categories

Resources