java.util.ConcurrentModificationException after iterating over a list of objects - android

My application logs data about incoming phone calls and sms for particular dates and keeps them in a list. I want the application to check if there is already an entry for that date when a new phone call or sms comes in. If this is the case, I want the application to increment a value in the list.
However, when I try to do that I get this error: java.util.ConcurrentModificationException
How can I remedy this problem?
My codes looks like this
public void addLog(String phonenumber, String type, long date, int incoming, int outgoing)
{
//Check if log exists or else create it.
Log newLog = new Log(phonenumber, type, date, incoming, outgoing);
//Iterates through logs
for (Log log : logs)
{
if (log.getPhonenumber() == phonenumber && log.getDate() == date && log.getType() == type)
{
updateLog(newLog, log.getId());
}
else
{
android.util.Log.i("Datamodel", "Adding log");
logs.add(newLog);
//add to database
}
}
}
public void updateLog(Log newLog, long id)
{
//check for outgoing or incoming
if (newLog.getIncoming() == 1)
{
for (Log log : logs)
{
if (log.getId() == id)
{
//Increments incoming
int incoming = log.getIncoming();
android.util.Log.i("Datamodel", "Updating incoming");
log.setIncoming(incoming++);
}
else
{
//Increments outgoing
int outgoing = log.getOutgoing();
android.util.Log.i("Datamodel", "Updating outgoing");
log.setOutgoing(outgoing++);
}
}
}
//Update the list
//Add to database
}

A for loop, such as your for (Log log : logs), actually uses an Iterator underneath to iterate over the elements in the Collection (where logs is your Collection in this case).
A well-known fact about Iterator is that you must not attempt to modify the Collection while you're looping or iterating over it; to do otherwise will result in the ConcurrentModificationException.
There are already a large number of Q&As on SO regarding Iterator and CME, so rather than me duplicating advice, I advise looking at solutions offered here.

Related

What's the best way to keep offline devices consistent with AWS/DynamoDB

