I am developing an Android app which needs to know when a contact is added/updated/deleted.
So I read several posts for it. I understand that we can get notified through Content observers whenever a contacts gets changed, but we can't get which contacts have been added/updated/deleted. So I have read the official APIs and prepared my design how to capture that particular contact.
So what I thought at the start
We will store all the contact IDs, deleted flag and version
Whenever contacts get changed I will get my table's row count and row count from Android's system.
If my rowcount is less than systems row count then a contact has been deleted.
If my rowcount is greater than systems row count then a contact has been added.
And if these are not the cases then one of the contacts version has been changed.
I have also learned that Android doesn't delete the contact if it is deleted by user, but it sets 0 on deleted flag. So in these cases the row count will be same.
Android also changes the row ID of a contact many times as stated in the official docs. So how can we uniquely identify them like lookup uri and if not then we have to put observer for that also.
So I want to know whether the above is correct? And in the case a contact is added will it be added to the last row of cursor or not means if I check the last row of system database for contacts will it give me the contact added or not.
Let me explain as much as I could. Basically your policy looks pretty good, but actually it is bit more complex than you thought.
On Android, a contact can be associated with several raw contacts, which may be provided from many data providers, such as Google, Facebook, Skype and so on. For example, if one of your friends in your local contacts is also using Skype, there are two raw contacts existing separately in ContactContracts.RawContacts, but they will be aggregated automatically and show up just as one contact when you query to ContactsContract.Contacts.
That is also why it's hard to identify a contact uniquely, because you can split or join them anytime you want. LOOKUP_KEY isn't very handy for this case.
Many apps except Google only provide a one-way sync, i.e. only from service to contacts, so they are read-only. In this case, the deleted flag will not be used and simply deleted during their synchronization process. Thus, you can not simply rely on the flag.
Though there isn't a good simple solution, I guess it's much easier to achieve what you want, if you observe for a specific RawContacts, not Contacts. Hope this helps your understanding.
I think the best practice is to monitoring whenever a contact has aggregate to another one and identify them by the contactName, not the _ID or CONTACT_ID.
Take a look at this possibly contacts operations:
Insert
A Contact cannot be created explicitly. When a raw contact is inserted, the provider will first try to find a Contact representing the same person. If one is found, the raw contact's CONTACT_ID column gets the _ID of the aggregate Contact. If no match is found, the provider automatically inserts a new Contact and puts its _ID into the CONTACT_ID column of the newly inserted raw contact.
Update
Only certain columns of Contact are modifiable: TIMES_CONTACTED, LAST_TIME_CONTACTED, STARRED, CUSTOM_RINGTONE, SEND_TO_VOICEMAIL. Changing any of these columns on the Contact also changes them on all constituent raw contacts.
Delete
Be careful with deleting Contacts! Deleting an aggregate contact deletes all constituent raw contacts. The corresponding sync adapters will notice the deletions of their respective raw contacts and remove them from their back end storage.
Query
If you need to read an individual contact, consider using CONTENT_LOOKUP_URI instead of CONTENT_URI.
If you need to look up a contact by the phone number, use PhoneLookup.CONTENT_FILTER_URI, which is optimized for this purpose.
If you need to look up a contact by partial name, e.g. to produce filter-as-you-type suggestions, use the CONTENT_FILTER_URI URI.
If you need to look up a contact by some data element like email address, nickname, etc, use a query against the ContactsContract.Data table. The result will contain contact ID, name etc.
The problem, though, is that you could have two 'Phillip Morris' in your contact list that aren't the same person.
For further information, see this section of Android Classes Documentation.
Related
Hello I am creating an app where people essentially join groups to do tasks, and each group has a unique name. I want to be able to update each of the users document that has to do with a specific group without having to for loop each user and update with each iteration.
I want to know if its a good idea to have a unique key like this in mongoDB.
{
...
"specific_group_name": (whatever data point here)
...
}
in each of the users document, so I can just call a simple
updateToMany(eq("specific_group_name", (whatever data point here)), Bson object)
To decrease the run time that is involved, just in case there is alot of users within the group.
Thank you
Just a point to note, instead of a specific group name, better make sure that it's specific groupId. Also pay special attention to cases when you have to remove group from the people, and also if there's cases when a person in a particular group shouldn't receive this update.
What you want to do is entirely valid though. If you put specific_group_name/id in the collection, then you're moving the selection logic to db. If you're doing a one-by-one update, then you have more flexibility on how to select users to update on Java/application side.
If selection is simple (a.k.a always update people in this group) then go ahead
I have an application where members can post content, but then I have a problem. My problem is that I want to do an activity where the publications of only the author member are shown but Firestore asks me to create an Index to do this, the problem is that the document of each member is based on its UID.
For example:
My UID is: x54asdASD544
Then I try to make this query:
Query query = mMembersDB.document("POST").collection("x54asdASD544").whereEqualTo("type", "public").orderBy("date");
That's when Firestore asks me to create an index, then I create it and it works perfect after creating the index, but if I'm going to do the same, to see the publications of another author, then he asks me to create another index.
For example:
Query query = mMembersDB.document("POST").collection("x54assds").whereEqualTo("type", "public").orderBy("date");
The UID changed, then the previously created index does not work anymore.
Any solution to be able to repair this?
Any way to create indexes automatically?
Each uniquely named collection and subcollection must have its own indexes. You can't share index definitions between collections. They must be defined separately. There is no way to automatically create indexes.
Naming a subcollection based on a UID doesn't sound like a very effective way to model your data. Consider putting all the documents in one collection, put the UID as a field to those documents, and use the UID as a filter to find what you want.
Is it possible to search for an unknown collection that contains a document with identifying fields like an email?
My structure is like this:
Each user gets their own collection based on a unique ID. Each collection contains a user doc and a accounts doc. User doc contains the information about the user that I would like to search for. Accounts doc contains a collection of bank accounts that I want to transfer money between users.
My problem is that I don't want users to type in the long unique id to enter the collection but to type the email of the user that is inside the collection\user document. The email is unique.
Have I just made a bad structure for my project or is there something I can do?
UPDATE
Thanks, Alex and Frank for the feedback.
I went on and changed my structure to as shown:
/users/$uid/accounts/$accountid.
Did a java Query collectionReference = db.collection("users").whereEqualTo("uEmail", userEmail); and saving the document.getId() as a String userId.
I Then use the UserId in a spinner to enable the user to pick an account from the userId accounts collection.
As Alex said, there is no way to load data from a collection (with the client-side SDKs) unless you know the collection name.
But in this case, it seems like your collections are named after the user's UID.
That means that if the user is signed in, you can know their collection by:
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
CollectionReference userCollection = FirebaseFirestore.getInstance().collection(uid);
A few notes:
It is much more idiomatic to store your structure with a top-level collection of users, and then a document for each user under that, and then subcollections for the other data under that. So for example: /users/$uid/accounts/$accountid.
The server-side SDKs do have a method to get a list of collections, for example like this listCollections method in the Node.js SDK. But these SDKs are only to be used in a trusted environment, such as your development machine, a server you control, or Cloud Functions, and not directly on the client. Even with this SDK though, you'll have to iterate the list of collections and check each in turn, because as said before: you can only read data from a collection of which you know the name.
If you're trying to look up the collection/UID for another user than the one who's signed in to the app, you may need to way to map an email address to a UID. Such functionality is not available in the client-side SDKs. But similar as the point above, there is a method getUserByEmail in the Admin SDKs.
Is it possible to search for an unknown collection that contains a document with identifying fields like an email?
No, you should know the name of your collection in order to be able to use it in your reference. There are no wildcards in Cloud Firestore paths to collections/documents. You have to identify every collection and every document by their specific ids.
I am busy practicing on designing a simple todo list webapp whereby a user can authenticate into the app and save todo list items. The user is also only able to to view/edit the todo list items that they added.
This seems to be a general feature (authenticated user only views their own data) in most web applications (or applications in general).
To me what is important is having knowledge of the different options for accomplishing this. What I would like to achieve is a solution that can handle lots of users' data effectively. At the moment I am doing this using a Relational Database, but noSQL answers would be useful to me as well.
The following ideas came to mind:
Add a user_id column each time this "feature" is needed.
Add an association table (in the example above a user_todo_list_item table) that associates the data.
Design in such a way that you have a table per user per "feature" ... so you would have a todolist_userABC table. It's an option but I do not like it much since a thousand user's means a thousand tables?!
Add row level security to the specific "feature". I am not familiar on how this works but it seems to be a valid option. I am also not sure whether this is database vendor specific.
Of my choices I went with the user_id column on the todolist_item table. Although it can do the job, I feel that a user_id column might be problematic when reading data if the data within the table gets large enough. One could add an index I guess but I am not sure of the index's effectiveness.
What I don't like about it is that I need to have a user_id for every table where I desire this type of feature which doesn't seem correct to me? It also seems that when I implement the database layer I would have to add this to my queries for every feature (unless I use some AOP)?
I had a look around (How does Trello store data in MongoDB? (Collection per board?)), but it does not speak about the techniques regarding user_id columns or things like that. I also tried reading about this in some security frameworks (Spring Security to be specific) but it seems that it only goes into privileges/permissions on a table level and not a row level?
So the question is whether my choice was appropriate and if there are better techniques to do this?
Your choice is the natural thing to do.
The table-per-user is a non-starter (anything that modifies the database structure in response to user action is usually suspect).
Row-level security isn't really an option for webapps - it requires each user session to have a separate, persistent connection to the database, which is rarely practical. And yes, it is vendor-specific.
How you index your tables depends entirely on your usage patterns and types of queries you want to run. Is 'show all TODOs for a user' a query you want to support (seems like it would be)? Then and index on the user id is obviously needed.
Why does having a user_id column seem wrong to you? If you want to restrict access by user, you need to be able to identify which user the record belongs to. Doesn't actually mean that every table needs it - for example, if one record composes another (say, your TODOs have 'steps', each step belongs to a single TODO), only the root of the object graph needs the user id.
I am able to programmatically insert an entry into the Android call log with a number, date, duration & type BUT I cannot figure out how to also include a photo, label and name?
The entry I'm adding is for an existing Contact with the exact same number. I've noticed on a Motorola device the name & pic appears if the number matches an existing Contact but on my HTC Incredible something is missing?
I do the following.. (didn't know what to even try for the photo)
ContentValues values = new ContentValues();
values.put(CallLog.Calls.NUMBER, number);
values.put(CallLog.Calls.CACHED_NAME, name);
values.put(CallLog.Calls.CACHED_NUMBER_LABEL, label);
values.put(CallLog.Calls.DATE, date);
values.put(CallLog.Calls.DURATION, duration);
values.put(CallLog.Calls.TYPE, myCallType);
context.getContentResolver().insert(CallLog.Calls.CONTENT_URI, values);
You cannot insert photos into the call log itself; those are stored in the Contact that is linked to the call log entry. See ContactsContract.Data for more information about how to do that.
As far as the HTC device not updating the call log with an existing photo, it could be tied to the way that HTC Sense caches call log entries; I have seen similar issues that only appear on Sense devices.
Call log entries are typically stored in the calls table in the Contacts app database (/data/data/com.android.providers.contacts/databases/contacts2.db). For some reason, it appears that HTC Sense does not update existing call log entries if contact data changes, but other ROMs do.
For example, if I use an HTC EVO LTE with stock Sense 5.0 to place a call to someone I have saved as "Jenny Smith", an entry is created in the calls table, which includes a name field -- where it stores Jenny Smith. If I update her contact information to "Jenny Jones" and place a new call to her, a new entry is stored in the calls table with the name Jenny Jones, but the previous entry is not changed. The actual Call History screen shows all of the calls as Jenny Jones, but the database itself has old name.
Other devices I have tested (including an Nexus 5) update previous entries in the calls table when the Call Log is viewed. Your Motorola device likely behaves like the Nexus 5, refreshing the data as needed.
I am not a database wizard, but have a reasonable amount of familiarity with them. From a database structure perspective, it seems odd to me that any contact-specific information is stored in that calls table (it contains a raw_contacts_id field that is linked to the same field in the contacts table of that database where all of the actual contact information is stored), but that's how it is implemented.
The only reasonable way of doing it (that works for me anyway) is to add a contact directly to the handset and then add your call log pointing at that contact, afterwards you can then remove the contact from the handset. The only issue with this is if your app gets killed before removing the contact so you would have to implement some sort of clean up on next app launch. It's all very messy for what should be a simple task.