I have been struggling with what I believe is a screen repainting problem for a RecylerView when my underlying model is modified by another Thread. But have run out of ideas.
My application receives messages from a MQTT topic and displays it in a RecyclerView as a sort of "history" or "log" display.
This works fine as long as the MQTT session does not auto reconnect.
Once the MQTT session reconnects after a dropped connection, I still receive messages from the MQTT topic, the messages are still added to my model, I still raise the "Data changed" notification, I still invalidate the RecyclerView control, but the RecyclerView is no longer repainted to reveal the new message on screen.
If I manually force a refresh/repaint of the screen (e.g. scroll the recycler view, switch to another app and back again etc) then the RecyclerView is repainted and shows the "missing" messages.
My question is: what is it about the RecyclerView that seems to be causing it to not repaint when the underlying model is modified as a result of messages being received from an MQTT topic, but only if the MQTT session is dropped and reconnected?????
And obviously, what do I need to do to fix it?????
Update
I've tried adding the following method (which is activated by the onClick of a Floating button).
public void buttonClick (View v) {
mAdapter.add("Button Message");
Toast.makeText(getApplicationContext(),"Button message added", Toast.LENGTH_SHORT).show();
}
This method suffers from the same problem as messages received from the MQTT topic. If I click it before the MQTT auto-reconnect, the "Button Message" is added to my RecyclerView and displayed.
Once MQTT session is dropped and then auto reconnected, even this "buttonClick" method's "Button Message" is no longer displayed unless I force a refresh of the RecyclerList. FWIW, the "Toast" is always displayed (before and after the MQTT autoreconnect).
Could it be that I've stumbled upon some sort of wierd bug in RecyclerView???
FWIW 1, I've read many posts trying to get RecyclerView to work in relation to background thread updates of the underlying data model. Some suggest running the notification on the MainUI thread. I believe that this does make a difference. Previously it never displayed any of the messages received from the MQTT topic, now it does - but not if the connection is lost and reconnected.
FWIW 2, I know that the notifyDataSetChanged should be used as a last resort as it is least efficient. I have tried some of the other notify methods. These other notification methods produce the exact same problem as notifyDataSetChanged. At this point I am trying to keep it simple, so as to get it to work. Next I can focus on efficiency.
Here are the relevant code snippets.
Firstly, the MQTT callback which is invoked when a message is received:
#Override
public void messageArrived(final String topic, MqttMessage message) throws Exception {
final String msg = new String(message.getPayload());
if ("glennm/test/temp".equals(topic)) {
Log.i("RCVD", "Temperature: " + msg); // This code is shown to illustrate a control that
if (textViewTemperature != null) { // always seems to be redisplayed when a message is received
textViewTemperature.setText(msg + "°"); // even if the connection is lost and reconnected
textViewTemperature.invalidate();
temperatureHistory.add(msg);
temperatureHistory.dump("TEMP");
} else {
Log.e("NULL", "textView temperature control is null");
}
} else if ("glennm/test/humid".equals(topic)) {
// Code that updates the humidity text view omitted for brevity (as it is basically the same as the temperature code above.
} else { /***** This is the problem area - other messages logged to the Recycler view ****/
String wrk = topic;
if (topic != null && topic.toLowerCase().startsWith("glennm/test")) {
wrk = topic.substring(12);
}
final String topicToShow = wrk;
textViewOtherTopic.setText(topicToShow);
textViewOtherMessage.setText(msg);
// mAdapter.add(topicToShow + ": " + msg);
// The notify that the add method calls ***MUST*** be run on the main UI thread.
// Failure to do so means that the call will sometimes be ignored and the
// Recycler view is not updated to show the new incoming value.
// https://stackoverflow.com/questions/36467236/notifydatasetchanged-recyclerview-is-it-an-asynchronous-call/36512407#36512407
// This seems to help, but we still seem to have the same behaviour if the MQTT connection resets.
runOnUiThread(new Runnable() {
// recyclerView.post(new Runnable() {
#Override
public void run() {
mAdapter.add(topicToShow + ": " + msg);
recyclerView.invalidate();
}
});
Log.i("RCVD", "Other Topic: " + topic + ", message: " + msg);
}
}
}
Secondly, the code that is invoked to add the message to the underlying data model (and notify the UI to repaint it).
public void add(String msg) {
Log.d("HISTORY", "Adding message: " + msg);
messageList.add(msg);
while (messageList.size() > MAX_HISTORY) {
messageList.remove(0);
}
Log.d("HISTORY", "Notifying data set changed");
_parent.runOnUiThread(new Runnable () {
#Override
public void run() {
notifyDataSetChanged();
}
});
Log.d("HISTORY", "Notifying data set changed - complete");
}
Finally, here are three screen snapshots which try to illustrate the problem. In the first, messages have been received from the MQTT topic and are displayed both in the journal and the "current message" field (textViewOtherTopic and textViewOtherMessage controls) located below the humidity.
What happens between the first and second screen shot is that the MQTT service lost its connection and auto-reconnected. After that, the received message is only displayed in the "current message" view and not the "message log" recycler view (despite being added to the model).
Only when the Recycler View is forced to be repainted by an external (manual) user action (e.g. scrolling) does the missing message(s) show up.
Here is an example of a command using the mosquitto mqtt client that is used to post a message to the app:
mosquitto_pub -h test.mosquitto.org -t "glennm/test/comment" -q 1 -m "It's a new day, but still cold! 3"
Following is the full code for the two classes (including many of the commented out attempts I've made)...
The Main activity:
package com.gtajb.tempmonitorapp;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.util.ArrayList;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private MqttAndroidClient mqttAndroidClient;
private String serverUri = "tcp://test.mosquitto.org:1883";
public static final String clientId = UUID.randomUUID().toString();
public final String subscriptionTopic = "glennm/test/#";
public final String publishTopic = "glennm/test/tome";
public final String publishMessage = "Hello from Android test client";
private TextView textViewTemperature;
private TextView textViewHumidity;
private TextView textViewOtherTopic;
private TextView textViewOtherMessage;
private MessageCallBack messageCallBack = new MessageCallBack();
private RecyclerView recyclerView;
private MessageHistory mAdapter;
private RecyclerView.LayoutManager layoutManager;
private ReadingsHistory temperatureHistory = new ReadingsHistory();
private ReadingsHistory humidityHistory = new ReadingsHistory();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("INFO", "Createing the mqtt client with client ID: " + clientId);
mqttAndroidClient = new MqttAndroidClient(getApplicationContext(), serverUri, clientId);
mqttAndroidClient.setCallback(new MqttClientCallback());
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setAutomaticReconnect(true);
mqttConnectOptions.setCleanSession(false);
textViewTemperature = findViewById(R.id.temperatureValueLabel);
textViewTemperature.setText("Temp goes here");
textViewHumidity = findViewById(R.id.humidtyValueLabel);
textViewHumidity.setText("Humid goes here");
textViewOtherTopic = findViewById(R.id.otherTopicValueLabel);
textViewOtherMessage = findViewById(R.id.otherMessageValueLabel);
textViewOtherTopic.setText(".");
textViewOtherMessage.setText(".");
recyclerView = findViewById(R.id.historyPanel);
mAdapter = new MessageHistory(new ArrayList<String>(), this, recyclerView);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(mAdapter);
mAdapter.add("A test message");
messageCallBack = new MessageCallBack();
try {
mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();
disconnectedBufferOptions.setBufferEnabled(true);
disconnectedBufferOptions.setBufferSize(100);
disconnectedBufferOptions.setPersistBuffer(false);
disconnectedBufferOptions.setDeleteOldestMessages(false);
mqttAndroidClient.setBufferOpts(disconnectedBufferOptions);
//subscribeToTopic();
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e("CONNECT", "Failed to connect to " + serverUri);
Log.e("CONNECT", exception.getMessage());
}
});
} catch (MqttException e) {
Log.e("CONNECT", "Exception connecting to " + serverUri);
Log.e("CONNECT", e.getMessage());
Log.e("CONNECT", Log.getStackTraceString(e));
e.printStackTrace();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
Log.i("CONN", "Closing MQTT connection");
mqttAndroidClient.close();
}
public class MqttClientCallback implements MqttCallbackExtended {
#Override
public void connectComplete(boolean reconnect, String serverURI) {
if (reconnect) {
Log.i("CONN", "Reconnected to: " + serverURI);
subscribeToTopic();
} else {
Log.i("CONN", "Connected to: " + serverURI);
subscribeToTopic();
}
}
#Override
public void connectionLost(Throwable cause) {
Log.i("CONN", "Connection lost");
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
Log.i("MSG", topic + " - " + new String(message.getPayload()));
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.i("PUB", "Delivery complete");
}
}
public void subscribeToTopic() {
try {
Log.i("TOPIC", "Subscribing to: " + subscriptionTopic);
// mqttAndroidClient.subscribe(subscriptionTopic, 0, null, new IMqttActionListener() {
// #Override
// public void onSuccess(IMqttToken asyncActionToken) {
// Log.i("SUBS", "Subscription to " + subscriptionTopic + " on " + serverUri + " successful");
// }
//
// #Override
// public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
// Log.i("SUBS", "Subscription to " + subscriptionTopic + " on " + serverUri + " FAILED");
// }
// });
mqttAndroidClient.subscribe(subscriptionTopic, 0, messageCallBack);
} catch (MqttException e) {
Log.e("SUBS", "Failed to subscribe to topic: " + subscriptionTopic + " on " + serverUri);
Log.e("SUBS", e.getMessage());
}
}
public void setTemperatureValue(String val) {
textViewTemperature.setText(val);
}
public void setHumidityValue(String val) {
textViewHumidity.setText(val);
}
public class MessageCallBack implements IMqttMessageListener , IMqttActionListener {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.i("MQTT", "Successful operation " + asyncActionToken.toString());
textViewTemperature.setText("Subscribed");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.i("MQTT", "Un Successful operation + " + asyncActionToken.toString());
textViewTemperature.setText("Not Subscribed");
}
#Override
public void messageArrived(final String topic, MqttMessage message) throws Exception {
final String msg = new String(message.getPayload());
if ("glennm/test/temp".equals(topic)) {
Log.i("RCVD", "Temperature: " + msg);
if (textViewTemperature != null) {
textViewTemperature.setText(msg + "°");
textViewTemperature.invalidate();
temperatureHistory.add(msg);
temperatureHistory.dump("TEMP");
} else {
Log.e("NULL", "textView temperature control is null");
}
} else if ("glennm/test/humid".equals(topic)) {
Log.i("RCVD", "Humidity: " + msg);
textViewHumidity.setText(msg + "%");
textViewHumidity.invalidate();
humidityHistory.add(msg);
humidityHistory.dump("HUMID");
} else {
String wrk = topic;
if (topic != null && topic.toLowerCase().startsWith("glennm/test")) {
wrk = topic.substring(12);
}
final String topicToShow = wrk;
textViewOtherTopic.setText(topicToShow);
textViewOtherMessage.setText(msg);
// mAdapter.add(topicToShow + ": " + msg);
// The notify that the add method calls ***MUST*** be run on the main UI thread.
// Failure to do so means that the call will sometimes be ignored and the
// Recycler view is not updated to show the new incoming value.
// https://stackoverflow.com/questions/36467236/notifydatasetchanged-recyclerview-is-it-an-asynchronous-call/36512407#36512407
// This seems to help, but we still seem to have the same behaviour.
runOnUiThread(new Runnable() {
// recyclerView.post(new Runnable() {
#Override
public void run() {
mAdapter.add(topicToShow + ": " + msg);
// recyclerView.invalidate();
}
});
Log.i("RCVD", "Other Topic: " + topic + ", message: " + msg);
// Context context = getApplicationContext();
// Toast msgPopup = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
// msgPopup.show();
}
}
}
}
The Message History class:
package com.gtajb.tempmonitorapp;
import android.app.Activity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MessageHistory extends RecyclerView.Adapter<MessageHistory.Callback> {
private ArrayList<String> messageList = new ArrayList();
public static final int MAX_HISTORY = 100;
private Activity _parent;
private RecyclerView rv;
public class Callback extends RecyclerView.ViewHolder {
TextView mTextView;
Callback(View itemView) {
super(itemView);
mTextView = itemView.findViewById(R.id.row_text);
}
}
public MessageHistory(ArrayList<String> messageList, Activity parent, RecyclerView rv) {
super();
this.messageList = messageList;
this._parent = parent;
this.rv = rv;
add("Test Message 1");
add("Test Message 2");
}
#NonNull
#Override
public Callback onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.history_row, parent, false);
return new Callback(v);
}
#Override
public void onBindViewHolder(#NonNull Callback holder, int position) {
Log.d("HISTORY", "Setting " + position + ": " + messageList.get(position));
holder.mTextView.setText(messageList.get(position));
}
#Override
public int getItemCount() {
Log.d("HISTORY", "Message Count: " + messageList.size());
return messageList.size();
}
/**
* Add a message to the message log.
* #param msg the message to add.
*/
public void add(String msg) {
Log.d("HISTORY", "Adding message: " + msg);
messageList.add(msg);
while (messageList.size() > MAX_HISTORY) {
messageList.remove(0);
}
// getItemCount();
Log.d("HISTORY", "Notifying data set changed");
_parent.runOnUiThread(new Runnable () {
#Override
public void run() {
notifyDataSetChanged();
}
});
//this.notifyDataSetChanged();
Log.d("HISTORY", "Notifying data set changed - complete");
// rv.invalidate();
// rv.refreshDrawableState();
// this.notifyItemInserted(messageList.size());
// final RecyclerView.Adapter adapter = this;
// _parent.runOnUiThread(new Runnable() {
// #Override
// public void run() {
// adapter.notifyDataSetChanged();
// }
// });
}
}
Related
I'm using the socialAuth plugin to connect a user to linkdin within my app. I have the connection set up correctly and retrieves data. However, I'm unsure how I can get my main activity to wait until the socialAuthListeners have fired and finished. I know a little about threading but I haven't used it with listeners before. Here's my code:
public class LinkdinAuth {
private static final String TAG = "TEST";
// SocialAuth Components
SocialAuthAdapter adapter;
ProgressDialog mDialog;
private Context context;
private boolean loggedIn = false;
private Bundle LinkdinData;
public LinkdinAuth(Context C){
this.context = C;
LinkdinData = new Bundle();
adapter = new SocialAuthAdapter(new ResponseListener());
}
public void adapterAuthorize(View v){
adapter.authorize(v.getContext(), Provider.LINKEDIN);
}
private final class ResponseListener implements DialogListener
{
public void onComplete(Bundle values) {
String providerName = values.getString(SocialAuthAdapter.PROVIDER);
Log.d("Main", "providername = " + providerName);
mDialog = new ProgressDialog(context);
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mDialog.setMessage("Loading...");
//Get profile information
adapter.getUserProfileAsync(new ProfileDataListener());
// get Job and Education information
mDialog.show();
adapter.getCareerAsync(new CareerListener());
loggedIn = true;
Log.d("Main", "LOGGED IN = " + loggedIn );
Toast.makeText(context, providerName + " connected", Toast.LENGTH_SHORT).show();
}
#Override
public void onBack() {
Log.d("Main", "Dialog Closed by pressing Back Key");
}
#Override
public void onCancel() {
Log.d("Main", "Cancelled");
}
#Override
public void onError(SocialAuthError e) {
Log.d("Main", "Error");
e.printStackTrace();
}
}
// To receive the profile response after authentication
private final class ProfileDataListener implements SocialAuthListener<Profile> {
#Override
public void onExecute(String provider, Profile t) {
Log.d("Sign Up", "Receiving Data");
mDialog.dismiss();
Profile profileMap = t;
LinkdinData.putString("Validated ID", profileMap.getValidatedId() );
LinkdinData.putString("First Name", profileMap.getFirstName());
LinkdinData.putString("Last Name", profileMap.getLastName());
LinkdinData.putString("Email", profileMap.getEmail());
LinkdinData.putString("Country", profileMap.getCountry());
LinkdinData.putString("Language", profileMap.getLanguage());
LinkdinData.putString("Location", profileMap.getLocation());
LinkdinData.putString("Profile Image URL", profileMap.getProfileImageURL());
}
#Override
public void onError(SocialAuthError arg0) {
// TODO Auto-generated method stub
}
}
private final class CareerListener implements SocialAuthListener<Career> {
#Override
public void onExecute(String provider, Career t) {
Log.d("Custom-UI", "Receiving Data");
mDialog.dismiss();
Career careerMap = t;
//get education
Log.d("Main", "Education:");
if(careerMap.getEducations() != null){
for(Education E: careerMap.getEducations()){
Log.d("Main", "School = " +E.getSchoolName() );
Log.d("Main", "Major = " + E.getFieldOfStudy() );
Log.d("Main", "Degree = " + E.getDegree() );
Log.d("Main", "Start Date = " + E.getStartDate() );
Log.d("Main", "End Date = " + E.getEndDate() );
}
}
Log.d("SignUp", "Career");
if(careerMap.getPositions() != null){
for(Position P: careerMap.getPositions()){
LinkdinData.putString("Company Name", P.getCompanyName() );
LinkdinData.putString("Job Title", P.getTitle() );
Log.d("Main", "Industry = " + P.getIndustry() );
Log.d("Main", "Start Date = " + P.getStartDate() );
Log.d("Main", "End Date = " + P.getEndDate() );
}
}
}
#Override
public void onError(SocialAuthError e) {
}
}
public boolean isLoggedIn(){
return loggedIn;
}
public Bundle getLinkdinData(){
return LinkdinData;
}
So, as you can see. I have 2 listeners that get data after authorization goes through. And my main activity makes creates an instance, calls the adapterAuthroizeMethod and then if the user logs in a flag is set. Then getLinkedData is called. However I would like it to wait until I know the listeners have fired before calling getlinkdinData. Here's what my Main Activity does after a button press:
L.adapterAuthorize(v);
loggedInWithLinkdin = L.isLoggedIn();
Bundle B = L.getLinkdinData();
Intent i = new Intent(getBaseContext(), UserRegistration.class);
i.putExtra("linkdin bundle", B);
//startActivity(i);
Any ideas?
thanks
Well, not a recommend solution but more of a hack.
Here is what you can do.
Wrap the aync call around this construct :
AtomicBoolean done = new AtomicBoolean(false);
Global ans; // the return value holder
try{
result = someAsyncCall(query, new Thread()); // this new thread is for listener callback
result.setResultListener(result -> {
// do something with result.
ans = result.getAns() ; // set global ans
done.set(true);
synchronized (done) {
done.notifyAll(); // notify the main thread which is waiting
}
});
}
catch (Exception e ) {
Log(e);
}
synchronized (done) {
while (done.get() == false) {
done.wait(); // wait here until the listener fires
}
}
return ans; // return global ans
I'm using aSmack. My app listens a chatroom and reacts to the messages but it never send a message. The app doesn't receive more messages if the chatroom remains in silence for a while and then a new message is sent. I researched and I think that XEP-0199 is the solution here. I see that #Flow (the current Smack maintainer) implemented it and the issue related was closed.
I think that I need to use PingProvider but I really don't know how to connect this class with the Connection.
How can I enable the XEP-0199? How can I use PingProvider?
Connection code:
smack = SmackAndroid.init(getActivity().getApplicationContext());
connection = new XMPPConnection(App.getServer());
connection.addConnectionListener(new ConnectionListener() {
private final static String SMACK = "SMACK";
#Override
public void reconnectionSuccessful() {
Log.i(SMACK , "reconnectionSuccessful");
}
#Override
public void reconnectionFailed(Exception e) {
Log.i(SMACK, "reconnectionFailed", e);
}
#Override
public void reconnectingIn(int seconds) {
Log.i(SMACK, "reconnectingIn " + seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
Log.i(SMACK, "connectionClosedOnError", e);
}
#Override
public void connectionClosed() {
Log.i(SMACK, "connectionClosed");
}
});
connection.connect();
connection.login(user, password);
I fix the problem implementing the ping response manually:
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
connection.sendPacket(new Pong((Ping) packet));
}
}, new PacketFilter() {
#Override
public boolean accept(Packet packet) {
return packet instanceof Ping;
}
});
To prevent user from disconnecting your session
PingManager pm = PingManager.getInstanceFor(MyApplication.connection) ;
pm.setPingInterval(5) ; // 5 sec
pm.pingMyServer() ;
pm.registerPingFailedListener(new PingFailedListener() {
#Override
public void pingFailed() {
Log.e(TAG , "pingFailed") ;
}
});
XEP 0199 is not a solution, Ping is used to check weather the server is up or not. actually you will send ping to the server.
Now as fas as your problem is concerned. Show me the message stanza that you are trying to send. and also check if the chat-room is public or private. you can not send a message to a private chat room.
Answer Updated:
Try using this code for detecting message recieve
PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
Network.connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Message message = (Message) packet;
if (message.getBody() != null) {
String fromName = StringUtils.parseBareAddress(message.getFrom());
Log.i("XMPPClient", "Got text [" + message.getBody() + "] from [" + fromName + "]");
//recieve.setText(message.getBody());
/*messages.add(fromName + ":");
messages.add(message.getBody());*/
// Add the incoming message to the list view
item = new RowItem(R.drawable.billing, message.getBody());
adapter = new CustomListViewAdapter(getBaseContext(),
R.layout.list_item, rowItems);
rowItems.add(item);
//listView.setAdapter(adapter);
}
}
}, filter);
I called PingManager.getInstanceFor method to enable XEP-0199 support.
I am working on Web socket communication with Autobahn library.
The problem I have is after connecting server, then message should be sent without connection again. But the message is sent with different connection that it connects to server every single time to send a message.
public class WebSocket_Connector extends Activity{
private static final String TAG = "ECHOCLIENT";
private static final String TAG1 = "My app";
public final WebSocketConnection mConnection = new WebSocketConnection();
private String tmpString = "";
public void connect(final String wsuri) {
Log.d(TAG, "Connecting to: " + wsuri);
try {
mConnection.connect(wsuri, new WebSocketHandler() {
#Override
public void onOpen() {
Log.d(TAG, "Status: Connected to " + wsuri );
Log.d(TAG, "Connection successful!\n");
mConnection.sendTextMessage(tmpString);
tmpString = "";
}
#Override
public void onTextMessage(String payload) {
Log.d(TAG, "Got echo: " + payload);
}
#Override
public void onClose(int code, String reason) {
Log.d(TAG, "Connection closed.");
}
});
} catch (WebSocketException e) {
Log.d(TAG, e.toString());
}
}
public void sendMessage(String message) {
if (mConnection.isConnected()) {
Log.d(TAG1, "Messeage is sent : " + message);
mConnection.sendTextMessage(message);
}
else {
tmpString = message;
connect("ws://192.168.3.100:7681");
}
}
}
This is the code I have, and...When you see "sendMessage" method, it always goes to 'else' not, if loop. Any suggestion 'experts' please..?
i don't know the package name you are dealing with for websocket. So first it has to be provided to get reliable answer to your question. But let say if it is something similar to :
https://code.google.com/p/weberknecht/source/browse/trunk/src/main/de/roderick/weberknecht/WebSocketConnection.java?r=2
note: i have not seen there isConnected() method but assume that it is added somewhere else.
you can see from source that onOpen() (line 88) is called before connected = true; on line (91). if this member var will be used as result of isConnected() then your code always will follow "else" part of the condition.
i would advice to dig into websocket api and its usage pattern further.
I am working on Autobahn Web socket communication. There is a carousel view in my application, and there are four images. When users click on of the images, then connects to server with websocket and send message. But the problem is that when I select the images, it connects to server correctly, but client(android device) connects to the websocket every single time when the message is sent.
Here is my code..
if (pos == 0) {
product_photo.setImageResource(R.drawable.myoffers_0);
product_photo.setOnClickListener(new ImageButton.OnClickListener(){
public void onClick(View v){
String id = "Product0";
Log.d(TAG, "Current product is : " + id);
A.sendMessage(id);
}
});
}
Websocket.class
public class WebSocket_Connector extends Activity{
private static final String TAG = "ECHOCLIENT";
private static final String TAG1 = "My app";
public final WebSocketConnection mConnection = new WebSocketConnection();
private String tmpString = "";
public void connect(final String wsuri) {
Log.d(TAG, "Connecting to: " + wsuri);
try {
mConnection.connect(wsuri, new WebSocketHandler() {
#Override
public void onOpen() {
Log.d(TAG, "Status: Connected to " + wsuri );
Log.d(TAG, "Connection successful!\n");
mConnection.sendTextMessage(tmpString);
tmpString = "";
}
#Override
public void onTextMessage(String payload) {
Log.d(TAG, "Got echo: " + payload);
}
#Override
public void onClose(int code, String reason) {
Log.d(TAG, "Connection closed.");
}
});
} catch (WebSocketException e) {
Log.d(TAG, e.toString());
}
}
public void sendMessage(String message) {
if (mConnection.isConnected()) {
Log.d(TAG1, "Messeage is sent : " + message);
mConnection.sendTextMessage(message);
}
else {
tmpString = message;
connect("ws://xxx.xxx.x.xxx:xxxx");
}
}
}
It doens't go to 'if (mConnection.isConnected())' here..always goes to else.
EDIT:
I want to implement a quiz-application on Android and Browser via Web Interface.
I'm looking for a way to communicate between the server and the clients. I tried socket.io but couldn't get it working with android.
I'm using a node.js server hosted on nodester (nodester.com).
I tried some libs but couldn't get it working.
I'm now working with einaros/ws from https://github.com/einaros/ws
The server code is:
var clients = [],
numClients = 0;
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({port: 20083});
wss.on('connection', function(ws) {
ws.on('message', function(message) {
console.log(wss.clients);
console.log('received: %s', message);
incomingMessage(message, ws)
});
/*
ws.on('eigenesEvent', function(message) {
console.log('eigenes Event ausgelöst: ' + message);
});
*/
});
function incomingMessage(msg, ws) {
//console.log(wss.clients);
var obj = JSON.parse(msg);
if(obj.type == "connect") {
for(var i=0;i<clients.length;i++) {
if(clients[i] == obj.id) {
ws.send(JSON.stringify({
to: obj.id,
message: "name vergeben"
}));
return;
}
}
clients[numClients] = obj.id;
numClients++;
for(var i=0;i<clients.length;i++) {
console.log("Client" + i + ": " + clients[i]);
}
ws.send(JSON.stringify({
to: "all",
message: obj.id + " connected"
}));
}
if(obj.type == "disconnect") {
for(var i=0;i<clients.length;i++) {
if(clients[i] == obj.id) {
clients.splice(i, 1);
numClients--;
for(var i=0;i<clients.length;i++) {
console.log("Client" + i + ": " + clients[i]);
}
}
}
ws.send(JSON.stringify({
to: "all",
message: obj.id + " disconnected"
}));
return;
}
if(obj.type == "answer") {
if("id" in obj) {
if(obj.answer == "a") {
ws.send(JSON.stringify({
to: obj.id,
message: "a is correct"
}));
} else {
ws.send(JSON.stringify({
to: obj.id,
message: "answer is incorrect"
}));
}
}
}
if(obj.type == "something") {
if("id" in obj) {
ws.send(JSON.stringify({
to: obj.id,
message: "received: " + obj.message
}));
}
}
}
From a HTML-Site i can connect to the server via:
connect = function() {
var host = "ws://einaros.nodester.com";
try{
socket = new WebSocket(host);
console.log('WebSocket - status ' + socket.readyState);
socket.onopen = function(msg) {
console.log("Welcome - status " + this.readyState);
socket.send(JSON.stringify({
id: model.getClientName(),
type: "connect"
}));
model.setConnectionStatus(true);
};
socket.onmessage = function(msg) {
console.log("onmessage - msg: " + msg.data);
checkMessage(msg.data);
};
socket.onclose = function(msg) {
console.log("Disconnected - status " + this.readyState);
model.setConnectionStatus(false);
};
}
catch(ex){
console.log(ex);
}
},
On the Android-Client side i'm using AutobahnAndroid from: http://autobahn.ws/android
The client code for android is:
package ps.mediengestaltung;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import de.tavendo.autobahn.WebSocketConnection;
import de.tavendo.autobahn.WebSocketException;
import de.tavendo.autobahn.WebSocketHandler;
public class MainActivity extends Activity {
public final WebSocketConnection mConnection = new WebSocketConnection();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final String wsuri = "ws://einaros.nodester.com";
try {
mConnection.connect(wsuri, new WebSocketHandler() {
#Override
public void onOpen() {
Log.d("TAG", "Status: Connected to " + wsuri);
mConnection.sendTextMessage("Hello Server!");
}
#Override
public void onTextMessage(String payload) {
Log.d("TAG", "Got echo: " + payload);
}
#Override
public void onClose(int code, String reason) {
Log.d("TAG", "Connection lost.");
}
});
} catch (WebSocketException e) {
Log.d("TAG", e.toString());
}
}
}
In LogCat i get:
08-01 08:48:13.017: D/TAG(704): Status: Connected to ws://einaros.nodester.com
08-01 08:48:13.167: D/TAG(704): Connection lost.
What am i doing wrong? Any hints?
The reason could be: Weberknecht only implements the (outdated) Hixie-76 version of WebSocket.
You might try AutobahnAndroid, which implements the final RFC6455 version of WebSocket.
Another things: the WebSocket server you are using is no longer maintained (as far as I know). It also only implements Hixie-76 - which is no longer supported by Chrome/Firefox.
Try one of these:
https://github.com/einaros/ws
https://github.com/Worlize/WebSocket-Node
Disclaimer: I am the author of Autobahn and work for Tavendo.
You are asking your phone to connect to the localhost. You aren't running node on the phone right? :)
URI url = new URI("ws://127.0.0.1:8080/test");
This should instead be pointing to your nodester address/port.