Last active
August 29, 2020 18:21
-
-
Save Zeyad-37/9e3fac136ab6ee520d151f9c00563fe9 to your computer and use it in GitHub Desktop.
A generic wrapper class around realm to perform all CRUD operations, while handling its realm instances and supplying live objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class RealmManager { | |
private static final String REALM_OBJECT_INVALID = "RealmObject is invalid", | |
JSON_INVALID = "JSONObject is invalid", NO_ID = "Could not find id!"; | |
private static DataBaseManager sInstance; | |
private static Handler backgroundHandler; | |
private static HandlerThread handlerThread; | |
private RealmManager() { | |
} | |
/** | |
* Use this function to re-instantiate general realm manager or instance for the first time. | |
* Previous instances would be deleted and new created | |
*/ | |
static void init() { | |
sInstance = new RealmManager(); | |
} | |
/** | |
* @return RealmManager the implemented instance of the DatabaseManager. | |
*/ | |
static DataBaseManager getInstance() { | |
if (sInstance == null) | |
init(); | |
return sInstance; | |
} | |
private static boolean hasKitKat() { | |
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; | |
} | |
private Handler provideBackgroundHandler() { | |
if (handlerThread == null) | |
handlerThread = new HandlerThread("backgroundThread"); | |
if (!handlerThread.isAlive()) | |
handlerThread.start(); | |
return new Handler(handlerThread.getLooper()); | |
} | |
/** | |
* Gets an {@link Observable} which will emit an Object. | |
* | |
* @param dataClass Class type of the items to get. | |
* @param idColumnName Name of the id field. | |
* @param itemId The user id to retrieve data. | |
*/ | |
@NonNull | |
public Observable<?> getById(@NonNull final String idColumnName, final int itemId, Class dataClass) { | |
return Observable.defer(() -> { | |
int finalItemId = itemId; | |
if (finalItemId <= 0) | |
finalItemId = Utils.getMaxId(dataClass, idColumnName); | |
Realm realm = Realm.getDefaultInstance(); | |
return realm.where(dataClass).equalTo(idColumnName, finalItemId).findAll().asObservable() | |
.filter(results -> ((RealmResults) results).isLoaded()) | |
.map(o -> realm.copyFromRealm((RealmResults) o)) | |
.doOnUnsubscribe(() -> closeRealm(realm)); | |
}); | |
} | |
/** | |
* Gets an {@link Observable} which will emit a List of Objects. | |
* | |
* @param clazz Class type of the items to get. | |
*/ | |
@NonNull | |
public Observable<List> getAll(Class clazz) { | |
return Observable.defer(() -> { | |
Realm realm = Realm.getDefaultInstance(); | |
return realm.where(clazz).findAll().asObservable() | |
.filter(results -> ((RealmResults) results).isLoaded()) | |
.map(o -> realm.copyFromRealm((RealmResults) o)) | |
.doOnUnsubscribe(() -> closeRealm(realm)); | |
}); | |
} | |
/** | |
* Takes a query to be executed and return a list of containing the result. | |
* | |
* @param queryFactory The query used to look for inside the DB. | |
* @param <T> the return type from the query | |
* @return {@link List<T>} a result list that matches the given query. | |
*/ | |
public <T extends RealmModel> Observable<List<T>> getQuery(RealmQueryProvider<T> queryFactory) { | |
return Observable.defer(() -> { | |
Realm realm = Realm.getDefaultInstance(); | |
return queryFactory.create(realm).findAll().asObservable() | |
.filter(RealmResults::isLoaded) | |
.map(realm::copyFromRealm) | |
.doOnUnsubscribe(() -> closeRealm(realm)); | |
}); | |
} | |
/** | |
* Puts and element into the DB. | |
* | |
* @param realmObject Element to insert in the DB. | |
* @param dataClass Class type of the items to be put. | |
*/ | |
@NonNull | |
public Observable<?> put(@Nullable RealmObject realmObject, @NonNull Class dataClass) { | |
if (realmObject != null) { | |
return Observable.defer(() -> { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
RealmObject result = executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmObject)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
RealmObject result = executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmObject)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} | |
/** | |
* Puts and element into the DB. | |
* | |
* @param realmModel Element to insert in the DB. | |
* @param dataClass Class type of the items to be put. | |
*/ | |
@NonNull | |
public Observable<?> put(@Nullable RealmModel realmModel, @NonNull Class dataClass) { | |
if (realmModel != null) { | |
return Observable.defer(() -> { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
RealmModel result = executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmModel)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
RealmModel result = executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmModel)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} | |
/** | |
* Puts and element into the DB. | |
* | |
* @param jsonObject Element to insert in the DB. | |
* @param dataClass Class type of the items to be put. | |
*/ | |
@NonNull | |
public Observable<?> put(@Nullable JSONObject jsonObject, @Nullable String idColumnName, @NonNull Class dataClass) { | |
if (jsonObject != null) { | |
return Observable.defer(() -> { | |
try { | |
updateJsonObjectWithIdValue(jsonObject, idColumnName, dataClass); | |
} catch (@NonNull JSONException | IllegalArgumentException e) { | |
return Observable.error(e); | |
} | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
RealmModel result = executeWriteOperationInRealm(realm, () -> realm.createOrUpdateObjectFromJson(dataClass, jsonObject)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
RealmModel result = executeWriteOperationInRealm(realm, () -> realm.createOrUpdateObjectFromJson(dataClass, jsonObject)); | |
if (RealmObject.isValid(result)) { | |
return Observable.just(Boolean.TRUE); | |
} else | |
return Observable.error(new IllegalArgumentException(REALM_OBJECT_INVALID)); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} else | |
return Observable.defer(() -> Observable.error(new IllegalArgumentException(JSON_INVALID))); | |
} | |
/** | |
* Puts and element into the DB. | |
* | |
* @param jsonArray Element to insert in the DB. | |
* @param idColumnName Name of the id field. | |
* @param dataClass Class type of the items to be put. | |
*/ | |
@NonNull | |
public Observable<?> putAll(@NonNull JSONArray jsonArray, String idColumnName, @NonNull Class dataClass) { | |
return Observable.defer(() -> { | |
try { | |
updateJsonArrayWithIdValue(jsonArray, idColumnName, dataClass); | |
} catch (@NonNull JSONException | IllegalArgumentException e) { | |
return Observable.error(e); | |
} | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
executeWriteOperationInRealm(realm, () -> realm.createOrUpdateAllFromJson(dataClass, jsonArray)); | |
return Observable.just(Boolean.TRUE); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
executeWriteOperationInRealm(realm, () -> realm.createOrUpdateAllFromJson(dataClass, jsonArray)); | |
return Observable.just(Boolean.TRUE); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} | |
/** | |
* Puts and element into the DB. | |
* | |
* @param realmModels Element to insert in the DB. | |
* @param dataClass Class type of the items to be put. | |
*/ | |
public Observable<?> putAll(@NonNull List<RealmObject> realmModels, @NonNull Class dataClass) { | |
return Observable.defer(() -> { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmModels)); | |
return Observable.just(Boolean.TRUE); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
executeWriteOperationInRealm(realm, () -> realm.copyToRealmOrUpdate(realmModels)); | |
return Observable.just(Boolean.TRUE); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} | |
/** | |
* Evict all elements of the DB. | |
* | |
* @param clazz Class type of the items to be deleted. | |
*/ | |
@NonNull | |
public Observable<Boolean> evictAll(@NonNull Class clazz) { | |
return Observable.defer(() -> { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
executeWriteOperationInRealm(realm, () -> realm.delete(clazz)); | |
return Observable.just(Boolean.TRUE); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
executeWriteOperationInRealm(realm, () -> realm.delete(clazz)); | |
return Observable.just(Boolean.TRUE); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}); | |
} | |
/** | |
* Evict element of the DB. | |
* | |
* @param realmModel Element to deleted from the DB. | |
* @param clazz Class type of the items to be deleted. | |
*/ | |
public void evict(@NonNull final RealmObject realmModel, @NonNull Class clazz) { | |
Observable.defer(() -> { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
executeWriteOperationInRealm(realm, (Executor) realmModel::deleteFromRealm); | |
return Observable.just(Boolean.TRUE); | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
executeWriteOperationInRealm(realm, (Executor) realmModel::deleteFromRealm); | |
return Observable.just(Boolean.TRUE); | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
}).subscribeOn(Schedulers.immediate()) | |
.subscribe(new EvictSubscriberClass(clazz)); | |
} | |
/** | |
* Evict element by id of the DB. | |
* | |
* @param clazz Class type of the items to be deleted. | |
* @param idFieldName The id used to look for inside the DB. | |
* @param idFieldValue Name of the id field. | |
*/ | |
public boolean evictById(@NonNull Class clazz, @NonNull String idFieldName, final long idFieldValue) { | |
if (hasKitKat()) | |
try (Realm realm = Realm.getDefaultInstance()) { | |
RealmModel toDelete = realm.where(clazz).equalTo(idFieldName, idFieldValue).findFirst(); | |
if (toDelete != null) { | |
executeWriteOperationInRealm(realm, () -> RealmObject.deleteFromRealm(toDelete)); | |
return !RealmObject.isValid(toDelete); | |
} else return false; | |
} | |
else { | |
Realm realm = Realm.getDefaultInstance(); | |
try { | |
RealmModel toDelete = realm.where(clazz).equalTo(idFieldName, idFieldValue).findFirst(); | |
if (toDelete != null) { | |
executeWriteOperationInRealm(realm, () -> RealmObject.deleteFromRealm(toDelete)); | |
return !RealmObject.isValid(toDelete); | |
} else return false; | |
} finally { | |
closeRealm(realm); | |
} | |
} | |
} | |
/** | |
* Evict a collection elements of the DB. | |
* | |
* @param idFieldName The id used to look for inside the DB. | |
* @param list List of ids to be deleted. | |
* @param dataClass Class type of the items to be deleted. | |
*/ | |
@NonNull | |
public Observable<?> evictCollection(@NonNull String idFieldName, @NonNull List<Long> list, | |
@NonNull Class dataClass) { | |
return Observable.defer(() -> { | |
boolean isDeleted = true; | |
for (int i = 0, size = list.size(); i < size; i++) | |
isDeleted = isDeleted && evictById(dataClass, idFieldName, list.get(i)); | |
return Observable.just(isDeleted); | |
}); | |
} | |
private void closeRealm(Realm realm) { | |
try { | |
if (backgroundHandler == null) | |
backgroundHandler = provideBackgroundHandler(); | |
backgroundHandler.post(() -> { | |
if (!realm.isClosed()) { | |
realm.close(); | |
Log.d(RealmManager.class.getSimpleName(), "realm instance closed!"); | |
} | |
}); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
private void executeWriteOperationInRealm(@NonNull Realm realm, @NonNull Executor executor) { | |
if (realm.isInTransaction()) | |
realm.cancelTransaction(); | |
realm.beginTransaction(); | |
executor.run(); | |
realm.commitTransaction(); | |
} | |
private <T> T executeWriteOperationInRealm(@NonNull Realm realm, @NonNull ExecuteAndReturn<T> executor) { | |
T toReturnValue; | |
if (realm.isInTransaction()) | |
realm.cancelTransaction(); | |
realm.beginTransaction(); | |
toReturnValue = executor.run(); | |
realm.commitTransaction(); | |
return toReturnValue; | |
} | |
@NonNull | |
private JSONArray updateJsonArrayWithIdValue(@NonNull JSONArray jsonArray, @Nullable String idColumnName, | |
Class dataClass) throws JSONException { | |
if (idColumnName == null || idColumnName.isEmpty()) | |
throw new IllegalArgumentException(NO_ID); | |
for (int i = 0, length = jsonArray.length(); i < length; i++) | |
if (jsonArray.opt(i) instanceof JSONObject) | |
updateJsonObjectWithIdValue(jsonArray.optJSONObject(i), idColumnName, dataClass); | |
return jsonArray; | |
} | |
@NonNull | |
private JSONObject updateJsonObjectWithIdValue(@NonNull JSONObject jsonObject, @Nullable String idColumnName, | |
Class dataClass) throws JSONException { | |
if (idColumnName == null || idColumnName.isEmpty()) | |
throw new IllegalArgumentException(NO_ID); | |
if (jsonObject.optInt(idColumnName) == 0) | |
jsonObject.put(idColumnName, Utils.getNextId(dataClass, idColumnName)); | |
return jsonObject; | |
} | |
public interface RealmQueryProvider<T extends RealmModel> { | |
RealmQuery<T> create(Realm realm); | |
} | |
private interface Executor { | |
void run(); | |
} | |
private interface ExecuteAndReturn<T> { | |
@NonNull | |
T run(); | |
} | |
private static class EvictSubscriberClass extends Subscriber<Object> { | |
private final Class mClazz; | |
EvictSubscriberClass(Class clazz) { | |
mClazz = clazz; | |
} | |
@Override | |
public void onCompleted() { | |
unsubscribe(); | |
} | |
@Override | |
public void onError(@NonNull Throwable e) { | |
e.printStackTrace(); | |
unsubscribe(); | |
} | |
@Override | |
public void onNext(Object o) { | |
Log.d(RealmManager.class.getName(), mClazz.getName() + " deleted!"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment