I have a RecyclerView for chat app with firebase realtime database and local SQLite database. I am saving message into local database (SQLite) then calling adapter.notifyDataSetChanged().
If the message is already in the database (message unique id) then SQLite will return 0 on database insertOrThrow method. I am checking the availability like this.
if (id == 0) {
Log.d(TAG,"Database Already Has Value Of This Random Id ");
adapter.notifyDataSetChanged();
} else {
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
adapter.notifyDataSetChanged();
}
However, even the else statement is being called, my RecyclerView screen is not updated, but in the background, if I type or receive any message it saves into the local database but doesn't show on screen.
Chat RecyclerView works in following cases
When I clear app from recent app
Stop Fetching data from the local database
First Launch of Chat screen
I am facing this issue when I come directly to chat screen from notification.
That's How I load chat fragment normally.
getFragmentManager().beginTransaction().add(R.id.Navigation_Drawer, chatFragment).commit();
Loading Fragment from Notification using Asynctask
((Navigation_Drawer)context).getFragmentManager().beginTransaction().replace(R.id.Navigation_Drawer, chatFragment).commit();
That's how i launches ChatFragment from notification.
public class FireBase_Messaging_Service extends FirebaseMessagingService {
public static final String TAG="###FireBase MSG###";
public static final int NOTIFICATION=5;
String UserName;
String ID;
String Msg;
Map<String,String> data;
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d(TAG,"From "+remoteMessage.getFrom());
if (remoteMessage.getData().size()>0){
data = remoteMessage.getData();
Log.d(TAG,"Message Data "+remoteMessage.getData());
data = remoteMessage.getData();
UserName = data.get("name");
ID = data.get("ID");
Msg = data.get("Message");
showNotification(Msg,ID,UserName);
}
if (remoteMessage.getNotification()!=null){
Log.d(TAG,"Message Notification Body "+remoteMessage.getNotification().getBody());
// Toast.makeText(this, "Notification "+remoteMessage.getNotification().getBody(), Toast.LENGTH_LONG).show();
}
}
private void showNotification(String Message,String ID,String UserName) {
Log.d(TAG,"Show Notification "+Message+" "+ID);
Intent intent=new Intent(this, Navigation_Drawer.class);
intent.putExtra("Type","Text");
//intent.putExtra("Type",MsgType);
intent.putExtra("ID",ID);
intent.putExtra("uname",UserName);
intent.putExtra("Message",Msg);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent=PendingIntent.getActivity(this,NOTIFICATION,intent,PendingIntent.FLAG_UPDATE_CURRENT);
int color = getResources().getColor(R.color.black);
String ChannelID = "Message";
notificationChannel(ChannelID,"Chat");
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),ChannelID)
.setSmallIcon(R.drawable.default_x)
.setColor(color)
.setContentTitle(UserName)
.setContentText(Message)
.setChannelId(ChannelID)
.setTicker("My App")
.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND | Notification.FLAG_SHOW_LIGHTS)
.setLights(0xff00ff00, 1000, 500) // To change Light Colors
.setStyle(new NotificationCompat.BigTextStyle().bigText(Message))//For Expandable View
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
managerCompat.notify(NOTIFICATION,builder.build());
}
#Override
public void onDeletedMessages() {
super.onDeletedMessages();
}
private void notificationChannel (String ChannelID, String channelName) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(ChannelID,channelName, NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(Color.GREEN);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
}
}
I also noticed by logging that after adding data to list message.add(chat_wrapper) it first showing an increase in size but when while loop is over, it shows the last size of the ArrayList.
Here's the ChatFragment class.
public class Chat_Screen_Fragment extends Fragment implements View.OnClickListener, ChildEventListener{
public static final String TAG = "###CHAT SCREEN###";
List<Chat_Wrapper> message = new ArrayList<>();
Chat_Adapter adapter;
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.chat_screen_main_fragment,container,false);
setRetainInstance(true);
// GET INTENT VALUES FROM USER PROFILE CLASS
UserName_Intent = getArguments().getString("Get_Name");
UserImage_Intent = getArguments().getString("Get_Image");
UserPhone_Intent = getArguments().getString("Get_Phone");
UserID_Intent = getArguments().getString("Get_ID");
FirebaseToken_Intent = getArguments().getString("Get_Token"); //Firebase Token of other person
Room_Name_Intent = getArguments().getString("Get_Other"); // Room Name of chat
UserLastSeen_Intent=getArguments().getString("LastSeen");
//Sender_FCMToken = Session.getFirebaseID();
// RECYCLER VIEW
recyclerView = v.findViewById(R.id.Chat_Screen_Message_List);
layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setStackFromEnd(true);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
databaseReference = FirebaseDatabase.getInstance().getReference().child(Room_Name_Intent);
databaseReference.addChildEventListener(this);
adapter = new Chat_Adapter(getActivity(), message);
recyclerView.setAdapter(adapter);
// FETCH OLD MESSAGE FROM DATABASE
chatDatabase();
return v;
}
// FIREBASE REAL TIME DATABASE WHICH FETCH ALL MESSAGES (SYNC) FROM ONLINE DATABASE
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
append_chat_conversation(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
private synchronized void append_chat_conversation(DataSnapshot dataSnapshot) {
iterator = dataSnapshot.getChildren().iterator();
while (iterator.hasNext()) {
// GETTING DATA FROM FIREBASE DATABASE
Chat_Msg = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FROM = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FROM_ID = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_TO = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_TimeStamp = (String) ((DataSnapshot) iterator.next()).getValue();
Chat_Type= (String) ((DataSnapshot) iterator.next()).getValue();
Random_ID=(String) ((DataSnapshot) iterator.next()).getValue();
Chat_FCM_FROM= (String) ((DataSnapshot) iterator.next()).getValue();
Chat_FCM_TO= (String) ((DataSnapshot) iterator.next()).getValue();
Log.d(TAG, "Chat Items " + Chat_Msg + " " + Random_ID);
Chat_Database tempChatDatabase = new Chat_Database(getActivity());
boolean hasValue = tempChatDatabase.CheckValueExist(Random_ID);
Log.d(TAG,"DATABASE ALREADY HAS VALUE OF TIMESTAMP= "+hasValue);
if (!hasValue) {
Log.d(TAG,"DATABASE DON'T HAVE SAME ENTRY FOR TIME STAMP. ENTERED INTO HAS VALUE");
Log.d(TAG,"Chat Message "+Chat_Msg);
if (Chat_Type.equals("Typed_Message")) {
Log.d(TAG, "VIEW TYPE IS Message " + Chat_Msg);
long id = chat_database.Insert_Chat(Session.getUserID(),Room_Name_Intent, UserID_Intent, "Text", Chat_Msg, Chat_FROM, Chat_TO, Chat_TimeStamp, Chat_FCM_FROM, Chat_FCM_TO, Session.getPhoneNO(), UserPhone_Intent,Random_ID,UserImage_Intent,UserLastSeen_Intent,Chat_FROM_ID);
//Adding Chat Data Into Database
Log.d(TAG,"Database Entry ID "+id);
if (id == 0) {
Log.d(TAG,"Database Already Has Value Of This Random Id ");
adapter.notifyDataSetChanged();
continue;
} else {
Log.d(TAG,"Database Don't Has Value Of This Random Id ");
Log.d(TAG,"Message Size "+message.size());
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
Log.d(TAG,"Message Size "+message.size());
adapter.notifyDataSetChanged();
Log.d(TAG,"Adapter Notified Data Set "+adapter.getItemCount());
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
}
Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
chat_database.isDatabaseClose();
}
private void chatDatabase(){
//Database Init and Filling Adapter
Log.d(TAG,"Chat Database Function");
chat_database=new Chat_Database(getActivity());
chatCursor=chat_database.getUserChat(UserID_Intent);
boolean checkDB_Exist=functions.DatabaseExist(getActivity(),"CHAT_DATABASE.DB");
boolean chatItemsCounts=chatCursor.getCount()>0;
chatCursor.moveToFirst();
Log.d(TAG,"Value At Chat Database "+ checkDB_Exist+" "+chatItemsCounts);
if (checkDB_Exist && chatCursor.getCount()>0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)){
Log.d(TAG,"Database Exist Chat Database");
message.clear();
chatCursor.moveToFirst();
do {
database_rowID=chatCursor.getInt(chatCursor.getColumnIndex("ID"));
database_userID=chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
database_RoomName =chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
database_ReceiverID=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
database_MessageType=chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
database_Message=chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
database_MsgFrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
database_MsgTo=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
database_TimeStamp=chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
database_FCMfrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
database_FCMto=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
database_LocalPath=chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
database_PhoneFrom=chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
database_PhoneTo=chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
Log.d(TAG,"Value Of Database Message String = "+database_Message);
Log.d(TAG,"Row ID of Database "+database_rowID);
// Check Message Type
Log.d(TAG,"Message Type Is Text");
Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom,null,null,database_rowID);
message.add(text);
}
while(chatCursor.moveToNext());
Room_Name_Intent = database_RoomName;
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter.notifyDataSetChanged();
chatCursor.close();
boolean value = chat_database.isDatabaseClose();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size()-1);
}
});
Log.d(TAG,"Value Of Database Close or Not "+value);
}
}
}
I think you need to implement the LoaderCallbacks here and to listen to the changes done in the database to update the RecyclerView accordingly. I have created a repository in Github here which shows the database read, write and update and making the changes in the database to have the proper effect in corresponding RecyclerView with registering a content observer to the database.
The whole idea is to have an observer attached to your database table which will notify you of any change in the table. When a change is detected the database read query will be generated automatically and on reloading the data again from your table will trigger the RecyclerView update inside onLoadFinished function.
I have a demo implementation in my Github project, which shows a list of users to be shown in a RecyclerView and updating the RecyclerView automatically without calling notifyDataSetChanged. Please check the adapter which controls the data to be shown in the RecyclerView again. This is the simplest implementation that you might have in your case avoiding any complexity.
Hope that helps.
Update
After going through the code, I have come up with some fixes that you should do. It is pretty difficult to understand why it is not working as you expected it to be because the code given is not very well formatted and complete. However, I can suggest you some key fixes here which might help.
In the append_chat_conversation function, you do not have to call notifyDataSetChanged each time you find a new chat message and add that to the message. I would like to suggest that, do not add the chat messages in your message list. You might get rid of the following if-else block. Just updating the chat database and inserting new chat messages into it is fine. So I would suggest you remove the following section.
if (id == 0) {
Log.d(TAG,"Database Already Has Value Of This Random Id ");
adapter.notifyDataSetChanged();
continue;
} else {
Log.d(TAG,"Database Don't Has Value Of This Random Id ");
Log.d(TAG,"Message Size "+message.size());
Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
message.add(chat_wrapper);
Log.d(TAG,"Message Size "+message.size());
adapter.notifyDataSetChanged();
Log.d(TAG,"Adapter Notified Data Set "+adapter.getItemCount());
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
});
}
Call chatDatabase function before this line to load messages from the database.
// FETCH MESSAGE FROM DATABASE
chatDatabase();
Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
chat_database.isDatabaseClose();
Now fix your chatDatabase function. You are creating a new LinearLayoutManager and assigning it to your RecyclerView each time you are calling the chatDatabase function. I would suggest removing it as well.
Modify your chatDatabase function like the following.
private void chatDatabase() {
//Database Init and Filling Adapter
Log.d(TAG, "Chat Database Function");
chat_database = new Chat_Database(getActivity());
chatCursor = chat_database.getUserChat(UserID_Intent);
boolean checkDB_Exist = functions.DatabaseExist(getActivity(), "CHAT_DATABASE.DB");
boolean chatItemsCounts = chatCursor.getCount() > 0;
chatCursor.moveToFirst();
Log.d(TAG, "Value At Chat Database " + checkDB_Exist + " " + chatItemsCounts);
if (checkDB_Exist && chatCursor.getCount() > 0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)) {
Log.d(TAG, "Database Exist Chat Database");
message.clear();
chatCursor.moveToFirst();
do {
database_rowID = chatCursor.getInt(chatCursor.getColumnIndex("ID"));
database_userID = chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
database_RoomName = chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
database_ReceiverID = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
database_MessageType = chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
database_Message = chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
database_MsgFrom = chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
database_MsgTo = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
database_TimeStamp = chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
database_FCMfrom = chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
database_FCMto = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
database_LocalPath = chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
database_PhoneFrom = chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
database_PhoneTo = chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
Log.d(TAG, "Value Of Database Message String = " + database_Message);
Log.d(TAG, "Row ID of Database " + database_rowID);
// Check Message Type
Log.d(TAG, "Message Type Is Text");
Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom, null, null, database_rowID);
message.add(text);
} while (chatCursor.moveToNext());
Room_Name_Intent = database_RoomName;
adapter.setChatMessages(message); // Add a public function in your adapter to set the chat messages
adapter.notifyDataSetChanged();
chatCursor.close();
boolean value = chat_database.isDatabaseClose();
recyclerView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Moving to Bottom");
recyclerView.smoothScrollToPosition(message.size() - 1);
}
});
Log.d(TAG, "Value Of Database Close or Not " + value);
}
}
The setChatMessages function in your adapter will look something like this.
public void setChatMessages(ArrayList<Message> messageList) {
this.message = messageList;
}
I solved this issue by digging bit into android lifecycle. As i mentioned above i am facing this issue only when i enter ChatFragment from notification. I was only activity state issue. Code was working. What i was doing i always recreate activity on notification tap ( In Firebase Code ).
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
After digging lots of code and changing multiple flags in Intent. Only android:launchMode="singleTask" solves my issue. Which only create activity if it's not exist. Now its not calling activity onDestroy method when tapping on notification. It's start activity from onStart which avoid activity to recreate.
I add this flag in AndroidManifest's NavigationDrawer activity tag.
<activity
android:name=".Navigation_Drawer"
android:launchMode="singleTask"
android:theme="#style/AppTheme"/>
These kind of issue is really headache for developers but i don't know that with this single line of code i can solve this issue. Thanks everyone for help.
Related
I have an app where a user can create/login an account, when the user logs in, I pass their valid email as an intent to the main/landing activity .
I also have another activity for the user profile, from which I pass the intent from the landing activity (the user email).
With the user email I created queries to get all the user projects (it's a PM tool kind of thing) - in my landing activity i have a fragment also where I use these queries based on the user email.
In my user profile activity i also created queries to get the users details (name, email etc) to show in their profile where they can change it etc.
========
The issue is, initially when I log in with valid details and I'm brought to the landing activity, I get the users projects which is great, I can also navigate to the users profile activity and I get the users details which is what I want.
Then when I move back to the landing activity my intent (users emaill) which was passed from the Login activity is no longer valid, so I do not get any results from my DB queries and when I move back to the profile activity the intent is null so i can't get the current user anymore.
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.ppmtoolmobile.model.User.getFirstName()' on a null object reference
I wanted some advice on how to handle this to avoid getting NPE when moving back and forth.
I removed the variables for components to make it more readable, but I have initialized them all etc..
Landing Activity / ProjectActivity.java
public class ProjectActivity extends AppCompatActivity implements View.OnClickListener, MyRecyclerAdapter.OnProjectClickListener {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_project);
// My dao implementation with DB queries
daoHelper = new DaoHelper(this);
// getting current username through intent from LoginActivity.class
authenticatedUser = getIntent().getStringExtra("authenticatedUser");
Toast.makeText(this, "project activity: " + authenticatedUser, Toast.LENGTH_SHORT).show();
// current user id
userId = daoHelper.getCurrentUserId(authenticatedUser);
// Getting users first name and amount of projects (This will be displayed in the heading of the main screen)
userFirstName = daoHelper.getCurrentUserFirstName(authenticatedUser);
projectCount = daoHelper.getProjectCount(userId);
welcomeUserTextView1.setText("Welcome " + userFirstName + ", " + userId);
displayUserProjectCountTextView.setText("You currently have " + projectCount + " projects");
loadFragment(new ProjectFragment());
// Perform item selected listener
bottomNavView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch(item.getItemId())
{
case R.id.nav_profile:
Intent goToProfileActivityIntent = new Intent(ProjectActivity.this, ProfileActivity.class);
goToProfileActivityIntent.putExtra("authenticatedUser", authenticatedUser);
startActivity(goToProfileActivityIntent);
overridePendingTransition(0,0);
return true;
case R.id.nav_home:
return true;
case R.id.nav_settings:
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
overridePendingTransition(0,0);
return true;
}
return false;
}
});
}
}
ProfileActivity.java
public class ProfileActivity extends AppCompatActivity implements View.OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
// getting current username through intent from ProjectActivity.class
authenticatedUser = getIntent().getStringExtra("authenticatedUser");
Toast.makeText(this, "profile activity: " + authenticatedUser, Toast.LENGTH_SHORT).show();
daoHelper = new DaoHelper(this);
loadUserDetails();
// Perform item selected listener
bottomNavView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch(item.getItemId())
{
case R.id.nav_home:
startActivity(new Intent(getApplicationContext(), ProjectActivity.class));
overridePendingTransition(0,0);
return true;
case R.id.nav_profile:
return true;
case R.id.nav_settings:
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
overridePendingTransition(0,0);
return true;
}
return false;
}
});
}
private void loadUserDetails() {
// I get NPE here when moving from ProjectActivity for the second time
User user = daoHelper.getUserDetails(authenticatedUser);
profileFirstNameEditText.setText(user.getFirstName());
profileLastNameEditText.setText(user.getLastName());
profileEmailAddressEditText.setText(user.getEmailAddress());
}
}
DaoHelper.java methods
// get user details
public User getUserDetails(String theEmailAddress) {
SQLiteDatabase db = this.getReadableDatabase();
User user = null;
Cursor cursor = db.query(USER_TABLE,// Selecting Table
new String[]{COLUMN_USER_ID, COLUMN_USER_FIRST_NAME, COLUMN_USER_LAST_NAME, COLUMN_USER_EMAIL_ADDRESS, COLUMN_USER_PASSWORD},//Selecting columns want to query
COLUMN_USER_EMAIL_ADDRESS + " = ?",
new String[]{String.valueOf(theEmailAddress)},//Where clause
null, null, null);
System.out.println("cursor count: " + cursor.getCount());
if(cursor.moveToNext()) {
long userId = cursor.getLong(0);
String firstName = cursor.getString(1);
String lastName = cursor.getString(2);
String emailAddress = cursor.getString(3);
String password = cursor.getString(4);
user = new User(userId, firstName, lastName, emailAddress, password);
}
return user;
}
// get project count of user
public int getProjectCount(long userId) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * FROM " + PROJECT_TABLE + " WHERE " + COLUMN_USER_PROJECT_FK + " = ?", new String[]{String.valueOf(userId)})
return cursor.getCount();
}
// get all of users projects
#RequiresApi(api = Build.VERSION_CODES.O)
public List<Project> getUserProjects(long userId) {
SQLiteDatabase db = this.getReadableDatabase();
List<Project> projectList = new ArrayList<>();
Cursor cursor = db.rawQuery("SELECT * FROM " + PROJECT_TABLE + " WHERE " + COLUMN_USER_PROJECT_FK + " = ?", new String[]{String.valueOf(userId)});
while(cursor.moveToNext()) {
long id = cursor.getLong(0);
String title = cursor.getString(1);
String description = cursor.getString(2);
String dateCreated = cursor.getString(3);
String dateDue = cursor.getString(4);
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime dateCreatedFormatted = LocalDateTime.parse(dateCreated, formatter);
LocalDateTime dateDueFormatted = LocalDateTime.parse(dateDue, formatter);
String priority = cursor.getString(5);
String checklist = cursor.getString(6);
int theUserId = cursor.getInt(7);
Project project = new Project(id, title, description, dateCreatedFormatted, dateDueFormatted, priority, checklist, theUserId);
projectList.add(project);
}
return projectList;
}
The problem here is, your are starting another ProjectActivity instance in your ProfileActivity's onNavigationItemSelected listener of bottomNavView, which has no arguments (startActivity(new Intent(getApplicationContext(), ProjectActivity.class));)
That's why in your second instance of ProjectActivity, it has no value for parameter authenticatedUser and returning empty string.
You can fix this by modifying code of bottomNavView's onNavigationItemSelected listener in your ProfileActivity class.
Replace your switch case logic for id R.id.nav_home like below in ProfileActivity class
case R.id.nav_home:
finish();
overridePendingTransition(0,0);
return true;
Or, if you want to keep multiple instance of same activity (ProjectActivity and ProfileActivity), then you can add parameter to Intent instance in ProfileActivity's bottomNavView's itemSelectedListener.
In that case, your code would become something like below
case R.id.nav_home:
Intent goToProjectActivity = new Intent(ProfileActivity.this, ProjectActivity.class);
goToProjectActivity.putExtra("authenticatedUser", authenticatedUser);
startActivity(goToProjectActivity);
overridePendingTransition(0,0);
return true;
I'm receiving notifications using OneSignal, if my app was in the background, and I click on the came notification, the app opening normally as I wanted going to the requested activity without any problem!
But if the app was not running i mean totally closed, after tapping the notification nothing happening at all! app not opening! notification going away without opening the app! any advice?
Here is the code i'm using to handle OneSignal notifications
private class NotificationOpenedHandler implements OneSignal.NotificationOpenedHandler {
// This fires when a notification is opened by tapping on it.
#Override
public void notificationOpened(OSNotificationOpenResult result) {
OSNotificationAction.ActionType actionType = result.action.type;
JSONObject data = result.notification.payload.additionalData;
String launchUrl = result.notification.payload.launchURL; // update docs launchUrl
String body = result.notification.payload.body; // update docs launchUrl
String customKey;
String openURL = null;
Object activityToLaunch = MainActivity.class;
if (data != null) {
customKey = data.optString("customkey", null);
openURL = data.optString("openURL", null);
if (customKey != null)
Log.i("OneSignalExample", "customkey set with value: " + customKey);
if (openURL != null)
Log.i("OneSignalExample", "openURL to webview with URL value: " + openURL);
}
if (actionType == OSNotificationAction.ActionType.ActionTaken) {
Log.i("OneSignalExample", "Button pressed with id: " + result.action.actionID);
if (result.action.actionID.equals("id1")) {
Log.i("OneSignalExample", "button id called: " + result.action.actionID);
activityToLaunch = Notifications.class;
} else
Log.i("OneSignalExample", "button id called: " + result.action.actionID);
}
// The following can be used to open an Activity of your choice.
Intent toActivity = new Intent(MainActivity.this, Notifications.class);
toActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
toActivity.putExtra("openURL", launchUrl);
toActivity.putExtra("body", body);
Log.i("OneSignalExample", "openURL = " + launchUrl);
//my Code
String message;
SharedPreferences sharedPreferences = getSharedPreferences(NOTES, Context
.MODE_PRIVATE);
String jsonLink = sharedPreferences.getString(NOTES_LINKS, null);
String jsonTitle = sharedPreferences.getString(NOTES_TITLE, null);
if (jsonLink != null && jsonTitle != null) {
Gson gson = new Gson();
ArrayList<String> linkList = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
}.getType());
ArrayList<String> titleList = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
}.getType());
if (linkList.contains(launchUrl)) {
message = "Notification Exist!";
Log.i("Notifications","Notification Exist!");
} else {
linkList.add(launchUrl);
titleList.add(body.trim());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(NOTES_LINKS, new Gson().toJson(linkList));
editor.putString(NOTES_TITLE, new Gson().toJson(titleList));
editor.apply();
Log.i("Notifications","Notification Stored!");
}
} else {
ArrayList<String> linkList = new ArrayList<>();
ArrayList<String> titleList = new ArrayList<>();
linkList.add(launchUrl);
titleList.add(body.trim());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(NOTES_LINKS, new Gson().toJson(linkList));
editor.putString(NOTES_TITLE, new Gson().toJson(titleList));
editor.apply();
Log.i("Notifications","Stored");
}
// startActivity(intent);
startActivity(toActivity);
}
}
I am just digging for another issue with onesignal notification, and found this question,
I would like to answer like:
what value have you set for NotificationOpened.DEFAULT in Manifest.
"Enable" will open your application.
<application> <meta-data android:name="com.onesignal.NotificationOpened.DEFAULT" android:value="Enable" /></application>
i was facing the same issue before and was able to solve it by creating an application class and initializing oneSignal in it
public class Application extends android.app.Application {
#Override
public void onCreate() {
super.onCreate();
// OneSignal Initialization
OneSignal.startInit(this)
.inFocusDisplaying(OneSignal.OSInFocusDisplayOption.Notification)
.unsubscribeWhenNotificationsAreDisabled(true)
.setNotificationOpenedHandler(new NotificationHandler(this))
.init();
}
}
you also need to add these in your manifist
<application
android:name="Application"
...>
<meta-data android:name="com.onesignal.NotificationOpened.DEFAULT" android:value="DISABLE" />
</application>
I am using Google auth via firebase and am logging the users in successfully. I also have retrieved the list of contacts from the phonebook (device) and displaying it on a listview in a fragment in my app. But now I wish to show the users amongst my contacts who have my app installed, so that when clicked on they will go to the private chat with them, the other contacts, when clicked on will enable them to send an app invite. In a nutshell: I want to view the list of contacts who have the app installed on their device.
I was able to achieve this in three straightforward steps.
Get a list of your phone contacts
Get a list of all the phone numbers on Firestore
Compare the two lists and return common elements.
In order to use my approach, you need to have a collection on Firestore that has the phone number of all your users as documents just like the image below:
Here are the steps:
Step 1: I got a list of all the user's contacts by using ContentResolver. You can use the method below to retrieve this list provided you have the READ_CONTACTS permission granted.
public ArrayList<String> getContacts(ContentResolver cr) {
// list to be returned
ArrayList<String> numbers = new ArrayList<>();
// cursor
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if ((cur != null ? cur.getCount() : 0) > 0) {
while (cur != null && cur.moveToNext()) {
String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
String phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i(TAG, "Name: " + name);
numbers.add(formatRightWay(phoneNo));
}
pCur.close();
}
}
}
if(cur!=null){
cur.close();
}
return numbers;
}
Step 2: I got a list of all the phone numbers on Firestore by fetching the document IDs of the user collection. Here's a quick implementation:
firestore.collection("users").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
list.add(document.getId());
}
// this is the list you need
Log.d(TAG, list.toString());
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Step 3: Write a method that compares the two lists and returns similar elements.
public static ArrayList<String> shuffleBothLists(ArrayList<String> phoneContacts, List<String> firebaseContacts) {
ArrayList<String> result = new ArrayList<>();
for(String s: firebaseContacts) {
if(phoneContacts.contains(s) && !result.contains(s)) {
result.add(s);
}
}
return result;
}
The list returned by the method above are your contacts that have the app installed.
Cheers!
It is not possible to list the contacts directly. You need to create one node for users in firebase database to store users details after registration and then you can retrieve those user details.
I am getting you in means that you are using firebase. Now you want to upload all contacts to your server in firebase databse by your app if installed in one's device.
Try below code:
public class YourActivity extends AppCompatActivity {
ProgressDialog dialog;
DatabaseReference yourReference;//your database reference
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
yourReference = FirebaseDatabase.getInstance().getReference().child("users");
setContentView(R.layout.activity_your);
dialog = new ProgressDialog(this);
dialog.setMessage("Uploading contacts...");
// Query for contacts through content resolver. You will get a cursor.
Cursor contacts = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
},
null,
null,
null
);
// If you have a list as your data, firebase facilitates you to upload that easily by a single HashMap object. Create a HashMap object.
HashMap<String,Object> map = new HashMap<>();
// Loop contacts cursor with map to put all contacts in map. I used contact name as key and number as its value (simple and pretty way).
if(contacts!=null) {
while(contacts.moveToNext()){
map.put(
contacts.getString(contacts.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)),
contacts.getString(contacts.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
);
}
contacts.close();
}
dialog.show();
//write map to firebase database reference...
yourReference.updateChildren(map)
//this onSuccessListener is optional. You can terminate above line of code by ";" (semicolon).
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
dialog.dismiss();
Toast.makeText(YourActivity.this, "Contacts uploaded suffessfully!", Toast.LENGTH_SHORT).show();
}
})
//this onFailureListener is also optional.
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
dialog.dismiss();
Log.w("MKN","Error: "+e.getMessage());
Toast.makeText(YourActivity.this, "Contacts upload failed.", Toast.LENGTH_SHORT).show();
}
});
}
}
You will need to provide READ_CONTACTS permission to query Contacts table.
Also in firebase rules, value for "write" key must be "true" to write to the database.
First retrieve the contact list ..
'ContentResolver cr = getContext().getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);
if (cur.getCount() > 0) {
while (cur.moveToNext()) {
String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
Cursor cur1 = cr.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
new String[]{id}, null);
while (cur1.moveToNext()) {
//to get the contact names
HashMap<String, String> map = new HashMap<>();
String name=cur1.getString(cur1.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String email = cur1.getString(cur1.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
if( email != null ){
map.put("name", name);
map.put("email", email);
getContactList.add(map);
}
}
cur1.close();
}
}'
After this you can maintain a firebase database table that can store authenticated user's information, you can sync your contacts with the list you fetch from firebase user's database.
'mapChat = new HashMap<>();
Log.d("Debug", clist.toString());
userReference1 = FirebaseDatabase.getInstance().getReference().child("Users");
userReference1.keepSynced(true);
userReference1.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (int x = 0; x < clist.size(); x++) {
//Log.d("Debug" ,list.get(x).get("email").toString());
for (DataSnapshot dsp : dataSnapshot.getChildren()) {
if (dsp.hasChild("email")) {
// Log.d("Debug" , "setnewuser " + dsp.child("email").getValue().toString());
if (dsp.child("email").getValue().toString().equals(clist.get(x).get("email").toString())) {
Log.d("Debug", "contact updated");
String uid = dsp.getKey().toString();
reference1 = FirebaseDatabase.getInstance().getReference().child("Users").child(id).child("contacts").child(uid);
mapChat.put("name", clist.get(x).get("name"));
mapChat.put("email", clist.get(x).get("email"));
mapChat.put("chats", "false");
reference1.setValue(mapChat);
}
}
}
}
reference1.onDisconnect();
contactIdInterface1.contactUpdated();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});'
Enable and Implement Phone number sign in method in firebase, so u can retrieve the contacts from firebase and compare it will local contact list after that its easy to implement ur logic
In my application i am using "apn" for GPRS connection display. I code a spinner in which i get the Telenor GPRS, Telenor MMS and Telenor WAP.
**I want to add Wi-Fi option in this spinner. and when i select Wi-Fi option device start sensing Wi-Fi.
Q: How can i add option of Wi-Fi in my spinner??
**
This is my Code
Spinner GPRS;
String [] name_of_GPRS__available;
int [] apn_id; public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.configuration);
EnumerateAPNs();
/* this is a android enviroment in which you can develop an android application in which you
* share all your basic necessities of thrkife bghhr4y2ghrrr*/
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
GPRS = (Spinner)findViewById(R.id.GPRS);
ArrayAdapter<?> spinner_array = new ArrayAdapter<Object>(this,android.R.layout.simple_dropdown_item_1line,name_of_GPRS__available);
spinner_array.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
Editor prefsEditor = prefs.edit();
prefsEditor.putString("Object", name_of_GPRS__available.toString());
prefsEditor.commit();
GPRS.setAdapter(spinner_array);
//GPRS.setOnItemSelectedListener(MyOnItemSelectedListener());
GPRS.setOnItemSelectedListener(new MyOnItemSelectedListener());`
GPRS.setAdapter(spinner_array);
//GPRS.setOnItemSelectedListener(MyOnItemSelectedListener());
GPRS.setOnItemSelectedListener(new MyOnItemSelectedListener());
public void onItemSelected(AdapterView<?> parent, View view,
final int position, long id) {
// An item was selected. You can retrieve the selected item using
// parent.getItemAtPosition(position)
SetDefaultAPN(apn_id[position]);
Toast.makeText(parent.getContext(), "ETracking System Selects " +
parent.getItemAtPosition(position).toString(), Toast.LENGTH_LONG).show();
}
public void onNothingSelected(AdapterView<?> parent) {
// Another interface callback
}
public boolean SetDefaultAPN(int id)
{
boolean res = false;
ContentResolver resolver = Configuration.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("apn_id", id);
try
{
resolver.update(Uri.parse("content://telephony/carriers/preferapn"), values, null, null);
Cursor c = resolver.query(
Uri.parse("content://telephony/carriers/preferapn"),
null,
"_id="+id,
null,
null);
if(c != null)
{
res = true;
c.close();
}
}
catch (SQLException e)
{
//Log.d("TAG", e.getMessage());
}
return res;
}
/*
* Enumerate all APN data
*/
private void EnumerateAPNs()
{
Cursor c = this.getContentResolver().query(
Uri.parse("content://telephony/carriers/current"), null, null, null, null);
if (c != null)
{
//String s = "All APNs:\n";
//Log.d("TAG", s);
try
{
printAllData(c); //Print the entire result set
}
catch(SQLException e)
{
Toast.makeText(Configuration.this, "No Network Connection Available", Toast.LENGTH_LONG).show();
}
c.close();
}
}
/*
* Print all data records associated with Cursor c.
* Return a string that contains all record data.
* For some weird reason, Android SDK Log class cannot print very long string message.
* Thus we have to log record-by-record.
*/
private void printAllData(Cursor c)
{
//if(c == null) return null;
if(c.moveToFirst())
{
name_of_GPRS__available = new String[c.getCount()];
apn_id = new int [c.getCount()];
int i= 0;
do{
name_of_GPRS__available [i]= c.getString(c.getColumnIndex("name"));
apn_id[i]=c.getInt(c.getColumnIndex("_id"));
//Log.d("TAG",name[i]);
i++;
}while(c.moveToNext());
//Log.d("TAG","End Of Records");
//name_of_GPRS_available [1]=" GPRS";
}
}
Kindly guide me. How can i do it. I'll be very thankful to you
I assume that this is for a Gingerbread Device as Setting the Default APN was removed in ICS (4.0 - API 14), deprecated.
Have you tried adding in:
wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
wifiManager.setWifiEnabled(true);
You will need to add these permissions to your Android Manifest:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"></uses-permission>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
You can also have an additional Spinner come up to have the User select from available Wifi spots by using Scan Result: http://developer.android.com/reference/android/net/wifi/ScanResult.html
and then
WifiManager: http://developer.android.com/reference/android/net/wifi/WifiManager.html
to set the Desired Network if there is not a default connection in place.
Markana has a nice tutorial on using Wifi this way: http://marakana.com/forums/android/examples/40.html
I am trying to get contacts from call log. I can get the contact numbers from main contacts using this code :
public void getContacts(View view) {
Intent intentContact = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intentContact, 0);
}
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
if (requestCode == 0)
{
try {
to.setText(getContactInfo(intent));
} catch(NullPointerException e) {
// Do nothing ;)
}
}
}
protected String getContactInfo(Intent intent)
{
String phoneNumber = to.getText().toString();
Cursor cursor = managedQuery(intent.getData(), null, null, null, null);
while (cursor.moveToNext())
{
String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
if(phoneNumber.endsWith(">"))
phoneNumber += ", "+name;
else
phoneNumber += name;
String hasPhone = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
if ( hasPhone.equalsIgnoreCase("1"))
hasPhone = "true";
else
hasPhone = "false" ;
if (Boolean.parseBoolean(hasPhone))
{
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId,null, null);
while (phones.moveToNext())
{ phoneNumber = phoneNumber + " <" + phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))+">";
}
phones.close();
}
}
cursor.close();
return phoneNumber;
}
What this does is when we click a "Contact" button it open a list with all the contacts, the user can select any contact and that selected contact will be added in the "To" field. I want to do the exactly same thing, but instead of displaying all the contacts i want to display only those who were recently used (call log) for selection.
Also it would be nice if you can tell how to do this with groups also.
I got this going using my own version. i used a dialog and handed it the cursor to the call log. Here is the function:
public void getCallLog() {
String[] callLogFields = { android.provider.CallLog.Calls._ID,
android.provider.CallLog.Calls.NUMBER,
android.provider.CallLog.Calls.CACHED_NAME /* im not using the name but you can*/};
String viaOrder = android.provider.CallLog.Calls.DATE + " DESC";
String WHERE = android.provider.CallLog.Calls.NUMBER + " >0"; /*filter out private/unknown numbers */
final Cursor callLog_cursor = getActivity().getContentResolver().query(
android.provider.CallLog.Calls.CONTENT_URI, callLogFields,
WHERE, null, viaOrder);
AlertDialog.Builder myversionOfCallLog = new AlertDialog.Builder(
getActivity());
android.content.DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int item) {
callLog_cursor.moveToPosition(item);
Log.v("number", callLog_cursor.getString(callLog_cursor
.getColumnIndex(android.provider.CallLog.Calls.NUMBER)));
callLog_cursor.close();
}
};
myversionOfCallLog.setCursor(callLog_cursor, listener,
android.provider.CallLog.Calls.NUMBER);
myversionOfCallLog.setTitle("Choose from Call Log");
myversionOfCallLog.create().show();
}
You can use ContactsContract.Contacts.CONTENT_STREQUENT_URI which will give you both Frequently called and Starred contacts.
From API 21 is possible to use this: https://developer.android.com/reference/kotlin/android/provider/CallLog.Calls#CACHED_LOOKUP_URI
CACHED_LOOKUP_URI added in API level 21 static val CACHED_LOOKUP_URI:
String The cached URI to look up the contact associated with the phone
number, if it exists.
This value is typically filled in by the dialer app for the caching
purpose, so it's not guaranteed to be present, and may not be current
if the contact information associated with this number has changed.