Basically
Incrementing values seems like it should be so simple but it seems like it's not in my case.. I know I've still got so much to learn especially with VTL which is entirely new to me so please excuse my ignorance.
All I want is to be able to add to/subtract from an existing value possibly offline and for the server to capture all those changes when coming online, keeping multiple (intermittently online) devices consistent.
Use cases:
Keep count of inventory/
Keep balances for Accounts/
Votes/
Visits/
etc/
Scenario
device 1 makes n changes to Item A.quantityOnHand, ( i.e: A delivery is made from a driver using the app)
device 2 makes n changes to Item A.quantityOnHand,( i.e: Some Sales are made of the item over time)
device n makes n changes to Items/Customers/Cash/Votes/Visits/Some other Counting operation.
All changes need to be captured and at any time devices could go offline.
So Far..
I have looked at a custom resolver.. something like this simple change in my resolver:
#if ( $entryKeyAttributeName == "transacted")
##perhaps use regex and filter a set prefix
$util.qr($expAdd.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#end
I found that this only works once, for the most current version of the model. The result is only the latest update is synced, depending on current server versions, resulting in inconsistent data.
I thought the custom conflict handling could help, Perhaps If I have a locally stored 'last-synced-value' and then use the differences to create a modified model corrected with the changed local values taken into account.
Could this work?
What is the downside of creating _increment logic locally in a DB?
It seems like it would be a simple process?
if amplify.isConnected -> save normally, immediate update using model.
if amplify.isNotConnected:
->diff model saved and queued separately in a local DB (with a 'incremented value' field)
->query(model) finds and sums any relevant model during unSynced/offline State
upon Amplify.isNowSynced && connected
->save the now synced and updated models with the relevant increments
->delete the increment rows once response from server is received and checked for consistency
I did something similar in the below extension... i used another SQflite db with one table..
Can you see any downside to something like the followingextension?
extension AmplifyHelper on DataStoreCategory {
Future<bool> incrementValue<T extends Model>(T model, incrementValue, QueryField incField,
{QueryPredicate? where}) async {
/// If online and connected, just save in the normal way
if (_networkService.connectivityResult.value != ConnectivityResult.none) {
try {
save(model, where: where);
return true;
} catch (e) {
debugPrint('*** An error occurred trying to save while online ***');
return false;
}
} else {
/// Otherwise, create a map to save in a sqflite DB
var map = <String, dynamic>{};
map['dyn_tableName'] = model.getInstanceType().modelName();
map['dyn_rowId'] = model.getId();
map['dyn_increment'] = incrementValue;
map['dyn_field'] = incField.fieldName;
map['dyn_fromValue'] = model.toJson()[incField.fieldName];
return _dbService.updateOrInsertTable(map);
}
}
Future<List> queryWithOffline<T extends Model>(ModelType<T> modelType,
{QueryPredicate? where, QueryPagination? pagination, List<QuerySortBy>? sortBy}) async {
/// Get normal results ( basically unchanged from increments, contains the last-synced value for any increment)
List<T> amplifyResult = await query(modelType, where: where, pagination: pagination, sortBy: sortBy);
/// Get any increments from other DB
List<Map<String, dynamic>> offlineList = await _dbService.getOfflineTableRows(tableName: modelType.modelName());
if (offlineList.isNotEmpty && amplifyResult.isNotEmpty) {
/// If there is something in there SUM the relevant fields for each row and return.
List<T> listWithOffline = [];
for (var rowMap in offlineList) {
ModelField field = modelProvider.modelSchemas
.firstWhere((mdl) => mdl.name == rowMap['dyn_tableName'])
.fields![rowMap['dyn_field']]!;
Map<String, dynamic> modelMap =
amplifyResult.firstWhere((item) => item.getId() == rowMap['dyn_rowId']).toJson();
modelMap[field.name] = modelMap[field.name] + rowMap['dyn_increment'];
listWithOffline.add(modelType.fromJson(modelMap));
}
return listWithOffline;
} else {
/// There is nothing in the sync DB, just return data.
return amplifyResult;
}
}
Future<bool> returnToOnline<T extends Model>() async {
/// Called when Amplify is synced /ready after coming online
/// Check if Amplify is resynced
if (!_amplifySyncService.amplifyHasDirt) {
List<Map<String, dynamic>> offlineList = await _dbService.getOfflineTableRows();
if (offlineList.isNotEmpty) {
List<T> listWithOffline = [];
ModelType<T>? modelType;
List<T> amplifyResult = [];
for (var rowMap in offlineList) {
///Basically the same process of match and sum as above
if (modelType == null || modelType.modelName() != rowMap['dyn_tableName']) {
modelType = modelProvider.getModelTypeByModelName(rowMap['dyn_tableName']) as ModelType<T>?;
amplifyResult = await Amplify.DataStore.query(modelType!);
}
ModelField field = modelProvider.modelSchemas
.firstWhere((mdl) => mdl.name == rowMap['dyn_tableName'])
.fields![rowMap['dyn_field']]!;
Map<String, dynamic> modelMap = amplifyResult.firstWhere((item) => item.getId() == rowMap['dyn_rowId']).toJson();
modelMap[field.name] = modelMap[field.name] + rowMap['dyn_increment'];
listWithOffline.add(modelType.fromJson(modelMap));
}
for (var mdl in listWithOffline) {
/// Saving the updated model with the increments added
await Amplify.DataStore.save(mdl);
if (await _dbService.deleteRow(mdl.getId())) {
debugPrint('${mdl.getId()} has been processed from the jump queue');
// TODO: final looks...
// if(isNowSynced(mdl){
listWithOffline.remove(mdl);
// }else{
// rollback(mdl);
// }
}
} else {
debugPrint('No jump queue to process');
}
} else {
print('*** Amplify Had Dirt! ***');
}
return true;
}
}
Thanks for reading

android: calling Google API .getPlaceById with multiple place_id's

In order to reduce the number of API calls, I'm trying to query place details by passing several place_ids at a time (up to 10). I haven't found any useful information beyond the docs.
https://developers.google.com/android/reference/com/google/android/gms/location/places/GeoDataApi.html#getPlaceById(com.google.android.gms.common.api.GoogleApiClient, java.lang.String...)
Notably, the constructor is:
public abstract PendingResult<PlaceBuffer> getPlaceById (GoogleApiClient client, **String... placeIds**)
and the doc says: Returns Place objects for each of the given place IDs.
I don't have any problem when passing a single place_id, but when I pass a comma delimted string of id's, all of which are known to be good, I get a status = "SUCCESS" but a buffer count of 0.
Does anyone know the correct way to pass multiple id's to getPlaceById()?
Here's my code if that helps at all:
Places.GeoDataApi.getPlaceById(mGoogleApiClient, searchIds)
.setResultCallback(new ResultCallback<PlaceBuffer>() {
#Override
public void onResult(PlaceBuffer places) {
int cnt = places.getCount();
if (places.getStatus().isSuccess() && places.getCount() > 0) {
for (int i=0; i < places.getCount(); i++) {
final Place myPlace = places.get(i);
Log.d("<< cache >> ", "Place found: " + myPlace.getName());
}
} else {
Log.d("<< cache >> ", "Place not found");
}
places.release();
}
});
It's a varargs argument. You call it like this:
Places.GeoDataApi.getPlaceById(mGoogleApiClient,
"placeId1", "placeId2", "placeId3");
More detail in this SO question: How does the Java array argument declaration syntax "..." work?
Although I've read that String... can be passed as either a comma delimited string or a string array, for one reason or other, getPlaceById appears to require an array. When I use this code to prepare the place id parameter, it works fine:
String search[] = new String[idsToSearch.size()];
search = idsToSearch.toArray(search);

Firebase - random query

I am making and application that show a first screen with a bunch of randomly picked data from a node in Firebase database.
To present user with different data every time is kind of important for my application
Is there anyway to achieve this in native Android, all snapshots are of same model
There is no direct way provided by firebase database but you can do this using Collections.shuffle()
What i did was,Take the snapshot and store it in an arraylist.
private ArrayList<Integer> array=new ArrayList<>();
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot imageSnapshot : dataSnapshot.getChildren()) {
MyClass myclass = imageSnapshot.getValue(MyClass.class);
array.add(myclass.someFunction());
}
}
Then call the shuffle method on this array list.
Collections.shuffle(array); // randomize the arraylist
Now you can do whatever you want with this randomized arraylist.
Don't think there is a way to randomly grab data from the Firebase database as all queries that you can construct end up being deterministic in some way, either based on the generated push ids (which in turn are based on time) or some other key ordered lexicographically. I think the best way would be to grab a list of data from a node and randomly choose the data client side.
There actually is a possibility to do that without Loading the whole list client side. First you have to generate a numeric id either as child id or as an extra node.
That how your database would look like:
notes:
-KXe8LJJzczEJs3YYwRe
numericid : 001
-KXeePWrWBXvpw4g9n0p
numericid : 002
or
notes:
001
002
to create the numbers as String you can use DecimalFormat
String newint = new DecimalFormat("000").format(oldint);
Now you can get the children count in your valueeventlistener an use Math.random() to get a random child, e.g. for the second Database Design
FirebaseDatabase().getInstance().getReference().child("notes").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Long childrencount = dataSnapshot.getChildrenCount();
if(childrencount.equals(0))return;
int random = getRandomInteger(safeLongToInt(childrencount), 1);
String selectedchild = new DecimalFormat("000").format(random);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
You also need to add these safeLongtoInt and getRandomInteger
public static int getRandomInteger(int maximum, int minimum){
return ((int) (Math.random()*(maximum - minimum))) + minimum;
}
public static int safeLongToInt(long l) {
if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
throw new IllegalArgumentException
(l + " cannot be cast to int without changing its value.");
}
return (int) l;
}
selectedchild is your random child id.

