Introduction
What I want to accomplish [sounds] simple. I want to prompt a user with a login dialog until the user successfully authenticates.
What I planned to do is use an AsyncTask for the data handling and web requests, but this has turned into a nightmare quickly; most likely due to my lack of experience in Android.
However, I know this can be done, as I've seen it before in other apps.
What I want to accomplish
The question is how? I know what I want to do:
1. Initially prompt a user to login.
2. Send authentication data.
3. If successful, continue the application.
4. If unsuccessful, reprompt the user until success.
What I have so far
What I have so far is my AsyncTask (LoginTask) which will handle the web requests and login data:
public class LoginTask extends AsyncTask<String, Void, App.STATUS>
{
private boolean m_proceed = false;
private String m_username, m_key;
#Override
protected void onPreExecute()
{
// Check if there is a dialog on screen. //
m_proceed = !App.DIALOG_ONSCREEN;
}
#Override
protected App.STATUS doInBackground(String ... p_args)
{
// Do not do this if a dialog is on screen. //
if(!m_proceed)
return App.STATUS.DENIED;
// Make a web request. //
try
{
URL t_url = new URL("https://mysite.com/api/login");
HttpsURLConnection t_con = (HttpsURLConnection)t_url.openConnection();
t_con.setRequestMethod("POST");
t_con.setRequestProperty("User-Agent", "Mozilla/5.0");
t_con.setDoOutput(true);
DataOutputStream t_wr = new DataOutputStream(t_con.getOutputStream());
t_wr.writeBytes("username="+p_args[0]+"&password="+p_args[1]);
t_wr.flush();
t_wr.close();
t_con.connect();
BufferedReader t_in = new BufferedReader(new InputStreamReader(t_con.getInputStream()));
String t_input_line;
StringBuffer t_response = new StringBuffer();
while((t_input_line = t_in.readLine()) != null)
{
t_response.append(t_input_line);
}
t_in.close();
// If denied, return failed. If accepted, set the username and key. //
if(t_response.toString().equals("DENIED"))
return App.STATUS.FAILED;
else
{
m_key = t_response.toString();
m_username = p_args[0];
}
return App.STATUS.ACCEPTED;
}
catch(Exception err)
{
System.err.println(err.getMessage());
}
return App.STATUS.FAILED;
}
#Override
protected void onPostExecute(App.STATUS p_status)
{
// Authenticate the user if the username and key are valid. //
if(p_status == App.STATUS.ACCEPTED)
App.acceptCredentials(m_username, m_key);
// The dialog is no longer on the screen. //
App.DIALOG_ONSCREEN = false;
}
}
And the main activity (HomeActivity) which will prompt the user if they are not authenticated, and will show content if they are:
public class HomeActivity extends Activity
{
#Override
public void onCreate(Bundle p_data)
{
// Basic crap... //
super.onCreate(p_data);
setContentView(R.layout.activity_home);
// Are we authenticated? //
if(!App.isAuthenticated())
{
// Create the dialog. //
LayoutInflater t_infl = LayoutInflater.from(this);
View t_login_view = t_infl.inflate(R.layout.login_dialog, null);
AlertDialog.Builder t_builder = new AlertDialog.Builder(this);
t_builder.setTitle("Login").setView(t_login_view).setPositiveButton("Login", new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which)
{
// What should go here? //
}
});
t_builder.create();
}
// How do I keep checking if the user is not authenticated, and keep showing the dialog as such? //
}
}
What I need help with
My main question is how do I design my program in such a way that I can easily keep displaying the login dialog until the user has successfully authenticated? I thought of using a while loop, but then it would keep displaying dialogs and hamper performance. It's pretty tricky when I have asynchronous and synchronous tasks working in tandem.
I'm not looking for straight code, but general insight would be much appreciated.
Thank you for taking your time to read this and thank you for helping!
The solution
HomeActivity.java
private void promptLogin()
{
final Context t_main_context = this;
// Create the dialog. //
LayoutInflater t_infl = LayoutInflater.from(this);
final View t_login_view = t_infl.inflate(R.layout.login_dialog, null);
final AlertDialog t_dialog = new AlertDialog.Builder(this)
.setTitle("Login")
.setCancelable(false)
.setView(t_login_view)
.setPositiveButton("Login", null)
.create();
t_dialog.show();
t_dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View t_view)
{
String t_username = ((EditText)t_login_view.findViewById(R.id.in_username)).getText().toString(),
t_password = ((EditText)t_login_view.findViewById(R.id.in_password)).getText().toString();
try
{
new LoginTask(t_main_context, t_dialog).execute(t_username, t_password);
}
catch(Exception err)
{
err.printStackTrace();
}
}
});
}
#Override
public void onCreate(Bundle p_data)
{
// Basic crap... //
super.onCreate(p_data);
setContentView(R.layout.activity_home);
// Are we authenticated? //
if(!App.isAuthenticated())
promptLogin();
}
LoginTask.java
private String m_username, m_key;
private Context m_context;
private AlertDialog m_dialog;
private ProgressDialog m_loading;
public LoginTask(Context p_context, AlertDialog p_dialog)
{
m_context = p_context;
m_dialog = p_dialog;
m_loading = ProgressDialog.show(m_context, "", "Logging in...", true);
}
#Override
protected App.STATUS doInBackground(String ... p_args)
{
// Make a web request. //
try
{
URL t_url = new URL("https://mysite.com/api/login");
HttpsURLConnection t_con = (HttpsURLConnection)t_url.openConnection();
t_con.setRequestMethod("POST");
t_con.setRequestProperty("User-Agent", "Mozilla/5.0");
t_con.setDoOutput(true);
DataOutputStream t_wr = new DataOutputStream(t_con.getOutputStream());
t_wr.writeBytes("username="+p_args[0]+"&password="+p_args[1]);
t_wr.flush();
t_wr.close();
t_con.connect();
BufferedReader t_in = new BufferedReader(new InputStreamReader(t_con.getInputStream()));
String t_input_line;
StringBuffer t_response = new StringBuffer();
while((t_input_line = t_in.readLine()) != null)
{
t_response.append(t_input_line);
}
t_in.close();
// If denied, return failed. If accepted, set the username and key. //
if(t_response.toString().equals("DENIED"))
return App.STATUS.FAILED;
else
{
m_key = t_response.toString();
m_username = p_args[0];
}
return App.STATUS.ACCEPTED;
}
catch(Exception err)
{
System.err.println(err.getMessage());
}
return App.STATUS.FAILED;
}
#Override
protected void onPostExecute(App.STATUS p_status)
{
m_loading.dismiss();
// Authenticate the user if the username and key are valid. //
if(p_status == App.STATUS.ACCEPTED)
{
App.acceptCredentials(m_username, m_key);
m_dialog.dismiss();
}
else
Toast.makeText(m_context, "Login failed", Toast.LENGTH_SHORT).show();
}
So what I did in promptLogin() in HomeActivity.java was that I overrode the button onClickListener, so that the dialog would not close unless closed by t_dialog.dismiss(). I then sent the web request to LoginTask and passed the dialog as a parameter, so that the dialog would only close until I dismissed the dialog.
I only dismiss the dialog when the credentials are accepted, as you can see in onPostExecute().
In this way, the dialog stays on screen until the user successfully logs in, which is the behavior I was looking for.
Thanks everyone for helping!
1. Initially prompt a user to login.
keep prompting the dialog here, with setCancelable(false), so the user will not cancel the login process. Then create a View.OnclickListner on the button that the user have to click on in order to send data to your server. Let's say Login button.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Delete entry")
.setMessage("Are you sure you want to delete this entry?")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// send data here with AsyncTask
}
})
.setIcon(R.drawable.ic_my_icon);
builder.setCancelable(false);
builder.show();
2. Send authentication data.
Use your AsyncTask here, do the sending task in doInBackgroud() method, and return something onPostExecute() to know if authentication succeeded or not. if success, dismiss the dialog, if not, you keep the dialog and wait for the user to click again the Login button.
protected void onPostExecute(Boolean result) {
if(result) {
// he is now authenticated, dismiss dialog and continue in your app
dialog.dismiss();
} else {
// nothing to do, until he succeed
}
}
3. If successful, continue the application.
Dismiss the dialog here buy using the dismiss() method.
4. If unsuccessful, reprompt the user until success.
Don't do anything, let the dialog shown, until the authentication process succeeds. You can also show something to the user (a toast, an image etc) to tell him that he hasn't logged in yet.
Hope it's clear for you.
You have to create one activity which handles your login, and another which is your main activity. If the login fails, nothing happens, if it succeeds you start the second activity. No need for your complicated setup.
You can also have a look at the Volley library, makes http connections pretty easy.
Related
From my main activity, I need to call an inner class and in a method within the class, I need to show AlertDialog. After dismissing it, when the OK button is pressed, forward to Google Play for purchase.
Things work perfectly for most of the times, but for few users it is crashing on builder.show() and I can see "android.view.WindowManager$BadTokenException: Unable to add window" from crash log. Please suggest.
My code is pretty much like this:
public class classname1 extends Activity{
public void onCreate(Bundle savedInstanceState) {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.<view>);
//call the <className1> class to execute
}
private class classNamename2 extends AsyncTask<String, Void, String>{
protected String doInBackground(String... params) {}
protected void onPostExecute(String result){
if(page.contains("error"))
{
AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
builder.setCancelable(true);
builder.setMessage("");
builder.setInverseBackgroundForced(true);
builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton){
dialog.dismiss();
if(!<condition>)
{
try
{
String pl = "";
mHelper.<flow>(<class>.this, SKU, RC_REQUEST,
<listener>, pl);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
});
builder.show();
}
}
}
}
I have also seen the error in another alert where I am not forwarding to any other activity. It's simple like this:
AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
builder.setCancelable(true);
//if successful
builder.setMessage(" ");
builder.setInverseBackgroundForced(true);
builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton){
// dialog.dismiss();
}
});
builder.show();
}
android.view.WindowManager$BadTokenException: Unable to add window"
Problem :
This exception occurs when the app is trying to notify the user from
the background thread (AsyncTask) by opening a Dialog.
If you are trying to modify the UI from background thread (usually
from onPostExecute() of AsyncTask) and if the activity enters
finishing stage i.e.) explicitly calling finish(), user pressing home
or back button or activity clean up made by Android then you get this
error.
Reason :
The reason for this exception is that, as the exception message says,
the activity has finished but you are trying to display a dialog with
a context of the finished activity. Since there is no window for the
dialog to display the android runtime throws this exception.
Solution:
Use isFinishing() method which is called by Android to check whether
this activity is in the process of finishing: be it explicit finish()
call or activity clean up made by Android. By using this method it is
very easy to avoid opening dialog from background thread when activity
is finishing.
Also maintain a weak reference for the activity (and not a strong
reference so that activity can be destroyed once not needed) and check
if the activity is not finishing before performing any UI using this
activity reference (i.e. showing a dialog).
eg.
private class chkSubscription extends AsyncTask<String, Void, String>{
private final WeakReference<login> loginActivityWeakRef;
public chkSubscription (login loginActivity) {
super();
this.loginActivityWeakRef= new WeakReference<login >(loginActivity)
}
protected String doInBackground(String... params) {
//web service call
}
protected void onPostExecute(String result) {
if(page.contains("error")) //when not subscribed
{
if (loginActivityWeakRef.get() != null && !loginActivityWeakRef.get().isFinishing()) {
AlertDialog.Builder builder = new AlertDialog.Builder(login.this);
builder.setCancelable(true);
builder.setMessage(sucObject);
builder.setInverseBackgroundForced(true);
builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton){
dialog.dismiss();
}
});
builder.show();
}
}
}
}
Update :
Window Tokens:
As its name implies, a window token is a special type of Binder token
that the window manager uses to uniquely identify a window in the
system. Window tokens are important for security because they make it
impossible for malicious applications to draw on top of the windows of
other applications. The window manager protects against this by
requiring applications to pass their application's window token as
part of each request to add or remove a window. If the tokens don't
match, the window manager rejects the request and throws a
BadTokenException. Without window tokens, this necessary
identification step wouldn't be possible and the window manager
wouldn't be able to protect itself from malicious applications.
A real-world scenario:
When an application starts up for the first time,
the ActivityManagerService creates a special kind of window token
called an application window token, which uniquely identifies the
application's top-level container window. The activity manager gives
this token to both the application and the window manager, and the
application sends the token to the window manager each time it wants
to add a new window to the screen. This ensures secure interaction
between the application and the window manager (by making it
impossible to add windows on top of other applications), and also
makes it easy for the activity manager to make direct requests to the
window manager.
I had dialog showing function:
void showDialog(){
new AlertDialog.Builder(MyActivity.this)
...
.show();
}
I was getting this error and i just had to check isFinishing() before calling this dialog showing function.
if(!isFinishing())
showDialog();
The possible reason is the context of the alert dialog. You may be finished that activity so its trying to open in that context but which is already closed.
Try changing the context of that dialog to you first activity beacause it won't be finished till the end.
e.g
rather than this.
AlertDialog alertDialog = new AlertDialog.Builder(this).create();
try to use
AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();
first you cannot extend AsyncTask without override doInBackground
second try to create AlterDailog from the builder then call show().
private boolean visible = false;
class chkSubscription extends AsyncTask<String, Void, String>
{
protected void onPostExecute(String result)
{
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setCancelable(true);
builder.setMessage(sucObject);
builder.setInverseBackgroundForced(true);
builder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton)
{
dialog.dismiss();
}
});
AlertDialog myAlertDialog = builder.create();
if(visible) myAlertDialog.show();
}
#Override
protected String doInBackground(String... arg0)
{
// TODO Auto-generated method stub
return null;
}
}
#Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
visible = true;
}
#Override
protected void onStop()
{
visible = false;
super.onStop();
}
I am creating Dialog in onCreate and using it with show and hide. For me the root cause was not dismissing onBackPressed, which was finishing the Home activity.
#Override
public void onBackPressed() {
new AlertDialog.Builder(this)
.setTitle("Really Exit?")
.setMessage("Are you sure you want to exit?")
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog,
int which) {
Home.this.finish();
return;
}
}).create().show();
I was finishing the Home Activity onBackPressed without closing / dismissing my dialogs.
When I dismissed my dialogs the crash disappeared.
new AlertDialog.Builder(this)
.setTitle("Really Exit?")
.setMessage("Are you sure you want to exit?")
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog,
int which) {
networkErrorDialog.dismiss() ;
homeLocationErrorDialog.dismiss() ;
currentLocationErrorDialog.dismiss() ;
Home.this.finish();
return;
}
}).create().show();
I try this it solved.
AlertDialog.Builder builder = new AlertDialog.Builder(
this);
builder.setCancelable(true);
builder.setTitle("Opss!!");
builder.setMessage("You Don't have anough coins to withdraw. ");
builder.setMessage("Please read the Withdraw rules.");
builder.setInverseBackgroundForced(true);
builder.setPositiveButton("OK",
(dialog, which) -> dialog.dismiss());
builder.create().show();
In my case I refactored code and put the creation of the Dialog in a separate class. I only handed over the clicked View because a View contains a context object already. This led to the same error message although all ran on the MainThread.
I then switched to handing over the Activity as well and used its context in the dialog creation
-> Everything works now.
fun showDialogToDeletePhoto(baseActivity: BaseActivity, clickedParent: View, deletePhotoClickedListener: DeletePhotoClickedListener) {
val dialog = AlertDialog.Builder(baseActivity) // <-- here
.setTitle(baseActivity.getString(R.string.alert_delete_picture_dialog_title))
...
}
I , can't format the code snippet properly, sorry :(
I got this error, but mine was coming from the Toasts, not a Dialog.
I have Activity and Fragments in my layout. Code for the Toast was in the Activity class. Fragments gets loaded before the Activity.
I think the Toast code was hit before the Context/Activity finished initializing. I think it was the getApplicationContext() in the command Toast.makeText(getApplicationContext(), "onMenutItemActionCollapse called", Toast.LENGTH_SHORT).show();
Try this :
public class <class> extends Activity{
private AlertDialog.Builder builder;
public void onCreate(Bundle savedInstanceState) {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.<view>);
builder = new AlertDialog.Builder(<class>.this);
builder.setCancelable(true);
builder.setMessage(<message>);
builder.setInverseBackgroundForced(true);
//call the <className> class to execute
}
private class <className> extends AsyncTask<String, Void, String>{
protected String doInBackground(String... params) {
}
protected void onPostExecute(String result){
if(page.contains("error")) //when not subscribed
{
if(builder!=null){
builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton){
dialog.dismiss();
if(!<condition>)
{
try
{
String pl = "";
mHelper.<flow>(<class>.this, SKU, RC_REQUEST,
<listener>, pl);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
});
builder.show();
}
}
}
}
with this globals variables idea,
I saved MainActivity instance in onCreate();
Android global variable
public class ApplicationController extends Application {
public static MainActivity this_MainActivity;
}
and Open dialog like this. it worked.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Global Var
globals = (ApplicationController) this.getApplication();
globals.this_MainActivity = this;
}
and in a thread, I open dialog like this.
AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
Open MainActivity
Start a thread.
Open dialog from thread -> work.
Click "Back button" ( onCreate will be called and remove first MainActivity)
New MainActivity will start. ( and save it's instance to globals )
Open dialog from first thread --> it will open and work.
: )
In the onSuccess block after signing in, I'm initializing my realm configurations then creating a User object.
if (SyncUser.currentUser() != null) {
RealmConfig.form = new SyncConfiguration.Builder(SyncUser.currentUser(), getString(R.string.form)).name("form.realm").build();
RealmConfig.user = new SyncConfiguration.Builder(SyncUser.currentUser(), getString(R.string.user)).name("user.realm").build();
RealmConfig.userObjects = new SyncConfiguration.Builder(SyncUser.currentUser(), getString(R.string.user_objects))
.name("userObjects.realm")
.waitForInitialRemoteData()
.build();
}
Realm.getInstanceAsync(RealmConfig.userObjects, new Realm.Callback() {
#Override
public void onSuccess(Realm realm) {
User user = realm.where(User.class).equalTo("id",
SyncUser.currentUser().getIdentity()).findFirst();
if (user == null) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
User user = realm.createObject(User.class, UUID.randomUUID().toString());
user.setEmail(mEmailView.getText().toString().trim());
}
});
// go to CreateUserName
} else {
// go to Dashboard
Intent intent = new Intent(getBaseContext(), MainActivity.class);
intent.putExtra("isAdmin", isAdmin);
getBaseContext().startActivity(intent);
}
}
});
I cannot open the instance I want to create the user because of this crash...additionally I am getting an error saying "RealmException: Exception happens when initializing Realm in the background thread."
WOW. Ok so the whole time I was hitting "Enter" on my laptop's keyboard and it was calling the login function twice in rapid succession and that must've been screwing it up. I clicked the "Sign In" button on the actual screen and it worked without issues.
Sometimes there's just nobody to blame but yourself...
I am trying to load progressBar as dialog while the client app send some data to server and dismiss the bar when data is received back from server.The below code has implemented this model.. with and interface being implemented inside an activity.
private void setup(){
ConnectionHandler connectionHandler = new ConnectionHandler() {
#Override
public void onSendData(){
runOnUiThread(new Runnable() {
#Override
public void run() {
progressDialog = new ProgressDialog(SignupActivity.this);
Log.d("progress bar", "getData: true" );
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle("Loading..");
progressDialog.setMessage("Please wait");
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.show();
}
});
}
#Override
public void onReceiveData(List<Model> model) {
//do nothing for now
LoginMessage message = (LoginMessage)model.get(0);
if(message.getUserCode() != 0){
//registration successfull prompt to verify code;
Log.d("LoginMessage", "onListUpdate: " + message.toJson());
Intent intent = new Intent(getBaseContext(), VerificationActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(getString(R.string.login_message),message.toJson());
intent.putExtra("email", credentials.getEmail());
progressDialog.dismiss();
startActivity(intent);
}else{
//something went wrong;
}
}
#Override
public Class<? extends Model> getType() {
return LoginMessage.class;
}
};
connectivity = new Connectivity(this, Properties.LOCALHOST + Properties.REGISTRATION_PHP);
connectivity.setConnectionHandler(connectionHandler);
}
The setup method is called when activity is created and connectivity is responsible for calling the onSendData() and onReceiveData().Everything was working fine untill i decided to have progressBar to make client wait for the data to load. Here the error is thrown saying android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application. I followed the solution shown in the other similar problem but didn't worked for me.
Some advised to use
progressBar = new ProgressBar(Activity.this) instead of
progressBar = new ProgressBar(getApplicationContext()) or new ProgressBar(getBaseContext)
but none of them worked for me.What is this error actually is saying to me.
Why is this error thrown?? please help
this is my first post so please bear with me.
I'm a high school developer and recently released an Android app on the play store. I'm using Crashlytics to capture exceptions, and for this some reason it throws this error.
Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy#1989547c is not valid; is your activity running?
It was reported specifically on the LG D855, Nexus 5, and the Huawei PLK AL10 occurring on versions 5.0, 5.0.2, 5.1.1 and 6.0.1. I've looked online, and have found that this occurs when an activity does not exist. This error occurs on the initial startup of the app.
The following is the code I use for an Alert Dialog which simply asks if the user wants to see a tutorial (y/n)
public void showTutorialDialog() {
AlertDialog tutorialDialog = new AlertDialog.Builder(this)
.setTitle(R.string.tutorial_question_title)
.setCancelable(false)
.setMessage(R.string.tutorial_question)
.setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// Take to tutorial
// Assume isn't backer for now..
finish();
Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
i.putExtra("from", "StartupActivity");
startActivity(i);
}
})
.setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// No tutorial, ask if they are a backer
showBackerDialog();
}
}).show();
Upon the initial start of the app, I load the users purchase details using IabHelper in a separate class. This class, called PurchaseRetriever, retrieves the content of users purchases asynchronously and stores it in an ArrayList. This is how my code works.
if (mManager.isUserFirstTime()) {
// Initialize purchase retriever.
// The rest will be done when the observer reports that purchase data has been retrieved.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new FirstStartupObserver(this));
StartupManager.FIRST = true;
loadImageContent();
It runs using the Observer pattern, so when the purchase details are queried it calls the update() method in FirstStartupObserver, which then by a reference to StartupActivity, calls startupActivity.showTutorialDialog();where the error occurs.
I've tested it on multiple devices I and my friends own personally (Nexus 6, Nexus 5, Nexus 7 tablet, Samsung Galaxy Tab, various devices on Samsung Remote Lab) yet it works fine on my end...
Any advice appreciated, thanks.
Edit: Here is StartupActivity.
/**
* Main startup activity. Determines which activity to launch.
* Puts the user in one place or another depending on if they are a backer.
*/
public class StartupActivity extends AppCompatActivity {
private StartupManager mManager;
private ProgressBar bar;
// --- Used if first time app loading to query purchase info
private PurchaseRetriever mPurchases;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Used in either cases
// If first time, displayed, if not, hidden//
//hideNavBar();
User.init(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_startup);
bar = (ProgressBar)this.findViewById(R.id.progressBar);
mManager = new StartupManager(this);
// Returns true if data was corrupt before
if (mManager.isDataCorrupt()) {
bar.setVisibility(View.VISIBLE);
loadImageContent();
// Reset watch to default black
// Internally starts NewMainActivity
ErrorManager.fixCorruptData(bar, this);
} else {
// Stays true until user selects watch
if (mManager.isUserFirstTime()) {
// Initialize purchase retriever.
// The rest will be done when the observer reports that purchase data has been retrieved.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new FirstStartupObserver(this));
StartupManager.FIRST = true;
loadImageContent();
} else {
// NOT first time starting app.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new AfterFirstStartupObserver(this));
loadImageContent();
}
}
}
// Two main dialogs used
public void showTutorialDialog() {
AlertDialog tutorialDialog = new AlertDialog.Builder(this)
.setTitle(R.string.tutorial_question_title)
.setCancelable(false)
.setMessage(R.string.tutorial_question)
.setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// Take to tutorial
// Assume isn't backer for now..
finish();
Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
i.putExtra("from", "StartupActivity");
startActivity(i);
}
})
.setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// No tutorial, ask if they are a backer
showBackerDialog();
}
}).show();
tutorialDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
tutorialDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
}
private void showBackerDialog() {
// Show AlertDialog ask if they are kickstarter backer
AlertDialog askDialog = new AlertDialog.Builder(this)
.setTitle(getResources().getString(R.string.startup_dialog_title))
.setCancelable(false)
.setMessage(getResources().getString(R.string.startup_dialog_message))
.setPositiveButton(getResources().getString(R.string.startup_dialog_pos), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// User is a backer, take to watch chooser screen, then it takes to login screen
// Also look at Timer with TimerTask
new Thread(new Runnable() {
#Override
public void run() {
try {
Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
i.putExtra("from", "StartupActivityBacker");
startActivity(i);
} finally {
finish();
}
}
}).start();
}
})
.setNegativeButton(getResources().getString(R.string.startup_dialog_neg), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// User is not a backer, take to MainActivity
new Thread(new Runnable() {
#Override
public void run() {
try {
Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
i.putExtra("from", "StartupActivityNonBacker");
startActivity(i);
} finally {
finish();
}
}
}).start();
}
}).show();
askDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
askDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
}
Here is the code for FirstStartupObserver.'
public class FirstStartupObserver implements Observer {
private StartupActivity startupActivity;
public FirstStartupObserver(StartupActivity startupActivity) {
this.startupActivity = startupActivity;
}
// Called when the observable is done loading purchase detail
// (Only called when user runs app first time)
#Override
public void update(Observable observable, Object data) {
// Set default first-time watch
// Query product data (the Watchfaces purchased in the form of a WatchFace object)
PurchaseRetriever mPurchases = PurchaseRetriever.getInstance(startupActivity);
if (mPurchases.hasSuccess()) {
ArrayList<DynamicLoader.WatchFace> facesOwned = mPurchases.getPurchasedFaces();
for (DynamicLoader.WatchFace f : facesOwned) {
f.setPurchased(true);
}
// Check if coming from v1.4
if (UpgradeManager.isUpgrading(startupActivity)) {
// Then it calls the code below, but after the async task.
String accessCode = UpgradeManager.getOldAccessCode(startupActivity);
String accessToken = UpgradeManager.getOldAccessToken(startupActivity);
UpgradeManager.migrateBacker(startupActivity, accessCode, accessToken);
} else {
// Ask if they want to see tutorial.
// This is when the exception occurs!!!
startupActivity.showTutorialDialog();
}
return;
} else {
Log.d("TAG", "Showing fail dialog");
DialogUtils.showIabFailDialog(startupActivity, this);
}
}
}
token android.os.BinderProxy#1989547c is not valid; is your activity
running?
This means that you're trying to show your popup while your activity is being destroyed or after it's destroyed.
You can check if your activity isDestroyed like below:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && !isDestroyed()) {
showTutorialDialog();
}
If you're supporting below api 17 devices you can try to use isFinishing in else case. I did not test if it's working as expected. (If i'm wrong please correct me.)
else {
if (!isFinishing()) {
showTutorialDialog();
}
}
Or for a quick fix you can surround with try catch
This is usually caused by doing something in an AsyncTask or other background task which holds a reference to the Activity and tries to display the dialog when the work is done. In this case, it sounds like your FirstStartupObserver is holding a reference to the activity and trying to show a dialog, but the activity may have been destroyed by the time PurchaseRetriever completes its work.
Don't try to test the activity state, and don't catch the BadTokenException. That just masks the problem. The simplest solution would be to cancel the PurchaseRetriever when the activity is paused. If you want the background work to survive configuration changes like rotations but still be restricted to the user-perceived lifetime of the activity, do the work in a retained fragment. Finally, if the background work should continue when the user navigates between activities or puts the app in the background, do the work in a Service and save the result where the activity can retrieve it.
token android.os.BinderProxy#1989547c is not valid; is your activity
running?
You are trying to load the AlertDialog too early, when the Activity doesn't exist! In my apps I load a little tutorial when the activity lifecycle is completed:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
...
...
...
showTutorialDialog();
}
I have placed the parse method inside onCreate method. But my problem is how to show the Android Loading... Dialog??
Parse.initialize(this, "a", "b");
ParseQuery query = new ParseQuery("Category");
query.findInBackground(new FindCallback() {
#Override
public void done(List<ParseObject> catObjects, ParseException arg1) {
Log.d("Catlength", String.valueOf(catObjects.size()));
for(int i =0; i<catObjects.size(); i++){
Log.d("lengthName"+String.valueOf(i), String.valueOf(catObjects.get(i).getInt("Id")));
Category category = new Category();
category.Name= catObjects.get(i).getString("CatName");
category.id= catObjects.get(i).getInt("Id");
categories.add(category);
}
if(categories.size()>0){
setListAdapter(new CategoryArrayAdapter(CategoryListActivity.this, R.layout.row_category, categories));
}
else{
Toast.makeText(CategoryListActivity.this, "Our servers are busy. Hit refresh..", 3000).show();
}
}
});
Everything works fine in the above code but I couldn't figure out how to show the Dialog.
I'm unable to use AsycTask also as parse sdk invokes its own thread in the background and before the findInBackground execution finishes, the doInBackground completes the Asyc thread. That's why I invoked it in the main thread.
As the result I always get no results in my ArrayList.
Can someone please enlighten me.
I was in the same situation regarding the progress dialog, tried a few tricks and finally just declared a ProgressDialog class member:
protected ProgressDialog proDialog;
then created two methods:
protected void startLoading() {
proDialog = new ProgressDialog(this);
proDialog.setMessage("loading...");
proDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
proDialog.setCancelable(false);
proDialog.show();
}
protected void stopLoading() {
proDialog.dismiss();
proDialog = null;
}
and called startLoading() before the background operation and stopLoading()
inside the background operation after I got the the results.
startLoading();
ParseUser.logInInBackground(userName.getText().toString(), hashedPass, new LogInCallback() {
public void done(ParseUser user, ParseException e) {
if (user != null) {
Log.d(Constants.TAG, "User Loged in.");
ParseManager.sCurrentUser = user;
stopLoading();
finish();
} else {
stopLoading();
invalidCreds();
}
}
});
if you want to use AsyncTask don't call findInBackground() you can use find().
you can check it out in the api https://parse.com/docs/android/api/com/parse/ParseQuery.html#find()
hope this helps.
It's easy to get the progress of both uploads and downloads using ParseFile by passing a ProgressCallback to saveInBackground and getDataInBackground. For example:
byte[] data = "Working at Parse is great!".getBytes();
ParseFile file = new ParseFile("resume.txt", data);
file.saveInBackground(new SaveCallback() {
public void done(ParseException e) {
// Handle success or failure here ...
}
}, new ProgressCallback() {
public void done(Integer percentDone) {
// Update your progress spinner here. percentDone will be between 0 and 100.
}
});