android realm predefined database performance - android

I have 6236 rows with Arabic character I used predefined database and load it successfully here is the code for read the file
private String copyBundledRealmFile(InputStream inputStream, String outFileName) {
try {
File file = new File(this.getFilesDir(), outFileName);
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, bytesRead);
}
outputStream.close();
return file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
and make configure for realm here
copyBundledRealmFile(SplashScreen.this.getResources().openRawResource(R.raw.tests), "test");
RealmConfiguration config1 = new RealmConfiguration.Builder(SplashScreen.this)
.name("test")
.schemaVersion(1)
.migration(new Migration())
.build();
Realm.setDefaultConfiguration(config1);
and make check to read and copy realm one time
but problem loading the data take about 5 seconds every time the app open to make the configure and have instance of realm
here is the code of realm instance
private static MyReleam instance;
private final Realm realm;
public MyReleam(Application application) {
realm = Realm.getDefaultInstance();
}
public static MyReleam with(Fragment fragment) {
if (instance == null) {
instance = new MyReleam(fragment.getActivity().getApplication());
}
return instance;
}
public static MyReleam with(Activity activity) {
if (instance == null) {
instance = new MyReleam(activity.getApplication());
}
return instance;
}
public static MyReleam with(Application application) {
if (instance == null) {
instance = new MyReleam(application);
}
return instance;
}
public Realm getRealm() {
return realm;
}
and use it here
this.realm = MyReleam.with(this).getRealm();
how can I optimize using it and decrease time of loading

1.) Use initialData() or assetFile() (preferably assetFile()) instead of populating in a migration
2.) Forget everything you've read in this tutorial because it's an outdated mess (I can tell that's where the MyReleam is from) and refer to my article instead

thanks for answer of EpicPandaForce I use documentation as https://realm.io/docs/java/latest/api/io/realm/RealmConfiguration.Builder.html
the I was conflict in asset pass it was only the name of files in asset
RealmConfiguration config1 = new RealmConfiguration.Builder(SplashScreen.this).assetFile(SplashScreen.this,"tests")//name of files in assets (test)
.name("test")
.schemaVersion(1).migration(new Migration())
.build();
Realm.setDefaultConfiguration(config1);

Related

How to do a Realm migration in Android?

I'm using Realms as a database in Android app. Works fine, but I've added a new label in my user model and I'm getting the error that I need to migrate my schema:
java.lang.RuntimeException: Unable to create application com.apelucy.apelucy.app.base.MyApplication: io.realm.exceptions.RealmMigrationNeededException: Migration is required due to the following errors:
- Property 'User.testRealm' has been added.
How can I do the migration? I've found other solutions here but I can't implement them in my code. I can't use a solution of delete and install the app. I now that work in development, but I need to update the app in production.
My UserRespository class:
public class UserRepository {
private static UserRepository sInstance = null;
private Context mContext = null;
public static UserRepository getInstance(Context context) {
if (sInstance == null) {
sInstance = new UserRepository();
sInstance.mContext = context;
}
return sInstance;
}
// DATABASE Methods
public void storeUser(final User user) {
AppSingleton.getInstance().setUser(user);
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 -> realm1.insertOrUpdate(user));
} finally {
if (realm != null) {
realm.close();
}
}
}
public User retrieveUser() {
Realm realm = null;
User user = null;
try {
realm = Realm.getDefaultInstance();
User userRealmResult = realm.where(User.class)
.findFirst();
if (userRealmResult != null) {
user = realm.copyFromRealm(userRealmResult);
}
} finally {
if (realm != null) {
realm.close();
}
}
return user;
}
public void clearUser() {
// Clear Database objects
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 -> realm1.delete(User.class));
} finally {
if (realm != null) {
realm.close();
}
}
}
}
Init realm in my Application:
Realm.init(this);
My model change:
#SerializedName("test")
#Expose
private String testRealm;
Migrations allow you to modify the schema of the application, which means that it lets you add, remove, rename tables/fields in the Realm schema. If you change a RealmModel class, then you must write the migration that will map the existing Realm file to reflect the new model classes.
RealmConfiguration config = new RealmConfiguration.Builder()
.schemaVersion(1)
.migration(new MyMigration())
.build();
Realm.setDefaultConfiguration(config);
The default schema version is 0.
Migrations are fairly straightforward:
you must increment the schema version, so Realm knows you want to increment the schema's version to a specific number
you must supply a migration that will handle the change from one version to another
Migrations describe the operations to do when you need to go from one schema version to another:
public class MyMigration implements RealmMigration {
#Override
public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema schema = realm.getSchema();
// Migrate from version 0 to version 1
if (oldVersion == 0) {
RealmObjectSchema userSchema = schema.get("User");
userSchema.addField("testRealm", String.class);
oldVersion++;
}
if (oldVersion == 1) { // ...
// ...
}
}
#Override
public int hashCode() { return MyMigration.class.hashCode(); }
#Override
public boolean equals(Object object) { return object != null && object instanceof MyMigration; }
}
Add this in your Application file. This will Realm to delete everything if you add a new table to a column.
RealmConfiguration config = new RealmConfiguration.Builder().name("dbname.realm")
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(config);

