I have the following table with a PrimaryKey in it. I have inserted some values in the table. Now I need to update a particular value in a particular row. I have a row with gameType as Puzzle and I need to update the currentLevel in the row. But I am not able to achieve that.
GamesDetails table:
public class GamesDetail extends RealmObject {
#PrimaryKey
private String gameType;
private int currentLevel;
private int totalLevel;
private int totalCoins;
private int currentBadge;
public String getGameType() {
return gameType;
}
public void setGameType(String gameType) {
this.gameType = gameType;
}
public int getCurrentLevel() {
return currentLevel;
}
public void setCurrentLevel(int currentLevel) {
this.currentLevel = currentLevel;
}
public int getTotalLevel() {
return totalLevel;
}
public void setTotalLevel(int totalLevel) {
this.totalLevel = totalLevel;
}
public int getTotalCoins() {
return totalCoins;
}
public void setTotalCoins(int totalCoins) {
this.totalCoins = totalCoins;
}
public int getCurrentBadge() {
return currentBadge;
}
public void setCurrentBadge(int currentBadge) {
this.currentBadge = currentBadge;
}
}
Here is what I have tried to update a particular row in the table:
final GamesDetail puzzleGameDetail = realm.where(GamesDetail.class).equalTo("gameType","Puzzle").findFirst();
final int[] nextLevel = {puzzleGameDetail.getCurrentLevel()};
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
puzzleGameDetail.setCurrentLevel(++nextLevel[0]);
realm.copyToRealmOrUpdate(puzzleGameDetail);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.e(TAG, "Done");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.e(TAG,error.getMessage());
}
});
But the value is not getting updated and I am getting this following error:
Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
How can I update a particular value in a particular row in the table ?
When calling executeTransactionAsync, the execute block will run in a background thread, any Realm objects access from that thread need to be created/queried on that thread from the Realm instance which is the param of execute.
Move your finding GamesDetail query inside execute block and rest will work fine.
Related
Hi guys I am trying to save objects with relations in Backendless via API. I have two classes namely Task and Reminder. A task can be associated with many reminders hence I want a 1:N relationship between the Task table and Reminder table in Backendless. My Task class is as follows:
public class Task {
public Date created;
public Date updated;
private List<Reminder> reminders = null;
private String ownerId;
#PrimaryKey
#NonNull
private String objectId;
#NonNull
private String taskTitle;
#NonNull
private Date deadline;
#NonNull
private int isCompleted = 0;
#NonNull
private int isExpired = 0;
public String getOwnerId() {
return ownerId;
}
public void setOwnerId(String ownerId) {
this.ownerId = ownerId;
}
#NonNull
public String getObjectId() {
return objectId;
}
public void setObjectId(#NonNull String objectId) {
this.objectId = objectId;
}
public List<Reminder> getReminders() {
return reminders;
}
public void setReminders(List<Reminder> reminders) {
this.reminders = reminders;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
#NonNull
public int getIsCompleted() {
return isCompleted;
}
public void setIsCompleted(#NonNull int isCompleted) {
this.isCompleted = isCompleted;
}
#NonNull
public int getIsExpired() {
return isExpired;
}
public void setIsExpired(#NonNull int isExpired) {
this.isExpired = isExpired;
}
public String getTaskTitle() {
return taskTitle;
}
public void setTaskTitle(String taskTitle) {
this.taskTitle = taskTitle;
}
public Date getDeadline() {
return deadline;
}
public void setDeadline(Date deadline) {
this.deadline = deadline;
}
}
Reminder Class:
public class Reminder {
private String title;
private Date time;
private String objectId;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
}
I am saving the objects and setting up the relation as below:
public void saveTaskToServer(final Task task) {
List<Reminder> remindersList = new ArrayList<>();
remindersList = task.getReminders();
final List<Reminder> savedReminders = new ArrayList<>();
if(remindersList!=null && remindersList.size()!=0) {
for
(Reminder reminder : remindersList) {
reminder.setTitle(task.getTaskTitle());
Backendless.Persistence.save(reminder, new AsyncCallback<Reminder>() {
#Override
public void handleResponse(Reminder response) {
savedReminders.add(response);
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("error saving remidners", fault.toString());
}
});
}
}
Backendless.Persistence.save(task, new AsyncCallback<Task>() {
#Override
public void handleResponse(Task response) {
newTask = response;
Log.i("id is ", newTask.getObjectId());
insertTask(response);
snackbarMessage.postValue("Task Created Successfully.");
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("error", fault.getMessage());
}
});
Backendless.Persistence.of(Task.class).addRelation(task, "reminders", savedReminders, new AsyncCallback<Integer>() {
#Override
public void handleResponse(Integer response) {
Log.i("response", "added" + response);
newTask.setReminders(savedReminders);
}
#Override
public void handleFault(BackendlessFault fault) {
Log.i("response", "error" + fault.toString());
}
});
}
I have tried saving the relation using the tablename:Class:n instead of the parentColumnName. Also tried saving the objectids of the reminders instead of the reminder objects themselves.The task and reminder objects get saved properly in the backendless console in their respective tables but the reminder column in the Task table still remains empty and no relations get added. Relations count in the backendless call in Android Studio also returns 0. Any advice is really appreciated. I have been following this example.
My relations were not getting saved because I was using the async callbacks in backendless!! I dont know why I didnt see that before. Since the save calls were being made before the async callbacks could finish I was ending up with null values. Fixed it by making the calls synchronous and wrapping them in an async task.
solved
Android - Firebase - Getting Child of Child Data
how to view leaf nodes both children and grandchildren
E/data: {6640={first={sub3=dbms, m5=3, m2=2, sub2=dl, sub1=cis, m3=2, sub4=socio, m4=2, sub5=english, m1=4}}}
firebase data structure
the values i want are
the children inside 6640 eg : first and second
the grandchildren inside first eg : sub1--sub5 and m1-m5
the data above is a snapshot data snapshot from a query.. how do i access each values from the following snapshot.. i created a data module and used a for each loop like this
for (DataSnapshot post : dataSnapshot.getChildren())
{
trial_module m2 = post.getValue(trial_module.class);
Log.e("data",dataSnapshot.getValue().toString());
data2.add(m2);
}
do i need to create mutiple classes to hold data?
public class Result_module {
String sub1,sub2,sub3,sub4,sub5;
long m1,m2,m3,m4,m5;
public String getSub1() {
return sub1;
}
public void setSub1(String sub1) {
this.sub1 = sub1;
}
public String getSub2() {
return sub2;
}
public void setSub2(String sub2) {
this.sub2 = sub2;
}
public String getSub3() {
return sub3;
}
public void setSub3(String sub3) {
this.sub3 = sub3;
}
public String getSub4() {
return sub4;
}
public void setSub4(String sub4) {
this.sub4 = sub4;
}
public String getSub5() {
return sub5;
}
public void setSub5(String sub5) {
this.sub5 = sub5;
}
public long getM1() {
return m1;
}
public void setM1(long m1) {
this.m1 = m1;
}
public long getM2() {
return m2;
}
public void setM2(long m2) {
this.m2 = m2;
}
public long getM3() {
return m3;
}
public void setM3(long m3) {
this.m3 = m3;
}
public long getM4() {
return m4;
}
public void setM4(long m4) {
this.m4 = m4;
}
public long getM5() {
return m5;
}
public void setM5(long m5) {
this.m5 = m5;
}
}
Recently I was trying this:
I have a list of jobs backed by data source (I am using paging library) and each item in job list is having a save button, and that save button updates the status of the job from unsaved to saved (or vice versa) in database and once updated it invalidates the DataSource, now that invalidation should cause reload for the current page immediately, but that isn't happening.
I checked values in database they actually get updated but that isn't the case with the UI.
Code:
public class JobsPagedListProvider {
private JobListDataSource<JobListItemEntity> mJobListDataSource;
public JobsPagedListProvider(JobsRepository jobsRepository) {
mJobListDataSource = new JobListDataSource<>(jobsRepository);
}
public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
return new LivePagedListProvider<Integer, JobListItemEntity>() {
#Override
protected DataSource<Integer, JobListItemEntity> createDataSource() {
return mJobListDataSource;
}
};
}
public void setQueryFilter(String query) {
mJobListDataSource.setQuery(query);
}
}
Here is my custom datasource:
public class JobListDataSource<T> extends TiledDataSource<T> {
private final JobsRepository mJobsRepository;
private final InvalidationTracker.Observer mObserver;
String query = "";
#Inject
public JobListDataSource(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
mJobsRepository.setJobListDataSource(this);
mObserver = new InvalidationTracker.Observer(JobListItemEntity.TABLE_NAME) {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
invalidate();
}
};
jobsRepository.addInvalidationTracker(mObserver);
}
#Override
public boolean isInvalid() {
mJobsRepository.refreshVersionSync();
return super.isInvalid();
}
#Override
public int countItems() {
return DataSource.COUNT_UNDEFINED;
}
#Override
public List<T> loadRange(int startPosition, int count) {
return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}
public void setQuery(String query) {
this.query = query;
}
}
Here is the code in JobsRepository that updates job from unsaved to saved:
public void saveJob(JobListItemEntity entity) {
Completable.fromCallable(() -> {
JobListItemEntity newJob = new JobListItemEntity(entity);
newJob.isSaved = true;
mJobDao.insert(newJob);
Timber.d("updating entity from " + entity.isSaved + " to "
+ newJob.isSaved); //this gets printed in log
//insertion in db is happening as expected but UI is not receiving new list
mJobListDataSource.invalidate();
return null;
}).subscribeOn(Schedulers.newThread()).subscribe();
}
Here is the Diffing logic for job list:
private static final DiffCallback<JobListItemEntity> DIFF_CALLBACK = new DiffCallback<JobListItemEntity>() {
#Override
public boolean areItemsTheSame(#NonNull JobListItemEntity oldItem, #NonNull JobListItemEntity newItem) {
return oldItem.jobID == newItem.jobID;
}
#Override
public boolean areContentsTheSame(#NonNull JobListItemEntity oldItem, #NonNull JobListItemEntity newItem) {
Timber.d(oldItem.isSaved + " comp with" + newItem.isSaved);
return oldItem.jobID == newItem.jobID
&& oldItem.jobTitle.compareTo(newItem.jobTitle) == 0
&& oldItem.isSaved == newItem.isSaved;
}
};
JobListDataSource in JobRepository (only relevant portion is mentioned below):
public class JobsRepository {
//holds an instance of datasource
private JobListDataSource mJobListDataSource;
//setter
public void setJobListDataSource(JobListDataSource jobListDataSource) {
mJobListDataSource = jobListDataSource;
}
}
getJobs() in JobsRepository:
public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
if (!isJobListInit) {
Observable<JobList> jobListObservable = mApiService.getOpenJobList(
mRequestJobList.setPageNo(startPosition/count + 1)
.setMaxResults(count)
.setSearchKeyword(query));
List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);
//make a synchronous network call since we have no data in db to return
if(jobs.size() == 0) {
JobList jobList = jobListObservable.blockingSingle();
updateJobList(jobList, startPosition);
} else {
//make an async call and return cached version meanwhile
jobListObservable.subscribe(new Observer<JobList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(JobList jobList) {
updateJobList(jobList, startPosition);
}
#Override
public void onError(Throwable e) {
Timber.e(e);
}
#Override
public void onComplete() {
}
});
}
}
return mJobDao.getJobsLimitOffset(count, startPosition);
}
updateJobList in jobsRepository:
private void updateJobList(JobList jobList, int startPosition) {
JobListItemEntity[] jobs = jobList.getJobsData();
mJobDao.insert(jobs);
mJobListDataSource.invalidate();
}
After reading the source code of DataSource I realized this:
A DataSource once invalidated will never become valid again.
invalidate() says: If invalidate has already been called, this method does nothing.
I was actually having a singleton of my custom DataSource (JobListDataSource) provided by JobsPagedListProvider, so when I was invalidating my DataSource in saveJob() (defined in JobsRepository), it was trying to get new DataSource instance (to fetch latest data by again calling loadRange() - that's how refreshing a DataSource works)
but since my DataSource was singleton and it was already invalid so no loadRange() query was being made!
So make sure you don't have a singleton of DataSource and invalidate your DataSource either manually (by calling invalidate()) or have a InvalidationTracker in your DataSource's constructor.
So the final solution goes like this:
Don't have a singleton in JobsPagedListProvider:
public class JobsPagedListProvider {
private JobListDataSource<JobListItemEntity> mJobListDataSource;
private final JobsRepository mJobsRepository;
public JobsPagedListProvider(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
}
public LivePagedListProvider<Integer, JobListItemEntity> jobList() {
return new LivePagedListProvider<Integer, JobListItemEntity>() {
#Override
protected DataSource<Integer, JobListItemEntity> createDataSource() {
//always return a new instance, because if DataSource gets invalidated a new instance will be required(that's how refreshing a DataSource works)
mJobListDataSource = new JobListDataSource<>(mJobsRepository);
return mJobListDataSource;
}
};
}
public void setQueryFilter(String query) {
mJobListDataSource.setQuery(query);
}
}
Also make sure if you're fetching data from network you need to have right logic to check whether data is stale before querying the network else it will requery everytime the DataSource gets invalidated.
I solved it by having a insertedAt field in JobEntity which keeps track of when this item was inserted in DB and checking if it is stale in getJobs() of JobsRepository.
Here is the code for getJobs():
public List<JobListItemEntity> getJobs(String query, int startPosition, int count) {
Observable<JobList> jobListObservable = mApiService.getOpenJobList(
mRequestJobList.setPageNo(startPosition / count + 1)
.setMaxResults(count)
.setSearchKeyword(query));
List<JobListItemEntity> jobs = mJobDao.getJobsLimitOffset(count, startPosition);
//no data in db, make a synchronous call to network to get the data
if (jobs.size() == 0) {
JobList jobList = jobListObservable.blockingSingle();
updateJobList(jobList, startPosition, false);
} else if (shouldRefetchJobList(jobs)) {
//data available in db, so show a cached version and make async network call to update data
jobListObservable.subscribe(new Observer<JobList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(JobList jobList) {
updateJobList(jobList, startPosition, true);
}
#Override
public void onError(Throwable e) {
Timber.e(e);
}
#Override
public void onComplete() {
}
});
}
return mJobDao.getJobsLimitOffset(count, startPosition);
}
Finally remove InvalidationTracker in JobListDatasource as we are handling invalidation manually:
public class JobListDataSource<T> extends TiledDataSource<T> {
private final JobsRepository mJobsRepository;
String query = "";
public JobListDataSource(JobsRepository jobsRepository) {
mJobsRepository = jobsRepository;
mJobsRepository.setJobListDataSource(this);
}
#Override
public int countItems() {
return DataSource.COUNT_UNDEFINED;
}
#Override
public List<T> loadRange(int startPosition, int count) {
return (List<T>) mJobsRepository.getJobs(query, startPosition, count);
}
public void setQuery(String query) {
this.query = query;
}
}
I want to set a custom resolution for this scenario:
1- increment an integer field in realmobject in one device in offline mode
2- increment the same integer field in same realmobject in another device in offline mode
The default custom resolution is last update wins but in my case I want
the increment in both devices take effect on result after going live not last update.
I tried this code for test:
Realm realm = Realm.getDefaultInstance();
final RealmResults<Number> results= realm.where(Number.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
int num = results.get(0).getNumber()+1;
results.get(0).setNumber(num);
}
});
the Number class is like this:
public class Number extends RealmObject {
#PrimaryKey
private String id;
private int number;
public String getId() {
return id;
}
public void increment(){
this.number++;
}
public void setId(String id) {
this.id = id;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
This problem is very crucial to my app. If I can't do this in client side
I will not be able to use realm mobile platform which I was get so interested in.
Maybe you can use list of commands for such objects, persist them in offline and sync/merge on going online. Commands can be something like increment, decrement, multiplyBy2 and so on.
Documentation says:
Inserts in lists are ordered by time.
If two items are inserted at the same position, the item that was
inserted first will end up before the other item. This means that
if both sides append items to the end of a list they will end up in
order of insertion time.
So you will always have list of applied commands sorted by date.
The documentation currently says that counters are supported by the protocol but not exposed at the language level yet, so I guess you will have to implement it yourself.
The easiest way will be to just store it as a List of integers (1 for increment, -1 for decrement), and then use List.sum() (https://realm.io/docs/java/2.2.1/api/io/realm/RealmList.html#sum-java.lang.String-) to quickly get the aggregate result.
public class Counter extends RealmObject {
private int count;
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
}
public class Number extends RealmObject {
#PrimaryKey
private String id;
private RealmList<Counter> counters;
public void incrementNumber(){
Counter c = realm.createObject(Counter.class);
c.setCount(1);
this.getCounters().add(c);
}
public int getNumber() {
// Get the aggregate result of all inc/decr
return this.getCounters().sum("count");
}
public void setNumber(int number) {
this.getCounters().deleteAllFromRealm();
Counter c = realm.createObject(Counter.class);
c.setCount(number);
this.getCounters().add(c);
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
private RealmList<Counter> getCounters() { return counters; }
private void setCounters(RealmList<Counter> counters) { this.counters = counters; }
}
```
Thanks to #ast code example. I also solved the problem by caching command pattern here is my code:
public class CommandPattern extends RealmObject {
#PrimaryKey
private String id;
private String commandName;
public String getCommandName() {
return commandName;
}
public void setCommandName(String commandName) {
this.commandName = commandName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_increment:
if (isOnline()) {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
updateNumberOnRealm();
}
});
realm.close();
} else {
addMethodToCache("increment");
}
public void addMethodToCache(final String methodName) {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
commandPattern = new CommandPattern();
commandPattern.setId(UUID.randomUUID().toString());
commandPattern.setCommandName(methodName);
realm.copyToRealmOrUpdate(commandPattern);
}
});
realm.close();
}
public void invokeCachedCommands() {
realm = Realm.getDefaultInstance();
commandsCached = realm.where(CommandPattern.class).findAll();
commandsCached.addChangeListener(new RealmChangeListener<RealmResults<CommandPattern>>() {
#Override
public void onChange(final RealmResults<CommandPattern> element) {
if(!element.isEmpty()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for (CommandPattern command : element) {
if(command != null) {
if (command.getCommandName().equals("increment")) {
//updateNumberOnRealm();
RealmResults<Number> results = realm.where(Number.class).findAll();
results.get(0).increment();
command.deleteFromRealm();
}
}
}
}
});
}
}
});
realm.close();
}
before getting increment action done I check online state and if it is offline the increment string cached in Command Pattern object
after going online again those cached commands get invoked by following code:
IntentFilter intentFilter = new IntentFilter(NetworkStateChangeReceiver.NETWORK_AVAILABLE_ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
boolean isNetworkAvailable = intent.getBooleanExtra(IS_NETWORK_AVAILABLE, false);
if (isNetworkAvailable) {
invokeCachedCommands();
}else{
if(commandsCached != null) {
commandsCached.removeChangeListeners();
}
}
}
}, intentFilter);
this is general custom conflict resolution and can be used for any type of command
I have a singleton to cache objects, from here I create an observable from a List, this List is a response from the API which is filled with objects. (JSON)
private static BehaviorSubject<List<Model>> observableModelsList;
private static Observable<List<Model>> observable = myAPI.loadModelsRx();
private static Subscription subscription;
private PoiSingleton() {
}
public static PoiSingleton getInstance() {
return ourInstance;
}
public static void resetObservable() {
observablePoisList = BehaviorSubject.create();
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
subscription = observable.subscribe(new Subscriber<List<Model>>() {
#Override
public void onCompleted() {
// do nothing
}
#Override
public void onError(Throwable e) {
observablePoisList.onError(e);
}
#Override
public void onNext(List<Model> models) {
observablePoisList.onNext(models);
}
});
}
public static Observable<List<Poi>> getPoisObservable() {
if (observablePoisList == null) {
resetObservable();
}
return observablePoisList;
}
What I want to achieve is create a HashMap from the List, the key should be the ID of the object and the value the object itself.
I am new to Android and Retrofit/RxJava so in what method/stage is it responsible to create the HashMap?