Android: Quickest way to filter lists as user types a query

Good day all, I have a list of Objects (Let's call them ContactObject for simplicity). This object contains 2 Strings, Name and Email.
This list of objects will number somewhere around 2000 in size. The goal here is to filter that list as the user types letters and display it on the screen (IE in a recyclerview) if they match. Ideally, It would filter where the objects with a not-null name would be above an object with a null name.
As of right now, the steps I am taking are:
1) Create 2 lists to start and get the String the user is typing to compare to
List<ContactObject> nameContactList = new ArrayList<>();
List<ContactObject> emailContactList = new ArrayList<>();
String compareTo; //Passed in as an argument
2) Loop though the master list of ContactObjects via an enhanced for loop
3) Get the name and email Strings
String name = contactObject.getName();
String email = contactObject.getEmail();
4) If the name matches, add it to the list. Intentionally skip this loop if the name is not null and it gets added to the list to prevent doubling.
if(name != null){
if(name.toLowerCase().contains(compareTo)){
nameContactList.add(contactObject);
continue;
}
}
if(email != null){
if(email.toLowerCase().contains(compareTo)){
emailContactList.add(contactObject);
}
}
5) Outside of the for loop now as the object lists are build, use a comparator to sort the ones with names (I do not care about sorting the ones with emails at the moment)
Collections.sort(nameContactList, new Comparator<ContactObject>() {
public int compare(ContactObject v1, ContactObject v2) {
String fName1, fName2;
try {
fName1 = v1.getName();
fName2 = v2.getName();
return fName1.compareTo(fName2);
} catch (Exception e) {
return -1;
}
}
});
6) Loop through the built lists (one sorted) and then add them to the master list that will be used to set into the adapter for the recyclerview:
for(ContactObject contactObject: nameContactList){
masterList.add(contactObject);
}
for(ContactObject contactObject: emailContactList){
masterList.add(contactObject);
}
7) And then we are all done.
Herein lies the problem, this code works just fine, but it is quite slow. When I am filtering through the list of 2000 in size, it can take 1-3 seconds each time the user types a letter.
My goal here is to emulate apps that allow you to search the contact list of the phone, but seem to always to it quicker than I am able to replicate.
Does anyone have any recommendations as to how I can speed this process up at all?
Is there some hidden Android secret I don't know of that only allows you to query a small section of the contacts in quicker succession?

