My aim is to store some data into a SQLite database using Room.
So I made a lot of #Entities in POJO.
For each #Entity I made a #Dao with at least these queries:
#Dao
public interface RouteDao {
#Query("SELECT * FROM route")
LiveData<List<Route>> getAll();
#Insert
void insertAll(List<Route> routes);
#Query("DELETE FROM route")
void deleteAll();
}
My Singleton Room #Database is:
#Database(entities = {Agency.class, Calendar.class, CalendarDate.class, FeedInfo.class, Route.class, Stop.class, StopTime.class, Transfer.class, Trip.class}, version = 1)
#TypeConverters(MyConverters.class)
public abstract class GtfsDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "gtfs-db";
private static GtfsDatabase INSTANCE;
public abstract AgencyDao agencyDao();
public abstract CalendarDao calendarDao();
public abstract CalendarDateDao calendarDateDao();
public abstract FeedInfoDao feedInfoDao();
public abstract RouteDao routeDao();
public abstract StopDao stopDao();
public abstract StopTimeDao stopTimeDao();
public abstract TransferDao transferDao();
public abstract TripDao tripDao();
public static synchronized GtfsDatabase getDatabase(Context context) {
return INSTANCE == null ? INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
GtfsDatabase.class,
DATABASE_NAME
).build() : INSTANCE;
}
}
When I open the app for the first time, I fill the database with data in a background IntentService:
public static void importData(Context context, Map<String, String> data) {
GtfsDatabase db = GtfsDatabase.getDatabase(context);
db.beginTransaction();
try {
db.agencyDao().deleteAll();
db.calendarDao().deleteAll();
db.calendarDateDao().deleteAll();
db.feedInfoDao().deleteAll();
db.routeDao().deleteAll();
db.stopDao().deleteAll();
db.stopTimeDao().deleteAll();
db.transferDao().deleteAll();
db.tripDao().deleteAll();
db.agencyDao().insertAll(rawToAgencies(data.get(AGENCY_FILE)));
db.calendarDao().insertAll(rawToCalendars(data.get(CALENDAR_FILE)));
db.calendarDateDao().insertAll(rawToCalendarDates(data.get(CALENDAR_DATES_FILE)));
db.feedInfoDao().insertAll(rawToFeedInfos(data.get(FEED_INFO_FILE)));
db.routeDao().insertAll(rawToRoutes(data.get(ROUTES_FILE)));
db.tripDao().insertAll(rawToTrips(data.get(TRIPS_FILE)));
db.stopDao().insertAll(rawToStops(data.get(STOPS_FILE)));
db.stopTimeDao().insertAll(rawToStopsTimes(data.get(STOP_TIMES_FILE)));
db.transferDao().insertAll(rawToTransfers(data.get(TRANSFERS_FILE)));
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(context.getString(R.string.empty), false).apply();
} finally {
db.endTransaction();
}
}
I am absolutely sure that the data is correct. I debugged each line and I can say 100% sure that the list of objects I pass to these functions is correct. No error at all.
When this service is finished (if(!sharedPreferences.getBoolean(getString(R.string.empty), true))) I try to access to the database in another activity and this show me that is empty.
I checked with this library debugCompile 'com.amitshekhar.android:debug-db:1.0.0' and every table is really empty.
What I'm doing wrong?
I know you cannot see all my code, and maybe there's something wrong, so my actual question is: is the above code correct?
Solved.
Android Room is handling transactions automatically.
#Query are asynchronous, while #Insert and #Delete are synchronous.
My error was to try including all those operations in a single transaction.
The solution is: let that Room handles them automatically.
public static void importData(Context context, Map<String, String> data) {
GtfsDatabase db = GtfsDatabase.getDatabase(context);
db.agencyDao().deleteAll();
db.calendarDao().deleteAll();
db.calendarDateDao().deleteAll();
db.feedInfoDao().deleteAll();
db.routeDao().deleteAll();
db.stopDao().deleteAll();
db.stopTimeDao().deleteAll();
db.transferDao().deleteAll();
db.tripDao().deleteAll();
db.agencyDao().insertAll(rawToAgencies(data.get(AGENCY_FILE)));
db.calendarDao().insertAll(rawToCalendars(data.get(CALENDAR_FILE)));
db.calendarDateDao().insertAll(rawToCalendarDates(data.get(CALENDAR_DATES_FILE)));
db.feedInfoDao().insertAll(rawToFeedInfos(data.get(FEED_INFO_FILE)));
db.routeDao().insertAll(rawToRoutes(data.get(ROUTES_FILE)));
db.tripDao().insertAll(rawToTrips(data.get(TRIPS_FILE)));
db.stopDao().insertAll(rawToStops(data.get(STOPS_FILE)));
db.stopTimeDao().insertAll(rawToStopsTimes(data.get(STOP_TIMES_FILE)));
db.transferDao().insertAll(rawToTransfers(data.get(TRANSFERS_FILE)));
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(context.getString(R.string.empty), false).apply();
}
Related
I have two separate tables for my application, one is called users and the other is called passwords.
Users:
#Entity(tableName = "users")
public class Users {
// some setters and getters here
}
Passwords:
#Entity(tableName = "passwords")
public class Passwords {
// some setters and getters here
}
And this is how I'm accessing the database:
usersdb = Room.databaseBuilder(this, Users.class,"mymaindb")
.allowMainThreadQueries()
.build();
// Then later in some other activity when I need to use passwords table
passwords = Room.databaseBuilder(this, passwords.class,"mymaindb")
.allowMainThreadQueries()
.build();
The issue I'm having is that after a fresh install, when I access passwords then I can't access users or vice versa.
I get the following error:
Caused by: java.lang.IllegalStateException: Room cannot verify the
data integrity. Looks like you've changed schema but forgot to update
the version number. You can simply fix this by increasing the version
number.
But when I try to have two separate databases like passwords_db and users_db instead of mymaindb, then it works completely fine.
So is there a way I can have multiple tables under one database? If so, what am I doing wrong then? Thanks in advance!
I think you got this all wrong, Room.databaseBuilder should only be called once to setup the database and in that database class, you will construct multiple tables. For example:
Room.databaseBuilder(this, MyRoomDb.class, "mymaindb")
.allowMainThreadQueries()
.build()
And your MyRoomDb should look like this
#Database(
entities = {
Users.class,
Passwords.class
},
version = VERSION
)
public abstract class MyRoomDb extends RoomDatabase {
...
}
You have few variants how to solve this problem:
Add tables back but increase the version of database;
#Database(entities={Users.class, Passwords.class}, version = 2)
Clean the application settings and build the new database;
Just clean the application cache and try to recreate the database.
It's simple like adding one entity table. Just add another table name using ",". And another table will be added.
#Database(entities = [TableUser::class, TableRecord::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun myDao(): MyDao
}
Here's another way you can have what you need by using one RoomDB class for all your tables:
#Database(entities = {Users.class, Passwords.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
private static MyDatabase INSTANCE;
public abstract UsersDAO usersDAO();
public abstract PasswordsDAO passwordsDAO();
public static MyDatabase getDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class, "mydatabase")
.build();
}
return INSTANCE;
}
}
Your Entity will look like this:
#Entity(tableName = "user_table")
public class User {
#PrimaryKey(autoGenerate = true)
private long id;
private String username;
private String email;
.............//Constructor goes here
.............//Getters and Setters go here
}
The DAO looks like this:
#Dao
public interface UsersDAO {
#Query("SELECT * FROM user_table")
List<User> getAll();
}
Then you'll fetch data like this:
AsyncTask.execute(() -> {
MyDatabase.getDatabase(getApplicationContext()).usersDAO().getAll();
});
if you dont want to change the code you can simply uninstall and install the app again. But this will delete your data saved in database till now.
I had to create an account to ask this question because I couldn't find the right way to do this. The only thing that comes close is this question here, but it doesn't go all the way and I'm still stuck. Here we go...
I'm trying to build an app following as much of the Architecture Components principles.
I'm currently trying to add a row in one of my database table, and get the ID of this row in return, to then insert a row in another table, with a reference to the first one.
I've created my database object:
#Entity(indices = {#Index("id")})
public class Search {
#PrimaryKey(autoGenerate = true) private int id;
...
And the corresponding DAO:
#Dao
public interface SearchDao {
#Insert
long insert(Search search);
...
As you can see, my DAO returns a long with the created ID. This is the behavior which was pointed out in the question I linked before, and documented here.
Since I'm following Android Architecture Components principles, I'm using a Repository class to do all my database related work. In this Repository, I've created a public method to insert a new object, which is creating and executing an AsyncTask to do the work:
public class Repository {
public void insertSearch(Search search) {
new insertSearchAsyncTask(this.mSearchDao).execute(search);
}
...
private static class insertSearchAsyncTask extends AsyncTask<Search, Void, Long> {
private SearchDao mAsyncTaskDao;
insertSearchAsyncTask(SearchDao dao) {
this.mAsyncTaskDao = dao;
}
#Override
protected Long doInBackground(final Search... params) {
long id = this.mAsyncTaskDao.insert(params[0]);
return id;
}
}
I know I can use the onPostExecute(long id) method to do stuff with the result of the doInBackground method, but this onPostExecute method cannot return anything to the insertSearch method, where I created the AsyncTask and executed it.
I know need to change the return type of my insertSearch method to long. However if I want to have something to return, I need to get the result of the execution of the AsyncTask. How can I do that?
I've tried this (according to the validated answer):
public class Repository {
private long result_id = 0;
public long insertSearch(Search search) {
new insertSearchAsyncTask(this.mSearchDao).execute(search);
return result_id;
}
private class insertSearchAsyncTask extends AsyncTask<Search, Void, Long> {
private SearchDao mAsyncTaskDao;
insertSearchAsyncTask(SearchDao dao) {
this.mAsyncTaskDao = dao;
}
#Override
protected Long doInBackground(final Search... params) {
long id = this.mAsyncTaskDao.insert(params[0]);
return id;
}
#Override
protected void onPostExecute(Long search_id) {
result_id = search_id;
}
}
}
But this feels very very wrong. I had to make the insertSearchAsyncTask class not-static, and I have to store the result of the insert in an attribute of my Repository.
I'm hoping there is a better/correct way of doing this.
I've also looked at other suggested answers on the link above, especially one about Delegates, but this doesn't suit my need as I need the method insertSearch to return the result, not another one called by the AsyncTask when it finishes.
I hope I've explained my problem clearly enough.
Any idea anyone?
Thanks a lot!!
i am storing user information in a local room database. In activities and fragments I use AndroidViewModel and LiveData to listen to changes made to the database and update the UI.
Now I want to analyze all of the past user data to give recommendations for future decisions. My recommendations change on every change to the database made by the user so I need to update my reommendations frequently while doing the same calculations over and over again.
I was thinking about starting a service on app start that listens to database changes via ViewModel and LiveData and updates my recommendations (which are also stored in the same database). But somehow a Service cannot
get a ViewModel via viewModel = ViewModelProviders.of(this).get(DataViewModel.class);
observe a LiveData object as it is not a LifecycleOwner.
Basically I simply need to read all entries from the database, analyze the data and update 5-10 values every time the database content changes.
How and where should I do my calculations if not in a service? Maybe I am trapped in a wrong thought and a service is not the right way to do it so any idea on how to do this is very much appreciated!
observe a LiveData object as it is not a LifecycleOwner
Use observeForever() on the LiveData, manually unregistering via removeObserver() when appropriate (onDestroy() of the service, if not sooner).
Bear in mind that standard service limitations apply here (e.g., services run for ~1 minute on Android 8.0+ unless they are foreground services), so it may be that you need to consider other approaches anyway.
I ended up using a Service and solved my problem as follows:
In the onCreate method of my Application object I bind MyService to it:
serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder iBinder) {
service = ((MyService.MyLocalBinder) iBinder ).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
};
Intent intent = new Intent(this, MyService.class);
getApplicationContext().bindService(intent, serviceConnection, BIND_AUTO_CREATE);
Binding a Service to the application context should keep the Service alive as long as the application is not destroyed. In MyService I get an instance of ViewModel via AndroidViewModelFactory like this
MyViewModel myViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()).create(MyViewModel.class);
and I am able to observe the fetched LiveData from the ViewModel via observeForever like this
Observer<List<Entry>> obsEntries = new Observer<List<Entry>>() {
#Override
public void onChanged(#Nullable List<Entry> entries) {
//perform calculations with entries in here
}
};
viewModel.getEntries().observeForever(obsEntries);
Important: Remove the observer from the LiveData reference in onDestroy of the Service (that is why I keep a local reference to the Observer object):
#Override
public void onDestroy() {
super.onDestroy();
viewModel.getEntries().removeObserver(obsEntries);
}
Thanks everybody!
Nothing to change in Entity.
In DAO, say for e.g.,
#Dao
public interface TimeDao {
// ...
// ...
// for a started Service using startService() in background
#Query("select * from times where name = :bName")
List<TimeEntity> getServiceTimes(String bName);
}
which is not a LiveData
In Database, for e.g.,
#Database(entities = {DataEntity.class, TimeEntity.class}, version = 1)
public abstract class BrRoomDatabase extends RoomDatabase {
public abstract TimeDao iTimeDao();
public static BrRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
// ...
}
return INSTANCE;
}
}
An interface class
public interface SyncServiceSupport {
List<TimeEntity> getTimesEntities(String brName);
}
An implementation class for it.
public class SyncServiceSupportImpl implements SyncServiceSupport {
private TimeDao timeDao;
public SyncServiceSupportImpl(Context context) {
timeDao = BrRoomDatabase.getDatabase(context).iTimeDao();
// getDatabase() from where we get DB instance.
// .. and ..
// public abstract TimeDao iTimeDao(); <= defined abstract type in RoomDatabase abstract class
}
#Override
public List<TimeEntity> getTimesEntities(String name) {
return timeDao.getServiceTimes(name);
}
}
And finally... in the service..
public class SyncService extends Service {
//..
// now, iEntities will have a List of TimeEntity objects from DB ..
// .. retrieved using #Query
private static List<TimeEntity> iEntities = new ArrayList<>();
private static SyncServiceSupportImpl iSyncService;
private static void getFromDB(String brName) {
new AsyncTask<String, Void, Void>() {
#Override
protected Void doInBackground(String... params) {
iEntities = iSyncService.getTimesEntities(params[0]);
return null;
}
#Override
protected void onPostExecute(Void agentsCount) {
}
}.execute(brName);
}
#Override
public void onCreate() {
super.onCreate();
//init service
iSyncService = new SyncServiceSupportImpl(SyncService.this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// this can be elsewhere also !..
getFromDB(myName);
}
}
I tried from this example
https://stackoverflow.com/a/9472019/485978
I have a service, this service just connects to the database directly and I put all data in a bean which is located inside this class.
public class ServiceApplication extends Application {
private static ServiceApplication mInstance;
public ServiceApplication() {
super();
}
public static ServiceApplication getInstance() {
return mInstance;
}
#Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
private Person personalData;
public Person getPersonalData() {
return personalData;
}
public void setPersonalData(Person personalData) {
this.personalData = personalData;
}
}
When retrieving a data from the database I used an AsyncTask where in doBackground() this is the code
ServiceApplication.getInstance().setPersonalData(personalData);
Log.d("AndroidService", "First name: "+ ServiceApplication.getInstance().getPersonalData().getFirstName());
So far it can retrieved the First Name.
However when I try to access those data from another activity all I get is null.
I tried two ways but it produces null
First implementation:
ServiceApplication app = (ServiceApplication) getApplication();
String name = (app.getPersonalData() != null) ? app.getPersonalData().getFirstName().trim() : "user";
Second implementation:
String name = (ServiceApplication.getInstance().getPersonalData() != null) ? ServiceApplication.getInstance().getPersonalData().getFirstName().trim() : "user";
Do you guys no how to persist the data and how to retrieve it from other activities?
If you are trying to pass data between a service and an activity you have to use another approach, as described here. If you just want to load Person data from a database into a singleton then you don't need to a Service. Just open a connection to the db, read the data and store it. Something like:
...
public Person getPersonalData() {
if(personalData == null) {
... open the db and load the data here ...
}
return personalData;
}
...
Note that only mInstance is static. It does not guarantee that you will receive the same personalData object. personalData could be and is definitely null in your case.
You might want to declare personalData as static as well.
private static Person personalData;
But in this case, you will reset the data on your next call to setPersonalData.
I would recommend you create a static structure as
private static ArrayList<Person> pList = new ArrayList<Person>();
Then in your setPersonalData() you can add objects to your pList.
You can replace your getPersonalData to return the pList. Hence you can use the same singleton instance from you activity to add data to the list and use the same to retrieve
I'm new to RequestFactory but with generous help of Thomas Broyer and after reviewing documents below it's getting much better :)
Getting Started with RequestFactory
Request Factory Moving Parts
RequestFactory changes in GWT 2.4
But could you please explain why Locator<>.find() is being called so unnecessarily (in my opinion) often ?
In my sample project I have two entities Organization and Person that maintain parent-child relationship. When I fetch Organization Objectify automatically fetches child Person.
Also I created two methods in my service layer findOrganizationById and saveOrganization that load and persist objects.
Now consider two scenarios:
When I call findOrganizationById in the client following calls occur on server side:
OrderDao.findOrganizationById(1)
PojoLocator.getId(Key<?>(Organization(1)))
PojoLocator.getId(Key<?>(Organization(1)/Person(2)))
PojoLocator.getId(Key<?>(Organization(1)))
PojoLocator.find(Key<?>(Organization(1)))
PojoLocator.getId(Key<?>(Organization(1)/Person(2)))
PojoLocator.find(Key<?>(Organization(1)/Person(2)))
By calling OrderDao.findOrganizationById I've already received full graph of objects. Why call .find twice in addition to that? It's extra load on Datastore that cost me money. Of course I cache it but it would be neat to fix it. How can I avoid these additional calls ?
Similar thing happens when I save object(s) by calling saveOrganization in the client. Following calls occur on server side:
PojoLocator.find(Key<?>(Organization(1)))
PojoLocator.find(Key<?>(Organization(1)/Person(2)))
OrderDao.saveOrganization(1)
PojoLocator.getId(Key<?>(Organization(1)))
PojoLocator.find(Key<?>(Organization(1)))
PojoLocator.getId(Key<?>(Organization(1)/Person(2)))
PojoLocator.find(Key<?>(Organization(1)/Person(2)))
I can understand need for fetching two objects from DataStore before updating it. RequestFactory sends deltas to the server so it needs to have entire object before persisting it. Still since I load full graph at once it would be nice not to have second call which is PojoLocator.find(Key<?>(Organization(1)/Person(2))). And I truly can't understand need for .find() calls after persisting.
Thoughts ?
My proxies
#ProxyFor(value = Organization.class, locator = PojoLocator.class)
public interface OrganizationProxy extends EntityProxy
{
public String getName();
public void setName(String name);
public String getAddress();
public void setAddress(String address);
public PersonProxy getContactPerson();
public void setContactPerson(PersonProxy contactPerson);
public EntityProxyId<OrganizationProxy> stableId();
}
#ProxyFor(value = Person.class, locator = PojoLocator.class)
public interface PersonProxy extends EntityProxy
{
public String getName();
public void setName(String name);
public String getPhoneNumber();
public void setPhoneNumber(String phoneNumber);
public String getEmail();
public void setEmail(String email);
public OrganizationProxy getOrganization();
public void setOrganization(OrganizationProxy organization);
}
My service
public interface AdminRequestFactory extends RequestFactory
{
#Service(value = OrderDao.class, locator = InjectingServiceLocator.class)
public interface OrderRequestContext extends RequestContext
{
Request<Void> saveOrganization(OrganizationProxy organization);
Request<OrganizationProxy> findOrganizationById(long id);
}
OrderRequestContext contextOrder();
}
and finally my Locator<>
public class PojoLocator extends Locator<DatastoreObject, String>
{
#Inject Ofy ofy;
#Override
public DatastoreObject create(Class<? extends DatastoreObject> clazz)
{
try
{
return clazz.newInstance();
} catch (InstantiationException e)
{
throw new RuntimeException(e);
} catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
#Override
public DatastoreObject find(Class<? extends DatastoreObject> clazz, String id)
{
Key<DatastoreObject> key = Key.create(id);
DatastoreObject load = ofy.load(key);
return load;
}
#Override
public Class<DatastoreObject> getDomainType()
{
return null; // Never called
}
#Override
public String getId(DatastoreObject domainObject)
{
Key<DatastoreObject> key = ofy.fact().getKey(domainObject);
return key.getString();
}
#Override
public Class<String> getIdType()
{
return String.class;
}
#Override
public Object getVersion(DatastoreObject domainObject)
{
return domainObject.getVersion();
}
}
The pairs of getId and find at the end are the default implementation of Locator#isLive: it assumes an object is live (i.e. still exists in the data store) if finding it by its ID returns a non-null value.
RF checks each EntityProxy it ever seen during the request/response for their liveness when constructing the response, to tell the client when an entity has been deleted (on the client side, it'd then fire an EntityProxyChange event with a DELETE write operation.
You can of course override isLive in your Locator with a more optimized implementation if you can provide one.