Room database destructive migration triggered on app restart when db is prepopulated - android

I have a Room database, containing two entities: MusicItem and FormatItem, with the former containing a foreign key to the latter. Using "DB Browser for SQLite" I have filled the table for FormatItem with some formats and added this DB as a prepackaged DB to the Room database with createFromAsset.
The database seems to be working fine - the user can add, remove and edit items in the database as expected, but for some reason, when the app is closed and opened again, the DB is empty. It seems that the fallbackToDestructiveMigration method is called (I haven't implemented a migration strategy yet) when building the database, and I don't understand why. Room doesn't say anything about the prepackaged DB (meaning the schema is correct). When I disable createFromAsset everything works fine, and the data is persistent after closing and restarting the app.
My question is why is the migration code called, and how can I prevent the deletion of the data.
My code:
class ItemsRoomDatabase:
public abstract class ItemsRoomDatabase extends RoomDatabase {
private static final String TAG = ItemsRoomDatabase.class.getSimpleName();
private static ItemsRoomDatabase INSTANCE;
static ItemsRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (ItemsRoomDatabase.class) {
if (INSTANCE == null) {
Log.d(TAG, "called");
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
ItemsRoomDatabase.class, "items_database")
// Wipes and rebuilds instead of migrating
// if no Migration object.
// Migration is not part of this practical.
// TODO: add migration support
.fallbackToDestructiveMigration()
.createFromAsset("prepackaged_db.db")
.build();
}
}
}
return INSTANCE;
}
public abstract MusicItemDao musicItemDao();
public abstract FormatItemDao formatItemDao();
}
class MusicItem:
#Entity(tableName = "music_items_table",
foreignKeys = #ForeignKey(entity = FormatItem.class,
parentColumns = "id", childColumns = "formatId", onDelete = NO_ACTION))
class MusicItem implements Parcelable {
enum Classification {
COLLECTION,
WISHLIST,
NONE;
}
#PrimaryKey
#NonNull
private final String id;
#NonNull
private final String title;
#NonNull
private final String artist;
private final String thumbUrl;
private List<String> genres;
#NonNull
private List<Track> tracks;
private String year;
#NonNull
private Classification classification;
#NonNull
private int formatId;
.
.
.
}
class FormatItem:
#Entity(tableName = "format_items_table")
class FormatItem {
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
#NonNull
private final String name;
#NonNull
private int itemsCount = 0;
FormatItem(String name) {
this.name = name;
}
int getId() {
return id;
}
String getName() {
return name;
}
int getItemsCount() {
return itemsCount;
}
void setId(int id) {
this.id = id;
}
void setItemsCount(int itemsCount) {
this.itemsCount = itemsCount;
}
}

Related

Populate Room Database with Arraylist

