in my team's Android application I have a service running from boot which communicates with a server to perform operations such as logging in, registering, chatting between phones and updating the phone database.
I need to make my service communicate with the activity bi-directionally: for example I am working on the login activity at the moment and the username and passwords are Strings taken from a text field on the app screen and I have been able to pass them to the service for it to send an authorisation command to the server.
public void loginPressed(View v){
usernameStr = usernameField.getText().toString();
passwordStr = passwordField.getText().toString();
if (!bound) return;
Bundle b = new Bundle();
Message msg = Message.obtain(null, ChatService.LOGIN);
try {
b.putString("username", usernameStr);
b.putString("password", passwordStr);
msg.setData(b);
messenger.send(msg);
}
catch (RemoteException e) {
}
This works as I would have expected. When the server responds with a message saying whether or not the login was sucessful, I need it to pass a message back to the activity so that I can start the main activity if succesful or prompt for re-entry if not.
I tried to use the msg.replyTo field to get the return messenger to send the information back, but when I run the app it force closes with a null pointer exception and I have no idea why this is happening. Here is the code that seems to be the culprit:
private class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch(msg.what) {
case LOGIN:
Bundle b = msg.getData();
String username = b.getString("username");
String password = b.getString("password");
String loginMessage = TCPCall.login(username, password);
connection.sendMessage(loginMessage);
String loginReturn = connection.retrieveMessage();
Message m;
Scanner s = new Scanner(loginReturn);
s.useDelimiter(",");
String c = s.next();
String status = s.next();
String message = s.next();
if (status.equals("OK")) {
m = Message.obtain(null, LoginActivity.OK);
try {
msg.replyTo.send(m);
} catch (RemoteException e) {}
}
else {
m = Message.obtain(null, LoginActivity.ERR);
try {
msg.replyTo.send(m);
} catch (RemoteException e) {}
}
break;
The null pointer seems to be coming from the
msg.replyTo.send(m);
line of code in both cases (login succesful and login failed)
Any help to fix this problem would be greatly appreciated :)
As Gregg points out in the comments. You need to set msg.replyTo = messenger; int he place where you send the original message.
An example can be found here: http://www.survivingwithandroid.com/2014/01/android-bound-service-ipc-with-messenger.html
I think you forgot to send response to Login Activity by bundle from Service.
So, i made some changes in Messenger Service
define one global variable and made some changes in Incoming Handler
static final int LOGIN_STATUS = 1;
private class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch(msg.what) {
case LOGIN:
Bundle b = msg.getData();
String username = b.getString("username");
String password = b.getString("password");
String loginMessage = TCPCall.login(username, password);
connection.sendMessage(loginMessage);
String loginReturn = connection.retrieveMessage();
Message m = Message.obtain(null, LOGIN_STATUS);
Scanner s = new Scanner(loginReturn);
s.useDelimiter(",");
String c = s.next();
String status = s.next();
String message = s.next();
if (status.equals("OK")) {
b.putString("responseC",c);
b.putString("responseStatus",status);
b.putString("responseMessage",message)
m.setData(b);
try {
msg.replyTo.send(m);
} catch (RemoteException e) {}
}
else {
/*if something is wrong with username and password you can put
a toast*/
}
break;
Now we have to catch this response in our LoginActivity and
take IncomingHandler in Login Activity also
class IncomingHandler extends Handler{
#Override
public void handleMessage(Message msg) {
switch (msg.what){
case ChatService.LOGIN_STATUS:
String C = msg.getData().getString("responseC");
String Status = msg.getData().getString("responseStatus");
String Message = msg.getData().getString("responseMessage");
//Here is your response in LoginActivity, enjoy!!!
break;
default:
super.handleMessage(msg);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
public void loginPressed(View v){
usernameStr = usernameField.getText().toString();
passwordStr = passwordField.getText().toString();
if (!bound) return;
Bundle b = new Bundle();
Message msg = Message.obtain(null, ChatService.LOGIN_SATUS,0,0);
try {
b.putString("username", usernameStr);
b.putString("password", passwordStr);
msg.setData(b);
msg.replyTo = mMessenger;
messenger.send(msg);
}
catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
This code is working perfectly, hope it will help you,
Thanks
Related
I am using Gmail api and many users are complaining that sending emails does not work. For most users it works fine and I am not able to reproduce the issue. In Firebase I get the following crash report.
Non-fatal Exception: com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:297)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:868)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
at com.dummydomain.myapp.EmailUtils.sendMessage(EmailUtils.java:397)
(...)
Caused by com.google.android.gms.auth.d: NeedPermission
at com.google.android.gms.auth.zze.zzb(zze.java:13)
at com.google.android.gms.auth.zzd.zza(zzd.java:77)
at com.google.android.gms.auth.zzd.zzb(zzd.java:20)
at com.google.android.gms.auth.zzd.getToken(zzd.java:7)
at com.google.android.gms.auth.zzd.getToken(zzd.java:5)
at com.google.android.gms.auth.zzd.getToken(zzd.java:2)
at com.google.android.gms.auth.GoogleAuthUtil.getToken(GoogleAuthUtil.java:55)
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.getToken(GoogleAccountCredential.java:267)
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:292)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:868)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
at com.dummydomain.myapp.EmailUtils.sendMessage(EmailUtils.java:397)
(...)
Below are the essentials of my authentication process. Manifest.permission.GET_ACCOUNTS is already granted at this point, and the appropriate API things in the Google Cloud Console are correctly configured and my app is verified for using the sensitive permission/scope GMAIL_SEND.
private void authenticate() {
String[] SCOPES = {GmailScopes.GMAIL_SEND};
GoogleAccountCredential mCredential = GoogleAccountCredential.usingOAuth2(
context,
Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff());
startActivityForResult(mCredential.newChooseAccountIntent(),REQUEST_ACCOUNT_PICKER);
}
// which returns in
public void onActivityResult(...) {
// (...)
switch(requestCode) {
case REQUEST_ACCOUNT_PICKER:
if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
// Check if not a gmail account, since gmail api only works with that gmail accounts...
if(!accountName.contains("#gmail.com") && !accountName.contains("#googlemail.com")){
// --> tell user to select a google account
return;
}
mCredential.setSelectedAccount(new Account(accountName, BuildConfig.APPLICATION_ID));
// Got account, now test if we have access
new CheckAccessTask().execute();
}else{
// (...)
}
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode != Activity.RESULT_OK) {
// choose new account
startActivityForResult(mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}else{
// Got authorization, so test email
new CheckAccessTask().execute();
}
break;
}
}
private class CheckAccessTask extends AsyncTask<Void, Void, Boolean> {
private Exception mLastError = null;
#Override
protected Boolean doInBackground(Void... params) {
try {
// Check if we got token - will crash with UserRecoverableAuthException
// if user didn't accept the google consent screen
mCredential.getToken();
// access granted, return true
return true;
} catch (Exception e) {
mLastError = e;
cancel(true);
return false;
}
}
#Override
protected void onCancelled() {
if (mLastError != null) {
if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
// Play Services not found --> cancel activation
} else if (mLastError instanceof UserRecoverableAuthException) {
startActivityForResult(((UserRecoverableAuthException) mLastError).getIntent(), REQUEST_AUTHORIZATION);
} else if (mLastError instanceof UserRecoverableAuthIOException) {
startActivityForResult(((UserRecoverableAuthIOException) mLastError).getIntent(), REQUEST_AUTHORIZATION);
} else {
// Other error --> cancel activation
}
} else {
// --> cancel activation
}
}
#Override
protected void onPostExecute(Boolean accessGranted) {
if (accessGranted){
// SUCCESS! Save email in shared preferences for later use
String accountName = mCredential.getSelectedAccountName();
prefs.putString(Constants.SENDER_ACCOUNT, accountName);
}
}
}
Essentials of sending email process (in actual code emails are HTML)
private void sendEmail(){
GoogleAccountCredential mCredential = GoogleAccountCredential.usingOAuth2(
context,
Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff());
// set sender account
String senderAccount = prefs.getString(Constants.SENDER_ACCOUNT, null);
if(senderAccount == null) return; // Authentication not complete
mCredential.setSelectedAccount(new Account(senderAccount, BuildConfig.APPLICATION_ID));
// Initialize service object
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
Gmail mGmailApiService = new Gmail.Builder(
transport, jsonFactory, mCredential)
.setApplicationName("My-App")
.build();
// Construct email
com.google.api.services.gmail.model.Message message;
try {
// create MimeMessage
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage mimeMessage = new MimeMessage(session);
mimeMessage.setSubject("Test email");
mimeMessage.setText("Hello this is an email sent from android");
mimeMessage.setFrom(new InternetAddress(mCredential.getSelectedAccountName()));
mimeMessage.setRecipients(javax.mail.Message.RecipientType.TO, InternetAddress.parse(senderAccount)); // send to yourself
// Convert MimeMessage to Message
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
mimeMessage.writeTo(bytes);
String encodedEmail = Base64.encodeBase64URLSafeString(bytes.toByteArray());
message = new com.google.api.services.gmail.model.Message();
message.setRaw(encodedEmail);
} catch (MessagingException | IOException e) {
e.printStackTrace();
return;
}
// Send email
try {
mGmailApiService.users().messages().send("me", message).execute(); // (EmailUtils:397)
// Success, email sent!
} catch (IOException e) {
e.printStackTrace();
// ==== THIS is where users get UserRecoverableAuthIOExceptions ==== //
}
}
With firebase logging I've found out that the crash happens both shortly after authentication as well long after. I can also see that it sometimes happens that the code sends one email successfully and then crashes on the next one right after. I also get quite a few reports of SocketTimeoutException which I believe are caused by slow/faulty internet connection though.
Thank you for your time.
EDIT:
The only way I am able to reproduce the error is by manually removing my app from the list of "Third-party apps with account access". But I don't see why this should happen without user interaction.
I am developing an app and I use Socket.io on it, I initialize the socket in a class that extends Application and looks like this:
public class Inicio extends Application{
private Socket mSocket;
private SharedPreferences spref;
#Override
public void onCreate(){
super.onCreate();
try{
spref = getSharedPreferences("accountData", Context.MODE_PRIVATE);
IO.Options op = new IO.Options();
op.forceNew = true;
op.reconnection = true;
op.query = "tok=" + spref.getString("sessiontoken", "") + "&usr=" + spref.getString("userid", "");
mSocket = IO.socket(Constants.serverAddress, op);
}catch(URISyntaxException e){
throw new RuntimeException(e);
}
}
public Socket getmSocket(){
return mSocket;
}
}
So I can get and use the same socket instance in other parts of my application's code calling the following way:
Inicio appClass = (Inicio) getApplication();
mSocket = appClas.getmSocket();
mSocket.connect();
But there is a small problem that motivated me to post this question, can you see when I call to SharedPreferences in the Application class? I do this because I need to send the session token and user account ID to properly start the socket connection with my server, the problem is:
Imagine that a user opens the app for the first time and does not have an account yet, he will login or register and then the session token and user ID will be saved to SharedPreferences, but when the app started and ran the Application class, SharedPreferences was still empty and did not have the required token and user ID to establish the connection, so the user would have to reopen the app now to be able to use the socket successfully.
So I ask you: What are my alternative options for solving the problem? Is there another structure besides the Application class that I could use to not suffer from this problem? Or is there some way to bypass this problem?
What I'm doing to get around the problem for now is to restart the app programmatically when login occurs but I believe this is looks like a sad joke and not the ideal way to do it.
Thanks, I apologize for this long question of mine, but I'll be grateful for any help.
Separate your soket creation logic like below:
private void createSoket() {
spref = getSharedPreferences("accountData", Context.MODE_PRIVATE);
String sessiontoken = spref.getString("sessiontoken", "");
String userId = spref.getString("userid", "");
if(!(TextUtils.isEmpty(sessiontoken) || TextUtils.isEmpty(userId))) {
try {
IO.Options op = new IO.Options();
op.forceNew = true;
op.reconnection = true;
op.query = "tok=" + sessiontoken + "&usr=" + userId;
mSocket = IO.socket(Constants.serverAddress, op);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
And when required soket check null and create before pass the instance.
public Socket getmSocket(){
if(mSoket == null)
createSoket();
return mSocket;
}
N.B: Without valid settionToken and userId, soket is null
This is complete Application class:
public class Inicio extends Application{
private Socket mSocket;
private SharedPreferences spref;
#Override
public void onCreate(){
super.onCreate();
createSoket();
}
private void createSoket() {
spref = getSharedPreferences("accountData", Context.MODE_PRIVATE);
String sessiontoken = spref.getString("sessiontoken", "");
String userId = spref.getString("userid", "");
if(!(TextUtils.isEmpty(sessiontoken) || TextUtils.isEmpty(userId))) {
try {
IO.Options op = new IO.Options();
op.forceNew = true;
op.reconnection = true;
op.query = "tok=" + sessiontoken + "&usr=" + userId;
mSocket = IO.socket(Constants.serverAddress, op);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
public Socket getmSocket(){
if(mSoket == null)
createSoket();
return mSocket;
}
}
I am building an android email client app. I managed to display the emails headers/senders in a RecylerView. Next, I want to start a new activity that will display the email content/attachments when the user chooses a specific mail.
I have a hard time doing this through intents. I am able to get the contents of the email (text, inline images, attachments) but I can't figure out a way to display them as close to the original format as possible. I thought of putting the text in a StringBuilder and sending it through an intent in order to display the text, but this way I can't display the inline images in the right place and there are also formatting problems.
Any kind of guidance towards the way I should approach this is much appreciated.
The class that displays the list of the available mails and gets the content of the specific mail to send it another activity for displaying it. I know the code is a little hazardous, I tried many approaches and it is far from the final form.
public class CheckMail extends Activity {
static List<Message> messages = new ArrayList<>();
String[] sender;
String[] date;
String[] subject;
boolean[] seen;
Context context = null;
ListView listView;
Intent intent;
Store store;
StringBuilder content = new StringBuilder();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_check_mail);
if (android.os.Build.VERSION.SDK_INT > 9)
{
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
context = this;
ReadEmails task = new ReadEmails();
task.execute();
}
public void writePart(Part p) throws Exception {
if (p instanceof Message)
this.writeEnvelope((Message) p);
//check if the content is plain text
if (p.isMimeType("text/plain")) {
content.append(p.getContent().toString());
}
//check if the content has attachment
else if (p.isMimeType("multipart/*")) {
System.out.println("This is a Multipart");
System.out.println("---------------------------");
Multipart mp = (Multipart) p.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++)
writePart(mp.getBodyPart(i));
}
//check if the content is a nested message
else if (p.isMimeType("message/rfc822")) {
System.out.println("This is a Nested Message");
System.out.println("---------------------------");
writePart((Part) p.getContent());
}
/*
//check if the content is an inline image
else if (p.isMimeType("image/jpeg")) {
System.out.println("--------> image/jpeg");
Object o = p.getContent();
InputStream x = (InputStream) o;
// Construct the required byte array
System.out.println("x.length = " + x.available());
while ((i = (int) ((InputStream) x).available()) > 0) {
int result = (int) (((InputStream) x).read(bArray));
if (result == -1)
int i = 0;
byte[] bArray = new byte[x.available()];
break;
}
FileOutputStream f2 = new FileOutputStream("/tmp/image.jpg");
f2.write(bArray);
}
else if (p.getContentType().contains("image/")) {
System.out.println("content type" + p.getContentType());
File f = new File("image" + new Date().getTime() + ".jpg");
DataOutputStream output = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(f)));
com.sun.mail.util.BASE64DecoderStream test =
(com.sun.mail.util.BASE64DecoderStream) p
.getContent();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = test.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
else {
Object o = p.getContent();
if (o instanceof String) {
System.out.println("This is a string");
System.out.println("---------------------------");
System.out.println((String) o);
}
else if (o instanceof InputStream) {
System.out.println("This is just an input stream");
System.out.println("---------------------------");
InputStream is = (InputStream) o;
is = (InputStream) o;
int c;
while ((c = is.read()) != -1)
System.out.write(c);
}
else {
System.out.println("This is an unknown type");
System.out.println("---------------------------");
System.out.println(o.toString());
}
}
*/
}
public void writeEnvelope(Message m) throws Exception {
System.out.println("This is the message envelope");
System.out.println("---------------------------");
Address[] a;
StringBuilder sender = new StringBuilder();
StringBuilder recipients = new StringBuilder();
String subject = "";
// FROM
if ((a = m.getFrom()) != null) {
for (int j = 0; j < a.length; j++)
sender.append(a[j].toString());
}
// TO
if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
for (int j = 0; j < a.length; j++)
recipients.append(a[j].toString());
}
// SUBJECT
if (m.getSubject() != null)
subject = m.getSubject();
intent.putExtra("Sender", sender.toString());
intent.putExtra("Recipients", recipients.toString());
intent.putExtra("Message", subject);
intent.putExtra("Date", m.getReceivedDate().toString());
}
class ReadEmails extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
// Create all the needed properties - empty!
Properties connectionProperties = new Properties();
// Create the session
Session session = Session.getDefaultInstance(connectionProperties, null);
try {
System.out.print("Connecting to the IMAP server...");
// Connecting to the server
// Set the store depending on the parameter flag value
store = session.getStore("imaps");
// Set the server depending on the parameter flag value
String server = "imap.gmail.com";
store.connect(server, "....#gmail.com", "password");
System.out.println("done!");
// Get the Inbox folder
Folder inbox = store.getFolder("Inbox");
// Set the mode to the read-only mode
inbox.open(Folder.READ_ONLY);
// Get messages
CheckMail.messages = Arrays.asList(inbox.getMessages());
System.out.println("Reading messages...");
sender = new String[messages.size()];
date = new String[messages.size()];
subject = new String[messages.size()];
seen = new boolean[messages.size()];
for (int i = 0; i < messages.size(); i++) {
try {
Address[] froms = messages.get(i).getFrom();
String email = froms == null ? null : ((InternetAddress) froms[0]).getAddress();
sender[i] = email;
date[i] = messages.get(i).getReceivedDate().toString();
subject[i] = messages.get(i).getSubject();
Flags flags = messages.get(i).getFlags();
Flags.Flag[] sf = flags.getSystemFlags();
for (int j = 0; j < sf.length; j++) {
if (sf[j] == Flags.Flag.SEEN)
seen[i] = true;
else
seen[i] = false;
}
} catch (MessagingException e) {
e.printStackTrace();
}
}
System.out.println("Done reading...");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
CustomListAdapter whatever = new CustomListAdapter((Activity) context, sender, date, subject, seen);
listView = (ListView) findViewById(R.id.listviewID);
listView.setAdapter(whatever);
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
try {
content.delete(0, content.length());
intent = new Intent(context, OpenMail.class);
writePart(messages.get(position));
intent.putExtra("Content", content.toString());
startActivity(intent);
}
catch (Exception e)
{e.printStackTrace();}
}
});
}
}
}
Since no one answered...I display emails via Javamail in some of my apps.
I think you're on the right track, having separate activities for the list and viewer is a good approach. [Or separate fragments because on a tablet you may want to display the list and the body on the same screen, side by side]
Couple issues that might come up:
I would be cautious about putting the email content in an extra to start the activity and/or committing it to saved instance state in the viewer activity because there are size limits [For example, 1MB for saved instance state on Android 7+]
Downloading the email in a ASyncTask in the activity might not be the best approach. I don't know the full purpose of the app, but I assume that is something that should succeed whether the user waits or not? The ASyncTask will continue to run if they task away, but it will hold on to the activity context causing a so called 'temporary memory leak'. It is probably best to put it in a service and download it in a separate thread. However, doing it in the activity as a proof of concept is perfectly reasonable...
I don't think that walking the message structure in the email list activity is the best approach. In my apps, I download email in a background service and commit the data to an SQL-DB via a ContentProvider. On the message viewer screen the email body is retrieved from the ContentProvider/SQL-DB using a component called the CursorLoader. It handles all the loading in the background so that the UI remains responsive whilst loading large mails. But in any event, I avoid passing the message body between activities.
A lot of emails have HTML parts (multipart/alternative: text/plain & text/html) so the viewer was implemented as a WebView. The WebView produced good looking emails with minimal effort.
Couple miscellaneous gotchas, when retrieving the mail, take care to call setPeek(true). It will stop the the READ flag being set. It is not an issue for GMail, but some IMAP servers will set this flag. Users will complain if any app other than their primary email app changes the READ flag. Also don't assume that any of the headers are present, SPAM emails are notorious for leaving out the message ID, subject and other fields. Finally, it might be worth considering implementing authentication via OAuth2 which will enable your app to connect to a user's GMail account via Javamail without needing their password.
I'm not sure that any of that really helps because it is a pretty big job, but one step at a time...Cheers!
http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/
Hi,
This is a tutorial about building a group chat app using socket programming. This app allows us to chat between multiple devices like android mobiles and web.
I want to send more than one "String" at a time to the server. I'm having trouble figuring that out.
The link to the tutorial where I downloaded the code is pasted above. I've already made the dynamic web page and I have it hosted on eapps.com At the very bottom of the this email is the edited code for the app. If you click the link above, you can see how I changed it.
The way it works is..
A web socket is created using WebSocketClient class and it has all the callback methods like onConnect, onMessage and onDisconnect.
In onMessage method parseMessage() is called to parse the JSON received from the socket server.
In parseMessage() method, the purpose of JSON is identified by reading the flag value.
When a new message is received, the message is added to list view data source and adapter.notifyDataSetChanged() is called to update the chat list.
sendMessageToServer() method is used to send the message from android device to socket server.
playBeep() method is called to play device’s default notification sound whenever a new message is received.
When you click the btnSend. it uses this method from the UtilsXd class. I've changed it a little in an attempt to pass an extra value.
public String getSendMessageJSONXD(String message, String whichPicIndex) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_MESSAGE);
jObj.put("sessionId", getSessionId());
jObj.put("message", message);
jObj.put("id", id);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
First of all, what I still don't understand is, where did the values for
String sessionId = jObj.getString("sessionId");
and
String onlineCount = jObj.getString("onlineCount");
from this method
private void parseMessage(final String msg, String idINDEX) {
come from.
They were't added in the JSON object created in the UtilsXD class so how are they created?
That's not the problem I'm having. This is.
superString is the value I want to pass to dictate which picture to show.
superString = (sharedPrefs.getString("prefSyncAvatar", "1"));
You can change your picture in from the settings.
When a message is received, a switch/case statement changes the picture of/ for the message received according to the value passed by superString.
I should be able to sit there and just receive messages, and whatever number the user passes, the profilePicture should be set according to that number.
Here's where the problem begins.
This constructer builds a message based of the message that's just been parsed.
// Message m = new Message(fromName, message, isSelf);
Message m = new Message(fromName, message, isSelf, id, name,
image, status, profilePic, timeStamp, url);
In this method.
private void parseMessage(final String msg, String idINDEX) {
I can pass an value to the string "id" excluding the JSON I need it to.
String id = idINDEX;
this works,
String id = "0";
this works,
String id = utils.getPictureId();
this works,
String id = jObj.getString("id");
This doesn't work.
This is the error I'm getting.
org.json.JSONException: No value for id (this is the issue)
I've added the key/value pair
jObj.put("id", id);
in
public String getSendMessageJSONXD(String message, String whichPicIndex) {
but it's not coming though to the message.
Here's where I think the problem is.
The method onMessage, isn't can't take an extra parameter because it's from a library project. And I can't find that method to make a new constructor.
#Override
public void onMessage(String message) {
Log.d(TAG, String.format("Got string message! %s", message));
parseMessage(message, superString);
}
#Override
public void onMessage(byte[] data) {
Log.d(TAG, String.format("Got binary message! %s",
bytesToHex(data)));
String hello = "99";
parseMessage(bytesToHex(data), superString);
}
/////// Here's the final code below ////////
// JSON flags to identify the kind of JSON response
private static final String TAG_SELF = "self", TAG_NEW = "new",
TAG_MESSAGE = "message", TAG_ID = "id", TAG_EXIT = "exit";
#SuppressWarnings("deprecation")
#SuppressLint({ "NewApi", "CutPasteId" })
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_chat);
showUserSettings();
getActionBar().setTitle("City Chat - Beta 1.3");
superString = (sharedPrefs.getString("prefSyncAvatar", "1"));
listView = (ListView) findViewById(R.id.list_view_messages);
feedItems = new ArrayList<FeedItem>();
// We first check for cached request
vollewStuff();
//
//
// THis is where this fun begins
btnSend = (Button) findViewById(R.id.btnSend);
inputMsg = (EditText) findViewById(R.id.inputMsg);
listViewMessages = (ListView) findViewById(R.id.list_view_messages);
utils = new UtilsXD(getApplicationContext());
// Getting the person name from previous screen
Intent i = getIntent();
name = i.getStringExtra("name");
Integer.parseInt((sharedPrefs.getString("prefSyncAvatar", "1")));
btnSend.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Sending message to web socket server
sendMessageToServer(utils.getSendMessageJSONXD(inputMsg
.getText().toString(), superString), superString);
utils.storePictureId((sharedPrefs.getString("prefSyncAvatar",
"1")));
// Clearing the input filed once message was sent
inputMsg.setText("");
}
});
listMessages = new ArrayList<Message>();
adapter = new MessagesListAdapter(this, listMessages, feedItems);
listViewMessages.setAdapter(adapter);
/**
* Creating web socket client. This will have callback methods
* */
client = new WebSocketClient(URI.create(WsConfig.URL_WEBSOCKET
+ URLEncoder.encode(name)), new WebSocketClient.Listener() {
#Override
public void onConnect() {
}
/**
* On receiving the message from web socket server
* */
#Override
public void onMessage(String message) {
Log.d(TAG, String.format("Got string message! %s", message));
parseMessage(message, superString);
// parseMessage(message,
// (sharedPrefs.getString("prefSyncAvatar", "1")));
}
#Override
public void onMessage(byte[] data) {
Log.d(TAG, String.format("Got binary message! %s",
bytesToHex(data)));
String hello = "99";
parseMessage(bytesToHex(data), superString);
// Message will be in JSON format
// parseMessage(bytesToHex(data),
// (sharedPrefs.getString("prefSyncAvatar", "1")));
}
/**
* Called when the connection is terminated
* */
#Override
public void onDisconnect(int code, String reason) {
String message = String.format(Locale.US,
"Disconnected! Code: %d Reason: %s", code, reason);
showToast(message);
//
// clear the session id from shared preferences
utils.storeSessionId(null);
}
#Override
public void onError(Exception error) {
Log.e(TAG, "Error! : " + error);
// showToast("Error! : " + error);
showToast("Are you sure you want to leave?");
}
}, null);
client.connect();
}
/**
* Method to send message to web socket server
* */
private void sendMessageToServer(String message, String id) {
if (client != null && client.isConnected()) {
client.send(message);
client.send(id);
}
}
/**
* Parsing the JSON message received from server The intent of message will
* be identified by JSON node 'flag'. flag = self, message belongs to the
* person. flag = new, a new person joined the conversation. flag = message,
* a new message received from server. flag = exit, somebody left the
* conversation.
* */
private void parseMessage(final String msg, String idINDEX) {
try {
jObj = new JSONObject(msg);
// JSON node 'flag'
String flag = jObj.getString("flag");
String id = idINDEX;
// if flag is 'self', this JSON contains session id
if (flag.equalsIgnoreCase(TAG_SELF)) {
String sessionId = jObj.getString("sessionId");
// Save the session id in shared preferences
utils.storeSessionId(sessionId);
Log.e(TAG, "Your session id: " + utils.getSessionId());
} else if (flag.equalsIgnoreCase(TAG_NEW)) {
// If the flag is 'new', new person joined the room
String name = jObj.getString("name");
String message = jObj.getString("message");
// number of people online
String onlineCount = jObj.getString("onlineCount");
showToast(name + message + ". Currently " + onlineCount
+ " people online!");
} else if (flag.equalsIgnoreCase(TAG_MESSAGE)) {
// if the flag is 'message', new message received
String fromName = name;
String message = jObj.getString("message");
String sessionId = jObj.getString("sessionId");
// switch (Integer.parseInt((sharedPrefs.getString(
// "prefSyncAvatar", "1"))))
boolean isSelf = true;
switch (Integer.parseInt(utils.getPictureId())) {
case 1:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatlion.png";
break;
case 2:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatmatt.png";
break;
case 3:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatroboman.png";
break;
case 4:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatalien.png";
break;
case 5:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatkitty.png";
break;
case 10:
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatkitty.png";
break;
}
// Checking if the message was sent by you
if (!sessionId.equals(utils.getSessionId())) {
fromName = jObj.getString("name");
// profilePic = jObj.getString("profilePic");
//
//
//
//
jObj.getString("message");
isSelf = false;
profilePic = "http://clxxxii.vm-host.net/clxxxii/citychatalien.png";
}
// profilePic =
// "http://clxxxii.vm-host.net/clxxxii/citychatlion.png";
Integer.parseInt(utils.getPictureId());
String name = "clxxxii";
String image = "http://i.huffpost.com/gen/1716876/thumbs/o-ATLANTA-TRAFFIC-facebook.jpg";
String status = "status";
String timeStamp = "1403375851930";
String url = "url";
// Message m = new Message(fromName, message, isSelf);
Message m = new Message(fromName, message, isSelf, id, name,
image, status, profilePic, timeStamp, url);
// Appending the message to chat list
appendMessage(m);
} else if (flag.equalsIgnoreCase(TAG_EXIT)) {
// If the flag is 'exit', somebody left the conversation
String name = jObj.getString("name");
String message = jObj.getString("message");
showToast(name + message);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
///////// I've updated the socket server from the first project. /////// I've added the JSON value "id" successfully /// But I how do I change the value without having to type in "5" please see below..
//
This is the JSONutilty method I changed.
//
public String getSendAllMessageJson(String sessionId, String fromName,
String message, String photoId) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_MESSAGE);
jObj.put("sessionId", sessionId);
jObj.put("name", fromName);
jObj.put("message", message);
jObj.put("id", photoId);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}
This is the method that is being used by SocketServer. I can successfully send a message from this activity, to the utility method to send over the network.
// Normal chat conversation message
json = jsonUtils //
.getSendAllMessageJson(sessionId, name, message, "5");
How can I retrieve a value sent over the network to place in spot where I have "5" instead of hard coding it?
Thanks!!!
jobj doesn't have the value id. Example of the JSON object looks like this:
{
"message": " joined conversation!",
"flag": "new",
"sessionId": "4",
"name": "Ravi Tamada",
"onlineCount": 6
}
(as shown in the part1 of the same tutorial).
That solves the first issue of onlineCount and sessionId.
Thanks #miselking, you where correct, The server was missing the JSON string "id". To actually solve the problem of post.
"I want to send more than one "String" at a time to the server. I'm having trouble figuring that out."
This is how you do it.
Step 1)
Adding the string photoId to the method
public String getSendAllMessageJson(String sessionId, String fromName,
String message, String photoId) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_MESSAGE);
jObj.put("sessionId", sessionId);
jObj.put("name", fromName);
jObj.put("message", message);
jObj.put("id", photoId);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}
Step 2)
Add the string to the correct sting to the method.
String json = null;
json = jsonUtils
.getSendAllMessageJson(sessionId, name, message,
photoId);
Step 3) Parse the JSON
try {
JSONObject jObj = new JSONObject(message);
msg = jObj.getString("message");
photoId = jObj.getString("id");
} catch (JSONException e) {
e.printStackTrace();
}
I am trying to add a feature to my android app that allows users to "checkin" with other people tagged to the checkin.
I have the checkins method working no problem and can tag some one by adding the user ID as a parameter (see code below)
public void postLocationTagged(String msg, String tags, String placeID, Double lat, Double lon) {
Log.d("Tests", "Testing graph API location post");
String access_token = sharedPrefs.getString("access_token", "x");
try {
if (isSession()) {
String response = mFacebook.request("me");
Bundle parameters = new Bundle();
parameters.putString("access_token", access_token);
parameters.putString("place", placeID);
parameters.putString("Message",msg);
JSONObject coordinates = new JSONObject();
coordinates.put("latitude", lat);
coordinates.put("longitude", lon);
parameters.putString("coordinates",coordinates.toString());
parameters.putString("tags", tags);
response = mFacebook.request("me/checkins", parameters, "POST");
Toast display = Toast.makeText(this, "Checkin has been posted to Facebook.", Toast.LENGTH_SHORT);
display.show();
Log.d("Tests", "got response: " + response);
if (response == null || response.equals("") ||
response.equals("false")) {
Log.v("Error", "Blank response");
}
} else {
// no logged in, so relogin
Log.d(TAG, "sessionNOTValid, relogin");
mFacebook.authorize(this, PERMS, new LoginDialogListener());
}
} catch(Exception e) {
e.printStackTrace();
}
}
This works fine (I've posted it in case it is of help to anyone else!), the problem i am having is i am trying to create a list of the users friends so they can select the friends they want to tag. I have the method getFriends (see below) which i am then going to use to generate an AlertDialog that the user can select from which in turn will give me the id to use in the above "postLocationTagged" method.
public void getFriends(CharSequence[] charFriendsNames,CharSequence[] charFriendsID, ProgressBar progbar) {
pb = progbar;
try {
if (isSession()) {
String access_token = sharedPrefs.getString("access_token", "x");
friends = charFriendsNames;
friendsID = charFriendsID;
Log.d(TAG, "Getting Friends!");
String response = mFacebook.request("me");
Bundle parameters = new Bundle();
parameters.putString("access_token", access_token);
response = mFacebook.request("me/friends", parameters, "POST");
Log.d("Tests", "got response: " + response);
if (response == null || response.equals("") ||
response.equals("false")) {
Log.v("Error", "Blank response");
}
} else {
// no logged in, so relogin
Log.d(TAG, "sessionNOTValid, relogin");
mFacebook.authorize(this, PERMS, new LoginDialogListener());
}
} catch(Exception e) {
e.printStackTrace();
}
}
When i look at the response in the log it reads:
"got responce: {"error":{"type":"OAuthException", "message":"(#200) Permissions error"}}"
I have looked through the graphAPI documentation and searched for similar questions but to no avail! I'm not sure if i need to request extra permissions for the app or if this is something your just not allowed to do! Any help/suggestions would be greatly appreciated.
You might need the following permissions:
user_checkins
friends_checkins
read_friendlists
manage_friendlists
publish_checkins
Check the related ones from the API docs. Before that, make sure that which line causes this permission error and try to fix it.
The solution is to implement a RequestListener when making the request to the Facebook graph API. I have the new getFriends() method (see below) which uses the AsyncGacebookRunner to request the data.
public void getFriends(CharSequence[] charFriendsNames,String[] sFriendsID, ProgressBar progbar) {
try{
//Pass arrays to store data
friends = charFriendsNames;
friendsID = sFriendsID;
pb = progbar;
Log.d(TAG, "Getting Friends!");
//Create Request with Friends Request Listener
mAsyncRunner.request("me/friends", new FriendsRequestListener());
} catch (Exception e) {
Log.d(TAG, "Exception: " + e.getMessage());
}
}
The AsyncFacebookRunner makes the the request using the custom FriendsRequestListener (see below) which implements the RequestListener class;
private class FriendsRequestListener implements RequestListener {
String friendData;
//Method runs when request is complete
public void onComplete(String response, Object state) {
Log.d(TAG, "FriendListRequestONComplete");
//Create a copy of the response so i can be read in the run() method.
friendData = response;
//Create method to run on UI thread
FBConnectActivity.this.runOnUiThread(new Runnable() {
public void run() {
try {
//Parse JSON Data
JSONObject json;
json = Util.parseJson(friendData);
//Get the JSONArry from our response JSONObject
JSONArray friendArray = json.getJSONArray("data");
//Loop through our JSONArray
int friendCount = 0;
String fId, fNm;
JSONObject friend;
for (int i = 0;i<friendArray.length();i++){
//Get a JSONObject from the JSONArray
friend = friendArray.getJSONObject(i);
//Extract the strings from the JSONObject
fId = friend.getString("id");
fNm = friend.getString("name");
//Set the values to our arrays
friendsID[friendCount] = fId;
friends[friendCount] = fNm;
friendCount ++;
Log.d("TEST", "Friend Added: " + fNm);
}
//Remove Progress Bar
pb.setVisibility(ProgressBar.GONE);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FacebookError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
Feel free to use any of this code in your own projects, or ask any questions about it.
You can private static final String[] PERMISSIONS = new String[] {"publish_stream","status_update",xxxx};xxx is premissions