using static variable for realm database variable

Is this a better way to write realm access variables? I am using static variable because I notice that I only need 1 declaration of database connection for all realm queries in other class. But I am curious if this is still good at coding standards.
using Realms;
using System;
using System.IO;
namespace RealmDatabase
{
public class RealmDBAccessVariable
{
public static readonly string dbPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"default.realm");
public static readonly RealmConfiguration config = new RealmConfiguration(dbPath, true);
}
}
This is the singleton pattern that I use for a Realm instance that I use on the UI Thread:
Xamarin.Android:
public sealed class UIRealm
{
static Realms.Realm instance;
public UIRealm(Activity activity, RealmConfiguration config)
{
if (instance == null)
activity.RunOnUiThread(() =>
{
instance = Realms.Realm.GetInstance(config);
});
else
throw new Exception("A Realm instance has already be created");
}
public static Realms.Realm Instance
{
get
{
if (instance == null)
throw new Exception("Call new UIRealm(Activity, RealmConfiguration) first");
return instance;
}
}
}
Usage:
// Create a UIRealm() only once
new UIRealm(this, new RealmConfiguration("realm.db"));
~~~
var aState = new State();
aState.Name = "WA";
UIRealm.Instance.Manage(aState);
var washingtonState = UIRealm.Instance.All<State>().Where((state => state.Name == "WA"));
Xamarin.Forms:
public sealed class UIRealm
{
static Realm instance;
public UIRealm(RealmConfiguration config)
{
if (instance == null)
Device.BeginInvokeOnMainThread(() =>
{
instance = Realm.GetInstance(config);
});
else
throw new Exception("A Realm instance has already be created");
}
public static Realm Instance
{
get
{
if (instance == null)
throw new Exception("Call new UIRealm(RealmConfiguration) first");
return instance;
}
}
}
Usage:
// Create a UIRealm() only once
new UIRealm(new RealmConfiguration("realm.db"));
~~~
var aState = new State();
aState.Name = "WA";
UIRealm.Instance.Manage(aState);
var washingtonState = UIRealm.Instance.All<State>().Where((state => state.Name == "WA"));
I think you will most probably run into problems the moment you try to write to the Realm on a background thread, and you attempt to access the realm for this operation which you opened on the UI thread.
static is generally insufficient, ThreadLocal is what would achieve this in Java.
I'm also not sure why this object is a RealmObject.

How to migrate unencryped realm to encrypt realm