I would like to build a Deckbuilder that allows you to save created decks locally on the device.
The Decklist are stored in Arraylists, called TransferDeck. Which I would like to store in room database. My issue is, that I do not know how to populate my database correctly, with the data comming out of the Arraylist.
I am used to working with Arraylist and below you see my try for storing the data:
So this is what I tried but what sadly does not work:
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
Below you can find the rest of my code, but the above one should be enough to make clear what I want to do...
I created the class SaveDeck which should be able to Save a Deck with a given Deckname:
:-
#Entity
public class SaveDeck implements Serializable {
#PrimaryKey(autoGenerate = true)
private int _id;
public SaveDeck(int _id, String deckName, int cardImage, int typeImage, Integer cardCost, String cardName, Integer cardNumber) {
this._id = _id;
DeckName = deckName;
CardImage = cardImage;
TypeImage = typeImage;
CardCost = cardCost;
CardName = cardName;
CardNumber = cardNumber;
}
#ColumnInfo(name = "DeckName")
private String DeckName;
#ColumnInfo(name = "CardImage")
private int CardImage;
#ColumnInfo(name = "TypeImage")
private int TypeImage;
#ColumnInfo(name = "CardCost")
private Integer CardCost;
#ColumnInfo(name = "CardName")
private String CardName;
#ColumnInfo(name = "CardNumber")
private Integer CardNumber;
}
I created the Dao Class as follows:
:-
#Dao
public interface DeckBuilderDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long[] insertCards(SaveDeck... saveDecks);
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long insertCard(SaveDeck saveDecks);
#Update
public int updateCardBaseEntries(SaveDeck... saveDecks);
#Update
public int updateCardBaseEntry(SaveDeck saveDecks);
#Delete
public int deleteCardBaseEntried(SaveDeck... saveDecks);
#Delete
public int deleteCardBaseEntry(SaveDeck saveDecks);
#Query("SELECT * FROM SaveDeck")
public SaveDeck[] getAllDecks();
//probably I do not need the getAllDecks Query. Right now I only need the following one:
#Query("SELECT * FROM SaveDeck WHERE DeckName = :NameOfDeck ORDER BY DeckName, CardName")
public SaveDeck getOneDeck(String NameOfDeck);
}
Furthermore created the DataBase Class:
#Database(entities = {SaveDeck.class}, version = 1)
public abstract class SaveDecksDataBase extends RoomDatabase {
public abstract DeckBuilderDao deckBuilderDao();
}
The last class is a fragment, where I try to populate my database, and in the populateDB() class is the issue
public class review_fragment extends Fragment {
private List<TransferDeck> mTransferDeck = DataHolder.getInstance().savedDecklistTransfer;
SaveDecksDataBase mSavedDecksDB;
Cursor mCursor;
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//return super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.review_fragment, container, false);
/*Introduce Cards Recycler*/
RecyclerView rvCards = view.findViewById(R.id.rv_review_cardlist);
rvCards.setLayoutManager(new GridLayoutManager(getActivity(), 5));
review_RViewAdapter_Cards adapterCards = new review_RViewAdapter_Cards(getContext(), mTransferDeck);
rvCards.setAdapter(adapterCards);
/*Init Room database*/
mSavedDecksDB = Room.databaseBuilder(getActivity(),SaveDecksDataBase.class,"SavedDecksDB.db").build();
populateDB(mTransferDeck);
return view;
}
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
}
I like to mention that this should be a comment rather than an answer.
First, either use AysncTask or use more robust Executors.newSingleThreadExecutor(). If you prefer the second one then it's best if you create a helper class (example). Example:
private void populateDB(final List<TransferDeck> mTransferDeck) {
AppExecutors.diskIO().execute(() -> {
for(int i = 0; i<mTransferDeck.size(); i++){
mSavedDecksDB.deckBuilderDao().insertCards(new SaveDeck(...);
}
});
}
(1) Create a blank constructor.
(4) Room Database should not be initialized there and it's best if it's singleton. So the your database class (3) can be like:
public abstract class SaveDecksDataBase extends RoomDatabase {
private static SaveDecksDataBase sINSTANCE;
private static final Object LOCK = new Object();
public static SaveDecksDataBase getDatabase(final Context context) {
if (sINSTANCE == null) {
synchronized (LOCK) {
if (sINSTANCE == null) {
sINSTANCE = Room.databaseBuilder(context.getApplicationContext(),
SaveDecksDataBase.class, "SavedDecksDB.db")
.build();
}
}
}
return sINSTANCE;
}
public abstract DeckBuilderDao deckBuilderDao();
}
Lastly, to get SaveDeck object you also has to use Executors or AsyncTask to do the work in background, and then populate the RecyclerView.
Android Room Database
Practice set

Using room persistence when trying to fetch items or insert returns null database

Working with room persistence, when trying to get the database to insert or make select in the items, the error appears:
AppDataBase.getMovieDao()' on a null object reference
The classes related to the process are as follows:
AppDataBase class:
#Database(entities = {Movies.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {
public static final String DB_NAME = "Movies";
private static AppDataBase INSTANCE;
public static AppDataBase getDataBase(Context context){
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDataBase.class,DB_NAME).build();
}
return INSTANCE;
}
public abstract MovieDao getMovieDao();
}
Dao Class:
#Dao
public interface MovieDao {
#Insert
void insertAll(Movies movies);
#Update
void updateAll(Movies... notes);
#Query("SELECT * FROM moviestb")
List<Movies> getAll();
#Delete
void deleteAll(Movies... notes);
}
Entity class:
#Entity(tableName = "moviestb")
public class Movies {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "idmovie")
private long id;
#ColumnInfo(name = "titlemovie")
private String titlemovie;
........
}
Searching for the registrations:
public void loadFromDB(){
db.getDataBase(view.getContext());
if(db.getMovieDao().getAll().size() > 0){
adapter.setResults(db.getMovieDao().getAll());
}else{
Toast.makeText(view.getContext(),"Não há filmes cadastrados",Toast.LENGTH_SHORT).show();
view.getActivity().finish();
}
}
Insert:
public View.OnClickListener onSaveClick(final String plot, final String diretor, final String autor,
final String nome, final String tipo, final String ano, final String ator, final String imdb) {
return new View.OnClickListener() {
#Override
public void onClick(View itemView) {
db.getDataBase(view.getContext());
Movies movies = new Movies(nome,plot,imdb,"",ator,ano,tipo,diretor,autor);
new InsertAsyncTask(db).execute(movies);
}
};
}
private class InsertAsyncTask extends AsyncTask<Movies,Void,Void>{
private AppDataBase db;
public InsertAsyncTask(AppDataBase appDataBase) {
db = appDataBase;
}
#Override
protected Void doInBackground(Movies... params) {
db.getMovieDao().insertAll(params[0]);
return null;
}
}
Searching all:
public void loadFromDB(){
db.getDataBase(view.getContext());
if(db.getMovieDao().getAll().size() > 0){
adapter.setResults(db.getMovieDao().getAll());
}else{
Toast.makeText(view.getContext(),"Não há filmes cadastrados",Toast.LENGTH_SHORT).show();
view.getActivity().finish();
}
}
The crash occurs while fetching the database, what am I doing wrong? thank you!
db is null, apparently. Your question does not show where you ever assign it a value.
Please note that getDataBase() is a static method. Perhaps you should have a db=AppDataBase.getDataBase() statement somewhere.

