I'm developing an IM app using the Quickblox API and I'm currently developing the Sign Up and Login features. Well, my problem is that everytime I try to login to the QBChatService
by calling QBChatService.login()
I'm getting this error from Log Cat:
E/Event: Could not dispatch event: class regmoraes.jusstalk.session.SessionEvents to subscribing class class regmoraes.jusstalk.session.LoginPresenter
E/Event: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I'm using MVP pattern and EventBus to send events from Models ( I called them Managers) to Presenters.
Here are my classes (interaction order between them at the end):
LoginActivity:
public class LoginActivity extends Activity implements LoginView, View.OnClickListener{
private AutoCompleteTextView mUserField;
private EditText mPasswordField;
private TextView mSignUpTextView;
private Button mLoginButton;
private ProgressBar mProgressBar;
private LoginUIPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mProgressBar = (ProgressBar) findViewById(R.id.login_progress);
mUserField = (AutoCompleteTextView) findViewById(R.id.email);
mPasswordField = (EditText) findViewById(R.id.password);
mLoginButton = (Button) findViewById(R.id.button_sign_in);
mLoginButton.setOnClickListener(this);
mSignUpTextView = (TextView) findViewById(R.id.textView_sign_up);
mSignUpTextView.setOnClickListener(this);
this.loginPresenter = new LoginPresenter(this);
}
@Override
public void showMessageDialog(List errors) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setMessage("chat login errors: " + errors).create().show();
}
@Override
public void startNewActivity(Class activity) {
Intent mIntent = new Intent(this, activity);
startActivity(mIntent);
finish();
}
@Override
public void showProgress(boolean show) {
if(show){
mProgressBar.setVisibility(View.VISIBLE);
mUserField.setVisibility(View.INVISIBLE);
mPasswordField.setVisibility(View.INVISIBLE);
mLoginButton.setVisibility(View.INVISIBLE);
}else{
mProgressBar.setVisibility(View.GONE);
mUserField.setVisibility(View.VISIBLE);
mPasswordField.setVisibility(View.VISIBLE);
}
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.button_sign_in:
loginPresenter.login(mUserField.getText().toString(),
mPasswordField.getText().toString());
break;
case R.id.textView_sign_up:
startNewActivity(SignUpActivity.class);
}
}
@Override
public void showToast(String message, int length) {
Toast.makeText(this, message,length).show();
}
}
LoginPresenter:
public class LoginPresenter implements LoginUIPresenter{
LoginView loginView;
SessionManager sessionManager;
public LoginPresenter(LoginView loginView) {
EventBus.getDefault().register(this);
/*...*/
}
@Override
public void login(String username, String password) {
loginView.showProgress(true);
sessionManager.login(username,password);
}
public void onEvent(SessionEvents sessionEvents){
switch (sessionEvents.getEvent()){
case SessionEvents.LOGIN_SUCCESSFULL:
sessionManager.loginToChatService();
break;
case SessionEvents.LOGIN_FAILED:
loginView.showProgress(false);
loginView.showToast("Problem when connecting", Toast.LENGTH_SHORT);
break;
case SessionEvents.CHAT_SERVICE_CONNECTED:
loginView.startNewActivity(MainActivity.class);
break;
default:break;
}
}
}
SessionManager:
public class SessionManagement implements SessionManager,ConnectionListener {
private String TAG = SessionManagement.class.getName();
private SharedPreferences mSharedPreferences;
private Context mContext;
private SessionEvents sessionEvents;
private QBUser currentUser;
public QBChatService qbChatService;
public SessionManagement(Context context) {
this.mContext = context;
this.mSharedPreferences = (mContext)
.getSharedPreferences("regmoraes.testapp", Context.MODE_PRIVATE);
initChatServiceIfNeeded();
this.sessionEvents = new SessionEvents();
this.qbChatService = QBChatService.getInstance();
}
/* .... */
private void initChatServiceIfNeeded() {
if (!QBChatService.isInitialized()) {
QBChatService.setDebugEnabled(true);
QBChatService.init(mContext);
QBChatService.getInstance().addConnectionListener(this);
}
}
@Override
public void login(final String username, final String password) {
final QBUser qbUser = new QBUser(username,password);
QBAuth.createSession(qbUser, new QBEntityCallbackImpl<QBSession>() {
@Override
public void onSuccess(QBSession qbSession, Bundle params) {
currentUser = qbUser;
currentUser.setId(qbSession.getId());
saveCredentials(currentUser.getLogin(), currentUser.getPassword());
sessionEvents.setEvent(SessionEvents.LOGIN_SUCCESSFULL);
EventBus.getDefault().post(sessionEvents);
}
@Override
public void onError(List<String> errors) {
sessionEvents.setEvent(SessionEvents.LOGIN_FAILED);
EventBus.getDefault().post(sessionEvents);
}
});
}
@Override
public void loginToChatService(){
qbChatService.login(currentUser, new QBEntityCallbackImpl() {
@Override
public void onSuccess() {
try {
qbChatService.startAutoSendPresence(30);
sessionEvents.setEvent(SessionEvents.CHAT_SERVICE_CONNECTED);
EventBus.getDefault().post(sessionEvents);
} catch (SmackException.NotLoggedInException e) {
e.printStackTrace();
}
}
@Override
public void onError(List errors) {
sessionEvents.setEvent(SessionEvents.LOGIN_FAILED);
EventBus.getDefault().post(sessionEvents);
}
});
}
}
This is how my classes interacts when user want to login:
LoginActivity
LoginActivity
calls LoginPresenter.signIn()
LoginPresenter
calls SessionManager.login()
SessionManager
send event LOGIN_SUCESSFULL to LoginPresenter
LoginPresenter
calls SessionManager.loginToChatService()
I know that the error is because of a Background Thread calling a UI Thread method, but the login
method works well, only the loginToChat
method that throws this error.
How could I fix this?
Thanks
As @logcat said:
It seems like the onEvent method is triggered by a background thread, unlike Android UI events which are already called on the UI thread for you.
And he was right, the onEvent method was triggered by the SessionManager.loginToChat() method, so to fix this, I had to make the onEvent be triggered on UI thread.
After searching the EvenBus Doc I saw this at the Delivery Threads and Threadmodes section:
EventBus can handle threading for you: events can be posted in threads different from the posting thread. (...)
In EventBus, you may define the thread that will call the event handling method onEvent by using a ThreadMode (...)
MainThread: Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is the main thread, event handler methods will be called directly. Event handlers using this mode must return quickly to avoid blocking the main thread. Example:
// Called in Android UI's main thread public void onEventMainThread(MessageEvent event) { textField.setText(event.message); }
So, what I had to do was to change the onEvent
method of LoginPresenter
to onEventMainThread
! In that way, the LoginPresenter can handle the received event on UI thread.