My last release app is using not encrypted realm.
Now, I want to update to use encrypted realm.
But I don't know how to migrate unencrypted data.
Help me please~ :(
Answer by myself.
I made util class to help migration. (unecncrypted file -> encrpyted file)
public class RealmEncryptionHelper {
private static final String ENCRYPTION_FILE_PREFIX = "encrypted_";
public static Realm createEncryptedRealm(Context context, RealmConfiguration.Builder builder) {
RealmConfiguration unencryptedConfig = builder.build();
RealmConfiguration encryptedConfig = builder.name(ENCRYPTION_FILE_PREFIX + unencryptedConfig.getRealmFileName())
.encryptionKey(AppSharedPreferences.getInstance(context).getRealmEncryptionKey())
.build();
migrationIfNeeded(unencryptedConfig, encryptedConfig);
return Realm.getInstance(encryptedConfig);
}
private static void migrationIfNeeded(RealmConfiguration unencryptedConfig, RealmConfiguration encryptedConfig) {
File unencryptedFile = new File(unencryptedConfig.getPath());
File encryptedFile = new File(encryptedConfig.getPath());
Realm unencryptedRealm = null;
if (!encryptedFile.exists() && unencryptedFile.exists()) {
try {
unencryptedRealm = Realm.getInstance(unencryptedConfig);
unencryptedRealm.writeEncryptedCopyTo(encryptedFile, encryptedConfig.getEncryptionKey());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (unencryptedRealm != null) {
unencryptedRealm.close();
unencryptedFile.delete();
}
}
}
}
}
#Orlando
like this?
class EncryptionMigration implements RealmMigration {
#Override
public void migrate(DynamicRealm dynamicRealm, long oldVersion, long newVersion) {
byte[] encryptionKey = "flkajskdf............................".getBytes();
if (oldVersion == UNENCRYPT_VERSION) {
try {
dynamicRealm.writeEncryptedCopyTo(new File(dynamicRealm.getPath()), encryptionKey);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Realm bad behavior in background thread

Changes in the database from a background thread is displayed only after restart the application. Why? How to fix?
public class UILApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
RealmConfiguration config = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(config);
}
In the background is next code inserting to DB:
public void setFollows(List<JSONObject> follows) {
Realm realm = Realm.getDefaultInstance();
List<Follow> finalFollows = new RealmList<>();
try {
for(JSONObject follow:follows){
finalFollows.add(new Follow(follow.getLong("id"),follow.getString("username"), follow.getString("profile_picture"), follow.getString("full_name")));
}
List<Follow> goneFollows = getGoneFollows(finalFollows);
List<Follow> newFollows = getNewFollows(finalFollows);
realm.beginTransaction();
if(goneFollows != null && !goneFollows.isEmpty()){
goneFollows = realm.copyToRealmOrUpdate(goneFollows);
L.d("getHistoryForUpdate goneFollows");
History history = getHistoryForUpdate();
history.getRemoveFollows().clear();
history.getRemoveFollows().addAll(goneFollows);
}
if(newFollows != null && !newFollows.isEmpty()){
newFollows = realm.copyToRealmOrUpdate(newFollows);
L.d("getHistoryForUpdate newFollows");
History history = getHistoryForUpdate();
history.getNewFollows().clear();
history.getNewFollows().addAll(newFollows);
}
finalFollows = realm.copyToRealmOrUpdate(finalFollows);
getFollows().clear();
getFollows().addAll(finalFollows);
realm.commitTransaction();
realm.close();
} catch (JSONException e) {
e.printStackTrace();
}
}
public History getHistoryForUpdate(){
Realm realm = Realm.getDefaultInstance();
String today = DateHandler.getOnlyDate();
History history = realm.where(History.class).equalTo("createDate", today).findFirst();
if(history == null){
L.d("getHistoryForUpdate new");
realm.beginTransaction();
history = new History();
history = realm.copyToRealm(history);
history.setCreateDate(today);
getHistoryList().add(history);
realm.commitTransaction();
}
L.d("getHistoryForUpdate");
realm.close();
return history;
}
In Fragment trying to enter new data but I get only after you restart the application
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
favorite = realm.where(Favorite.class).equalTo("id", Long.parseLong(userId))
.findFirst();
RealmList<History> historyList = favorite.getHistoryList();
...
}
Inside background thread are all well written and have access to the data at once, but to get the data from the application, it must be restarted
<receiver
android:name=".core.receivers.Alarm"
android:process=":remote" />
Success came after removal of the second attribute

Parsing large text file efficiency

Due to simplicity i have a text file with entries separated by ; and parses every line into an object. The problem is that the text file contains almost 10 000 rows.
I also need to create keys for each object im parsing so i can filter the results in a search interface.
It takes almost 16 seconds in emulator to parse the text and add the keys. I'm i doing something wrong here? Or is there a more efficient way?
Here is my database singleton:
public class Database {
private static Database instance = null; private final Map<String, List<Stop>> mDict = new ConcurrentHashMap<String, List<Stop>>();
public static Database getInstance() { if (instance == null) { instance = new Database(); } return instance; } public List<Stop> getMatches(String query) {
List<Stop> list = mDict.get(query);
return list == null ? Collections.EMPTY_LIST : list;
}
private boolean mLoaded = false;
/**
* Loads the words and definitions if they haven't been loaded already.
*
* #param resources Used to load the file containing the words and definitions.
*/
public synchronized void ensureLoaded(final Resources resources) {
if (mLoaded) return;
new Thread(new Runnable() {
public void run() {
try {
loadStops(resources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
private synchronized void loadStops(Resources resources) throws IOException
{
if (mLoaded) return;
Log.d("database", "loading stops");
InputStream inputStream = resources.openRawResource(R.raw.allstops);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line;
while((line = reader.readLine()) != null) {
String[] strings = TextUtils.split(line, ";");
addStop(strings[0], strings[1], strings[2]);
}
} finally {
reader.close();
}
Log.d("database", "loading stops completed");
mLoaded = true;
}
private void addStop(String name, String district, String id) {
Stop stop = new Stop(id, name, district);
int len = name.length();
for (int i = 0; i < len; i++) {
String prefix = name.substring(0, len - i).toLowerCase();
addMatch(prefix, stop);
}
}
private void addMatch(String query, Stop stop) {
List<Stop> matches = mDict.get(query);
if (matches == null) {
matches = new ArrayList<Stop>();
mDict.put(query, matches);
}
matches.add(stop);
}
}
Here is some sample data:
Mosseporten Senter;Norge;9021014089003000;59.445422;10.701055;273
Oslo Bussterminal;Norge;9021014089004000;59.911369;10.759665;273
Långegärde;Strömstad;9021014026420000;58.891462;11.007767;68
Västra bryggan;Strömstad;9021014026421000;58.893080;11.009997;7
Vettnet;Strömstad;9021014026422000;58.903184;11.020739;7
Ekenäs;Strömstad;9021014026410000;58.893610;11.048821;7
Kilesand;Strömstad;9021014026411000;58.878472;11.052983;7
Ramsö;Strömstad;9021014026430000;58.831531;11.067402;7
Sarpsborg;Norge;9021014089002000;59.280937;11.111763;273
Styrsö;Strömstad;9021014026180000;58.908110;11.115818;7
Capri/Källviken;Strömstad;9021014026440000;58.965200;11.124384;63
Lindholmens vändplan;Strömstad;9021014026156000;58.890212;11.128393;64
Öddö;Strömstad;9021014026190000;58.923490;11.130767;7
Källviksdalen;Strömstad;9021014026439000;58.962414;11.131962;64
Husevägen;Strömstad;9021014026505000;58.960094;11.133535;274
Caprivägen;Strömstad;9021014026284000;58.958404;11.134281;64
Stensviks korsväg;Strömstad;9021014026341000;59.001499;11.137203;63
Kungbäck;Strömstad;9021014026340000;59.006056;11.140313;63
Kase;Strömstad;9021014026173000;58.957649;11.141904;274
You should add the information into a SQLite database and ship the app with the database in res/raw.
Additionally, the db file can often be effectively compressed into a zip file.
See this for more information: Ship an application with a database
The fastest way to load that data into memory is to place it right into .java file. E.g. stopNames={"Stop1", "Stop2", ...}; latitudes={...};
I do this in my public transit app, loading similar amounts of the data this way takes under a second, filtering is instant.

Categories

Resources