GreenDao list entity with a list of entities

I am making an Android application using GreenDao, and I have these two entities:
#Entity
public class Quiz {
#Id(autoincrement = true)
private Long id;
private Date date;
private String type;
#ToMany(referencedJoinProperty = "quizId")
private List<Answer> answers;
}
#Entity
public class Answer {
#Id(autoincrement = true)
private Long id;
private int answer;
private float value;
private int questionNumber;
private String type;
private Long quizId;
}
I'm trying to fetch the list of Quiz using the following code:
DaoSession daoSession = AndroidAdapter.getDaoSession();
QuizDao quizDao = daoSession.getQuizDao();
List<Quiz> quizs = quizDao.loadAll();
But the list of answers always comes empty, what am I doing wrong?
My AndroidAdapter class:
public class AndroidAdapter {
public static Context getContext() {
return AppApplication.getContext();
}
public static DaoSession getDaoSession() {
return AppApplication.getDaoSession();
}
}
And AppApplication class
#Override
public void onCreate() {
super.onCreate();
context = this;
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,ENCRYPTED ? "exam-db-encrypted" : "exam-db");
Database db = ENCRYPTED ? helper.getEncryptedWritableDb("exam-secret") : helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
this.addAllEvents();
}
public static DaoSession getDaoSession() {
return daoSession;
}
For me, I've not entered some Answer entities in my db.
Once I've added a code to add those entities to the db and I've requested the list with a generated getter for the List<Answer> answers - it all worked as it should 😊

Android Realm apparently creating list, but then says it's size is 0