Calendar Instances provider source code?

I am looking through the android calendar source code for the Instances Content Provider to see how it works and gets populated.
The reason being is that I am trying to replicate its workings in my app but I have not found where it gets populated from in the source.
I know the Instances database cannot be written to but somewhere it has to be getting written to since its getting populated. I just want to see how they do some of the calculations for the values.
The only thing I can find about the Instances is this but that does not tell me what I want to know and just tells me the the query and the uri's not the code behind the values.
does anyone know where it is?
The code that handles Content Provider URIs that start with content://com.android.calendar/instances/* is in packages/providers/CalendarProvider/src/com/android/providers/calendar/CalendarProvider2.java.
From that file it is possible to look up further implementation details. For example, a lot of the initialization code, like CREATE TABLE calls, is in CalendarDatabaseHelper.java in the same project.
I hope this helps!
Instances are being populated in "updateInstancesLocked" method in CalendarInstancesHelper.java. Also please check "performInstanceExpansion" method in the same file.
Link to the above mention method in GrepCode
I have provided the snippet of the method below
/**
* Updates the instances table when an event is added or updated.
* #param values The new values of the event.
* #param rowId The database row id of the event.
* #param newEvent true if the event is new.
* #param db The database
*/
private void updateInstancesLocked(ContentValues values,
long rowId,
boolean newEvent,
SQLiteDatabase db) {
// If there are no expanded Instances, then return.
MetaData.Fields fields = mMetaData.getFieldsLocked();
if (fields.maxInstance == 0) {
return;
}
Long dtstartMillis = values.getAsLong(Events.DTSTART);
if (dtstartMillis == null) {
if (newEvent) {
// must be present for a new event.
throw new RuntimeException("DTSTART missing.");
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Missing DTSTART. No need to update instance.");
}
return;
}
Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
if (!newEvent) {
// Want to do this for regular event, recurrence, or exception.
// For recurrence or exception, more deletion may happen below if we
// do an instance expansion. This deletion will suffice if the exception
// is moved outside the window, for instance.
db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
}
String rrule = values.getAsString(Events.RRULE);
String rdate = values.getAsString(Events.RDATE);
String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
if (isRecurrenceEvent(rrule, rdate, originalEvent)) {
// The recurrence or exception needs to be (re-)expanded if:
// a) Exception or recurrence that falls inside window
boolean insideWindow = dtstartMillis <= fields.maxInstance &&
(lastDateMillis == null || lastDateMillis >= fields.minInstance);
// b) Exception that affects instance inside window
// These conditions match the query in getEntries
// See getEntries comment for explanation of subtracting 1 week.
boolean affectsWindow = originalInstanceTime != null &&
originalInstanceTime <= fields.maxInstance &&
originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
if (insideWindow || affectsWindow) {
updateRecurrenceInstancesLocked(values, rowId, db);
}
// TODO: an exception creation or update could be optimized by
// updating just the affected instances, instead of regenerating
// the recurrence.
return;
}
Long dtendMillis = values.getAsLong(Events.DTEND);
if (dtendMillis == null) {
dtendMillis = dtstartMillis;
}
// if the event is in the expanded range, insert
// into the instances table.
// TODO: deal with durations. currently, durations are only used in
// recurrences.
if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
ContentValues instanceValues = new ContentValues();
instanceValues.put(Instances.EVENT_ID, rowId);
instanceValues.put(Instances.BEGIN, dtstartMillis);
instanceValues.put(Instances.END, dtendMillis);
boolean allDay = false;
Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
if (allDayInteger != null) {
allDay = allDayInteger != 0;
}
// Update the timezone-dependent fields.
Time local = new Time();
if (allDay) {
local.timezone = Time.TIMEZONE_UTC;
} else {
local.timezone = fields.timezone;
}
computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
mDbHelper.instancesInsert(instanceValues);
}
}
Hope this helps!!!
Let me know if you are expecting something different

Categories

Resources