I have created an application that implements app to app calling using Sinch. It works only when the caller knows the name of the recipient.
To overcome this Sinch suggested to use PubNub to get the user state. They also have a tutorial here. The problem is that tutorial is old and PubNub has updated their API since. I tried to implement the functionality using their new API on my own using their docs, but it is not working or more accurately I don't know how to do it.
My current code is:
public class LoggedUsers extends Activity {
private PubNub pubNub;
String name;
private ArrayList users;
private JSONArray loggedUserList;
ListView UserList;
TextView allUsers;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user_list);
allUsers = (TextView) findViewById(R.id.JSONFromPubNub);
SharedPreferences sp = getSharedPreferences("User_Details", MODE_APPEND);
try {
name = sp.getString("UserName", "");
} catch (NullPointerException e) {
}
final PNConfiguration pnc = new PNConfiguration();
pnc.setPublishKey("publish key");
pnc.setSubscribeKey("subscribe key");
pnc.setUuid(name);
pubNub = new PubNub(pnc);
users = new ArrayList<String>();
UserList = (ListView) findViewById(R.id.listView);
String user = getUserStatus();
allUsers.setText(user);
final ArrayAdapter adapter = new ArrayAdapter(getApplicationContext(), R.layout.single_item_list, users);
UserList.setAdapter(adapter);
pubNub.addListener(new SubscribeCallback() {
#Override
public void status(PubNub pubnub, PNStatus status) {
if (status.getCategory() == PNStatusCategory.PNUnexpectedDisconnectCategory) {
// This event happens when radio / connectivity is lost
HashMap <String,String> map = new HashMap();
map.put("State","Offline");
pubNub.setPresenceState().channels(Arrays.asList("CallingChannel1")).state(map).uuid(pnc.getUuid());
} else if (status.getCategory() == PNStatusCategory.PNConnectedCategory) {
// Connect event. You can do stuff like publish, and know you'll get it.
// Or just use the connected event to confirm you are subscribed for
// UI / internal notifications, etc
HashMap <String,String> map = new HashMap();
map.put("State","Online");
pubNub.setPresenceState().channels(Arrays.asList("CallingChannel1")).state(map).uuid(pnc.getUuid());
/* if (status.getCategory() == PNStatusCategory.PNConnectedCategory) {
pubnub.publish().channel("awesomeChannel").message("hello!!").async(new PNCallback<PNPublishResult>() {
#Override
public void onResponse(PNPublishResult result, PNStatus status) {
// Check whether request successfully completed or not.
if (!status.isError()) {
// Message successfully published to specified channel.
}
// Request processing failed.
else {
// Handle message publish error. Check 'category' property to find out possible issue
// because of which request did fail.
//
// Request can be resent using: [status retry];
}
}
});
}*/
} else if (status.getCategory() == PNStatusCategory.PNReconnectedCategory) {
HashMap <String,String> map = new HashMap();
map.put("State","Online");
pubNub.setPresenceState().channels(Arrays.asList("CallingChannel1")).state(map).uuid(pnc.getUuid());
// Happens as part of our regular operation. This event happens when
// radio / connectivity is lost, then regained.
} else if (status.getCategory() == PNStatusCategory.PNDecryptionErrorCategory) {
// Handle messsage decryption error. Probably client configured to
// encrypt messages and on live data feed it received plain text.
}
}
#Override
public void message(PubNub pubnub, PNMessageResult message) {
}
#Override
public void presence(PubNub pubnub, PNPresenceEventResult presence) {
}
});
}
public String getUserStatus(){
final StringBuilder allUsers = new StringBuilder();
pubNub.subscribe().channels(Arrays.asList("CallingChannel1")).withPresence().execute();
pubNub.hereNow()
// tailor the next two lines to example
.channels(Arrays.asList("CallingChannel1"))
.includeState(true)
.includeUUIDs(true)
.async(new PNCallback<PNHereNowResult>() {
#Override
public void onResponse(PNHereNowResult result, PNStatus status) {
if (status.isError()) {
// handle error
return;
}
for (PNHereNowChannelData channelData : result.getChannels().values()) {
allUsers.append("---");
allUsers.append("channel:" + channelData.getChannelName());
allUsers.append("occoupancy: " + channelData.getOccupancy());
allUsers.append("occupants:");
for (PNHereNowOccupantData occupant : channelData.getOccupants()) {
allUsers.append("uuid: " + occupant.getUuid() + " state: " + occupant.getState());
}
}
}
});
return allUsers.toString();
}
#Override
protected void onResume() {
super.onResume();
}
}
Here are my problems:
I am trying to display all the data that I receive in a textview (later it will arranged in a listview or a recycler view) but I am getting a blank screen so I am getting null from the server.
The user status should be constantly updated to know if the user changes state (online -> offline) but there seems to be no async calls made in the code so I think it will be executed only once and then the dataset is not being changed.
How can I solve my problems?
PubNub Presence
You can monitor online and state changes using PubNub Presence. When you subscribe, subscribe with presence enabled and you will get state-change, join, leave & timeout events in the presence callback.
Callback callback = new Callback() {
#Override
public void successCallback(String channel, Object message) {
System.out.println(channel + " : "
+ message.getClass() + " : " + message.toString());
// take action on the presence events here
}
#Override
public void connectCallback(String channel, Object message) {
System.out.println("CONNECT on channel:" + channel
+ " : " + message.getClass() + " : "
+ message.toString());
}
#Override
public void disconnectCallback(String channel, Object message) {
System.out.println("DISCONNECT on channel:" + channel
+ " : " + message.getClass() + " : "
+ message.toString());
}
#Override
public void reconnectCallback(String channel, Object message) {
System.out.println("RECONNECT on channel:" + channel
+ " : " + message.getClass() + " : "
+ message.toString());
}
#Override
public void errorCallback(String channel, PubnubError error) {
System.out.println("ERROR on channel " + channel
+ " : " + error.toString());
}
};
try {
pubnub.presence("my_channel", callback);
}
catch (PubnubException e) {
System.out.println(e.toString());
}
It appears Sinch is using a rather old version of the PubNub Android SDK. I would think you could still use PubNub Android SDK v4 to do what you need to do outside of Sinch SDK unless there is some explicit requirements by Sinch to use the same version of the SDK.
Related
I am creating a BloodBank app in which, when the user requests for the blood, It takes the requested Blood group and search the same in the database. It displays list of all the users who can donate to that blood group.
In the list, I have already implemented an option to message and call the user. Additionally, I want the App to send a notification to all users who have the same blood group.
For achieving this I have subscribed the user to a topic at successful login and sent him a notification but I have done this through the console.
What I want to achieve is, as a user requests the blood and while showing him the list of all users who can donate, App should also send a notification to all the users who have subscribed to that topic.
So is there any possible way I can programmatically send FCM to all the users subscribed to the same topic.
Here I'm subscribing user to a topic at successful Login:
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
progressDialog.dismiss();
topicSubscription();
} else {
progressDialog.dismiss();
String exception = task.getException().getMessage();
HelperClass.showSnakbarMsg(rootView, exception);
}
}
});
}
private void topicSubscription() {
FirebaseMessaging.getInstance().subscribeToTopic("Blood")
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
String msg = getString(R.string.msg_subscribed);
if (!task.isSuccessful()) {
msg = getString(R.string.msg_subscribe_failed);
} else {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
Log.d("log", msg);
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
This is my Firebase messaging class:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
/**
* Called when message is received.
*
* #param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
Log.d(TAG, "From: " + remoteMessage.getFrom());
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
}
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
}
I have read about it and many have said to hit an API for this to send FCM programmatically. But I am creating the whole app in firebase so my DataBase is also in firebase and as my database is in firebase I can't use API for one notification only like I have to manage some table in DB for that notification and for only one table I have to manage a separate DB.
So is there any way that I can send FCM programmatically to all users who have subscribed to the same topic, on successful loading of the donor list which shows the user who can donate to the requested blood group.
Thanks
You can directly send push notification directly from android, to all the devices subscribed to the topic, check out the following link how to send msgs directly from android, but in this example user is sending message one to one, to send fcm message to user subscribed to a topic, you need to change the message format as specified by fcm documentation
User App
private void latLngNotification() {
Location loc1 = new Location("");
loc1.setLatitude(Double.parseDouble(userLat));
//loc1.setLongitude();
Location loc2 = new Location("");
loc2.setLatitude(Double.parseDouble(attendanceLat));
//loc2.setLongitude();
float distanceInMeters = loc1.distanceTo(loc2);
if (distanceInMeters > 50) {
//Toast.makeText(this, "distance: " + distanceInMeters, Toast.LENGTH_SHORT).show();
sendNotification();
} else {
//Toast.makeText(this, "distance: " + distanceInMeters, Toast.LENGTH_SHORT).show();
Toast.makeText(this, "You are in home...", Toast.LENGTH_SHORT).show();
}
}
private void sendNotification() {
String TOPIC = "/topics/admin_app"; //topic has to match what the receiver subscribed to
JSONObject notification = new JSONObject();
JSONObject notifcationBody = new JSONObject();
String title = "Quarantine outside";
String message = mobileno + " User is out of his area";
try {
notifcationBody.put("title", title);
notifcationBody.put("message", message);
notification.put("to", TOPIC);
notification.put("priority", "high");
notification.put("data", notifcationBody);
} catch (JSONException e) {
Log.e(TAG, "onCreate: " + e.getMessage());
}
Notification(notification);
}
private void Notification(JSONObject notification) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("https://fcm.googleapis.com/fcm/send", notification,
response -> Log.i(TAG, "onResponse: " + response.toString()),
error -> {
Toast.makeText(GetLocationActivity.this, "Request error", Toast.LENGTH_LONG).show();
Log.i(TAG, "onErrorResponse: Didn't work");
}) {
#Override
public Map<String, String> getHeaders() {
Map<String, String> params = new HashMap<>();
params.put("Authorization", "key=AAAAwxBqu5A:APA91bERpf-1rh02jLciILt1rsLv7HRrFVulMTEAJXJ5l_JGrSHf96qXvLQV0bIROob9e3xLK4VN8tWo-zBPUL39HjxyW4MsX5nKW_NiQlZGgLDCySVwHXADlg16mpLUjgASj--bk-_W");
params.put("Content-Type", "application/json");
return params;
}
};
MySingleton.getInstance(getApplicationContext()).addToRequestQueue(jsonObjectRequest);
}
Admin App
FirebaseMessaging.getInstance().subscribeToTopic("admin_app");
Intent intent = getIntent();
if (intent != null) {
String userPhone = intent.getStringExtra("message");
//Toast.makeText(this, userPhone, Toast.LENGTH_SHORT).show();
message_txt.setVisibility(View.VISIBLE);
message_txt.setText(userPhone);
} else {
message_txt.setVisibility(View.GONE);
//Toast.makeText(this, "no data", Toast.LENGTH_SHORT).show();
}
I am using Twilio Programmable chat APIs in android for 1 to 1 chat.
Following is my usecase:
1) I create a unique channel name
2) Check if the channel already exists or not
if channel exists:
user joins channel
else
create a channel with unique name
user joins channel
end
Now, what is happening is, when I am trying to join the channel, it is giving me the error: "Member already exists". So, at this point if I try to check the members of the channel, I get the Member object to be null.
I have 2 doubts at this point:
1) Shouldn't the user object contain this member if it is already a part of the channel?
2) If the user is already a part of the channel, I should be able to send mesages to the channel, by just adding the channelListener, which in this case is not happening.
I don't understand the issue. Following are my code snippets:
ChatClient.Properties props = new ChatClient.Properties.Builder()
.createProperties();
ChatClient.create(ChatActivity.this, accessToken, props, mChatClientCallback);
private CallbackListener<ChatClient> mChatClientCallback =
new CallbackListener<ChatClient>() {
#Override
public void onSuccess(ChatClient chatClient) {
mChatClient = chatClient;
//loadChannels();
Log.i(TAG, "Success creating Twilio Chat Client");
createOrJoinChannel();
}
#Override
public void onError(ErrorInfo errorInfo) {
Log.i(TAG,"Error creating Twilio Chat Client: " + errorInfo.getMessage());
}
};
private void createOrJoinChannel(){
//Only SID or unique name of channel can be supplied as parameter
mChatClient.getChannels().getChannel(UNIQUE_CHANNEL_NAME, new CallbackListener<Channel>() {
#Override
public void onSuccess(Channel channel) {
if (channel != null) {
joinChannel(channel);
} else {
Log.i(TAG, "Error occurred in getting channel");
}
}
#Override
public void onError(ErrorInfo errorInfo) {
Log.i(TAG,"Error retrieving channel: " + errorInfo.getMessage());
createChannel();
}
});
}
private void joinChannel(final Channel channel) {
Log.i(TAG, "inside join channel" + channel.getUniqueName());
Log.i(TAG, "channel status: " + channel.getStatus());
Members members = channel.getMembers();
if(members!=null){
ArrayList<Member> list = (ArrayList<Member>) members.getMembersList();
for(int i=0; i<list.size(); i++){
Log.i(TAG, "member " + i + list.get(i).getIdentity());
}
}else{
Log.i(TAG, "null object"); //Getting this even when I get
//"Member already exists" error
}
channel.join(new StatusListener() {
#Override
public void onSuccess() {
mGeneralChannel = channel;
mGeneralChannel.addListener(mDefaultChannelListener);
}
#Override
public void onError(ErrorInfo errorInfo) {
//Error joining channel: Member already exists
Log.i(TAG,"Error joining channel: " + errorInfo.getMessage());
});
}
private void createChannel(){
mChatClient.getChannels().createChannel(FRIENDLY_CHANNEL_NAME,
Channel.ChannelType.PUBLIC, new CallbackListener<Channel>() {
#Override
public void onSuccess(Channel channel) {
if (channel != null) {
setUniqueNameAndJoin(channel);
}
}
#Override
public void onError(ErrorInfo errorInfo) {
Log.i(TAG,"chats: " + "Unique name could not be set: " + errorInfo.getMessage());
}
});
}
private void setUniqueNameAndJoin(final Channel channel){
channel.setUniqueName(UNIQUE_CHANNEL_NAME, new StatusListener() {
#Override
public void onSuccess() {
Log.i(TAG, "channel with unique name created " + channel.getUniqueName());
joinChannel(channel);
}
#Override
public void onError(ErrorInfo errorInfo) {
super.onError(errorInfo);
}
});
}
private ChannelListener mDefaultChannelListener = new ChannelListener() {
#Override
public void onMessageAdded(final Message message) {
Log.i(TAG, "Message added");
ChatActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
// need to modify user interface elements on the UI thread
mMessages.add(message);
mMessagesAdapter.notifyItemInserted(mMessages.size()-1);
}
});
}
#Override
public void onMessageUpdated(Message message, Message.UpdateReason updateReason) {
Log.i(TAG, "Message updated: " + message.getMessageBody());
}
#Override
public void onMessageDeleted(Message message) {
Log.d(TAG, "Message deleted");
}
#Override
public void onMemberAdded(Member member) {
Log.i(TAG, "Member added: " + member.getIdentity());
}
#Override
public void onMemberUpdated(Member member, Member.UpdateReason updateReason) {
}
#Override
public void onMemberDeleted(Member member) {
}
#Override
public void onTypingStarted(Channel channel, Member member) {
}
#Override
public void onTypingEnded(Channel channel, Member member) {
}
#Override
public void onSynchronizationChanged(Channel channel) {
}
};
Can someone please explain to me, what am I doing wrong or what should be the correct way of doing things? Thanks in advance!
Twilio developer evangelist here.
You need to check the channel's status for your current user, using channel.getStatus(). There's a good example of this in the Android chat tutorial.
if (this.currentChannel.getStatus() == Channel.ChannelStatus.JOINED) {
// already in the channel, load the messages
} else {
// join the channel
}
I am using quick blox sdk to chat and show online users and update their online/offline status. Everything works fine but on first time login 5xxxx7 QBId presence is received as per below logs,
D/SMACK: RECV (1): <presence xmlns="jabber:client" from="5388507-28074#chat.quickblox.com/android_00000000-57d0-4ecf-ffff-ffffef05ac4a" to="16269676-28074#chat.quickblox.com" id="bQzT2-9587"/>
09-30 16:38:19.944 20513-21578/com.FCK.provider D/NativeCrypto: ssl=0xd86e0d40 sslRead buf=0xd403af98 len=104,timeo=60000
But in my presence listener nothing is logged:
rosterListener = new QBRosterListener() {
#Override
public void entriesDeleted(Collection<Integer> userIds) {
log("entriesDeleted: " + userIds);
}
#Override
public void entriesAdded(Collection<Integer> userIds) {
log("entriesAdded: " + userIds);
}
#Override
public void entriesUpdated(Collection<Integer> userIds) {
log("entriesUpdated: " + userIds);
}
#Override
public void presenceChanged(QBPresence presence) {
log("presenceChanged: " + presence);
EventBus.getDefault().post(presence);
if(chatServiceListener != null){
chatServiceListener.presenceChanged(presence);
}
// {type=online, user=5507990, status=null}
}
};
Even if I get presence manually by below code I get user as offline even if he is online and I can chat with him:
QBPresence presence = сhatRoster.getPresence(userID);
if (presence == null) {
log("No user in your roster");
return;
}
if (presence.getType() == QBPresence.Type.online) {
log("User " + userID + " is online");
} else {
log("User " + userID + " is offline");
}
Updated the Quick Blox SDK used with below code in gradle file and it is Solved:
dependencies {
compile("com.quickblox:quickblox-android-sdk-chat:2.6.1")
}
When sending upstream message most of the times the message does not get to my server, and even when the message received to the server the onMessageSent(String msgId) function isn't called (the onMessageReceived(RemoteMessage fcmMessage) work very well).
Why the function isn't called and why do I need to send 10 upstream messages to get response from the firebase cloud messaging to my server?
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String sendTo = SENDER_ID + "#gcm.googleapis.com";
RemoteMessage.Builder data = new RemoteMessage.Builder(sendTo);
data.addData("Hello", "World");
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
String messageID = getRandomString();
data.setMessageId(messageID);
Logger.d(TAG, "messageID: " + messageID);
FirebaseMessaging.getInstance().send(data.build());
}
} catch (Exception e) {
Logger.e(TAG, "Error sending upstream message: " + e.getMessage());
return "Error sending upstream message:" + e.getMessage();
}
return null;
}
#Override
protected void onPostExecute(String result) {
if (result != null) {
Logger.e(TAG, "send message failed: " + result);
}
}
}.execute(null, null, null);
}
Found out the problem!!!
The problem was on the server side.
Every time I send a message to the app (android), I started a new connection to the gcm server, when maintaining a continuous connection it worked great.
For the problem with the onMessageSent not called it was because, before you send the message you need to set time to live (setTtl(Time_in_seconds)) for the message.
RemoteMessage.Builder data = new RemoteMessage.Builder(mSendTo);
data.setMessageId(messageID);
data.setTtl(120);
data.addData("Hello", "World");
FirebaseMessaging.getInstance().send(data.build());
buttonUpstreamEcho.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Echo Upstream message logic");
String message = editTextEcho.getText().toString();
Log.d(TAG, "Message: " + message + ", recipient: " + token);
FirebaseMessaging.getInstance().send(new RemoteMessage.Builder(FCM_PROJECT_SENDER_ID + FCM_SERVER_CONNECTION)
.setMessageId(Integer.toString(RANDOM.nextInt()))
.addData("message", message)
.addData("action", BACKEND_ACTION_ECHO)
.build());
// To send a message to other device through the XMPP Server, you should add the
// receiverId and change the action name to BACKEND_ACTION_MESSAGE in the data
}
});
This is a sample Android project to showcase the Firebase Cloud Messaging (FCM) to manage upstream and downstream messages.
https://github.com/carlosCharz/FCMTest
This is the video in youtube that explains what it does.
https://www.youtube.com/watch?v=SEzOKSoAMG0
Hope you find it useful.
Using the builder pattern - it is always best to chain your calls to the setter methods. So my suggestion, and based on some working examples such as this one here, would be to change your code into something like this (note that I got rid of the for-loop - you can put it back if you need it, I don't see why - perhaps you were testing out?:
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String sendTo = SENDER_ID + "#gcm.googleapis.com";
String messageID = getRandomString();
try {
FirebaseMessaging.getInstance().send(new RemoteMessage.Builder(sendTo)
.setMessageId(messageID)
.addData("my_message", "Hello, World")
.build());
} catch (Exception e) {
Logger.e(TAG, "Error sending upstream message: " + e.getMessage());
return "Error sending upstream message:" + e.getMessage();
}
return null;
}
#Override
protected void onPostExecute(String result) {
if (result != null) {
Logger.e(TAG, "send message failed: " + result);
}
}
}.execute(null, null, null);
}
I hope this helps - try it out and let me know if it works or what errors you are getting.
I was hoping someone may be able to help or at least point me in the right direction. I'm writing an android app that publishes/receives messages and i'm trying to use PubNub to accomplish this task. In my view I firstly unsubscribe from a group name and then subscribe to it. When I then publish a message, I successfully get one message. If I then leave the view and re-enter it, when I publish a message I get a duplicate. If I do the same again, the message is triplicated and so on.
Could someone please look at my code or give me any advice.
Many thanks.
private Pubnub pubnub = new Pubnub("pub-key", "sub-key");
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(com.rickeshjobanputra.snapapp.R.layout.activity_group);
GroupName = intent.getStringExtra("GroupName");
String uuid = "12345";
pubnub.setUUID(uuid);
pubnub.unsubscribe(GroupName);
try {
pubnub.subscribe(GroupName, new Callback() {
#Override
public void successCallback(String channel, final Object message) {
System.out.println("SUBSCRIBE : " + channel + " : "
+ message.getClass() + " : " + message.toString());
}
});
} catch (PubnubException e) {
System.out.println(e.toString());
}
addListenerOnButton();
}
private void addListenerOnButton() {
final Context context = this;
button = (Button) findViewById(com.rickeshjobanputra.snapapp.R.id.snap_something_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pubnub.publish(GroupName, "test", new Callback() {
});
}
});
}
UPDATE
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
pubnub.unsubscribeAllChannels();
try {
pubnub.subscribe("test", new Callback() {
#Override
public void connectCallback(String channel, Object message) {
pubnub.publish("test", "Hello from the PubNub Java SDK", new Callback() {});
}
#Override
public void successCallback(String channel, final Object message) {
System.out.println("SUBSCRIBE : " + channel + " : "
+ message.getClass() + " : " + message.toString());
}
});
} catch (PubnubException e) {
System.out.println(e.toString());
}
}