I'm developing an android app which uses Realm as it's database.
This database already has some data in it when a user installs it. The problem is some of this Realm objects have a list of other objects wich apparently are being created, since I saw it in the debugger, but when i try to access this list like realmObject.getList.size(); the resulting output is 0.
More specifically, I have this model:
public class Muscle extends RealmObject{
#PrimaryKey
private int id;
private String name;
private RealmList<Routine> routines;
public Muscle(String name){
this.id = MyApplication.MuscleID.incrementAndGet();
this.name = name;
this.routines = new RealmList<Routine>();
}
public Muscle(){}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RealmList<Routine> getRoutines() {
return routines;
}
}
And this one:
public class Routine extends RealmObject {
#PrimaryKey
private int id;
#Required
private String name;
private RealmList<Detail> details;
public Routine(String name){
this.id = MyApplication.RoutineID.incrementAndGet();
this.name = name;
this.details = new RealmList<Detail>();
}
public Routine(){}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RealmList<Detail> getDetails() {
return details;
}
}
The records are being inserted in realm, since i can access and display them in my app (at least the Muscle records), and i can see in the debugger that the Routine list is also being created, but i can't access each muscles routines.
In MyApplication.java, where I save this records, the realm operations basically look like this:
public class MyApplication extends Application {
public DateFormat df;
public static AtomicInteger MuscleID = new AtomicInteger();
public static AtomicInteger RoutineID = new AtomicInteger();
public static AtomicInteger DetailID = new AtomicInteger();
#Override
public void onCreate() {
super.onCreate();
Realm.init(getApplicationContext());
setUpRealmConfig();
Realm realm = Realm.getDefaultInstance();
MuscleID = getIdByTable(realm, Muscle.class);
RoutineID = getIdByTable(realm, Routine.class);
DetailID = getIdByTable(realm, Detail.class);
realm.close();
}
private void setUpRealmConfig() {
RealmConfiguration config = new RealmConfiguration.Builder().initialData(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Muscle chest = new Muscle(getString(R.string.chest));
realm.copyToRealmOrUpdate(chest);
Muscle legs = new Muscle(getString(R.string.legs));
realm.copyToRealmOrUpdate(legs);
Routine cr1 = new Routine(getString(R.string.beginner_chest_1));
realm.copyToRealmOrUpdate(cr1);
chest.getRoutines().add(cr1);
}
})
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(config);
}
There's more but that's the relevant code. I have made sure that ids of each model increment automatically and that's working fine, all muscle objects are inserted in the database, and i can see them in my app no problem, however the routines apparently are created, since I watch the list size go up and the id increment in the debugger, but when I try to access it in my adapter like int workoutsNumber = muscle.getRoutines().size(); , workoutsNumber becomes 0.
I don't know what the problem is here. Everything seems to look fine in debugging, except for one thing i don't understand. The first thing in the debugger always is muscle = cannot find local variable 'muscle'
Here is an screenshot where you can see the objects are effectively being created, and the routine list is added to the muscle object, as well as you can see the error I mention above: debug screenshot
So why do i get 0 when doing int workoutsNumber = muscle.getRoutines().size(); if the list size should be 3?
Routine cr1 = new Routine(getString(R.string.beginner_chest_1));
realm.copyToRealmOrUpdate(cr1);
chest.getRoutines().add(cr1);
Should be
Muscle chest = new Muscle(getString(R.string.chest));
Muscle managedChestProxy = realm.copyToRealmOrUpdate(chest);
Routine cr1 = new Routine(getString(R.string.beginner_chest_1));
Routine managedRoutineProxy = realm.copyToRealmOrUpdate(cr1);
managedChestProxy.getRoutines().add(managedRoutineProxy);

How to store the arraylist in ormlite database

I am trying to save the arraylist of class objects into the ormlite database , but it is giving the error , java.lang.IllegalArgumentException: No fields have a DatabaseField annotation in class java.util.ArrayList
my code is
#DatabaseTable
public class ManageModelDetails {
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(foreign = true, foreignAutoRefresh = true)
private ArrayList<ModelDetails> listModelDetails;
// ===============================================
public ManageModelDetails() {
super();
}
// ===============================================
public ManageModelDetails(int id, ArrayList<ModelDetails> listModelDetails) {
super();
this.id = id;
this.listModelDetails = listModelDetails;
}
// ===============================================
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setModelList(ArrayList<ModelDetails> listModelDetails) {
this.listModelDetails = listModelDetails;
}
public ArrayList<ModelDetails> getModelList() {
return listModelDetails;
}
}
I think you need to use Foreign Collections. Take a look here:
Foreign Collections
Another similar question
If you want to save an ArrayList of objects to ORMLite the easiest way is this:
#DatabaseField(dataType = DataType.SERIALIZABLE)
private SerializedList<MyObject> myObjects;
and to get my list of objects:
public List<MyObject> getMyObjects() {
return myObjects;
}
and here is SerializedList:
public class SerializedList<E> extends ArrayList<E> implements Serializable {
}

Categories

Resources