I am currently refactoring some old app and I am trying to migrate from SQLite to Realm. I have used Realm before and I have never encountered problem like this. When I start my app for first time (after installation), I get this exception:
E/AndroidRuntime: FATAL EXCEPTION:
Process: xx.xxx.xxx.beta.realm, PID: 25947
java.lang.IllegalArgumentException: A RealmObject with no #PrimaryKey cannot be updated: class xx.xxx.xxx.realm.Vod
at io.realm.Realm.checkHasPrimaryKey(Realm.java:1184)
at io.realm.Realm.copyToRealmOrUpdate(Realm.java:713)
at xx.xxx.xxx.services.VodService$4.run(VodService.java:232)
at java.lang.Thread.run(Thread.java:818)
My Vod class looks like this and object is properly created, i.e. has value for PrimaryKey and all other fields:
public class Vod extends RealmObject {
#PrimaryKey
private String uuid;
private String name;
private Integer lengthMin;
private Boolean hasTrailer;
private String description;
private String originalName;
//etc...
//getters & setters
This will crash the app. But after that first time (and every next time) everything works fine - with same code and same data. But when I uninstall and reinstall app again, I will also encounter this exception again.
The part of code in question runs on background thread. If I move it to UI thread, everything works fine even for first time. But I want to parse network responses on background thread and not on UI. Also it isn't specific to Vod class, if I skip data for Vod and start with, for example, User class, then I get "no #PrimaryKey" exception for User class.
Sometimes it also throws this exception:
D/REALM: jni: ThrowingException 5, , .
D/REALM: Exception has been throw: File not found: .
E/AndroidRuntime: FATAL EXCEPTION:
Process: xx.xxx.xxx.beta.realm, PID: 28555
io.realm.exceptions.RealmIOException: File not found: .
at io.realm.internal.SharedGroup.createNativeWithImplicitTransactions(Native Method)
at io.realm.internal.SharedGroup.<init>(SharedGroup.java:67)
at io.realm.internal.SharedGroupManager.<init>(SharedGroupManager.java:47)
at io.realm.BaseRealm.<init>(BaseRealm.java:76)
at io.realm.Realm.<init>(Realm.java:126)
at io.realm.Realm.createAndValidate(Realm.java:246)
at io.realm.Realm.createInstance(Realm.java:231)
at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:114)
at io.realm.Realm.getDefaultInstance(Realm.java:181)
at xx.xxx.xx.services.RecordingService$9.run(RecordingService.java:317)
at java.lang.Thread.run(Thread.java:818)
which leads me to think, that there will be some problem with initialization of Realm file? I create it like this:
public class MainApplication extends Application {
private static Context mContext;
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
mRealmConfig = new RealmConfiguration.Builder(mContext).build();
Realm.setDefaultConfiguration(mRealmConfig);
}
}
and then use this to get instance in thread:
Realm localRealm = Realm.getDefaultInstance();
Am I doing something wrong? Has anyone has this type of problem before? I tried to search web for answer, but found nothing useful. Thanks for any help.
(I'm using io.realm:realm-android:0.87.4)
EDIT: I have confirmed (via ADB), that internal storage folder for my app package is removed after uninstall. While checking that, I found out that my files folder in path /data/data//files is empty during first app run. Is that correct behavior? Also, I cannot see anything inside Realm via Stetho.
Steps:
1. I uninstall app
ADB says cd: /data/data/xx.xxx.xxx.beta.realm: No such file or directory
I install and run app again
ADB run-as xx.xxx.xxx.beta.realm -> cd files -> ls -> empty
Inspect via Stetho, shows no data
Wait for minute or so (to give Realm time to create whatever needs)
Start network requests and app crashes ("no #PrimaryKey" exception) with first data to parse outside UI thread (parsing on UI works fine)
ADB -> files folder -> ls -> I can see everything, default.realm, default.realm.lock, default.realm.log, default.realm.log_a, default.realm.log_b
Start app again, everything works fine, I can even see realm data via
Stetho.
I really hope that I am making some stupid mistake and that everything will be fine at last. Also one more thing, on app start I see this log few times:
Rejecting re-init on previously-failed class java.lang.Class<io.realm.rx.RealmObservableFactory$4>
I have read this: https://github.com/realm/realm-java/issues/1990 and I don't think, that it is related to my problem, but just to be sure.
It's possible that your imports are messed up. Be sure to be importing io.realm.annotations.PrimaryKey, and not androidx.room.PrimaryKey, which might be a common issue.
Related
I am working on adding data persistence to an app using the Room library, based on this documentation. My database only has one column where a String corresponding to a user-selected radio button is to be placed. However, as soon as I attempt to insert a new object (in this case, an Attempt), it gives me the following error:
java.lang.ClassCastException: android.app.Application cannot be cast to com.example.mathreps.MathRepsApplication.
I believe this error comes from the following block of code within my Rating fragment:
private val dataViewModel: MathRepsViewModel by activityViewModels {
MathRepsViewModel.MathRepsViewModelFactory(
(activity?.application as MathRepsApplication).database
.attemptDao()
)
}
Which relates to a different class called MathRepsApplication
class MathRepsApplication: Application() {
val database: AttemptRoomDatabase by lazy {AttemptRoomDatabase.getDatabase(this)}
}
Since this problem most likely relates to multiple classes and packages, here is the link to the repository for the project.
My attempts to fix this have been unsuccessful, with them mostly consisting of heavily reviewing the database implementation and not much else, as I don't exactly know where to start.
I heard about Timber and was reading github README, but it's quietly confusing me.
Behavior is added through Tree instances. You can install an instance
by calling Timber.plant. Installation of Trees should be done as early
as possible. The onCreate of your application is the most logical
choice.
What behavior?
This is a logger with a small, extensible API which provides utility
on top of Android's normal Log class.
What more does it provide on top of Android's Log?
The DebugTree implementation will automatically figure out from which
class it's being called and use that class name as its tag. Since the
tags vary, it works really well when coupled with a log reader like
Pidcat.
What is DebugTree?
There are no Tree implementations installed by default because every
time you log in production, a puppy dies.
Again, what is a tree implementation? What does it do? And how do I stop killing puppies?
Two easy steps:
Install any Tree instances you want in the onCreate of your
application class.
Call Timber's static methods everywhere throughout your app.
Two easy steps for accomplishing what?
None of this has been explained in the Readme. It's pretty much a description for someone who already knows what it is :/
Problem :-
We do not want to print logs in Signed application as we may sometimes log sensible information . Generally to overcome this developers tend to write if condition before writing log
Example:-
if(BuildConfig.DEBUG) {
Log.d(TAG,userName);
}
so every time you want to print a log you need to write a if condition and a TAG which most times will be class name
Timber tackels these two problems
You just need to check condition once in application class and initialize Timber.plant
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
}
}
remaining all places we can just write Timber.d("Message") without any tag or if condition .
If you want a different tag then you can use
Timber.tag("Tag").d("message");
Edit :
Also you can plant your own trees in Timber , and do some cool stuff like Log only warnings and error in release , send warnings and error logs to server etc . eg
import timber.log.Timber;
public class ReleaseTree extends Timber.Tree {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == ERROR || priority == WARNING){
//Send to server
}
}
}
You can plant different trees for different build flavours .
Check out this article and have a listen to this podcast
Here's a little background. The process of querying the database (QueryDB) in my app begins in MainActivity.onCreate, where I have this code:
assets = getAssets(); // the SQLite database
DatabaseConnector
dbc = new DatabaseConnector(getApplicationContext(), assets);
dbc.setDbProcesslistener(this); // set way to know matches has been defined
dbc.findDBMatches();
And in the file in my question (named DatabaseConnector) I have:
void findDBMatches()
{
mContext.startService(new Intent(mContext, QueryDB.class));
}
Here's where the problem manifests itself. This code segment ...
public static class QueryDB extends IntentService
{
public QueryDB(String name)
{
super(name);
}
results in this error:
? E/libprocessgroup: failed to make and chown /acct/uid_10058: Read-only file system
? W/Zygote: createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?
com.dslomer64.servyhelperton E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.dslomer64.servyhelperton, PID: 335
java.lang.RuntimeException: Unable to instantiate service
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB:
java.lang.InstantiationException: class
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB has no zero argument constructor
The error doesn't tell me a line, but it does mention QueryDB. So I insert a zero-argument constructor for QueryDB and get an immediate error:
I circled extends IntentService because, in findDBMatches, I start a service for QueryDB and the first error message says Unable to instantiate service. However, in debugging, I found that execution didn't fail at the line mContext.startService(new Intent(mContext, QueryDB.class));. I had breakpoints set in the constructors for QueryDB but execution didn't go there.
I'm lost.
Before (foolishly) taking AS's advice about changes, the app worked fine. Now I could go back through the history and revert to the version before I began the changes, but there were plenty (of warnings) that I got rid of and I'd rather not do that. If anyone can, with such short snippets of code, suggest a fix, I can try it and maybe be good to go.
Further notes:
App won't work without QueryDB extending IntentService (get other immediate errors).
Note that mContext WAS declared static (which I now know not to do because of memory leaks) but it doesn't matter whether it's static or not. Same errors.
use super("QueryDB"); inside the constructor
public QueryDB {
super("QueryDB");
}
I had a bad crash case that was caused due to some Asyncs doing stuff in improper order in a SQLite and thing blew up. It took me some time to debug all that and access to the internal db would have helped immensely. I know how to access that internal db on a dev device but in case something goes wrong I would like to be able to get an instance of that db no matter the device. For error reporting I am using Crashlytics.
The question is: Is there a way to have Crashlytics run a piece of code (method, etc) during the crash collection/reporting? (For example, get db copy and email it, or something)
Couldn't find something in the documentation.
It is possible to get control prior to Crashlytics logging a crash. You essentially have to create your own uncaught exception handler and call Crashlytics' handler from there. Something like this in your Application class:
private UncaughtExceptionHandler originalUncaughtHandler;
#Override
public void onCreate() {
// initialize Fabric with Crashlytics
originalUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
// do the rest of your oncreate stuff
}
#Override
public void uncaughtException(Thread thread, Throwable ex) {
// do your work to add data to Crashlytics log
originalUncaughtHandler.uncaughtException(thread, ex);
}
No you can't. You can however set certain values before initiating Crashlytics. Like adding values to parameters so as to identify user. Like adding email id of user before creating a crashlytics session.
As #basu-singh said, you can add context to the crash, see https://docs.fabric.io/android/crashlytics/enhanced-reports.html
Or you can use your own UncaughtExceptionHandler, and then call Crashlytics. Though your code needs to be extra safe !
I'm getting the following error when using the encrypted SQLCipher database in my Android app, but only off and on:
net.sqlcipher.database.SQLiteException: not an error
at net.sqlcipher.database.SQLiteDatabase.dbopen(Native Method)
at net.sqlcipher.database.SQLiteDatabase.<init>(SQLiteDatabase.java:1950)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:900)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:947)
at net.sqlcipher.database.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:195)
at com.android.storage.DatabaseHelper.getReadable(DatabaseHelper.java:99)
...
I've got the proper files in the assets/ and libs/ folders because the database works fine most of the time. However, every once in awhile I'll see this error. I've seen this twice now on my phone and it's always been after resuming the app after hours of inactivity (I check for user's oauth token in db if it gets cleared from memory).
I call "SQLiteDatabase.loadLibs(this)" only from the Application::onCreate() method so my hunch is that this isn't getting called on a resume and is throwing the error. Does this sound possible? If so, where should I call loadLibs? A user could enter the app in any activity and I access the db if the token isn't in memory. I see my options as either calling loadLibs on each Activity::onCreate or calling it each time I attempt to open the db. Would it cause any harm or performance issues if I called it multiple times like this?
You might consider moving the SQLiteDatabase.loadLibs(this); to your application subclass of net.sqlcipher.database.SQLiteOpenHelper. You can then pass the static instance of your Application subclass as its argument. Something like the following might be an example:
public class SchemaManager extends net.sqlcipher.database.SQLiteOpenHelper {
private static SchemaManager instance;
public static synchronized SchemaManager getInstance() {
if(instance == null) {
SQLiteDatabase.loadLibs(YourApplication.getInstance());
instance = new SchemaManager(…)
}
return instance;
}
}
With regard to the exception that was provided, the Java routine calls into a JNI layer that calls sqlite3_open_v2, setting the soft heap limit and setting the busy timeout. I would suggest adding logging locally to verify you are passing a valid path and a non null passphrase when attempting to acquire the SQLiteDatabase instance when you get a crash. Calling SQLiteDatabase.loadLibs(this); multiple times shouldn't cause a noticeable performance impact, much of what occurs are calls to System.loadLibrary(…) which get mapped into Runtime.getRuntime().loadLibrary(…), once a dynamic library has been loaded, subsequent calls are ignored.